Merge "Rename pmInternal#getPackageUidInternal (1/4)"
diff --git a/Android.bp b/Android.bp
index 0780f88..e756b34 100644
--- a/Android.bp
+++ b/Android.bp
@@ -1415,3 +1415,30 @@
     name: "framework-telephony-jarjar-rules",
     srcs: ["telephony/framework-telephony-jarjar-rules.txt"],
 }
+
+// protolog start
+filegroup {
+    name: "protolog-common-src",
+    srcs: [
+        "core/java/com/android/internal/protolog/common/**/*.java",
+    ],
+}
+
+java_library {
+    name: "protolog-lib",
+    platform_apis: true,
+    srcs: [
+        "core/java/com/android/internal/protolog/ProtoLogImpl.java",
+        "core/java/com/android/internal/protolog/ProtoLogViewerConfigReader.java",
+        ":protolog-common-src",
+    ],
+}
+
+java_library {
+    name: "protolog-groups",
+    srcs: [
+        "core/java/com/android/internal/protolog/ProtoLogGroup.java",
+        ":protolog-common-src",
+    ],
+}
+// protolog end
diff --git a/ApiDocs.bp b/ApiDocs.bp
index 029699e..ca921ff 100644
--- a/ApiDocs.bp
+++ b/ApiDocs.bp
@@ -150,12 +150,10 @@
         ":current-support-api",
         ":current-androidx-api",
     ],
-    create_stubs: false,
 }
 
 doc_defaults {
     name: "framework-dokka-docs-default",
-    create_stubs: false,
 }
 
 droiddoc {
diff --git a/PREUPLOAD.cfg b/PREUPLOAD.cfg
index 2fd2e33..fc5efc6 100644
--- a/PREUPLOAD.cfg
+++ b/PREUPLOAD.cfg
@@ -6,6 +6,7 @@
 clang_format = --commit ${PREUPLOAD_COMMIT} --style file --extensions c,h,cc,cpp
                cmds/hid/
                cmds/input/
+               cmds/uinput/
                core/jni/
                libs/input/
                services/core/jni/
diff --git a/StubLibraries.bp b/StubLibraries.bp
index 0fe34fb..bb65387 100644
--- a/StubLibraries.bp
+++ b/StubLibraries.bp
@@ -299,6 +299,7 @@
     static_libs: [
         // License notices from art module
         "art-notices-for-framework-stubs-jar",
+        "framework-res-package-jar", // Export package of framework-res
     ],
     errorprone: {
         javacflags: [
diff --git a/apct-tests/perftests/autofill/Android.bp b/apct-tests/perftests/autofill/Android.bp
index 9ac8c87..4f6c21a 100644
--- a/apct-tests/perftests/autofill/Android.bp
+++ b/apct-tests/perftests/autofill/Android.bp
@@ -20,8 +20,9 @@
         "androidx.test.rules",
         "androidx.annotation_annotation",
         "apct-perftests-utils",
-        "collector-device-lib-platform",
+        "collector-device-lib",
     ],
     platform_apis: true,
     test_suites: ["device-tests"],
+    data: [":perfetto_artifacts"],
 }
diff --git a/apct-tests/perftests/autofill/AndroidManifest.xml b/apct-tests/perftests/autofill/AndroidManifest.xml
index 51f6a76..57595a2 100644
--- a/apct-tests/perftests/autofill/AndroidManifest.xml
+++ b/apct-tests/perftests/autofill/AndroidManifest.xml
@@ -16,11 +16,6 @@
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
     package="com.android.perftests.autofill">
 
-    <uses-sdk android:targetSdkVersion="28" />
-
-    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
-    <uses-permission android:name="android.permission.REAL_GET_TASKS" />
-
     <application>
         <uses-library android:name="android.test.runner" />
         <activity android:name="android.perftests.utils.PerfTestActivity"
diff --git a/apct-tests/perftests/autofill/AndroidTest.xml b/apct-tests/perftests/autofill/AndroidTest.xml
index 29f9f94..eee7bdc 100644
--- a/apct-tests/perftests/autofill/AndroidTest.xml
+++ b/apct-tests/perftests/autofill/AndroidTest.xml
@@ -21,8 +21,40 @@
         <option name="test-file-name" value="AutofillPerfTests.apk" />
     </target_preparer>
 
+    <!-- Needed for pushing the trace config file -->
+    <target_preparer class="com.android.tradefed.targetprep.RootTargetPreparer"/>
+    <target_preparer class="com.android.tradefed.targetprep.PushFilePreparer">
+        <option name="push-file" key="trace_config_detailed.textproto" value="/data/misc/perfetto-traces/trace_config.textproto" />
+    </target_preparer>
+
+    <!-- Needed for pulling the collected trace config on to the host -->
+    <metrics_collector class="com.android.tradefed.device.metric.FilePullerLogCollector">
+        <option name="pull-pattern-keys" value="perfetto_file_path" />
+    </metrics_collector>
+
+    <!-- Needed for storing the perfetto trace files in the sdcard/test_results-->
+    <option name="isolated-storage" value="false" />
+
     <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
         <option name="package" value="com.android.perftests.autofill" />
         <option name="hidden-api-checks" value="false"/>
+
+        <!-- Listener related args for collecting the traces and waiting for the device to stabilize. -->
+        <option name="device-listeners" value="android.device.collectors.ProcLoadListener,android.device.collectors.PerfettoListener" />
+        <!-- Guarantee that user defined RunListeners will be running before any of the default listeners defined in this runner. -->
+        <option name="instrumentation-arg" key="newRunListenerMode" value="true" />
+
+        <!-- ProcLoadListener related arguments -->
+        <!-- Wait for device last minute threshold to reach 3 with 2 minute timeout before starting the test run -->
+        <option name="instrumentation-arg" key="procload-collector:per_run" value="true" />
+        <option name="instrumentation-arg" key="proc-loadavg-threshold" value="3" />
+        <option name="instrumentation-arg" key="proc-loadavg-timeout" value="120000" />
+        <option name="instrumentation-arg" key="proc-loadavg-interval" value="10000" />
+
+        <!-- PerfettoListener related arguments -->
+        <option name="instrumentation-arg" key="perfetto_config_text_proto" value="true" />
+        <option name="instrumentation-arg" key="perfetto_config_file" value="trace_config.textproto" />
+
+        <option name="instrumentation-arg" key="newRunListenerMode" value="true" />
     </test>
 </configuration>
diff --git a/apct-tests/perftests/contentcapture/Android.bp b/apct-tests/perftests/contentcapture/Android.bp
index 66d7348..19a66ad 100644
--- a/apct-tests/perftests/contentcapture/Android.bp
+++ b/apct-tests/perftests/contentcapture/Android.bp
@@ -20,9 +20,10 @@
         "androidx.test.rules",
         "androidx.annotation_annotation",
         "apct-perftests-utils",
-        "collector-device-lib-platform",
+        "collector-device-lib",
         "compatibility-device-util-axt",
     ],
     platform_apis: true,
     test_suites: ["device-tests"],
+    data: [":perfetto_artifacts"],
 }
diff --git a/apct-tests/perftests/contentcapture/AndroidManifest.xml b/apct-tests/perftests/contentcapture/AndroidManifest.xml
index ee5577f..80957c7 100644
--- a/apct-tests/perftests/contentcapture/AndroidManifest.xml
+++ b/apct-tests/perftests/contentcapture/AndroidManifest.xml
@@ -16,11 +16,6 @@
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
     package="com.android.perftests.contentcapture">
 
-    <uses-sdk android:targetSdkVersion="28" />
-
-    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
-    <uses-permission android:name="android.permission.REAL_GET_TASKS" />
-
     <application>
         <uses-library android:name="android.test.runner" />
         <activity android:name="android.view.contentcapture.CustomTestActivity"
diff --git a/apct-tests/perftests/contentcapture/AndroidTest.xml b/apct-tests/perftests/contentcapture/AndroidTest.xml
index d2386bb..d8e0a17 100644
--- a/apct-tests/perftests/contentcapture/AndroidTest.xml
+++ b/apct-tests/perftests/contentcapture/AndroidTest.xml
@@ -21,7 +21,39 @@
         <option name="test-file-name" value="ContentCapturePerfTests.apk" />
     </target_preparer>
 
+    <!-- Needed for pushing the trace config file -->
+    <target_preparer class="com.android.tradefed.targetprep.RootTargetPreparer"/>
+    <target_preparer class="com.android.tradefed.targetprep.PushFilePreparer">
+        <option name="push-file" key="trace_config_detailed.textproto" value="/data/misc/perfetto-traces/trace_config.textproto" />
+    </target_preparer>
+
+    <!-- Needed for pulling the collected trace config on to the host -->
+    <metrics_collector class="com.android.tradefed.device.metric.FilePullerLogCollector">
+        <option name="pull-pattern-keys" value="perfetto_file_path" />
+    </metrics_collector>
+
+    <!-- Needed for storing the perfetto trace files in the sdcard/test_results-->
+    <option name="isolated-storage" value="false" />
+
     <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
         <option name="package" value="com.android.perftests.contentcapture" />
+
+        <!-- Listener related args for collecting the traces and waiting for the device to stabilize. -->
+        <option name="device-listeners" value="android.device.collectors.ProcLoadListener,android.device.collectors.PerfettoListener" />
+        <!-- Guarantee that user defined RunListeners will be running before any of the default listeners defined in this runner. -->
+        <option name="instrumentation-arg" key="newRunListenerMode" value="true" />
+
+        <!-- ProcLoadListener related arguments -->
+        <!-- Wait for device last minute threshold to reach 3 with 2 minute timeout before starting the test run -->
+        <option name="instrumentation-arg" key="procload-collector:per_run" value="true" />
+        <option name="instrumentation-arg" key="proc-loadavg-threshold" value="3" />
+        <option name="instrumentation-arg" key="proc-loadavg-timeout" value="120000" />
+        <option name="instrumentation-arg" key="proc-loadavg-interval" value="10000" />
+
+        <!-- PerfettoListener related arguments -->
+        <option name="instrumentation-arg" key="perfetto_config_text_proto" value="true" />
+        <option name="instrumentation-arg" key="perfetto_config_file" value="trace_config.textproto" />
+
+        <option name="instrumentation-arg" key="newRunListenerMode" value="true" />
     </test>
 </configuration>
diff --git a/apct-tests/perftests/multiuser/Android.bp b/apct-tests/perftests/multiuser/Android.bp
index 04432f2..fc45d4a 100644
--- a/apct-tests/perftests/multiuser/Android.bp
+++ b/apct-tests/perftests/multiuser/Android.bp
@@ -22,5 +22,6 @@
     ],
     platform_apis: true,
     test_suites: ["device-tests"],
+    data: [":perfetto_artifacts"],
     certificate: "platform",
 }
diff --git a/apct-tests/perftests/multiuser/AndroidManifest.xml b/apct-tests/perftests/multiuser/AndroidManifest.xml
index e4196dd..cb05651 100644
--- a/apct-tests/perftests/multiuser/AndroidManifest.xml
+++ b/apct-tests/perftests/multiuser/AndroidManifest.xml
@@ -17,7 +17,6 @@
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
         package="com.android.perftests.multiuser">
 
-    <uses-sdk android:targetSdkVersion="28" />
 
     <uses-permission android:name="android.permission.CONTROL_KEYGUARD" />
     <uses-permission android:name="android.permission.MANAGE_USERS" />
@@ -25,8 +24,6 @@
     <uses-permission android:name="android.permission.INTERACT_ACROSS_USERS" />
     <uses-permission android:name="android.permission.INTERACT_ACROSS_USERS_FULL" />
     <uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS" />
-    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
-    <uses-permission android:name="android.permission.REAL_GET_TASKS" />
 
     <application>
         <uses-library android:name="android.test.runner" />
diff --git a/apct-tests/perftests/multiuser/AndroidTest.xml b/apct-tests/perftests/multiuser/AndroidTest.xml
index 9117561..fbe5892 100644
--- a/apct-tests/perftests/multiuser/AndroidTest.xml
+++ b/apct-tests/perftests/multiuser/AndroidTest.xml
@@ -16,14 +16,51 @@
 <configuration description="Runs MultiUserPerfTests metric instrumentation.">
     <option name="test-suite-tag" value="apct" />
     <option name="test-suite-tag" value="apct-metric-instrumentation" />
+
     <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
         <option name="cleanup-apks" value="true" />
         <option name="test-file-name" value="MultiUserPerfTests.apk" />
         <option name="test-file-name" value="MultiUserPerfDummyApp.apk" />
     </target_preparer>
 
+    <!-- Needed for pushing the trace config file -->
+    <target_preparer class="com.android.tradefed.targetprep.RootTargetPreparer"/>
+    <target_preparer class="com.android.tradefed.targetprep.PushFilePreparer">
+        <option name="push-file" key="trace_config_detailed.textproto" value="/data/misc/perfetto-traces/trace_config.textproto" />
+        <!--Install the content provider automatically when we push some file in sdcard folder.-->
+        <!--Needed to avoid the installation during the test suite.-->
+        <option name="push-file" key="trace_config_detailed.textproto" value="/sdcard/sample.textproto" />
+    </target_preparer>
+
+    <!-- Needed for pulling the collected trace config on to the host -->
+    <metrics_collector class="com.android.tradefed.device.metric.FilePullerLogCollector">
+        <option name="pull-pattern-keys" value="perfetto_file_path" />
+    </metrics_collector>
+
+    <!-- Needed for storing the perfetto trace files in the sdcard/test_results-->
+    <option name="isolated-storage" value="false" />
+
     <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
         <option name="package" value="com.android.perftests.multiuser" />
         <option name="hidden-api-checks" value="false"/>
+
+        <!-- Listener related args for collecting the traces and waiting for the device to stabilize. -->
+        <option name="device-listeners" value="android.device.collectors.ProcLoadListener,android.device.collectors.PerfettoListener" />
+        <!-- Guarantee that user defined RunListeners will be running before any of the default listeners defined in this runner. -->
+        <option name="instrumentation-arg" key="newRunListenerMode" value="true" />
+
+        <!-- ProcLoadListener related arguments -->
+        <!-- Wait for device last minute threshold to reach 3 with 2 minute timeout before starting the test run -->
+        <option name="instrumentation-arg" key="procload-collector:per_run" value="true" />
+        <option name="instrumentation-arg" key="proc-loadavg-threshold" value="3" />
+        <option name="instrumentation-arg" key="proc-loadavg-timeout" value="120000" />
+        <option name="instrumentation-arg" key="proc-loadavg-interval" value="10000" />
+
+        <!-- PerfettoListener related arguments -->
+        <option name="instrumentation-arg" key="perfetto_config_text_proto" value="true" />
+        <option name="instrumentation-arg" key="perfetto_config_file" value="trace_config.textproto" />
+
+        <option name="instrumentation-arg" key="newRunListenerMode" value="true" />
+
     </test>
 </configuration>
diff --git a/apct-tests/perftests/textclassifier/Android.bp b/apct-tests/perftests/textclassifier/Android.bp
index 49952dc..c40e025 100644
--- a/apct-tests/perftests/textclassifier/Android.bp
+++ b/apct-tests/perftests/textclassifier/Android.bp
@@ -19,7 +19,9 @@
         "androidx.test.rules",
         "androidx.annotation_annotation",
         "apct-perftests-utils",
+        "collector-device-lib",
     ],
+    data: [":perfetto_artifacts"],
     platform_apis: true,
     test_suites: ["device-tests"],
 }
diff --git a/apct-tests/perftests/textclassifier/AndroidTest.xml b/apct-tests/perftests/textclassifier/AndroidTest.xml
index 3df51b8..3fac462 100644
--- a/apct-tests/perftests/textclassifier/AndroidTest.xml
+++ b/apct-tests/perftests/textclassifier/AndroidTest.xml
@@ -21,8 +21,40 @@
         <option name="test-file-name" value="TextClassifierPerfTests.apk" />
     </target_preparer>
 
+    <!-- Needed for pushing the trace config file -->
+    <target_preparer class="com.android.tradefed.targetprep.RootTargetPreparer"/>
+    <target_preparer class="com.android.tradefed.targetprep.PushFilePreparer">
+        <option name="push-file" key="trace_config_detailed.textproto" value="/data/misc/perfetto-traces/trace_config.textproto" />
+    </target_preparer>
+
+    <!-- Needed for pulling the collected trace config on to the host -->
+    <metrics_collector class="com.android.tradefed.device.metric.FilePullerLogCollector">
+        <option name="pull-pattern-keys" value="perfetto_file_path" />
+    </metrics_collector>
+
+    <!-- Needed for storing the perfetto trace files in the sdcard/test_results-->
+    <option name="isolated-storage" value="false" />
+
     <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
         <option name="package" value="com.android.perftests.textclassifier" />
         <option name="hidden-api-checks" value="false"/>
+
+         <!-- Listener related args for collecting the traces and waiting for the device to stabilize. -->
+        <option name="device-listeners" value="android.device.collectors.ProcLoadListener,android.device.collectors.PerfettoListener" />
+        <!-- Guarantee that user defined RunListeners will be running before any of the default listeners defined in this runner. -->
+        <option name="instrumentation-arg" key="newRunListenerMode" value="true" />
+
+        <!-- ProcLoadListener related arguments -->
+        <!-- Wait for device last minute threshold to reach 3 with 2 minute timeout before starting the test run -->
+        <option name="instrumentation-arg" key="procload-collector:per_run" value="true" />
+        <option name="instrumentation-arg" key="proc-loadavg-threshold" value="3" />
+        <option name="instrumentation-arg" key="proc-loadavg-timeout" value="120000" />
+        <option name="instrumentation-arg" key="proc-loadavg-interval" value="10000" />
+
+        <!-- PerfettoListener related arguments -->
+        <option name="instrumentation-arg" key="perfetto_config_text_proto" value="true" />
+        <option name="instrumentation-arg" key="perfetto_config_file" value="trace_config.textproto" />
+
+        <option name="instrumentation-arg" key="newRunListenerMode" value="true" />
     </test>
 </configuration>
diff --git a/apct-tests/perftests/textclassifier/src/android/view/textclassifier/TextClassifierPerfTest.java b/apct-tests/perftests/textclassifier/src/android/view/textclassifier/TextClassifierPerfTest.java
index 14a121d..324def8 100644
--- a/apct-tests/perftests/textclassifier/src/android/view/textclassifier/TextClassifierPerfTest.java
+++ b/apct-tests/perftests/textclassifier/src/android/view/textclassifier/TextClassifierPerfTest.java
@@ -18,6 +18,7 @@
 import android.content.Context;
 import android.perftests.utils.BenchmarkState;
 import android.perftests.utils.PerfStatusReporter;
+import android.service.textclassifier.TextClassifierService;
 
 import androidx.test.InstrumentationRegistry;
 import androidx.test.filters.LargeTest;
@@ -25,48 +26,73 @@
 import org.junit.Before;
 import org.junit.Rule;
 import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.junit.runners.Parameterized;
 
-import java.util.Arrays;
-import java.util.Collection;
 import java.util.Collections;
-import java.util.Random;
 
-@RunWith(Parameterized.class)
 @LargeTest
 public class TextClassifierPerfTest {
-    /** Request contains meaning text, rather than garbled text. */
-    private static final int ACTUAL_REQUEST = 0;
-    private static final String RANDOM_CHAR_SET = "abcdefghijklmnopqrstuvwxyz0123456789";
+    private static final String TEXT = " Oh hi Mark, the number is (323) 654-6192.\n"
+            + "Anyway, I'll meet you at 1600 Pennsylvania Avenue NW.\n"
+            + "My flight is LX 38 and I'll arrive at 8:00pm.\n"
+            + "Also, check out www.google.com.\n";
 
     @Rule
     public PerfStatusReporter mPerfStatusReporter = new PerfStatusReporter();
 
-    @Parameterized.Parameters(name = "size{0}")
-    public static Collection<Object[]> data() {
-        return Arrays.asList(new Object[][]{{ACTUAL_REQUEST}, {10}, {100}, {1000}});
-    }
-
     private TextClassifier mTextClassifier;
-    private final int mSize;
-
-    public TextClassifierPerfTest(int size) {
-        mSize = size;
-    }
 
     @Before
     public void setUp() {
         Context context = InstrumentationRegistry.getTargetContext();
-        TextClassificationManager textClassificationManager =
-                context.getSystemService(TextClassificationManager.class);
-        mTextClassifier = textClassificationManager.getTextClassifier(TextClassifier.LOCAL);
+        mTextClassifier = TextClassifierService.getDefaultTextClassifierImplementation(context);
+    }
+
+    @Test
+    public void testClassifyText() {
+        TextClassification.Request request =
+                new TextClassification.Request.Builder(TEXT, 0, TEXT.length()).build();
+        BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+        while (state.keepRunning()) {
+            mTextClassifier.classifyText(request);
+        }
+    }
+
+    @Test
+    public void testSuggestSelection() {
+        // Trying to select the phone number.
+        TextSelection.Request request =
+                new TextSelection.Request.Builder(
+                        TEXT,
+                        /* startIndex= */ 28,
+                        /* endIndex= */29)
+                        .build();
+        BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+        while (state.keepRunning()) {
+            mTextClassifier.suggestSelection(request);
+        }
+    }
+
+    @Test
+    public void testGenerateLinks() {
+        TextLinks.Request request =
+                new TextLinks.Request.Builder(TEXT).build();
+        BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+        while (state.keepRunning()) {
+            mTextClassifier.generateLinks(request);
+        }
     }
 
     @Test
     public void testSuggestConversationActions() {
-        String text = mSize == ACTUAL_REQUEST ? "Where are you?" : generateRandomString(mSize);
-        ConversationActions.Request request = createConversationActionsRequest(text);
+        ConversationActions.Message message =
+                new ConversationActions.Message.Builder(
+                        ConversationActions.Message.PERSON_USER_OTHERS)
+                        .setText(TEXT)
+                        .build();
+        ConversationActions.Request request = new ConversationActions.Request.Builder(
+                Collections.singletonList(message))
+                .build();
+
         BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
         while (state.keepRunning()) {
             mTextClassifier.suggestConversationActions(request);
@@ -75,36 +101,10 @@
 
     @Test
     public void testDetectLanguage() {
-        String text = mSize == ACTUAL_REQUEST
-                ? "これは日本語のテキストです" : generateRandomString(mSize);
-        TextLanguage.Request request = createTextLanguageRequest(text);
+        TextLanguage.Request request = new TextLanguage.Request.Builder(TEXT).build();
         BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
         while (state.keepRunning()) {
             mTextClassifier.detectLanguage(request);
         }
     }
-
-    private static ConversationActions.Request createConversationActionsRequest(CharSequence text) {
-        ConversationActions.Message message =
-                new ConversationActions.Message.Builder(
-                        ConversationActions.Message.PERSON_USER_OTHERS)
-                        .setText(text)
-                        .build();
-        return new ConversationActions.Request.Builder(Collections.singletonList(message))
-                .build();
-    }
-
-    private static TextLanguage.Request createTextLanguageRequest(CharSequence text) {
-        return new TextLanguage.Request.Builder(text).build();
-    }
-
-    private static String generateRandomString(int length) {
-        Random random = new Random();
-        StringBuilder stringBuilder = new StringBuilder(length);
-        for (int i = 0; i < length; i++) {
-            int index = random.nextInt(RANDOM_CHAR_SET.length());
-            stringBuilder.append(RANDOM_CHAR_SET.charAt(index));
-        }
-        return stringBuilder.toString();
-    }
 }
diff --git a/apct-tests/perftests/windowmanager/Android.bp b/apct-tests/perftests/windowmanager/Android.bp
index 9fa99853..dbfe6ab 100644
--- a/apct-tests/perftests/windowmanager/Android.bp
+++ b/apct-tests/perftests/windowmanager/Android.bp
@@ -23,6 +23,7 @@
         "platform-test-annotations",
     ],
     test_suites: ["device-tests"],
+    data: [":perfetto_artifacts"],
     platform_apis: true,
     certificate: "platform",
 }
diff --git a/apct-tests/perftests/windowmanager/AndroidTest.xml b/apct-tests/perftests/windowmanager/AndroidTest.xml
index 0a80cf9..aee02c7a 100644
--- a/apct-tests/perftests/windowmanager/AndroidTest.xml
+++ b/apct-tests/perftests/windowmanager/AndroidTest.xml
@@ -28,14 +28,42 @@
         <option name="run-command" value="cmd package compile -m speed com.android.perftests.wm" />
     </target_preparer>
 
+    <!-- Needed for pushing the trace config file -->
+    <target_preparer class="com.android.tradefed.targetprep.RootTargetPreparer"/>
+    <target_preparer class="com.android.tradefed.targetprep.PushFilePreparer">
+        <option name="push-file" key="trace_config_detailed.textproto" value="/data/misc/perfetto-traces/trace_config.textproto" />
+    </target_preparer>
+
+    <!-- Needed for storing the perfetto trace files in the sdcard/test_results-->
+    <option name="isolated-storage" value="false" />
+
     <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
         <option name="package" value="com.android.perftests.wm" />
         <option name="hidden-api-checks" value="false"/>
-        <option name="device-listeners" value="android.wm.WmPerfRunListener" />
+
+        <!-- Listener related args for collecting the traces and waiting for the device to stabilize. -->
+        <option name="device-listeners" value="android.wm.WmPerfRunListener,android.device.collectors.ProcLoadListener,android.device.collectors.PerfettoListener" />
+
+        <!-- Guarantee that user defined RunListeners will be running before any of the default listeners defined in this runner. -->
+        <option name="instrumentation-arg" key="newRunListenerMode" value="true" />
+
+        <!-- ProcLoadListener related arguments -->
+        <!-- Wait for device last minute threshold to reach 3 with 2 minute timeout before starting the test run -->
+        <option name="instrumentation-arg" key="procload-collector:per_run" value="true" />
+        <option name="instrumentation-arg" key="proc-loadavg-threshold" value="3" />
+        <option name="instrumentation-arg" key="proc-loadavg-timeout" value="120000" />
+        <option name="instrumentation-arg" key="proc-loadavg-interval" value="10000" />
+
+        <!-- PerfettoListener related arguments -->
+        <option name="instrumentation-arg" key="perfetto_config_text_proto" value="true" />
+        <option name="instrumentation-arg" key="perfetto_config_file" value="trace_config.textproto" />
+
+        <option name="instrumentation-arg" key="newRunListenerMode" value="true" />
     </test>
 
     <metrics_collector class="com.android.tradefed.device.metric.FilePullerLogCollector">
-        <option name="directory-keys" value="/data/local/WmPerfTests" />
-        <option name="collect-on-run-ended-only" value="true" />
+        <option name="directory-keys" value="/data/local/tmp/WmPerfTests" />
+        <!-- Needed for pulling the collected trace config on to the host -->
+        <option name="pull-pattern-keys" value="perfetto_file_path" />
     </metrics_collector>
 </configuration>
diff --git a/apct-tests/perftests/windowmanager/src/android/wm/WindowManagerPerfTestBase.java b/apct-tests/perftests/windowmanager/src/android/wm/WindowManagerPerfTestBase.java
index dc6245b..cc0e939a 100644
--- a/apct-tests/perftests/windowmanager/src/android/wm/WindowManagerPerfTestBase.java
+++ b/apct-tests/perftests/windowmanager/src/android/wm/WindowManagerPerfTestBase.java
@@ -55,7 +55,7 @@
      * is in /data because while enabling method profling of system server, it cannot write the
      * trace to external storage.
      */
-    static final File BASE_OUT_PATH = new File("/data/local/WmPerfTests");
+    static final File BASE_OUT_PATH = new File("/data/local/tmp/WmPerfTests");
 
     @BeforeClass
     public static void setUpOnce() {
diff --git a/apex/appsearch/framework/java/android/app/appsearch/SearchSpec.java b/apex/appsearch/framework/java/android/app/appsearch/SearchSpec.java
index c276ae1..6e644ff 100644
--- a/apex/appsearch/framework/java/android/app/appsearch/SearchSpec.java
+++ b/apex/appsearch/framework/java/android/app/appsearch/SearchSpec.java
@@ -168,7 +168,9 @@
         /** Sets the maximum number of results to retrieve from the query */
         @NonNull
         public SearchSpec.Builder setNumToRetrieve(int numToRetrieve) {
-            mResultSpecBuilder.setNumToRetrieve(numToRetrieve);
+            // Just retrieve everything in one page.
+            // TODO(b/152359656): Realign these two apis properly.
+            mResultSpecBuilder.setNumPerPage(numToRetrieve);
             return this;
         }
 
diff --git a/apex/jobscheduler/service/java/com/android/server/AppStateTrackerImpl.java b/apex/jobscheduler/service/java/com/android/server/AppStateTrackerImpl.java
index 876d73a..8e26052 100644
--- a/apex/jobscheduler/service/java/com/android/server/AppStateTrackerImpl.java
+++ b/apex/jobscheduler/service/java/com/android/server/AppStateTrackerImpl.java
@@ -125,10 +125,11 @@
     private int[] mTempExemptAppIds = mPowerExemptAllAppIds;
 
     /**
-     * Per-user packages that are in the EXEMPT bucket.
+     * Per-user packages that are in the EXEMPTED bucket.
      */
     @GuardedBy("mLock")
-    private final SparseSetArray<String> mExemptBucketPackages = new SparseSetArray<>();
+    @VisibleForTesting
+    final SparseSetArray<String> mExemptedBucketPackages = new SparseSetArray<>();
 
     @GuardedBy("mLock")
     final ArraySet<Listener> mListeners = new ArraySet<>();
@@ -180,7 +181,7 @@
         int ALL_UNEXEMPTED = 3;
         int ALL_EXEMPTION_LIST_CHANGED = 4;
         int TEMP_EXEMPTION_LIST_CHANGED = 5;
-        int EXEMPT_BUCKET_CHANGED = 6;
+        int EXEMPTED_BUCKET_CHANGED = 6;
         int FORCE_ALL_CHANGED = 7;
         int FORCE_APP_STANDBY_FEATURE_FLAG_CHANGED = 8;
 
@@ -195,7 +196,7 @@
             "ALL_UNEXEMPTED",
             "ALL_EXEMPTION_LIST_CHANGED",
             "TEMP_EXEMPTION_LIST_CHANGED",
-            "EXEMPT_BUCKET_CHANGED",
+            "EXEMPTED_BUCKET_CHANGED",
             "FORCE_ALL_CHANGED",
             "FORCE_APP_STANDBY_FEATURE_FLAG_CHANGED",
 
@@ -338,9 +339,9 @@
         }
 
         /**
-         * This is called when the EXEMPT bucket is updated.
+         * This is called when the EXEMPTED bucket is updated.
          */
-        private void onExemptBucketChanged(AppStateTrackerImpl sender) {
+        private void onExemptedBucketChanged(AppStateTrackerImpl sender) {
             // This doesn't happen very often, so just re-evaluate all jobs / alarms.
             updateAllJobs();
             unblockAllUnrestrictedAlarms();
@@ -424,6 +425,38 @@
         mHandler = new MyHandler(looper);
     }
 
+    private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            final int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, -1);
+            switch (intent.getAction()) {
+                case Intent.ACTION_USER_REMOVED:
+                    if (userId > 0) {
+                        mHandler.doUserRemoved(userId);
+                    }
+                    break;
+                case Intent.ACTION_BATTERY_CHANGED:
+                    synchronized (mLock) {
+                        mIsPluggedIn = (intent.getIntExtra(BatteryManager.EXTRA_PLUGGED, 0) != 0);
+                    }
+                    updateForceAllAppStandbyState();
+                    break;
+                case Intent.ACTION_PACKAGE_REMOVED:
+                    if (!intent.getBooleanExtra(Intent.EXTRA_REPLACING, false)) {
+                        final String pkgName = intent.getData().getSchemeSpecificPart();
+                        final int uid = intent.getIntExtra(Intent.EXTRA_UID, -1);
+                        // No need to notify for state change as all the alarms and jobs should be
+                        // removed too.
+                        mExemptedBucketPackages.remove(userId, pkgName);
+                        mRunAnyRestrictedPackages.remove(Pair.create(uid, pkgName));
+                        mActiveUids.delete(uid);
+                        mForegroundUids.delete(uid);
+                    }
+                    break;
+            }
+        }
+    };
+
     /**
      * Call it when the system is ready.
      */
@@ -465,8 +498,11 @@
             IntentFilter filter = new IntentFilter();
             filter.addAction(Intent.ACTION_USER_REMOVED);
             filter.addAction(Intent.ACTION_BATTERY_CHANGED);
-            filter.addAction(Intent.ACTION_PACKAGE_REMOVED);
-            mContext.registerReceiver(new MyReceiver(), filter);
+            mContext.registerReceiver(mReceiver, filter);
+
+            filter = new IntentFilter(Intent.ACTION_PACKAGE_REMOVED);
+            filter.addDataScheme(IntentFilter.SCHEME_PACKAGE);
+            mContext.registerReceiver(mReceiver, filter);
 
             refreshForcedAppStandbyUidPackagesLocked();
 
@@ -693,30 +729,6 @@
         }
     }
 
-    private final class MyReceiver extends BroadcastReceiver {
-        @Override
-        public void onReceive(Context context, Intent intent) {
-            if (Intent.ACTION_USER_REMOVED.equals(intent.getAction())) {
-                final int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, -1);
-                if (userId > 0) {
-                    mHandler.doUserRemoved(userId);
-                }
-            } else if (Intent.ACTION_BATTERY_CHANGED.equals(intent.getAction())) {
-                synchronized (mLock) {
-                    mIsPluggedIn = (intent.getIntExtra(BatteryManager.EXTRA_PLUGGED, 0) != 0);
-                }
-                updateForceAllAppStandbyState();
-            } else if (Intent.ACTION_PACKAGE_REMOVED.equals(intent.getAction())
-                    && !intent.getBooleanExtra(Intent.EXTRA_REPLACING, false)) {
-                final int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, -1);
-                final String pkgName = intent.getData().getSchemeSpecificPart();
-                if (mExemptBucketPackages.remove(userId, pkgName)) {
-                    mHandler.notifyExemptBucketChanged();
-                }
-            }
-        }
-    }
-
     final class StandbyTracker extends AppIdleStateChangeListener {
         @Override
         public void onAppIdleStateChanged(String packageName, int userId, boolean idle,
@@ -728,12 +740,12 @@
             synchronized (mLock) {
                 final boolean changed;
                 if (bucket == UsageStatsManager.STANDBY_BUCKET_EXEMPTED) {
-                    changed = mExemptBucketPackages.add(userId, packageName);
+                    changed = mExemptedBucketPackages.add(userId, packageName);
                 } else {
-                    changed = mExemptBucketPackages.remove(userId, packageName);
+                    changed = mExemptedBucketPackages.remove(userId, packageName);
                 }
                 if (changed) {
-                    mHandler.notifyExemptBucketChanged();
+                    mHandler.notifyExemptedBucketChanged();
                 }
             }
         }
@@ -755,7 +767,7 @@
         private static final int MSG_FORCE_ALL_CHANGED = 7;
         private static final int MSG_USER_REMOVED = 8;
         private static final int MSG_FORCE_APP_STANDBY_FEATURE_FLAG_CHANGED = 9;
-        private static final int MSG_EXEMPT_BUCKET_CHANGED = 10;
+        private static final int MSG_EXEMPTED_BUCKET_CHANGED = 10;
 
         private static final int MSG_ON_UID_STATE_CHANGED = 11;
         private static final int MSG_ON_UID_ACTIVE = 12;
@@ -803,9 +815,9 @@
             obtainMessage(MSG_FORCE_APP_STANDBY_FEATURE_FLAG_CHANGED).sendToTarget();
         }
 
-        public void notifyExemptBucketChanged() {
-            removeMessages(MSG_EXEMPT_BUCKET_CHANGED);
-            obtainMessage(MSG_EXEMPT_BUCKET_CHANGED).sendToTarget();
+        public void notifyExemptedBucketChanged() {
+            removeMessages(MSG_EXEMPTED_BUCKET_CHANGED);
+            obtainMessage(MSG_EXEMPTED_BUCKET_CHANGED).sendToTarget();
         }
 
         public void doUserRemoved(int userId) {
@@ -888,11 +900,11 @@
                     mStatLogger.logDurationStat(Stats.TEMP_EXEMPTION_LIST_CHANGED, start);
                     return;
 
-                case MSG_EXEMPT_BUCKET_CHANGED:
+                case MSG_EXEMPTED_BUCKET_CHANGED:
                     for (Listener l : cloneListeners()) {
-                        l.onExemptBucketChanged(sender);
+                        l.onExemptedBucketChanged(sender);
                     }
-                    mStatLogger.logDurationStat(Stats.EXEMPT_BUCKET_CHANGED, start);
+                    mStatLogger.logDurationStat(Stats.EXEMPTED_BUCKET_CHANGED, start);
                     return;
 
                 case MSG_FORCE_ALL_CHANGED:
@@ -1005,7 +1017,7 @@
             }
             cleanUpArrayForUser(mActiveUids, removedUserId);
             cleanUpArrayForUser(mForegroundUids, removedUserId);
-            mExemptBucketPackages.remove(removedUserId);
+            mExemptedBucketPackages.remove(removedUserId);
         }
     }
 
@@ -1148,7 +1160,7 @@
             }
             final int userId = UserHandle.getUserId(uid);
             if (mAppStandbyInternal.isAppIdleEnabled() && !mAppStandbyInternal.isInParole()
-                    && mExemptBucketPackages.contains(userId, packageName)) {
+                    && mExemptedBucketPackages.contains(userId, packageName)) {
                 return false;
             }
             return mForceAllAppsStandby;
@@ -1301,14 +1313,14 @@
 
             pw.println("Exempted bucket packages:");
             pw.increaseIndent();
-            for (int i = 0; i < mExemptBucketPackages.size(); i++) {
+            for (int i = 0; i < mExemptedBucketPackages.size(); i++) {
                 pw.print("User ");
-                pw.print(mExemptBucketPackages.keyAt(i));
+                pw.print(mExemptedBucketPackages.keyAt(i));
                 pw.println();
 
                 pw.increaseIndent();
-                for (int j = 0; j < mExemptBucketPackages.sizeAt(i); j++) {
-                    pw.print(mExemptBucketPackages.valueAt(i, j));
+                for (int j = 0; j < mExemptedBucketPackages.sizeAt(i); j++) {
+                    pw.print(mExemptedBucketPackages.valueAt(i, j));
                     pw.println();
                 }
                 pw.decreaseIndent();
@@ -1384,12 +1396,13 @@
                 proto.write(AppStateTrackerProto.TEMP_POWER_SAVE_EXEMPT_APP_IDS, appId);
             }
 
-            for (int i = 0; i < mExemptBucketPackages.size(); i++) {
-                for (int j = 0; j < mExemptBucketPackages.sizeAt(i); j++) {
+            for (int i = 0; i < mExemptedBucketPackages.size(); i++) {
+                for (int j = 0; j < mExemptedBucketPackages.sizeAt(i); j++) {
                     final long token2 = proto.start(AppStateTrackerProto.EXEMPTED_BUCKET_PACKAGES);
 
-                    proto.write(ExemptedPackage.USER_ID, mExemptBucketPackages.keyAt(i));
-                    proto.write(ExemptedPackage.PACKAGE_NAME, mExemptBucketPackages.valueAt(i, j));
+                    proto.write(ExemptedPackage.USER_ID, mExemptedBucketPackages.keyAt(i));
+                    proto.write(ExemptedPackage.PACKAGE_NAME,
+                            mExemptedBucketPackages.valueAt(i, j));
 
                     proto.end(token2);
                 }
diff --git a/apex/jobscheduler/service/java/com/android/server/JobSchedulerBackgroundThread.java b/apex/jobscheduler/service/java/com/android/server/JobSchedulerBackgroundThread.java
index 91d254d..a413f7b 100644
--- a/apex/jobscheduler/service/java/com/android/server/JobSchedulerBackgroundThread.java
+++ b/apex/jobscheduler/service/java/com/android/server/JobSchedulerBackgroundThread.java
@@ -17,10 +17,13 @@
 package com.android.server;
 
 import android.os.Handler;
+import android.os.HandlerExecutor;
 import android.os.HandlerThread;
 import android.os.Looper;
 import android.os.Trace;
 
+import java.util.concurrent.Executor;
+
 /**
  * Shared singleton background thread.
  *
@@ -31,6 +34,7 @@
     private static final long SLOW_DELIVERY_THRESHOLD_MS = 30_000;
     private static JobSchedulerBackgroundThread sInstance;
     private static Handler sHandler;
+    private static Executor sHandlerExecutor;
 
     private JobSchedulerBackgroundThread() {
         super("jobscheduler.bg", android.os.Process.THREAD_PRIORITY_BACKGROUND);
@@ -45,6 +49,7 @@
             looper.setSlowLogThresholdMs(
                     SLOW_DISPATCH_THRESHOLD_MS, SLOW_DELIVERY_THRESHOLD_MS);
             sHandler = new Handler(sInstance.getLooper());
+            sHandlerExecutor = new HandlerExecutor(sHandler);
         }
     }
 
@@ -63,4 +68,12 @@
             return sHandler;
         }
     }
+
+    /** Returns the singleton handler executor for JobSchedulerBackgroundThread */
+    public static Executor getExecutor() {
+        synchronized (JobSchedulerBackgroundThread.class) {
+            ensureThreadLocked();
+            return sHandlerExecutor;
+        }
+    }
 }
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 4194fd0..e2f13c560 100644
--- a/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java
+++ b/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java
@@ -4310,7 +4310,7 @@
             filter.addAction(Intent.ACTION_PACKAGE_REMOVED);
             filter.addAction(Intent.ACTION_PACKAGE_RESTARTED);
             filter.addAction(Intent.ACTION_QUERY_PACKAGE_RESTART);
-            filter.addDataScheme("package");
+            filter.addDataScheme(IntentFilter.SCHEME_PACKAGE);
             getContext().registerReceiver(this, filter);
              // Register for events related to sdcard installation.
             IntentFilter sdFilter = new IntentFilter();
diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java b/apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java
index a1a5004..b35a7be 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java
@@ -166,7 +166,7 @@
                 // Note: we can't directly do postDelayed(this::rampUpForScreenOn), because
                 // we need the exact same instance for removeCallbacks().
                 mHandler.postDelayed(mRampUpForScreenOff,
-                        mConstants.SCREEN_OFF_JOB_CONCURRENCY_INCREASE_DELAY_MS.getValue());
+                        mConstants.SCREEN_OFF_JOB_CONCURRENCY_INCREASE_DELAY_MS);
             }
         }
     }
@@ -189,7 +189,7 @@
             }
             final long now = JobSchedulerService.sElapsedRealtimeClock.millis();
             if ((mLastScreenOffRealtime
-                    + mConstants.SCREEN_OFF_JOB_CONCURRENCY_INCREASE_DELAY_MS.getValue())
+                    + mConstants.SCREEN_OFF_JOB_CONCURRENCY_INCREASE_DELAY_MS)
                     > now) {
                 return;
             }
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 4db4adc..cf4caea 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
@@ -39,7 +39,6 @@
 import android.app.usage.UsageStatsManagerInternal;
 import android.content.BroadcastReceiver;
 import android.content.ComponentName;
-import android.content.ContentResolver;
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
@@ -50,7 +49,6 @@
 import android.content.pm.PackageManagerInternal;
 import android.content.pm.ParceledListSlice;
 import android.content.pm.ServiceInfo;
-import android.database.ContentObserver;
 import android.net.Uri;
 import android.os.BatteryStats;
 import android.os.BatteryStatsInternal;
@@ -67,11 +65,10 @@
 import android.os.UserHandle;
 import android.os.UserManagerInternal;
 import android.os.WorkSource;
-import android.provider.Settings;
+import android.provider.DeviceConfig;
 import android.text.format.DateUtils;
 import android.util.ArrayMap;
 import android.util.IndentingPrintWriter;
-import android.util.KeyValueListParser;
 import android.util.Log;
 import android.util.Slog;
 import android.util.SparseArray;
@@ -88,7 +85,9 @@
 import com.android.server.AppStateTracker;
 import com.android.server.AppStateTrackerImpl;
 import com.android.server.DeviceIdleInternal;
+import com.android.server.JobSchedulerBackgroundThread;
 import com.android.server.LocalServices;
+import com.android.server.SystemService.TargetUser;
 import com.android.server.job.JobSchedulerServiceDumpProto.ActiveJob;
 import com.android.server.job.JobSchedulerServiceDumpProto.PendingJob;
 import com.android.server.job.controllers.BackgroundJobsController;
@@ -328,39 +327,70 @@
 
     // -- Pre-allocated temporaries only for use in assignJobsToContextsLocked --
 
-    private class ConstantsObserver extends ContentObserver {
-        private ContentResolver mResolver;
-
-        public ConstantsObserver(Handler handler) {
-            super(handler);
-        }
-
-        public void start(ContentResolver resolver) {
-            mResolver = resolver;
-            mResolver.registerContentObserver(Settings.Global.getUriFor(
-                    Settings.Global.JOB_SCHEDULER_CONSTANTS), false, this);
-            updateConstants();
+    private class ConstantsObserver implements DeviceConfig.OnPropertiesChangedListener {
+        public void start() {
+            DeviceConfig.addOnPropertiesChangedListener(DeviceConfig.NAMESPACE_JOB_SCHEDULER,
+                    JobSchedulerBackgroundThread.getExecutor(), this);
+            // Load all the constants.
+            onPropertiesChanged(DeviceConfig.getProperties(DeviceConfig.NAMESPACE_JOB_SCHEDULER));
         }
 
         @Override
-        public void onChange(boolean selfChange, Uri uri) {
-            updateConstants();
-        }
-
-        private void updateConstants() {
+        public void onPropertiesChanged(DeviceConfig.Properties properties) {
+            boolean apiQuotaScheduleUpdated = false;
+            boolean concurrencyUpdated = false;
             synchronized (mLock) {
-                try {
-                    mConstants.updateConstantsLocked(Settings.Global.getString(mResolver,
-                            Settings.Global.JOB_SCHEDULER_CONSTANTS));
-                    for (int controller = 0; controller < mControllers.size(); controller++) {
-                        final StateController sc = mControllers.get(controller);
-                        sc.onConstantsUpdatedLocked();
+                for (String name : properties.getKeyset()) {
+                    if (name == null) {
+                        continue;
                     }
-                    updateQuotaTracker();
-                } catch (IllegalArgumentException e) {
-                    // Failed to parse the settings string, log this and move on
-                    // with defaults.
-                    Slog.e(TAG, "Bad jobscheduler settings", e);
+                    switch (name) {
+                        case Constants.KEY_ENABLE_API_QUOTAS:
+                        case Constants.KEY_API_QUOTA_SCHEDULE_COUNT:
+                        case Constants.KEY_API_QUOTA_SCHEDULE_WINDOW_MS:
+                        case Constants.KEY_API_QUOTA_SCHEDULE_RETURN_FAILURE_RESULT:
+                        case Constants.KEY_API_QUOTA_SCHEDULE_THROW_EXCEPTION:
+                            if (!apiQuotaScheduleUpdated) {
+                                mConstants.updateApiQuotaConstantsLocked();
+                                updateQuotaTracker();
+                                apiQuotaScheduleUpdated = true;
+                            }
+                            break;
+                        case Constants.KEY_MIN_READY_NON_ACTIVE_JOBS_COUNT:
+                        case Constants.KEY_MAX_NON_ACTIVE_JOB_BATCH_DELAY_MS:
+                            mConstants.updateBatchingConstantsLocked();
+                            break;
+                        case Constants.KEY_HEAVY_USE_FACTOR:
+                        case Constants.KEY_MODERATE_USE_FACTOR:
+                            mConstants.updateUseFactorConstantsLocked();
+                            break;
+                        case Constants.KEY_SCREEN_OFF_JOB_CONCURRENCY_INCREASE_DELAY_MS:
+                            if (!concurrencyUpdated) {
+                                mConstants.updateConcurrencyConstantsLocked();
+                                concurrencyUpdated = true;
+                            }
+                            break;
+                        case Constants.KEY_MIN_LINEAR_BACKOFF_TIME_MS:
+                        case Constants.KEY_MIN_EXP_BACKOFF_TIME_MS:
+                            mConstants.updateBackoffConstantsLocked();
+                            break;
+                        case Constants.KEY_CONN_CONGESTION_DELAY_FRAC:
+                        case Constants.KEY_CONN_PREFETCH_RELAX_FRAC:
+                            mConstants.updateConnectivityConstantsLocked();
+                            break;
+                        default:
+                            // Too many max_job_* strings to list.
+                            if (name.startsWith(Constants.KEY_PREFIX_MAX_JOB)
+                                    && !concurrencyUpdated) {
+                                mConstants.updateConcurrencyConstantsLocked();
+                                concurrencyUpdated = true;
+                            }
+                            break;
+                    }
+                }
+                for (int controller = 0; controller < mControllers.size(); controller++) {
+                    final StateController sc = mControllers.get(controller);
+                    sc.onConstantsUpdatedLocked();
                 }
             }
         }
@@ -375,53 +405,52 @@
     }
 
     static class MaxJobCounts {
-        private final KeyValueListParser.IntValue mTotal;
-        private final KeyValueListParser.IntValue mMaxBg;
-        private final KeyValueListParser.IntValue mMinBg;
+        private final int mTotalDefault;
+        private final String mTotalKey;
+        private final int mMaxBgDefault;
+        private final String mMaxBgKey;
+        private final int mMinBgDefault;
+        private final String mMinBgKey;
+        private int mTotal;
+        private int mMaxBg;
+        private int mMinBg;
 
         MaxJobCounts(int totalDefault, String totalKey,
                 int maxBgDefault, String maxBgKey, int minBgDefault, String minBgKey) {
-            mTotal = new KeyValueListParser.IntValue(totalKey, totalDefault);
-            mMaxBg = new KeyValueListParser.IntValue(maxBgKey, maxBgDefault);
-            mMinBg = new KeyValueListParser.IntValue(minBgKey, minBgDefault);
+            mTotalKey = totalKey;
+            mTotal = mTotalDefault = totalDefault;
+            mMaxBgKey = maxBgKey;
+            mMaxBg = mMaxBgDefault = maxBgDefault;
+            mMinBgKey = minBgKey;
+            mMinBg = mMinBgDefault = minBgDefault;
         }
 
-        public void parse(KeyValueListParser parser) {
-            mTotal.parse(parser);
-            mMaxBg.parse(parser);
-            mMinBg.parse(parser);
+        public void update() {
+            mTotal = DeviceConfig.getInt(DeviceConfig.NAMESPACE_JOB_SCHEDULER,
+                    mTotalKey, mTotalDefault);
+            mMaxBg = DeviceConfig.getInt(DeviceConfig.NAMESPACE_JOB_SCHEDULER,
+                    mMaxBgKey, mMaxBgDefault);
+            mMinBg = DeviceConfig.getInt(DeviceConfig.NAMESPACE_JOB_SCHEDULER,
+                    mMinBgKey, mMinBgDefault);
 
-            if (mTotal.getValue() < 1) {
-                mTotal.setValue(1);
-            } else if (mTotal.getValue() > MAX_JOB_CONTEXTS_COUNT) {
-                mTotal.setValue(MAX_JOB_CONTEXTS_COUNT);
-            }
+            // Ensure total in the range [1, MAX_JOB_CONTEXTS_COUNT].
+            mTotal = Math.min(Math.max(1, mTotal), MAX_JOB_CONTEXTS_COUNT);
 
-            if (mMaxBg.getValue() < 1) {
-                mMaxBg.setValue(1);
-            } else if (mMaxBg.getValue() > mTotal.getValue()) {
-                mMaxBg.setValue(mTotal.getValue());
-            }
-            if (mMinBg.getValue() < 0) {
-                mMinBg.setValue(0);
-            } else {
-                if (mMinBg.getValue() > mMaxBg.getValue()) {
-                    mMinBg.setValue(mMaxBg.getValue());
-                }
-                if (mMinBg.getValue() >= mTotal.getValue()) {
-                    mMinBg.setValue(mTotal.getValue() - 1);
-                }
-            }
+            // Ensure maxBg in the range [1, total].
+            mMaxBg = Math.min(Math.max(1, mMaxBg), mTotal);
+
+            // Ensure minBg in the range [0, min(maxBg, total - 1)]
+            mMinBg = Math.min(Math.max(0, mMinBg), Math.min(mMaxBg, mTotal - 1));
         }
 
         /** Total number of jobs to run simultaneously. */
         public int getMaxTotal() {
-            return mTotal.getValue();
+            return mTotal;
         }
 
         /** Max number of BG (== owned by non-TOP apps) jobs to run simultaneously. */
         public int getMaxBg() {
-            return mMaxBg.getValue();
+            return mMaxBg;
         }
 
         /**
@@ -429,20 +458,34 @@
          * pending, rather than always running the TOTAL number of FG jobs.
          */
         public int getMinBg() {
-            return mMinBg.getValue();
+            return mMinBg;
         }
 
         public void dump(PrintWriter pw, String prefix) {
-            mTotal.dump(pw, prefix);
-            mMaxBg.dump(pw, prefix);
-            mMinBg.dump(pw, prefix);
+            pw.print(prefix);
+            pw.print(mTotalKey);
+            pw.print("=");
+            pw.print(mTotal);
+            pw.println();
+
+            pw.print(prefix);
+            pw.print(mMaxBgKey);
+            pw.print("=");
+            pw.print(mMaxBg);
+            pw.println();
+
+            pw.print(prefix);
+            pw.print(mMinBgKey);
+            pw.print("=");
+            pw.print(mMinBg);
+            pw.println();
         }
 
         public void dumpProto(ProtoOutputStream proto, long fieldId) {
             final long token = proto.start(fieldId);
-            mTotal.dumpProto(proto, MaxJobCountsProto.TOTAL_JOBS);
-            mMaxBg.dumpProto(proto, MaxJobCountsProto.MAX_BG);
-            mMinBg.dumpProto(proto, MaxJobCountsProto.MIN_BG);
+            proto.write(MaxJobCountsProto.TOTAL_JOBS, mTotal);
+            proto.write(MaxJobCountsProto.MAX_BG, mMaxBg);
+            proto.write(MaxJobCountsProto.MIN_BG, mMinBg);
             proto.end(token);
         }
     }
@@ -475,23 +518,11 @@
     }
 
     /**
-     * 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
+     * All times are in milliseconds. Any access to this class or its fields should be done while
      * holding the JobSchedulerService.mLock lock.
      */
     public static class Constants {
         // Key names stored in the settings value.
-        // TODO(124466289): remove deprecated flags when we migrate to DeviceConfig
-        private static final String DEPRECATED_KEY_MIN_IDLE_COUNT = "min_idle_count";
-        private static final String DEPRECATED_KEY_MIN_CHARGING_COUNT = "min_charging_count";
-        private static final String DEPRECATED_KEY_MIN_BATTERY_NOT_LOW_COUNT =
-                "min_battery_not_low_count";
-        private static final String DEPRECATED_KEY_MIN_STORAGE_NOT_LOW_COUNT =
-                "min_storage_not_low_count";
-        private static final String DEPRECATED_KEY_MIN_CONNECTIVITY_COUNT =
-                "min_connectivity_count";
-        private static final String DEPRECATED_KEY_MIN_CONTENT_COUNT = "min_content_count";
-        private static final String DEPRECATED_KEY_MIN_READY_JOBS_COUNT = "min_ready_jobs_count";
         private static final String KEY_MIN_READY_NON_ACTIVE_JOBS_COUNT =
                 "min_ready_non_active_jobs_count";
         private static final String KEY_MAX_NON_ACTIVE_JOB_BATCH_DELAY_MS =
@@ -499,28 +530,10 @@
         private static final String KEY_HEAVY_USE_FACTOR = "heavy_use_factor";
         private static final String KEY_MODERATE_USE_FACTOR = "moderate_use_factor";
 
-        // The following values used to be used on P and below. Do not reuse them.
-        private static final String DEPRECATED_KEY_FG_JOB_COUNT = "fg_job_count";
-        private static final String DEPRECATED_KEY_BG_NORMAL_JOB_COUNT = "bg_normal_job_count";
-        private static final String DEPRECATED_KEY_BG_MODERATE_JOB_COUNT = "bg_moderate_job_count";
-        private static final String DEPRECATED_KEY_BG_LOW_JOB_COUNT = "bg_low_job_count";
-        private static final String DEPRECATED_KEY_BG_CRITICAL_JOB_COUNT = "bg_critical_job_count";
-
-        private static final String DEPRECATED_KEY_MAX_STANDARD_RESCHEDULE_COUNT
-                = "max_standard_reschedule_count";
-        private static final String DEPRECATED_KEY_MAX_WORK_RESCHEDULE_COUNT =
-                "max_work_reschedule_count";
-        private static final String KEY_MIN_LINEAR_BACKOFF_TIME = "min_linear_backoff_time";
-        private static final String KEY_MIN_EXP_BACKOFF_TIME = "min_exp_backoff_time";
-        private static final String DEPRECATED_KEY_STANDBY_HEARTBEAT_TIME =
-                "standby_heartbeat_time";
-        private static final String DEPRECATED_KEY_STANDBY_WORKING_BEATS = "standby_working_beats";
-        private static final String DEPRECATED_KEY_STANDBY_FREQUENT_BEATS =
-                "standby_frequent_beats";
-        private static final String DEPRECATED_KEY_STANDBY_RARE_BEATS = "standby_rare_beats";
+        private static final String KEY_MIN_LINEAR_BACKOFF_TIME_MS = "min_linear_backoff_time_ms";
+        private static final String KEY_MIN_EXP_BACKOFF_TIME_MS = "min_exp_backoff_time_ms";
         private static final String KEY_CONN_CONGESTION_DELAY_FRAC = "conn_congestion_delay_frac";
         private static final String KEY_CONN_PREFETCH_RELAX_FRAC = "conn_prefetch_relax_frac";
-        private static final String DEPRECATED_KEY_USE_HEARTBEATS = "use_heartbeats";
         private static final String KEY_ENABLE_API_QUOTAS = "enable_api_quotas";
         private static final String KEY_API_QUOTA_SCHEDULE_COUNT = "aq_schedule_count";
         private static final String KEY_API_QUOTA_SCHEDULE_WINDOW_MS = "aq_schedule_window_ms";
@@ -529,12 +542,15 @@
         private static final String KEY_API_QUOTA_SCHEDULE_RETURN_FAILURE_RESULT =
                 "aq_schedule_return_failure";
 
+        private static final String KEY_SCREEN_OFF_JOB_CONCURRENCY_INCREASE_DELAY_MS =
+                "screen_off_job_concurrency_increase_delay_ms";
+
         private static final int DEFAULT_MIN_READY_NON_ACTIVE_JOBS_COUNT = 5;
         private static final long DEFAULT_MAX_NON_ACTIVE_JOB_BATCH_DELAY_MS = 31 * MINUTE_IN_MILLIS;
         private static final float DEFAULT_HEAVY_USE_FACTOR = .9f;
         private static final float DEFAULT_MODERATE_USE_FACTOR = .5f;
-        private static final long DEFAULT_MIN_LINEAR_BACKOFF_TIME = JobInfo.MIN_BACKOFF_MILLIS;
-        private static final long DEFAULT_MIN_EXP_BACKOFF_TIME = JobInfo.MIN_BACKOFF_MILLIS;
+        private static final long DEFAULT_MIN_LINEAR_BACKOFF_TIME_MS = JobInfo.MIN_BACKOFF_MILLIS;
+        private static final long DEFAULT_MIN_EXP_BACKOFF_TIME_MS = JobInfo.MIN_BACKOFF_MILLIS;
         private static final float DEFAULT_CONN_CONGESTION_DELAY_FRAC = 0.5f;
         private static final float DEFAULT_CONN_PREFETCH_RELAX_FRAC = 0.5f;
         private static final boolean DEFAULT_ENABLE_API_QUOTAS = true;
@@ -542,6 +558,7 @@
         private static final long DEFAULT_API_QUOTA_SCHEDULE_WINDOW_MS = MINUTE_IN_MILLIS;
         private static final boolean DEFAULT_API_QUOTA_SCHEDULE_THROW_EXCEPTION = true;
         private static final boolean DEFAULT_API_QUOTA_SCHEDULE_RETURN_FAILURE_RESULT = false;
+        private static final long DEFAULT_SCREEN_OFF_JOB_CONCURRENCY_INCREASE_DELAY_MS = 30_000;
 
         /**
          * Minimum # of non-ACTIVE jobs for which the JMS will be happy running some work early.
@@ -563,59 +580,61 @@
          */
         float MODERATE_USE_FACTOR = DEFAULT_MODERATE_USE_FACTOR;
 
+        /** Prefix for all of the max_job constants. */
+        private static final String KEY_PREFIX_MAX_JOB = "max_job_";
+
         // Max job counts for screen on / off, for each memory trim level.
         final MaxJobCountsPerMemoryTrimLevel MAX_JOB_COUNTS_SCREEN_ON =
                 new MaxJobCountsPerMemoryTrimLevel(
                         new MaxJobCounts(
-                                8, "max_job_total_on_normal",
-                                6, "max_job_max_bg_on_normal",
-                                2, "max_job_min_bg_on_normal"),
+                                8, KEY_PREFIX_MAX_JOB + "total_on_normal",
+                                6, KEY_PREFIX_MAX_JOB + "max_bg_on_normal",
+                                2, KEY_PREFIX_MAX_JOB + "min_bg_on_normal"),
                         new MaxJobCounts(
-                                8, "max_job_total_on_moderate",
-                                4, "max_job_max_bg_on_moderate",
-                                2, "max_job_min_bg_on_moderate"),
+                                8, KEY_PREFIX_MAX_JOB + "total_on_moderate",
+                                4, KEY_PREFIX_MAX_JOB + "max_bg_on_moderate",
+                                2, KEY_PREFIX_MAX_JOB + "min_bg_on_moderate"),
                         new MaxJobCounts(
-                                5, "max_job_total_on_low",
-                                1, "max_job_max_bg_on_low",
-                                1, "max_job_min_bg_on_low"),
+                                5, KEY_PREFIX_MAX_JOB + "total_on_low",
+                                1, KEY_PREFIX_MAX_JOB + "max_bg_on_low",
+                                1, KEY_PREFIX_MAX_JOB + "min_bg_on_low"),
                         new MaxJobCounts(
-                                5, "max_job_total_on_critical",
-                                1, "max_job_max_bg_on_critical",
-                                1, "max_job_min_bg_on_critical"));
+                                5, KEY_PREFIX_MAX_JOB + "total_on_critical",
+                                1, KEY_PREFIX_MAX_JOB + "max_bg_on_critical",
+                                1, KEY_PREFIX_MAX_JOB + "min_bg_on_critical"));
 
         final MaxJobCountsPerMemoryTrimLevel MAX_JOB_COUNTS_SCREEN_OFF =
                 new MaxJobCountsPerMemoryTrimLevel(
                         new MaxJobCounts(
-                                10, "max_job_total_off_normal",
-                                6, "max_job_max_bg_off_normal",
-                                2, "max_job_min_bg_off_normal"),
+                                10, KEY_PREFIX_MAX_JOB + "total_off_normal",
+                                6, KEY_PREFIX_MAX_JOB + "max_bg_off_normal",
+                                2, KEY_PREFIX_MAX_JOB + "min_bg_off_normal"),
                         new MaxJobCounts(
-                                10, "max_job_total_off_moderate",
-                                4, "max_job_max_bg_off_moderate",
-                                2, "max_job_min_bg_off_moderate"),
+                                10, KEY_PREFIX_MAX_JOB + "total_off_moderate",
+                                4, KEY_PREFIX_MAX_JOB + "max_bg_off_moderate",
+                                2, KEY_PREFIX_MAX_JOB + "min_bg_off_moderate"),
                         new MaxJobCounts(
-                                5, "max_job_total_off_low",
-                                1, "max_job_max_bg_off_low",
-                                1, "max_job_min_bg_off_low"),
+                                5, KEY_PREFIX_MAX_JOB + "total_off_low",
+                                1, KEY_PREFIX_MAX_JOB + "max_bg_off_low",
+                                1, KEY_PREFIX_MAX_JOB + "min_bg_off_low"),
                         new MaxJobCounts(
-                                5, "max_job_total_off_critical",
-                                1, "max_job_max_bg_off_critical",
-                                1, "max_job_min_bg_off_critical"));
+                                5, KEY_PREFIX_MAX_JOB + "total_off_critical",
+                                1, KEY_PREFIX_MAX_JOB + "max_bg_off_critical",
+                                1, KEY_PREFIX_MAX_JOB + "min_bg_off_critical"));
 
 
         /** Wait for this long after screen off before increasing the job concurrency. */
-        final KeyValueListParser.IntValue SCREEN_OFF_JOB_CONCURRENCY_INCREASE_DELAY_MS =
-                new KeyValueListParser.IntValue(
-                        "screen_off_job_concurrency_increase_delay_ms", 30_000);
+        long SCREEN_OFF_JOB_CONCURRENCY_INCREASE_DELAY_MS =
+                DEFAULT_SCREEN_OFF_JOB_CONCURRENCY_INCREASE_DELAY_MS;
 
         /**
          * The minimum backoff time to allow for linear backoff.
          */
-        long MIN_LINEAR_BACKOFF_TIME = DEFAULT_MIN_LINEAR_BACKOFF_TIME;
+        long MIN_LINEAR_BACKOFF_TIME_MS = DEFAULT_MIN_LINEAR_BACKOFF_TIME_MS;
         /**
          * The minimum backoff time to allow for exponential backoff.
          */
-        long MIN_EXP_BACKOFF_TIME = DEFAULT_MIN_EXP_BACKOFF_TIME;
+        long MIN_EXP_BACKOFF_TIME_MS = DEFAULT_MIN_EXP_BACKOFF_TIME_MS;
 
         /**
          * The fraction of a job's running window that must pass before we
@@ -651,61 +670,78 @@
         public boolean API_QUOTA_SCHEDULE_RETURN_FAILURE_RESULT =
                 DEFAULT_API_QUOTA_SCHEDULE_RETURN_FAILURE_RESULT;
 
-        private final KeyValueListParser mParser = new KeyValueListParser(',');
-
-        void updateConstantsLocked(String value) {
-            try {
-                mParser.setString(value);
-            } catch (Exception e) {
-                // Failed to parse the settings string, log this and move on
-                // with defaults.
-                Slog.e(TAG, "Bad jobscheduler settings", e);
-            }
-
-            MIN_READY_NON_ACTIVE_JOBS_COUNT = mParser.getInt(
+        private void updateBatchingConstantsLocked() {
+            MIN_READY_NON_ACTIVE_JOBS_COUNT = DeviceConfig.getInt(
+                    DeviceConfig.NAMESPACE_JOB_SCHEDULER,
                     KEY_MIN_READY_NON_ACTIVE_JOBS_COUNT,
                     DEFAULT_MIN_READY_NON_ACTIVE_JOBS_COUNT);
-            MAX_NON_ACTIVE_JOB_BATCH_DELAY_MS = mParser.getLong(
+            MAX_NON_ACTIVE_JOB_BATCH_DELAY_MS = DeviceConfig.getLong(
+                    DeviceConfig.NAMESPACE_JOB_SCHEDULER,
                     KEY_MAX_NON_ACTIVE_JOB_BATCH_DELAY_MS,
                     DEFAULT_MAX_NON_ACTIVE_JOB_BATCH_DELAY_MS);
-            HEAVY_USE_FACTOR = mParser.getFloat(KEY_HEAVY_USE_FACTOR,
+        }
+
+        private void updateUseFactorConstantsLocked() {
+            HEAVY_USE_FACTOR = DeviceConfig.getFloat(DeviceConfig.NAMESPACE_JOB_SCHEDULER,
+                    KEY_HEAVY_USE_FACTOR,
                     DEFAULT_HEAVY_USE_FACTOR);
-            MODERATE_USE_FACTOR = mParser.getFloat(KEY_MODERATE_USE_FACTOR,
+            MODERATE_USE_FACTOR = DeviceConfig.getFloat(DeviceConfig.NAMESPACE_JOB_SCHEDULER,
+                    KEY_MODERATE_USE_FACTOR,
                     DEFAULT_MODERATE_USE_FACTOR);
+        }
 
-            MAX_JOB_COUNTS_SCREEN_ON.normal.parse(mParser);
-            MAX_JOB_COUNTS_SCREEN_ON.moderate.parse(mParser);
-            MAX_JOB_COUNTS_SCREEN_ON.low.parse(mParser);
-            MAX_JOB_COUNTS_SCREEN_ON.critical.parse(mParser);
+        void updateConcurrencyConstantsLocked() {
+            MAX_JOB_COUNTS_SCREEN_ON.normal.update();
+            MAX_JOB_COUNTS_SCREEN_ON.moderate.update();
+            MAX_JOB_COUNTS_SCREEN_ON.low.update();
+            MAX_JOB_COUNTS_SCREEN_ON.critical.update();
 
-            MAX_JOB_COUNTS_SCREEN_OFF.normal.parse(mParser);
-            MAX_JOB_COUNTS_SCREEN_OFF.moderate.parse(mParser);
-            MAX_JOB_COUNTS_SCREEN_OFF.low.parse(mParser);
-            MAX_JOB_COUNTS_SCREEN_OFF.critical.parse(mParser);
+            MAX_JOB_COUNTS_SCREEN_OFF.normal.update();
+            MAX_JOB_COUNTS_SCREEN_OFF.moderate.update();
+            MAX_JOB_COUNTS_SCREEN_OFF.low.update();
+            MAX_JOB_COUNTS_SCREEN_OFF.critical.update();
 
-            SCREEN_OFF_JOB_CONCURRENCY_INCREASE_DELAY_MS.parse(mParser);
+            SCREEN_OFF_JOB_CONCURRENCY_INCREASE_DELAY_MS = DeviceConfig.getLong(
+                    DeviceConfig.NAMESPACE_JOB_SCHEDULER,
+                    KEY_SCREEN_OFF_JOB_CONCURRENCY_INCREASE_DELAY_MS,
+                    DEFAULT_SCREEN_OFF_JOB_CONCURRENCY_INCREASE_DELAY_MS);
+        }
 
-            MIN_LINEAR_BACKOFF_TIME = mParser.getDurationMillis(KEY_MIN_LINEAR_BACKOFF_TIME,
-                    DEFAULT_MIN_LINEAR_BACKOFF_TIME);
-            MIN_EXP_BACKOFF_TIME = mParser.getDurationMillis(KEY_MIN_EXP_BACKOFF_TIME,
-                    DEFAULT_MIN_EXP_BACKOFF_TIME);
-            CONN_CONGESTION_DELAY_FRAC = mParser.getFloat(KEY_CONN_CONGESTION_DELAY_FRAC,
+        private void updateBackoffConstantsLocked() {
+            MIN_LINEAR_BACKOFF_TIME_MS = DeviceConfig.getLong(DeviceConfig.NAMESPACE_JOB_SCHEDULER,
+                    KEY_MIN_LINEAR_BACKOFF_TIME_MS,
+                    DEFAULT_MIN_LINEAR_BACKOFF_TIME_MS);
+            MIN_EXP_BACKOFF_TIME_MS = DeviceConfig.getLong(DeviceConfig.NAMESPACE_JOB_SCHEDULER,
+                    KEY_MIN_EXP_BACKOFF_TIME_MS,
+                    DEFAULT_MIN_EXP_BACKOFF_TIME_MS);
+        }
+
+        private void updateConnectivityConstantsLocked() {
+            CONN_CONGESTION_DELAY_FRAC = DeviceConfig.getFloat(DeviceConfig.NAMESPACE_JOB_SCHEDULER,
+                    KEY_CONN_CONGESTION_DELAY_FRAC,
                     DEFAULT_CONN_CONGESTION_DELAY_FRAC);
-            CONN_PREFETCH_RELAX_FRAC = mParser.getFloat(KEY_CONN_PREFETCH_RELAX_FRAC,
+            CONN_PREFETCH_RELAX_FRAC = DeviceConfig.getFloat(DeviceConfig.NAMESPACE_JOB_SCHEDULER,
+                    KEY_CONN_PREFETCH_RELAX_FRAC,
                     DEFAULT_CONN_PREFETCH_RELAX_FRAC);
+        }
 
-            ENABLE_API_QUOTAS = mParser.getBoolean(KEY_ENABLE_API_QUOTAS,
-                DEFAULT_ENABLE_API_QUOTAS);
+        private void updateApiQuotaConstantsLocked() {
+            ENABLE_API_QUOTAS = DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_JOB_SCHEDULER,
+                    KEY_ENABLE_API_QUOTAS, DEFAULT_ENABLE_API_QUOTAS);
             // Set a minimum value on the quota limit so it's not so low that it interferes with
             // legitimate use cases.
             API_QUOTA_SCHEDULE_COUNT = Math.max(250,
-                    mParser.getInt(KEY_API_QUOTA_SCHEDULE_COUNT, DEFAULT_API_QUOTA_SCHEDULE_COUNT));
-            API_QUOTA_SCHEDULE_WINDOW_MS = mParser.getDurationMillis(
+                    DeviceConfig.getInt(DeviceConfig.NAMESPACE_JOB_SCHEDULER,
+                            KEY_API_QUOTA_SCHEDULE_COUNT, DEFAULT_API_QUOTA_SCHEDULE_COUNT));
+            API_QUOTA_SCHEDULE_WINDOW_MS = DeviceConfig.getLong(
+                    DeviceConfig.NAMESPACE_JOB_SCHEDULER,
                 KEY_API_QUOTA_SCHEDULE_WINDOW_MS, DEFAULT_API_QUOTA_SCHEDULE_WINDOW_MS);
-            API_QUOTA_SCHEDULE_THROW_EXCEPTION = mParser.getBoolean(
+            API_QUOTA_SCHEDULE_THROW_EXCEPTION = DeviceConfig.getBoolean(
+                    DeviceConfig.NAMESPACE_JOB_SCHEDULER,
                     KEY_API_QUOTA_SCHEDULE_THROW_EXCEPTION,
                     DEFAULT_API_QUOTA_SCHEDULE_THROW_EXCEPTION);
-            API_QUOTA_SCHEDULE_RETURN_FAILURE_RESULT = mParser.getBoolean(
+            API_QUOTA_SCHEDULE_RETURN_FAILURE_RESULT = DeviceConfig.getBoolean(
+                    DeviceConfig.NAMESPACE_JOB_SCHEDULER,
                     KEY_API_QUOTA_SCHEDULE_RETURN_FAILURE_RESULT,
                     DEFAULT_API_QUOTA_SCHEDULE_RETURN_FAILURE_RESULT);
         }
@@ -730,10 +766,11 @@
             MAX_JOB_COUNTS_SCREEN_OFF.low.dump(pw, "");
             MAX_JOB_COUNTS_SCREEN_OFF.critical.dump(pw, "");
 
-            SCREEN_OFF_JOB_CONCURRENCY_INCREASE_DELAY_MS.dump(pw, "");
+            pw.print(KEY_SCREEN_OFF_JOB_CONCURRENCY_INCREASE_DELAY_MS,
+                    SCREEN_OFF_JOB_CONCURRENCY_INCREASE_DELAY_MS).println();
 
-            pw.print(KEY_MIN_LINEAR_BACKOFF_TIME, MIN_LINEAR_BACKOFF_TIME).println();
-            pw.print(KEY_MIN_EXP_BACKOFF_TIME, MIN_EXP_BACKOFF_TIME).println();
+            pw.print(KEY_MIN_LINEAR_BACKOFF_TIME_MS, MIN_LINEAR_BACKOFF_TIME_MS).println();
+            pw.print(KEY_MIN_EXP_BACKOFF_TIME_MS, MIN_EXP_BACKOFF_TIME_MS).println();
             pw.print(KEY_CONN_CONGESTION_DELAY_FRAC, CONN_CONGESTION_DELAY_FRAC).println();
             pw.print(KEY_CONN_PREFETCH_RELAX_FRAC, CONN_PREFETCH_RELAX_FRAC).println();
 
@@ -759,11 +796,11 @@
             MAX_JOB_COUNTS_SCREEN_ON.dumpProto(proto, ConstantsProto.MAX_JOB_COUNTS_SCREEN_ON);
             MAX_JOB_COUNTS_SCREEN_OFF.dumpProto(proto, ConstantsProto.MAX_JOB_COUNTS_SCREEN_OFF);
 
-            SCREEN_OFF_JOB_CONCURRENCY_INCREASE_DELAY_MS.dumpProto(proto,
-                    ConstantsProto.SCREEN_OFF_JOB_CONCURRENCY_INCREASE_DELAY_MS);
+            proto.write(ConstantsProto.SCREEN_OFF_JOB_CONCURRENCY_INCREASE_DELAY_MS,
+                    SCREEN_OFF_JOB_CONCURRENCY_INCREASE_DELAY_MS);
 
-            proto.write(ConstantsProto.MIN_LINEAR_BACKOFF_TIME_MS, MIN_LINEAR_BACKOFF_TIME);
-            proto.write(ConstantsProto.MIN_EXP_BACKOFF_TIME_MS, MIN_EXP_BACKOFF_TIME);
+            proto.write(ConstantsProto.MIN_LINEAR_BACKOFF_TIME_MS, MIN_LINEAR_BACKOFF_TIME_MS);
+            proto.write(ConstantsProto.MIN_EXP_BACKOFF_TIME_MS, MIN_EXP_BACKOFF_TIME_MS);
             proto.write(ConstantsProto.CONN_CONGESTION_DELAY_FRAC, CONN_CONGESTION_DELAY_FRAC);
             proto.write(ConstantsProto.CONN_PREFETCH_RELAX_FRAC, CONN_PREFETCH_RELAX_FRAC);
 
@@ -975,24 +1012,24 @@
     }
 
     @Override
-    public void onStartUser(int userHandle) {
+    public void onUserStarting(@NonNull TargetUser user) {
         synchronized (mLock) {
-            mStartedUsers = ArrayUtils.appendInt(mStartedUsers, userHandle);
+            mStartedUsers = ArrayUtils.appendInt(mStartedUsers, user.getUserIdentifier());
         }
         // Let's kick any outstanding jobs for this user.
         mHandler.obtainMessage(MSG_CHECK_JOB).sendToTarget();
     }
 
     @Override
-    public void onUnlockUser(int userHandle) {
+    public void onUserUnlocking(@NonNull TargetUser user) {
         // Let's kick any outstanding jobs for this user.
         mHandler.obtainMessage(MSG_CHECK_JOB).sendToTarget();
     }
 
     @Override
-    public void onStopUser(int userHandle) {
+    public void onUserStopping(@NonNull TargetUser user) {
         synchronized (mLock) {
-            mStartedUsers = ArrayUtils.removeInt(mStartedUsers, userHandle);
+            mStartedUsers = ArrayUtils.removeInt(mStartedUsers, user.getUserIdentifier());
         }
     }
 
@@ -1406,7 +1443,7 @@
 
         mHandler = new JobHandler(context.getMainLooper());
         mConstants = new Constants();
-        mConstantsObserver = new ConstantsObserver(mHandler);
+        mConstantsObserver = new ConstantsObserver();
         mJobSchedulerStub = new JobSchedulerStub();
 
         mConcurrencyManager = new JobConcurrencyManager(this);
@@ -1520,7 +1557,7 @@
     @Override
     public void onBootPhase(int phase) {
         if (PHASE_SYSTEM_SERVICES_READY == phase) {
-            mConstantsObserver.start(getContext().getContentResolver());
+            mConstantsObserver.start();
             for (StateController controller : mControllers) {
                 controller.onSystemServicesReady();
             }
@@ -1692,8 +1729,8 @@
         switch (job.getBackoffPolicy()) {
             case JobInfo.BACKOFF_POLICY_LINEAR: {
                 long backoff = initialBackoffMillis;
-                if (backoff < mConstants.MIN_LINEAR_BACKOFF_TIME) {
-                    backoff = mConstants.MIN_LINEAR_BACKOFF_TIME;
+                if (backoff < mConstants.MIN_LINEAR_BACKOFF_TIME_MS) {
+                    backoff = mConstants.MIN_LINEAR_BACKOFF_TIME_MS;
                 }
                 delayMillis = backoff * backoffAttempts;
             } break;
@@ -1703,8 +1740,8 @@
                 }
             case JobInfo.BACKOFF_POLICY_EXPONENTIAL: {
                 long backoff = initialBackoffMillis;
-                if (backoff < mConstants.MIN_EXP_BACKOFF_TIME) {
-                    backoff = mConstants.MIN_EXP_BACKOFF_TIME;
+                if (backoff < mConstants.MIN_EXP_BACKOFF_TIME_MS) {
+                    backoff = mConstants.MIN_EXP_BACKOFF_TIME_MS;
                 }
                 delayMillis = (long) Math.scalb(backoff, backoffAttempts - 1);
             } break;
diff --git a/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java b/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java
index 6bc95bf..c033138 100644
--- a/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java
+++ b/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java
@@ -2131,7 +2131,7 @@
         }
 
         public List<UserHandle> getValidCrossProfileTargets(String pkg, int userId) {
-            final int uid = mPackageManagerInternal.getPackageUidInternal(pkg, 0, userId);
+            final int uid = mPackageManagerInternal.getPackageUid(pkg, /* flags= */ 0, userId);
             final AndroidPackage aPkg = mPackageManagerInternal.getPackage(uid);
             if (uid < 0
                     || aPkg == null
diff --git a/api/current.txt b/api/current.txt
index a59c7b8..c6af6be 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -5596,6 +5596,7 @@
     method public android.graphics.drawable.Icon getIcon();
     method public android.app.RemoteInput[] getRemoteInputs();
     method public int getSemanticAction();
+    method public boolean isAuthenticationRequired();
     method public boolean isContextual();
     method public void writeToParcel(android.os.Parcel, int);
     field @NonNull public static final android.os.Parcelable.Creator<android.app.Notification.Action> CREATOR;
@@ -5625,6 +5626,7 @@
     method @NonNull public android.app.Notification.Action.Builder extend(android.app.Notification.Action.Extender);
     method @NonNull public android.os.Bundle getExtras();
     method @NonNull public android.app.Notification.Action.Builder setAllowGeneratedReplies(boolean);
+    method @NonNull public android.app.Notification.Action.Builder setAuthenticationRequired(boolean);
     method @NonNull public android.app.Notification.Action.Builder setContextual(boolean);
     method @NonNull public android.app.Notification.Action.Builder setSemanticAction(int);
   }
@@ -11899,6 +11901,7 @@
     field public static final android.os.Parcelable.Creator<android.content.pm.PackageInstaller.SessionInfo> CREATOR;
     field public static final int INVALID_ID = -1; // 0xffffffff
     field public static final int STAGED_SESSION_ACTIVATION_FAILED = 2; // 0x2
+    field public static final int STAGED_SESSION_CONFLICT = 4; // 0x4
     field public static final int STAGED_SESSION_NO_ERROR = 0; // 0x0
     field public static final int STAGED_SESSION_UNKNOWN = 3; // 0x3
     field public static final int STAGED_SESSION_VERIFICATION_FAILED = 1; // 0x1
@@ -14247,6 +14250,10 @@
     enum_constant public static final android.graphics.BlurMaskFilter.Blur SOLID;
   }
 
+  public final class BlurShader extends android.graphics.Shader {
+    ctor public BlurShader(float, float, @Nullable android.graphics.Shader);
+  }
+
   public class Camera {
     ctor public Camera();
     method public void applyToCanvas(android.graphics.Canvas);
@@ -24064,6 +24071,8 @@
     method public boolean isSink();
     method public boolean isSource();
     field public static final int TYPE_AUX_LINE = 19; // 0x13
+    field public static final int TYPE_BLE_HEADSET = 26; // 0x1a
+    field public static final int TYPE_BLE_SPEAKER = 27; // 0x1b
     field public static final int TYPE_BLUETOOTH_A2DP = 8; // 0x8
     field public static final int TYPE_BLUETOOTH_SCO = 7; // 0x7
     field public static final int TYPE_BUILTIN_EARPIECE = 1; // 0x1
@@ -24207,6 +24216,7 @@
     method @NonNull public java.util.List<android.media.AudioPlaybackConfiguration> getActivePlaybackConfigurations();
     method @NonNull public java.util.List<android.media.AudioRecordingConfiguration> getActiveRecordingConfigurations();
     method public int getAllowedCapturePolicy();
+    method public int getAudioHwSyncForSession(int);
     method public android.media.AudioDeviceInfo[] getDevices(int);
     method public java.util.List<android.media.MicrophoneInfo> getMicrophones() throws java.io.IOException;
     method public int getMode();
@@ -31614,6 +31624,7 @@
     method @Nullable public String getPassphrase();
     method @Nullable public android.net.wifi.hotspot2.PasspointConfiguration getPasspointConfig();
     method @IntRange(from=0) public int getPriority();
+    method public int getPriorityGroup();
     method @Nullable public String getSsid();
     method public boolean isAppInteractionRequired();
     method public boolean isCredentialSharedWithUser();
@@ -31640,6 +31651,7 @@
     method @NonNull public android.net.wifi.WifiNetworkSuggestion.Builder setIsUserInteractionRequired(boolean);
     method @NonNull public android.net.wifi.WifiNetworkSuggestion.Builder setPasspointConfig(@NonNull android.net.wifi.hotspot2.PasspointConfiguration);
     method @NonNull public android.net.wifi.WifiNetworkSuggestion.Builder setPriority(@IntRange(from=0) int);
+    method @NonNull public android.net.wifi.WifiNetworkSuggestion.Builder setPriorityGroup(int);
     method @NonNull public android.net.wifi.WifiNetworkSuggestion.Builder setSsid(@NonNull String);
     method @NonNull public android.net.wifi.WifiNetworkSuggestion.Builder setUntrusted(boolean);
     method @NonNull public android.net.wifi.WifiNetworkSuggestion.Builder setWapiEnterpriseConfig(@NonNull android.net.wifi.WifiEnterpriseConfig);
@@ -36727,9 +36739,11 @@
   public final class PowerManager {
     method public void addThermalStatusListener(@NonNull android.os.PowerManager.OnThermalStatusChangedListener);
     method public void addThermalStatusListener(@NonNull java.util.concurrent.Executor, @NonNull android.os.PowerManager.OnThermalStatusChangedListener);
+    method @Nullable public java.time.Duration getBatteryDischargePrediction();
     method public int getCurrentThermalStatus();
     method public int getLocationPowerSaveMode();
     method public float getThermalHeadroom(@IntRange(from=0, to=60) int);
+    method public boolean isBatteryDischargePredictionPersonalized();
     method public boolean isDeviceIdleMode();
     method public boolean isIgnoringBatteryOptimizations(String);
     method public boolean isInteractive();
@@ -45970,7 +45984,9 @@
     method public final void addExistingConnection(android.telecom.PhoneAccountHandle, android.telecom.Connection);
     method public final void conferenceRemoteConnections(android.telecom.RemoteConnection, android.telecom.RemoteConnection);
     method public final void connectionServiceFocusReleased();
+    method @Nullable public final android.telecom.RemoteConference createRemoteIncomingConference(@Nullable android.telecom.PhoneAccountHandle, @Nullable android.telecom.ConnectionRequest);
     method public final android.telecom.RemoteConnection createRemoteIncomingConnection(android.telecom.PhoneAccountHandle, android.telecom.ConnectionRequest);
+    method @Nullable public final android.telecom.RemoteConference createRemoteOutgoingConference(@Nullable android.telecom.PhoneAccountHandle, @Nullable android.telecom.ConnectionRequest);
     method public final android.telecom.RemoteConnection createRemoteOutgoingConnection(android.telecom.PhoneAccountHandle, android.telecom.ConnectionRequest);
     method public final java.util.Collection<android.telecom.Conference> getAllConferences();
     method public final java.util.Collection<android.telecom.Connection> getAllConnections();
@@ -46201,6 +46217,7 @@
 
   public final class RemoteConnection {
     method public void abort();
+    method public void addConferenceParticipants(@NonNull java.util.List<android.net.Uri>);
     method public void answer();
     method public void disconnect();
     method public android.net.Uri getAddress();
@@ -46978,7 +46995,7 @@
     method public long getNci();
     method @IntRange(from=0, to=3279165) public int getNrarfcn();
     method @IntRange(from=0, to=1007) public int getPci();
-    method @IntRange(from=0, to=65535) public int getTac();
+    method @IntRange(from=0, to=16777215) public int getTac();
     method public void writeToParcel(android.os.Parcel, int);
     field @NonNull public static final android.os.Parcelable.Creator<android.telephony.CellIdentityNr> CREATOR;
   }
@@ -47827,6 +47844,7 @@
     method public void onDataConnectionStateChanged(int);
     method public void onDataConnectionStateChanged(int, int);
     method @RequiresPermission("android.permission.READ_PHONE_STATE") public void onDisplayInfoChanged(@NonNull android.telephony.TelephonyDisplayInfo);
+    method public void onEmergencyNumberListChanged(@NonNull java.util.Map<java.lang.Integer,java.util.List<android.telephony.emergency.EmergencyNumber>>);
     method @RequiresPermission("android.permission.READ_PRECISE_PHONE_STATE") public void onImsCallDisconnectCauseChanged(@NonNull android.telephony.ims.ImsReasonInfo);
     method public void onMessageWaitingIndicatorChanged(boolean);
     method @RequiresPermission("android.permission.MODIFY_PHONE_STATE") public void onPreciseDataConnectionStateChanged(@NonNull android.telephony.PreciseDataConnectionState);
@@ -47864,6 +47882,7 @@
     method @Nullable public android.net.LinkProperties getLinkProperties();
     method public int getNetworkType();
     method public int getState();
+    method public int getTransportType();
     method public void writeToParcel(@NonNull android.os.Parcel, int);
     field @NonNull public static final android.os.Parcelable.Creator<android.telephony.PreciseDataConnectionState> CREATOR;
   }
@@ -48342,7 +48361,9 @@
     method @Deprecated public String iccTransmitApduBasicChannel(int, int, int, int, int, String);
     method @Deprecated public String iccTransmitApduLogicalChannel(int, int, int, int, int, int, String);
     method public boolean isConcurrentVoiceAndDataSupported();
+    method @RequiresPermission(anyOf={android.Manifest.permission.ACCESS_NETWORK_STATE, android.Manifest.permission.READ_PHONE_STATE, "android.permission.READ_PRIVILEGED_PHONE_STATE"}) public boolean isDataConnectionAllowed();
     method @RequiresPermission(anyOf={android.Manifest.permission.ACCESS_NETWORK_STATE, android.Manifest.permission.MODIFY_PHONE_STATE}) public boolean isDataEnabled();
+    method @RequiresPermission(anyOf={android.Manifest.permission.ACCESS_NETWORK_STATE, android.Manifest.permission.READ_PHONE_STATE}) public boolean isDataEnabledForReason(int);
     method @RequiresPermission(anyOf={android.Manifest.permission.ACCESS_NETWORK_STATE, android.Manifest.permission.READ_PHONE_STATE}) public boolean isDataRoamingEnabled();
     method public boolean isEmergencyNumber(@NonNull String);
     method public boolean isHearingAidCompatibilitySupported();
@@ -48364,6 +48385,7 @@
     method @RequiresPermission(android.Manifest.permission.CALL_PHONE) public void sendUssdRequest(String, android.telephony.TelephonyManager.UssdResponseCallback, android.os.Handler);
     method public void sendVisualVoicemailSms(String, int, String, android.app.PendingIntent);
     method @Deprecated @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setDataEnabled(boolean);
+    method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setDataEnabledForReason(int, boolean);
     method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public int setForbiddenPlmns(@NonNull java.util.List<java.lang.String>);
     method public boolean setLine1NumberForDisplay(String, String);
     method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setNetworkSelectionModeAutomatic();
@@ -48411,6 +48433,10 @@
     field public static final int DATA_CONNECTING = 1; // 0x1
     field public static final int DATA_DISCONNECTED = 0; // 0x0
     field public static final int DATA_DISCONNECTING = 4; // 0x4
+    field public static final int DATA_ENABLED_REASON_CARRIER = 2; // 0x2
+    field public static final int DATA_ENABLED_REASON_POLICY = 1; // 0x1
+    field public static final int DATA_ENABLED_REASON_THERMAL = 3; // 0x3
+    field public static final int DATA_ENABLED_REASON_USER = 0; // 0x0
     field public static final int DATA_SUSPENDED = 3; // 0x3
     field public static final int DATA_UNKNOWN = -1; // 0xffffffff
     field public static final String EXTRA_ACTIVE_SIM_SUPPORTED_COUNT = "android.telephony.extra.ACTIVE_SIM_SUPPORTED_COUNT";
@@ -55175,7 +55201,7 @@
   }
 
   public class ViewPropertyAnimator {
-    method public android.view.ViewPropertyAnimator alpha(float);
+    method public android.view.ViewPropertyAnimator alpha(@FloatRange(from=0.0f, to=1.0f) float);
     method public android.view.ViewPropertyAnimator alphaBy(float);
     method public void cancel();
     method public long getDuration();
@@ -65717,7 +65743,7 @@
 
 package java.math {
 
-  public class BigDecimal extends java.lang.Number implements java.lang.Comparable<java.math.BigDecimal> java.io.Serializable {
+  public class BigDecimal extends java.lang.Number implements java.lang.Comparable<java.math.BigDecimal> {
     ctor public BigDecimal(char[], int, int);
     ctor public BigDecimal(char[], int, int, java.math.MathContext);
     ctor public BigDecimal(char[]);
@@ -65804,19 +65830,20 @@
     field public static final java.math.BigDecimal ZERO;
   }
 
-  public class BigInteger extends java.lang.Number implements java.lang.Comparable<java.math.BigInteger> java.io.Serializable {
+  public class BigInteger extends java.lang.Number implements java.lang.Comparable<java.math.BigInteger> {
+    ctor public BigInteger(byte[]);
+    ctor public BigInteger(int, byte[]);
+    ctor public BigInteger(@NonNull String, int);
+    ctor public BigInteger(@NonNull String);
     ctor public BigInteger(int, @NonNull java.util.Random);
     ctor public BigInteger(int, int, @NonNull java.util.Random);
-    ctor public BigInteger(@NonNull String);
-    ctor public BigInteger(@NonNull String, int);
-    ctor public BigInteger(int, byte[]);
-    ctor public BigInteger(byte[]);
     method @NonNull public java.math.BigInteger abs();
     method @NonNull public java.math.BigInteger add(@NonNull java.math.BigInteger);
     method @NonNull public java.math.BigInteger and(@NonNull java.math.BigInteger);
     method @NonNull public java.math.BigInteger andNot(@NonNull java.math.BigInteger);
     method public int bitCount();
     method public int bitLength();
+    method public byte byteValueExact();
     method @NonNull public java.math.BigInteger clearBit(int);
     method public int compareTo(@NonNull java.math.BigInteger);
     method @NonNull public java.math.BigInteger divide(@NonNull java.math.BigInteger);
@@ -65827,8 +65854,10 @@
     method @NonNull public java.math.BigInteger gcd(@NonNull java.math.BigInteger);
     method public int getLowestSetBit();
     method public int intValue();
+    method public int intValueExact();
     method public boolean isProbablePrime(int);
     method public long longValue();
+    method public long longValueExact();
     method @NonNull public java.math.BigInteger max(@NonNull java.math.BigInteger);
     method @NonNull public java.math.BigInteger min(@NonNull java.math.BigInteger);
     method @NonNull public java.math.BigInteger mod(@NonNull java.math.BigInteger);
@@ -65845,6 +65874,7 @@
     method @NonNull public java.math.BigInteger setBit(int);
     method @NonNull public java.math.BigInteger shiftLeft(int);
     method @NonNull public java.math.BigInteger shiftRight(int);
+    method public short shortValueExact();
     method public int signum();
     method @NonNull public java.math.BigInteger subtract(@NonNull java.math.BigInteger);
     method public boolean testBit(int);
diff --git a/api/module-lib-current.txt b/api/module-lib-current.txt
index 696e029..17545a4 100644
--- a/api/module-lib-current.txt
+++ b/api/module-lib-current.txt
@@ -7,6 +7,7 @@
 
   public class NotificationManager {
     method public boolean hasEnabledNotificationListener(@NonNull String, @NonNull android.os.UserHandle);
+    field public static final String ACTION_NOTIFICATION_LISTENER_ENABLED_CHANGED = "android.app.action.NOTIFICATION_LISTENER_ENABLED_CHANGED";
   }
 
 }
@@ -31,6 +32,29 @@
 
 }
 
+package android.media {
+
+  public class AudioManager {
+    field public static final int FLAG_FROM_KEY = 4096; // 0x1000
+  }
+
+}
+
+package android.media.session {
+
+  public final class MediaSession {
+    field public static final int FLAG_EXCLUSIVE_GLOBAL_PRIORITY = 65536; // 0x10000
+  }
+
+  public final class MediaSessionManager {
+    method public void dispatchMediaKeyEventAsSystemService(@NonNull android.view.KeyEvent);
+    method public boolean dispatchMediaKeyEventAsSystemService(@NonNull android.media.session.MediaSession.Token, @NonNull android.view.KeyEvent);
+    method public void dispatchVolumeKeyEventAsSystemService(@NonNull android.view.KeyEvent, int);
+    method public void dispatchVolumeKeyEventAsSystemService(@NonNull android.media.session.MediaSession.Token, @NonNull android.view.KeyEvent);
+  }
+
+}
+
 package android.net {
 
   public final class TetheringConstants {
@@ -78,6 +102,7 @@
   }
 
   public interface Parcelable {
+    method public default int getStability();
     field public static final int PARCELABLE_STABILITY_LOCAL = 0; // 0x0
     field public static final int PARCELABLE_STABILITY_VINTF = 1; // 0x1
   }
diff --git a/api/system-current.txt b/api/system-current.txt
index 11db781..41e8593 100755
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -301,6 +301,7 @@
     field public static final int config_helpIntentNameKey = 17039390; // 0x104001e
     field public static final int config_helpPackageNameKey = 17039387; // 0x104001b
     field public static final int config_helpPackageNameValue = 17039388; // 0x104001c
+    field public static final int config_systemAutomotiveCluster = 17039400; // 0x1040028
     field public static final int config_systemGallery = 17039399; // 0x1040027
   }
 
@@ -4195,32 +4196,38 @@
 
   public class AudioManager {
     method @Deprecated public int abandonAudioFocus(android.media.AudioManager.OnAudioFocusChangeListener, android.media.AudioAttributes);
-    method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public void addOnPreferredDeviceForStrategyChangedListener(@NonNull java.util.concurrent.Executor, @NonNull android.media.AudioManager.OnPreferredDeviceForStrategyChangedListener) throws java.lang.SecurityException;
+    method @Deprecated @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public void addOnPreferredDeviceForStrategyChangedListener(@NonNull java.util.concurrent.Executor, @NonNull android.media.AudioManager.OnPreferredDeviceForStrategyChangedListener) throws java.lang.SecurityException;
+    method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public void addOnPreferredDevicesForStrategyChangedListener(@NonNull java.util.concurrent.Executor, @NonNull android.media.AudioManager.OnPreferredDevicesForStrategyChangedListener) throws java.lang.SecurityException;
     method public void clearAudioServerStateCallback();
     method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public int dispatchAudioFocusChange(@NonNull android.media.AudioFocusInfo, int, @NonNull android.media.audiopolicy.AudioPolicy);
     method @IntRange(from=0) public long getAdditionalOutputDeviceDelay(@NonNull android.media.AudioDeviceInfo);
     method @NonNull @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public static java.util.List<android.media.audiopolicy.AudioProductStrategy> getAudioProductStrategies();
     method @NonNull @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public static java.util.List<android.media.audiopolicy.AudioVolumeGroup> getAudioVolumeGroups();
+    method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public int getDeviceVolumeBehavior(@NonNull android.media.AudioDeviceAttributes);
     method @NonNull @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public java.util.List<android.media.AudioDeviceAttributes> getDevicesForAttributes(@NonNull android.media.AudioAttributes);
     method @IntRange(from=0) public long getMaxAdditionalOutputDeviceDelay(@NonNull android.media.AudioDeviceInfo);
     method @IntRange(from=0) @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public int getMaxVolumeIndexForAttributes(@NonNull android.media.AudioAttributes);
     method @IntRange(from=0) @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public int getMinVolumeIndexForAttributes(@NonNull android.media.AudioAttributes);
     method @Nullable @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public android.media.AudioDeviceAttributes getPreferredDeviceForStrategy(@NonNull android.media.audiopolicy.AudioProductStrategy);
+    method @NonNull @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public java.util.List<android.media.AudioDeviceAttributes> getPreferredDevicesForStrategy(@NonNull android.media.audiopolicy.AudioProductStrategy);
     method @NonNull @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public int[] getSupportedSystemUsages();
     method @IntRange(from=0) @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public int getVolumeIndexForAttributes(@NonNull android.media.AudioAttributes);
     method public boolean isAudioServerRunning();
     method public boolean isHdmiSystemAudioSupported();
     method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public int registerAudioPolicy(@NonNull android.media.audiopolicy.AudioPolicy);
     method public void registerVolumeGroupCallback(@NonNull java.util.concurrent.Executor, @NonNull android.media.AudioManager.VolumeGroupCallback);
-    method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public void removeOnPreferredDeviceForStrategyChangedListener(@NonNull android.media.AudioManager.OnPreferredDeviceForStrategyChangedListener);
+    method @Deprecated @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public void removeOnPreferredDeviceForStrategyChangedListener(@NonNull android.media.AudioManager.OnPreferredDeviceForStrategyChangedListener);
+    method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public void removeOnPreferredDevicesForStrategyChangedListener(@NonNull android.media.AudioManager.OnPreferredDevicesForStrategyChangedListener);
     method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public boolean removePreferredDeviceForStrategy(@NonNull android.media.audiopolicy.AudioProductStrategy);
     method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public int requestAudioFocus(android.media.AudioManager.OnAudioFocusChangeListener, @NonNull android.media.AudioAttributes, int, int) throws java.lang.IllegalArgumentException;
     method @Deprecated @RequiresPermission(anyOf={android.Manifest.permission.MODIFY_PHONE_STATE, android.Manifest.permission.MODIFY_AUDIO_ROUTING}) public int requestAudioFocus(android.media.AudioManager.OnAudioFocusChangeListener, @NonNull android.media.AudioAttributes, int, int, android.media.audiopolicy.AudioPolicy) throws java.lang.IllegalArgumentException;
     method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public int requestAudioFocus(@NonNull android.media.AudioFocusRequest, @Nullable android.media.audiopolicy.AudioPolicy);
     method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public boolean setAdditionalOutputDeviceDelay(@NonNull android.media.AudioDeviceInfo, @IntRange(from=0) long);
     method public void setAudioServerStateCallback(@NonNull java.util.concurrent.Executor, @NonNull android.media.AudioManager.AudioServerStateCallback);
+    method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public void setDeviceVolumeBehavior(@NonNull android.media.AudioDeviceAttributes, int);
     method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public void setFocusRequestResult(@NonNull android.media.AudioFocusInfo, int, @NonNull android.media.audiopolicy.AudioPolicy);
     method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public boolean setPreferredDeviceForStrategy(@NonNull android.media.audiopolicy.AudioProductStrategy, @NonNull android.media.AudioDeviceAttributes);
+    method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public boolean setPreferredDevicesForStrategy(@NonNull android.media.audiopolicy.AudioProductStrategy, @NonNull java.util.List<android.media.AudioDeviceAttributes>);
     method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public void setSupportedSystemUsages(@NonNull int[]);
     method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public void setVolumeIndexForAttributes(@NonNull android.media.AudioAttributes, int, int);
     method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public void unregisterAudioPolicy(@NonNull android.media.audiopolicy.AudioPolicy);
@@ -4229,6 +4236,11 @@
     field public static final int AUDIOFOCUS_FLAG_DELAY_OK = 1; // 0x1
     field public static final int AUDIOFOCUS_FLAG_LOCK = 4; // 0x4
     field public static final int AUDIOFOCUS_FLAG_PAUSES_ON_DUCKABLE_LOSS = 2; // 0x2
+    field public static final int DEVICE_VOLUME_BEHAVIOR_ABSOLUTE = 3; // 0x3
+    field public static final int DEVICE_VOLUME_BEHAVIOR_ABSOLUTE_MULTI_MODE = 4; // 0x4
+    field public static final int DEVICE_VOLUME_BEHAVIOR_FIXED = 2; // 0x2
+    field public static final int DEVICE_VOLUME_BEHAVIOR_FULL = 1; // 0x1
+    field public static final int DEVICE_VOLUME_BEHAVIOR_VARIABLE = 0; // 0x0
     field @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public static final int STREAM_ASSISTANT = 11; // 0xb
     field public static final int SUCCESS = 0; // 0x0
   }
@@ -4239,8 +4251,12 @@
     method public void onAudioServerUp();
   }
 
-  public static interface AudioManager.OnPreferredDeviceForStrategyChangedListener {
-    method public void onPreferredDeviceForStrategyChanged(@NonNull android.media.audiopolicy.AudioProductStrategy, @Nullable android.media.AudioDeviceAttributes);
+  @Deprecated public static interface AudioManager.OnPreferredDeviceForStrategyChangedListener {
+    method @Deprecated public void onPreferredDeviceForStrategyChanged(@NonNull android.media.audiopolicy.AudioProductStrategy, @Nullable android.media.AudioDeviceAttributes);
+  }
+
+  public static interface AudioManager.OnPreferredDevicesForStrategyChangedListener {
+    method public void onPreferredDevicesForStrategyChanged(@NonNull android.media.audiopolicy.AudioProductStrategy, @NonNull java.util.List<android.media.AudioDeviceAttributes>);
   }
 
   public abstract static class AudioManager.VolumeGroupCallback {
@@ -7134,6 +7150,8 @@
     field @Deprecated public static final int METERED_OVERRIDE_METERED = 1; // 0x1
     field @Deprecated public static final int METERED_OVERRIDE_NONE = 0; // 0x0
     field @Deprecated public static final int METERED_OVERRIDE_NOT_METERED = 2; // 0x2
+    field @Deprecated public static final int RANDOMIZATION_AUTO = 3; // 0x3
+    field @Deprecated public static final int RANDOMIZATION_ENHANCED = 2; // 0x2
     field @Deprecated public static final int RANDOMIZATION_NONE = 0; // 0x0
     field @Deprecated public static final int RANDOMIZATION_PERSISTENT = 1; // 0x1
     field @Deprecated public static final int RECENT_FAILURE_AP_UNABLE_TO_HANDLE_NEW_STA = 17; // 0x11
@@ -8376,6 +8394,7 @@
     method @RequiresPermission(android.Manifest.permission.READ_DREAM_STATE) public boolean isAmbientDisplaySuppressedForToken(@NonNull String);
     method @RequiresPermission(anyOf={android.Manifest.permission.DEVICE_POWER, android.Manifest.permission.POWER_SAVER}) public boolean setAdaptivePowerSaveEnabled(boolean);
     method @RequiresPermission(anyOf={android.Manifest.permission.DEVICE_POWER, android.Manifest.permission.POWER_SAVER}) public boolean setAdaptivePowerSavePolicy(@NonNull android.os.BatterySaverPolicyConfig);
+    method @RequiresPermission(android.Manifest.permission.DEVICE_POWER) public void setBatteryDischargePrediction(@NonNull java.time.Duration, boolean);
     method @RequiresPermission(android.Manifest.permission.POWER_SAVER) public boolean setDynamicPowerSaveHint(boolean, int);
     method @RequiresPermission(anyOf={android.Manifest.permission.DEVICE_POWER, android.Manifest.permission.POWER_SAVER}) public boolean setPowerSaveModeEnabled(boolean);
     method @RequiresPermission(android.Manifest.permission.WRITE_DREAM_STATE) public void suppressAmbientDisplay(@NonNull String, boolean);
@@ -10482,7 +10501,7 @@
     method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public int getCurrentTtyMode();
     method @Nullable @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public String getDefaultDialerPackage(@NonNull android.os.UserHandle);
     method @Deprecated public android.content.ComponentName getDefaultPhoneApp();
-    method public java.util.List<android.telecom.PhoneAccountHandle> getPhoneAccountsForPackage();
+    method @Deprecated public java.util.List<android.telecom.PhoneAccountHandle> getPhoneAccountsForPackage();
     method @RequiresPermission(anyOf={android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE, android.Manifest.permission.READ_PHONE_STATE}) public java.util.List<android.telecom.PhoneAccountHandle> getPhoneAccountsSupportingScheme(String);
     method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public boolean isInEmergencyCall();
     method @RequiresPermission(anyOf={android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE, android.Manifest.permission.READ_PHONE_STATE}) public boolean isRinging();
@@ -10798,7 +10817,8 @@
 
   public class PhoneStateListener {
     method public void onCallAttributesChanged(@NonNull android.telephony.CallAttributes);
-    method public void onOutgoingEmergencyCall(@NonNull android.telephony.emergency.EmergencyNumber);
+    method @Deprecated public void onOutgoingEmergencyCall(@NonNull android.telephony.emergency.EmergencyNumber);
+    method public void onOutgoingEmergencyCall(@NonNull android.telephony.emergency.EmergencyNumber, int);
     method public void onOutgoingEmergencySms(@NonNull android.telephony.emergency.EmergencyNumber);
     method @RequiresPermission("android.permission.READ_PRECISE_PHONE_STATE") public void onPreciseCallStateChanged(@NonNull android.telephony.PreciseCallState);
     method public void onRadioPowerStateChanged(int);
@@ -10838,6 +10858,7 @@
     method @Deprecated public int getDataConnectionApnTypeBitMask();
     method @Deprecated public int getDataConnectionFailCause();
     method @Deprecated public int getDataConnectionState();
+    method public int getId();
   }
 
   public final class PreciseDisconnectCause {
@@ -11214,10 +11235,8 @@
     method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public boolean isAnyRadioPoweredOn();
     method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public boolean isApnMetered(int);
     method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public boolean isApplicationOnUicc(int);
-    method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public boolean isDataConnectionAllowed();
     method public boolean isDataConnectivityPossible();
     method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public boolean isDataEnabledForApn(int);
-    method @RequiresPermission(anyOf={android.Manifest.permission.ACCESS_NETWORK_STATE, android.Manifest.permission.READ_PHONE_STATE}) public boolean isDataEnabledWithReason(int);
     method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public boolean isEmergencyAssistanceEnabled();
     method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) @WorkerThread public boolean isIccLockEnabled();
     method @Deprecated @RequiresPermission(anyOf={android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE, android.Manifest.permission.READ_PHONE_STATE}) public boolean isIdle();
@@ -11249,7 +11268,6 @@
     method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public int setCarrierRestrictionRules(@NonNull android.telephony.CarrierRestrictionRules);
     method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setDataActivationState(int);
     method @Deprecated @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setDataEnabled(int, boolean);
-    method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setDataEnabledWithReason(int, boolean);
     method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setDataRoamingEnabled(boolean);
     method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setMultiSimCarrierRestriction(boolean);
     method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public boolean setOpportunisticNetworkState(boolean);
@@ -11287,10 +11305,6 @@
     field public static final int CARRIER_PRIVILEGE_STATUS_HAS_ACCESS = 1; // 0x1
     field public static final int CARRIER_PRIVILEGE_STATUS_NO_ACCESS = 0; // 0x0
     field public static final int CARRIER_PRIVILEGE_STATUS_RULES_NOT_LOADED = -1; // 0xffffffff
-    field public static final int DATA_ENABLED_REASON_CARRIER = 2; // 0x2
-    field public static final int DATA_ENABLED_REASON_POLICY = 1; // 0x1
-    field public static final int DATA_ENABLED_REASON_THERMAL = 3; // 0x3
-    field public static final int DATA_ENABLED_REASON_USER = 0; // 0x0
     field public static final String EXTRA_ANOMALY_DESCRIPTION = "android.telephony.extra.ANOMALY_DESCRIPTION";
     field public static final String EXTRA_ANOMALY_ID = "android.telephony.extra.ANOMALY_ID";
     field public static final String EXTRA_PHONE_IN_ECM_STATE = "android.telephony.extra.PHONE_IN_ECM_STATE";
@@ -12152,6 +12166,7 @@
     method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) @WorkerThread public int setProvisioningStringValue(int, @NonNull String);
     method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) @WorkerThread public void setRcsProvisioningStatusForCapability(int, boolean);
     method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public void unregisterProvisioningChangedCallback(@NonNull android.telephony.ims.ProvisioningManager.Callback);
+    field public static final int KEY_VOICE_OVER_WIFI_ENTITLEMENT_ID = 67; // 0x43
     field public static final int KEY_VOICE_OVER_WIFI_MODE_OVERRIDE = 27; // 0x1b
     field public static final int KEY_VOICE_OVER_WIFI_ROAMING_ENABLED_OVERRIDE = 26; // 0x1a
     field public static final int PROVISIONING_VALUE_DISABLED = 0; // 0x0
@@ -12724,6 +12739,9 @@
   public interface PacProcessor {
     method @Nullable public String findProxyForUrl(@NonNull String);
     method @NonNull public static android.webkit.PacProcessor getInstance();
+    method @NonNull public static android.webkit.PacProcessor getInstanceForNetwork(@Nullable android.net.Network);
+    method @Nullable public default android.net.Network getNetwork();
+    method public default void releasePacProcessor();
     method public boolean setProxyScript(@NonNull String);
   }
 
@@ -12863,6 +12881,7 @@
     method public android.webkit.CookieManager getCookieManager();
     method public android.webkit.GeolocationPermissions getGeolocationPermissions();
     method @NonNull public default android.webkit.PacProcessor getPacProcessor();
+    method @NonNull public default android.webkit.PacProcessor getPacProcessorForNetwork(@Nullable android.net.Network);
     method public android.webkit.ServiceWorkerController getServiceWorkerController();
     method public android.webkit.WebViewFactoryProvider.Statics getStatics();
     method @Deprecated public android.webkit.TokenBindingService getTokenBindingService();
diff --git a/api/test-current.txt b/api/test-current.txt
index 963ef7c..529dcf7 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -42,6 +42,7 @@
   public static final class R.string {
     field public static final int config_defaultAssistant = 17039393; // 0x1040021
     field public static final int config_defaultDialer = 17039395; // 0x1040023
+    field public static final int config_systemAutomotiveCluster = 17039400; // 0x1040028
     field public static final int config_systemGallery = 17039399; // 0x1040027
   }
 
@@ -969,6 +970,7 @@
     method public boolean isSystemApp();
     field public static final int PRIVATE_FLAG_PRIVILEGED = 8; // 0x8
     field public int privateFlags;
+    field public int targetSandboxVersion;
   }
 
   public class LauncherApps {
@@ -1016,6 +1018,7 @@
     field public static final String FEATURE_ADOPTABLE_STORAGE = "android.software.adoptable_storage";
     field public static final String FEATURE_FILE_BASED_ENCRYPTION = "android.software.file_based_encryption";
     field public static final int FLAG_PERMISSION_APPLY_RESTRICTION = 16384; // 0x4000
+    field public static final int FLAG_PERMISSION_GRANTED_BY_DEFAULT = 32; // 0x20
     field public static final int FLAG_PERMISSION_GRANTED_BY_ROLE = 32768; // 0x8000
     field public static final int FLAG_PERMISSION_ONE_TIME = 65536; // 0x10000
     field public static final int FLAG_PERMISSION_POLICY_FIXED = 4; // 0x4
@@ -1302,6 +1305,8 @@
     method public android.graphics.Point getStableDisplaySize();
     method public boolean isMinimalPostProcessingRequested(int);
     method @RequiresPermission(android.Manifest.permission.CONFIGURE_DISPLAY_BRIGHTNESS) public void setBrightnessConfiguration(android.hardware.display.BrightnessConfiguration);
+    field public static final int VIRTUAL_DISPLAY_FLAG_SHOULD_SHOW_SYSTEM_DECORATIONS = 512; // 0x200
+    field public static final int VIRTUAL_DISPLAY_FLAG_TRUSTED = 1024; // 0x400
   }
 
 }
@@ -1779,6 +1784,9 @@
     method public static float getMasterBalance();
     method public static final int getNumStreamTypes();
     method public static int setMasterBalance(float);
+    field public static final int DEVICE_ROLE_DISABLED = 2; // 0x2
+    field public static final int DEVICE_ROLE_NONE = 0; // 0x0
+    field public static final int DEVICE_ROLE_PREFERRED = 1; // 0x1
     field public static final int STREAM_DEFAULT = -1; // 0xffffffff
   }
 
@@ -2791,8 +2799,10 @@
 
   public final class PowerManager {
     method @RequiresPermission("android.permission.POWER_SAVER") public int getPowerSaveModeTrigger();
+    method @RequiresPermission("android.permission.DEVICE_POWER") public void setBatteryDischargePrediction(@NonNull java.time.Duration, boolean);
     method @RequiresPermission("android.permission.POWER_SAVER") public boolean setDynamicPowerSaveHint(boolean, int);
     method @RequiresPermission(anyOf={"android.permission.DEVICE_POWER", "android.permission.POWER_SAVER"}) public boolean setPowerSaveModeEnabled(boolean);
+    field public static final String ACTION_ENHANCED_DISCHARGE_PREDICTION_CHANGED = "android.os.action.ENHANCED_DISCHARGE_PREDICTION_CHANGED";
     field public static final int POWER_SAVE_MODE_TRIGGER_DYNAMIC = 1; // 0x1
     field public static final int POWER_SAVE_MODE_TRIGGER_PERCENTAGE = 0; // 0x0
   }
@@ -3230,6 +3240,7 @@
     field public static final String NAMESPACE_AUTOFILL = "autofill";
     field public static final String NAMESPACE_BIOMETRICS = "biometrics";
     field public static final String NAMESPACE_CONTENT_CAPTURE = "content_capture";
+    field public static final String NAMESPACE_JOB_SCHEDULER = "jobscheduler";
     field public static final String NAMESPACE_PERMISSIONS = "permissions";
     field public static final String NAMESPACE_PRIVACY = "privacy";
     field public static final String NAMESPACE_ROLLBACK = "rollback";
@@ -4065,7 +4076,8 @@
   }
 
   public class PhoneStateListener {
-    method public void onOutgoingEmergencyCall(@NonNull android.telephony.emergency.EmergencyNumber);
+    method @Deprecated public void onOutgoingEmergencyCall(@NonNull android.telephony.emergency.EmergencyNumber);
+    method public void onOutgoingEmergencyCall(@NonNull android.telephony.emergency.EmergencyNumber, int);
     method public void onOutgoingEmergencySms(@NonNull android.telephony.emergency.EmergencyNumber);
     field @RequiresPermission("android.permission.READ_ACTIVE_EMERGENCY_SESSION") public static final int LISTEN_OUTGOING_EMERGENCY_CALL = 268435456; // 0x10000000
     field @RequiresPermission("android.permission.READ_ACTIVE_EMERGENCY_SESSION") public static final int LISTEN_OUTGOING_EMERGENCY_SMS = 536870912; // 0x20000000
@@ -4612,6 +4624,7 @@
     method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) @WorkerThread public int setProvisioningStringValue(int, @NonNull String);
     method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) @WorkerThread public void setRcsProvisioningStatusForCapability(int, boolean);
     method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public void unregisterProvisioningChangedCallback(@NonNull android.telephony.ims.ProvisioningManager.Callback);
+    field public static final int KEY_VOICE_OVER_WIFI_ENTITLEMENT_ID = 67; // 0x43
     field public static final int KEY_VOICE_OVER_WIFI_MODE_OVERRIDE = 27; // 0x1b
     field public static final int KEY_VOICE_OVER_WIFI_ROAMING_ENABLED_OVERRIDE = 26; // 0x1a
     field public static final int PROVISIONING_VALUE_DISABLED = 0; // 0x0
@@ -5565,7 +5578,7 @@
     method @BinderThread public void onTaskAppeared(@NonNull android.app.ActivityManager.RunningTaskInfo, @NonNull android.view.SurfaceControl);
     method @BinderThread public void onTaskInfoChanged(@NonNull android.app.ActivityManager.RunningTaskInfo);
     method @BinderThread public void onTaskVanished(@NonNull android.app.ActivityManager.RunningTaskInfo);
-    method @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_STACKS) public final void registerOrganizer(int);
+    method @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_STACKS) public final void registerOrganizer();
     method @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_STACKS) public void setInterceptBackPressedOnTaskRoot(boolean);
     method @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_STACKS) public static void setLaunchRoot(int, @NonNull android.window.WindowContainerToken);
     method @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_STACKS) public final void unregisterOrganizer();
diff --git a/cmds/bmgr/src/com/android/commands/bmgr/Bmgr.java b/cmds/bmgr/src/com/android/commands/bmgr/Bmgr.java
index ed717c4..4b7eda0 100644
--- a/cmds/bmgr/src/com/android/commands/bmgr/Bmgr.java
+++ b/cmds/bmgr/src/com/android/commands/bmgr/Bmgr.java
@@ -19,6 +19,7 @@
 import android.annotation.IntDef;
 import android.annotation.UserIdInt;
 import android.app.backup.BackupManager;
+import android.app.backup.BackupManager.OperationType;
 import android.app.backup.BackupManagerMonitor;
 import android.app.backup.BackupProgress;
 import android.app.backup.BackupTransport;
@@ -666,7 +667,7 @@
 
         // The rest of the 'list' options work with a restore session on the current transport
         try {
-            mRestore = mBmgr.beginRestoreSessionForUser(userId, null, null);
+            mRestore = mBmgr.beginRestoreSessionForUser(userId, null, null, OperationType.BACKUP);
             if (mRestore == null) {
                 System.err.println(BMGR_ERR_NO_RESTORESESSION_FOR_USER + userId);
                 return;
@@ -821,7 +822,7 @@
 
         try {
             boolean didRestore = false;
-            mRestore = mBmgr.beginRestoreSessionForUser(userId, null, null);
+            mRestore = mBmgr.beginRestoreSessionForUser(userId, null, null, OperationType.BACKUP);
             if (mRestore == null) {
                 System.err.println(BMGR_ERR_NO_RESTORESESSION_FOR_USER + userId);
                 return;
diff --git a/cmds/bootanimation/BootAnimation.cpp b/cmds/bootanimation/BootAnimation.cpp
index 36ff20f..9c79612 100644
--- a/cmds/bootanimation/BootAnimation.cpp
+++ b/cmds/bootanimation/BootAnimation.cpp
@@ -417,7 +417,7 @@
     // this guest property specifies multi-display IDs to show the boot animation
     // multiple ids can be set with comma (,) as separator, for example:
     // setprop persist.boot.animation.displays 19260422155234049,19261083906282754
-    Vector<uint64_t> physicalDisplayIds;
+    Vector<PhysicalDisplayId> physicalDisplayIds;
     char displayValue[PROPERTY_VALUE_MAX] = "";
     property_get(DISPLAYS_PROP_NAME, displayValue, "");
     bool isValid = displayValue[0] != '\0';
@@ -435,7 +435,7 @@
     }
     if (isValid) {
         std::istringstream stream(displayValue);
-        for (PhysicalDisplayId id; stream >> id; ) {
+        for (PhysicalDisplayId id; stream >> id.value; ) {
             physicalDisplayIds.add(id);
             if (stream.peek() == ',')
                 stream.ignore();
diff --git a/cmds/idmap2/idmap2d/Idmap2Service.cpp b/cmds/idmap2/idmap2d/Idmap2Service.cpp
index a6c402c..15e22a3 100644
--- a/cmds/idmap2/idmap2d/Idmap2Service.cpp
+++ b/cmds/idmap2/idmap2d/Idmap2Service.cpp
@@ -166,7 +166,7 @@
 Status Idmap2Service::createIdmap(const std::string& target_apk_path,
                                   const std::string& overlay_apk_path, int32_t fulfilled_policies,
                                   bool enforce_overlayable, int32_t user_id ATTRIBUTE_UNUSED,
-                                  aidl::nullable<std::string>* _aidl_return) {
+                                  std::optional<std::string>* _aidl_return) {
   assert(_aidl_return);
   SYSTRACE << "Idmap2Service::createIdmap " << target_apk_path << " " << overlay_apk_path;
   _aidl_return->reset();
@@ -214,7 +214,7 @@
     return error("failed to write to idmap path " + idmap_path);
   }
 
-  *_aidl_return = aidl::make_nullable<std::string>(idmap_path);
+  *_aidl_return = idmap_path;
   return ok();
 }
 
diff --git a/cmds/idmap2/idmap2d/Idmap2Service.h b/cmds/idmap2/idmap2d/Idmap2Service.h
index ea931c9..1a44519 100644
--- a/cmds/idmap2/idmap2d/Idmap2Service.h
+++ b/cmds/idmap2/idmap2d/Idmap2Service.h
@@ -21,7 +21,6 @@
 
 #include <android-base/unique_fd.h>
 #include <binder/BinderService.h>
-#include <binder/Nullable.h>
 
 #include "android/os/BnIdmap2.h"
 
@@ -47,7 +46,7 @@
   binder::Status createIdmap(const std::string& target_apk_path,
                              const std::string& overlay_apk_path, int32_t fulfilled_policies,
                              bool enforce_overlayable, int32_t user_id,
-                             aidl::nullable<std::string>* _aidl_return) override;
+                             std::optional<std::string>* _aidl_return) override;
 
  private:
   // Cache the crc of the android framework package since the crc cannot change without a reboot.
diff --git a/cmds/idmap2/idmap2d/aidl/android/os/OverlayablePolicy.aidl b/cmds/idmap2/idmap2d/aidl/android/os/OverlayablePolicy.aidl
index 02b27a8..403d8c5 100644
--- a/cmds/idmap2/idmap2d/aidl/android/os/OverlayablePolicy.aidl
+++ b/cmds/idmap2/idmap2d/aidl/android/os/OverlayablePolicy.aidl
@@ -29,4 +29,5 @@
   const int ODM_PARTITION = 0x00000020;
   const int OEM_PARTITION = 0x00000040;
   const int ACTOR_SIGNATURE = 0x00000080;
+  const int CONFIG_SIGNATURE = 0x0000100;
 }
diff --git a/cmds/idmap2/libidmap2/ResourceMapping.cpp b/cmds/idmap2/libidmap2/ResourceMapping.cpp
index 34589a1..fd8b4eb 100644
--- a/cmds/idmap2/libidmap2/ResourceMapping.cpp
+++ b/cmds/idmap2/libidmap2/ResourceMapping.cpp
@@ -61,10 +61,13 @@
                               const ResourceId& target_resource) {
   static constexpr const PolicyBitmask sDefaultPolicies =
       PolicyFlags::ODM_PARTITION | PolicyFlags::OEM_PARTITION | PolicyFlags::SYSTEM_PARTITION |
-      PolicyFlags::VENDOR_PARTITION | PolicyFlags::PRODUCT_PARTITION | PolicyFlags::SIGNATURE;
+      PolicyFlags::VENDOR_PARTITION | PolicyFlags::PRODUCT_PARTITION | PolicyFlags::SIGNATURE |
+      PolicyFlags::CONFIG_SIGNATURE;
 
   // If the resource does not have an overlayable definition, allow the resource to be overlaid if
-  // the overlay is preinstalled or signed with the same signature as the target.
+  // the overlay is preinstalled, signed with the same signature as the target or signed with the
+  // same signature as reference package defined in SystemConfig under 'overlay-config-signature'
+  // tag.
   if (!target_package.DefinesOverlayable()) {
     return (sDefaultPolicies & fulfilled_policies) != 0
                ? Result<Unit>({})
diff --git a/cmds/idmap2/libidmap2_policies/include/idmap2/Policies.h b/cmds/idmap2/libidmap2_policies/include/idmap2/Policies.h
index f7987b0..cdce451 100644
--- a/cmds/idmap2/libidmap2_policies/include/idmap2/Policies.h
+++ b/cmds/idmap2/libidmap2_policies/include/idmap2/Policies.h
@@ -38,16 +38,18 @@
 constexpr const char* kPolicyOem = "oem";
 constexpr const char* kPolicyProduct = "product";
 constexpr const char* kPolicyPublic = "public";
+constexpr const char* kPolicyConfigSignature = "config_signature";
 constexpr const char* kPolicySignature = "signature";
 constexpr const char* kPolicySystem = "system";
 constexpr const char* kPolicyVendor = "vendor";
 
-inline static const std::array<std::pair<StringPiece, PolicyFlags>, 8> kPolicyStringToFlag = {
+inline static const std::array<std::pair<StringPiece, PolicyFlags>, 9> kPolicyStringToFlag = {
     std::pair{kPolicyActor, PolicyFlags::ACTOR_SIGNATURE},
     {kPolicyOdm, PolicyFlags::ODM_PARTITION},
     {kPolicyOem, PolicyFlags::OEM_PARTITION},
     {kPolicyProduct, PolicyFlags::PRODUCT_PARTITION},
     {kPolicyPublic, PolicyFlags::PUBLIC},
+    {kPolicyConfigSignature, PolicyFlags::CONFIG_SIGNATURE},
     {kPolicySignature, PolicyFlags::SIGNATURE},
     {kPolicySystem, PolicyFlags::SYSTEM_PARTITION},
     {kPolicyVendor, PolicyFlags::VENDOR_PARTITION},
diff --git a/cmds/idmap2/tests/R.h b/cmds/idmap2/tests/R.h
index 4f2ee1c..854b57f 100644
--- a/cmds/idmap2/tests/R.h
+++ b/cmds/idmap2/tests/R.h
@@ -43,16 +43,17 @@
     constexpr ResourceId not_overlayable = 0x7f020003;
     constexpr ResourceId other = 0x7f020004;
     constexpr ResourceId policy_actor = 0x7f020005;
-    constexpr ResourceId policy_odm = 0x7f020006;
-    constexpr ResourceId policy_oem = 0x7f020007;
-    constexpr ResourceId policy_product = 0x7f020008;
-    constexpr ResourceId policy_public = 0x7f020009;
-    constexpr ResourceId policy_signature = 0x7f02000a;
-    constexpr ResourceId policy_system = 0x7f02000b;
-    constexpr ResourceId policy_system_vendor = 0x7f02000c;
-    constexpr ResourceId str1 = 0x7f02000d;
-    constexpr ResourceId str3 = 0x7f02000f;
-    constexpr ResourceId str4 = 0x7f020010;
+    constexpr ResourceId policy_config_signature = 0x7f020006;
+    constexpr ResourceId policy_odm = 0x7f020007;
+    constexpr ResourceId policy_oem = 0x7f020008;
+    constexpr ResourceId policy_product = 0x7f020009;
+    constexpr ResourceId policy_public = 0x7f02000a;
+    constexpr ResourceId policy_signature = 0x7f02000b;
+    constexpr ResourceId policy_system = 0x7f02000c;
+    constexpr ResourceId policy_system_vendor = 0x7f02000d;
+    constexpr ResourceId str1 = 0x7f02000e;
+    constexpr ResourceId str3 = 0x7f020010;
+    constexpr ResourceId str4 = 0x7f020011;
 
     namespace literal {  // NOLINT(runtime/indentation_namespace)
       inline const std::string str1 = hexify(R::target::string::str1);
@@ -94,13 +95,14 @@
   constexpr ResourceId not_overlayable = 0x7f010000;
   constexpr ResourceId other = 0x7f010001;
   constexpr ResourceId policy_actor = 0x7f010002;
-  constexpr ResourceId policy_odm = 0x7f010003;
-  constexpr ResourceId policy_oem = 0x7f010004;
-  constexpr ResourceId policy_product = 0x7f010005;
-  constexpr ResourceId policy_public = 0x7f010006;
-  constexpr ResourceId policy_signature = 0x7f010007;
-  constexpr ResourceId policy_system = 0x7f010008;
-  constexpr ResourceId policy_system_vendor = 0x7f010009;
+  constexpr ResourceId policy_config_signature = 0x7f010003;
+  constexpr ResourceId policy_odm = 0x7f010004;
+  constexpr ResourceId policy_oem = 0x7f010005;
+  constexpr ResourceId policy_product = 0x7f010006;
+  constexpr ResourceId policy_public = 0x7f010007;
+  constexpr ResourceId policy_signature = 0x7f010008;
+  constexpr ResourceId policy_system = 0x7f010009;
+  constexpr ResourceId policy_system_vendor = 0x7f01000a;
 }  // namespace R::system_overlay_invalid::string
 // clang-format on
 
diff --git a/cmds/idmap2/tests/ResourceMappingTests.cpp b/cmds/idmap2/tests/ResourceMappingTests.cpp
index de039f4..3ec6ac2 100644
--- a/cmds/idmap2/tests/ResourceMappingTests.cpp
+++ b/cmds/idmap2/tests/ResourceMappingTests.cpp
@@ -237,7 +237,7 @@
 
   ASSERT_TRUE(resources) << resources.GetErrorMessage();
   auto& res = *resources;
-  ASSERT_EQ(res.GetTargetToOverlayMap().size(), 10U);
+  ASSERT_EQ(res.GetTargetToOverlayMap().size(), 11U);
   ASSERT_RESULT(MappingExists(res, R::target::string::not_overlayable, Res_value::TYPE_REFERENCE,
                               R::system_overlay_invalid::string::not_overlayable,
                               false /* rewrite */));
@@ -256,6 +256,10 @@
   ASSERT_RESULT(MappingExists(res, R::target::string::policy_public, Res_value::TYPE_REFERENCE,
                               R::system_overlay_invalid::string::policy_public,
                               false /* rewrite */));
+  ASSERT_RESULT(MappingExists(res, R::target::string::policy_config_signature,
+                              Res_value::TYPE_REFERENCE,
+                              R::system_overlay_invalid::string::policy_config_signature,
+                              false /* rewrite */));
   ASSERT_RESULT(MappingExists(res, R::target::string::policy_signature, Res_value::TYPE_REFERENCE,
                               R::system_overlay_invalid::string::policy_signature,
                               false /* rewrite */));
@@ -298,8 +302,9 @@
   ASSERT_EQ(resources->GetTargetToOverlayMap().size(), 0U);
 }
 
-// Overlays that are pre-installed or are signed with the same signature as the target can overlay
-// packages that have not defined overlayable resources.
+// Overlays that are pre-installed or are signed with the same signature as the target  or are signed
+// with the same signature as the reference package can overlay packages that have not defined
+// overlayable resources.
 TEST(ResourceMappingTests, ResourcesFromApkAssetsDefaultPolicies) {
   auto CheckEntries = [&](const PolicyBitmask& fulfilled_policies) -> void {
     auto resources = TestGetResourceMapping("/target/target-no-overlayable.apk",
@@ -309,7 +314,7 @@
 
     ASSERT_TRUE(resources) << resources.GetErrorMessage();
     auto& res = *resources;
-    ASSERT_EQ(resources->GetTargetToOverlayMap().size(), 10U);
+    ASSERT_EQ(resources->GetTargetToOverlayMap().size(), 11U);
     ASSERT_RESULT(MappingExists(res, R::target::string::not_overlayable, Res_value::TYPE_REFERENCE,
                                 R::system_overlay_invalid::string::not_overlayable,
                                 false /* rewrite */));
@@ -330,6 +335,10 @@
     ASSERT_RESULT(MappingExists(res, R::target::string::policy_public, Res_value::TYPE_REFERENCE,
                                 R::system_overlay_invalid::string::policy_public,
                                 false /* rewrite */));
+    ASSERT_RESULT(MappingExists(res, R::target::string::policy_config_signature,
+                                Res_value::TYPE_REFERENCE,
+                                R::system_overlay_invalid::string::policy_config_signature,
+                                false /* rewrite */));
     ASSERT_RESULT(MappingExists(res, R::target::string::policy_signature, Res_value::TYPE_REFERENCE,
                                 R::system_overlay_invalid::string::policy_signature,
                                 false /* rewrite */));
@@ -342,6 +351,7 @@
   };
 
   CheckEntries(PolicyFlags::SIGNATURE);
+  CheckEntries(PolicyFlags::CONFIG_SIGNATURE);
   CheckEntries(PolicyFlags::PRODUCT_PARTITION);
   CheckEntries(PolicyFlags::SYSTEM_PARTITION);
   CheckEntries(PolicyFlags::VENDOR_PARTITION);
diff --git a/cmds/idmap2/tests/TestConstants.h b/cmds/idmap2/tests/TestConstants.h
index 74ea18f..9641f6b 100644
--- a/cmds/idmap2/tests/TestConstants.h
+++ b/cmds/idmap2/tests/TestConstants.h
@@ -19,11 +19,11 @@
 
 namespace android::idmap2::TestConstants {
 
-constexpr const auto TARGET_CRC = 0x41c60c8c;
-constexpr const auto TARGET_CRC_STRING = "41c60c8c";
+constexpr const auto TARGET_CRC =  0x7c2d4719;
+constexpr const auto TARGET_CRC_STRING = "7c2d4719";
 
-constexpr const auto OVERLAY_CRC = 0xc054fb26;
-constexpr const auto OVERLAY_CRC_STRING = "c054fb26";
+constexpr const auto OVERLAY_CRC = 0x5afff726;
+constexpr const auto OVERLAY_CRC_STRING = "5afff726";
 
 }  // namespace android::idmap2::TestConstants
 
diff --git a/cmds/idmap2/tests/data/overlay/overlay-no-name-static.apk b/cmds/idmap2/tests/data/overlay/overlay-no-name-static.apk
index 7c25985..dab25b1 100644
--- a/cmds/idmap2/tests/data/overlay/overlay-no-name-static.apk
+++ b/cmds/idmap2/tests/data/overlay/overlay-no-name-static.apk
Binary files differ
diff --git a/cmds/idmap2/tests/data/overlay/overlay-no-name.apk b/cmds/idmap2/tests/data/overlay/overlay-no-name.apk
index c75f3e1..c8b95c2 100644
--- a/cmds/idmap2/tests/data/overlay/overlay-no-name.apk
+++ b/cmds/idmap2/tests/data/overlay/overlay-no-name.apk
Binary files differ
diff --git a/cmds/idmap2/tests/data/overlay/overlay-shared.apk b/cmds/idmap2/tests/data/overlay/overlay-shared.apk
index 93dcc82..0a8b737 100644
--- a/cmds/idmap2/tests/data/overlay/overlay-shared.apk
+++ b/cmds/idmap2/tests/data/overlay/overlay-shared.apk
Binary files differ
diff --git a/cmds/idmap2/tests/data/overlay/overlay-static-1.apk b/cmds/idmap2/tests/data/overlay/overlay-static-1.apk
index 5b8a6e4..fd41182 100644
--- a/cmds/idmap2/tests/data/overlay/overlay-static-1.apk
+++ b/cmds/idmap2/tests/data/overlay/overlay-static-1.apk
Binary files differ
diff --git a/cmds/idmap2/tests/data/overlay/overlay-static-2.apk b/cmds/idmap2/tests/data/overlay/overlay-static-2.apk
index 698a1fd..b24765f 100644
--- a/cmds/idmap2/tests/data/overlay/overlay-static-2.apk
+++ b/cmds/idmap2/tests/data/overlay/overlay-static-2.apk
Binary files differ
diff --git a/cmds/idmap2/tests/data/overlay/overlay.apk b/cmds/idmap2/tests/data/overlay/overlay.apk
index 1db303f..870575e 100644
--- a/cmds/idmap2/tests/data/overlay/overlay.apk
+++ b/cmds/idmap2/tests/data/overlay/overlay.apk
Binary files differ
diff --git a/cmds/idmap2/tests/data/signature-overlay/signature-overlay.apk b/cmds/idmap2/tests/data/signature-overlay/signature-overlay.apk
index 51e19de..e0fd204 100644
--- a/cmds/idmap2/tests/data/signature-overlay/signature-overlay.apk
+++ b/cmds/idmap2/tests/data/signature-overlay/signature-overlay.apk
Binary files differ
diff --git a/cmds/idmap2/tests/data/system-overlay-invalid/res/values/values.xml b/cmds/idmap2/tests/data/system-overlay-invalid/res/values/values.xml
index 7119d82..ebaf49c 100644
--- a/cmds/idmap2/tests/data/system-overlay-invalid/res/values/values.xml
+++ b/cmds/idmap2/tests/data/system-overlay-invalid/res/values/values.xml
@@ -26,6 +26,7 @@
     <string name="policy_odm">policy_odm</string>
     <string name="policy_oem">policy_oem</string>
     <string name="policy_actor">policy_actor</string>
+    <string name="policy_config_signature">policy_config_signature</string>
 
     <!-- Requests to overlay a resource that is not declared as overlayable. -->
     <string name="not_overlayable">not_overlayable</string>
diff --git a/cmds/idmap2/tests/data/system-overlay-invalid/system-overlay-invalid.apk b/cmds/idmap2/tests/data/system-overlay-invalid/system-overlay-invalid.apk
index bd99098..a63daf8 100644
--- a/cmds/idmap2/tests/data/system-overlay-invalid/system-overlay-invalid.apk
+++ b/cmds/idmap2/tests/data/system-overlay-invalid/system-overlay-invalid.apk
Binary files differ
diff --git a/cmds/idmap2/tests/data/system-overlay/system-overlay.apk b/cmds/idmap2/tests/data/system-overlay/system-overlay.apk
index a0fba43..90d2803 100644
--- a/cmds/idmap2/tests/data/system-overlay/system-overlay.apk
+++ b/cmds/idmap2/tests/data/system-overlay/system-overlay.apk
Binary files differ
diff --git a/cmds/idmap2/tests/data/target/res/values/overlayable.xml b/cmds/idmap2/tests/data/target/res/values/overlayable.xml
index ad4cd48..57e6c43 100644
--- a/cmds/idmap2/tests/data/target/res/values/overlayable.xml
+++ b/cmds/idmap2/tests/data/target/res/values/overlayable.xml
@@ -45,6 +45,10 @@
         <item type="string" name="policy_actor" />
     </policy>
 
+    <policy type="config_signature">
+        <item type="string" name="policy_config_signature"/>
+    </policy>
+
     <!-- Resources publicly overlayable -->
     <policy type="public">
         <item type="string" name="policy_public" />
diff --git a/cmds/idmap2/tests/data/target/res/values/values.xml b/cmds/idmap2/tests/data/target/res/values/values.xml
index 5230e25..00909a9 100644
--- a/cmds/idmap2/tests/data/target/res/values/values.xml
+++ b/cmds/idmap2/tests/data/target/res/values/values.xml
@@ -37,6 +37,7 @@
     <string name="policy_system">policy_system</string>
     <string name="policy_system_vendor">policy_system_vendor</string>
     <string name="policy_actor">policy_actor</string>
+    <string name="policy_config_signature">policy_config_signature</string>
 
     <string name="other">other</string>
 </resources>
diff --git a/cmds/idmap2/tests/data/target/target-no-overlayable.apk b/cmds/idmap2/tests/data/target/target-no-overlayable.apk
index 58504a7..cc3491d 100644
--- a/cmds/idmap2/tests/data/target/target-no-overlayable.apk
+++ b/cmds/idmap2/tests/data/target/target-no-overlayable.apk
Binary files differ
diff --git a/cmds/idmap2/tests/data/target/target.apk b/cmds/idmap2/tests/data/target/target.apk
index c80e5eb..4a58c5e 100644
--- a/cmds/idmap2/tests/data/target/target.apk
+++ b/cmds/idmap2/tests/data/target/target.apk
Binary files differ
diff --git a/cmds/incidentd/src/IncidentService.cpp b/cmds/incidentd/src/IncidentService.cpp
index dc16125..13bf197 100644
--- a/cmds/incidentd/src/IncidentService.cpp
+++ b/cmds/incidentd/src/IncidentService.cpp
@@ -554,6 +554,10 @@
             return NO_ERROR;
         }
         if (!args[0].compare(String8("section"))) {
+            if (argCount == 1) {
+                fprintf(out, "Not enough arguments for section\n");
+                return NO_ERROR;
+            }
             int id = atoi(args[1]);
             int idx = 0;
             while (SECTION_LIST[idx] != NULL) {
diff --git a/cmds/incidentd/tests/section_list.cpp b/cmds/incidentd/tests/section_list.cpp
index 3a45af0..60f7fb7 100644
--- a/cmds/incidentd/tests/section_list.cpp
+++ b/cmds/incidentd/tests/section_list.cpp
@@ -1,4 +1,4 @@
-// This file is a dummy section_list.cpp used for test only.
+// This file is a stub section_list.cpp used for test only.
 #include "section_list.h"
 
 #include "frameworks/base/cmds/incidentd/tests/test_proto.pb.h"
diff --git a/cmds/screencap/screencap.cpp b/cmds/screencap/screencap.cpp
index c1d8399..dec4a56 100644
--- a/cmds/screencap/screencap.cpp
+++ b/cmds/screencap/screencap.cpp
@@ -51,12 +51,11 @@
             "usage: %s [-hp] [-d display-id] [FILENAME]\n"
             "   -h: this message\n"
             "   -p: save the file as a png.\n"
-            "   -d: specify the physical display ID to capture (default: %"
-                    ANDROID_PHYSICAL_DISPLAY_ID_FORMAT ")\n"
+            "   -d: specify the physical display ID to capture (default: %s)\n"
             "       see \"dumpsys SurfaceFlinger --display-id\" for valid display IDs.\n"
             "If FILENAME ends with .png it will be saved as a png.\n"
             "If FILENAME is not given, the results will be printed to stdout.\n",
-            pname, displayId);
+            pname, to_string(displayId).c_str());
 }
 
 static int32_t flinger2bitmapFormat(PixelFormat f)
@@ -137,7 +136,7 @@
                 png = true;
                 break;
             case 'd':
-                displayId = atoll(optarg);
+                displayId = PhysicalDisplayId(atoll(optarg));
                 break;
             case '?':
             case 'h':
@@ -182,16 +181,17 @@
     ProcessState::self()->setThreadPoolMaxThreadCount(0);
     ProcessState::self()->startThreadPool();
 
-    ui::Dataspace outDataspace;
-    sp<GraphicBuffer> outBuffer;
-
-    status_t result = ScreenshotClient::capture(*displayId, &outDataspace, &outBuffer);
+    ScreenCaptureResults captureResults;
+    status_t result = ScreenshotClient::captureDisplay(displayId->value, captureResults);
     if (result != NO_ERROR) {
         close(fd);
         return 1;
     }
 
-    result = outBuffer->lock(GraphicBuffer::USAGE_SW_READ_OFTEN, &base);
+    ui::Dataspace dataspace = captureResults.capturedDataspace;
+    sp<GraphicBuffer> buffer = captureResults.buffer;
+
+    result = buffer->lock(GraphicBuffer::USAGE_SW_READ_OFTEN, &base);
 
     if (base == nullptr || result != NO_ERROR) {
         String8 reason;
@@ -207,13 +207,13 @@
 
     if (png) {
         AndroidBitmapInfo info;
-        info.format = flinger2bitmapFormat(outBuffer->getPixelFormat());
+        info.format = flinger2bitmapFormat(buffer->getPixelFormat());
         info.flags = ANDROID_BITMAP_FLAGS_ALPHA_PREMUL;
-        info.width = outBuffer->getWidth();
-        info.height = outBuffer->getHeight();
-        info.stride = outBuffer->getStride() * bytesPerPixel(outBuffer->getPixelFormat());
+        info.width = buffer->getWidth();
+        info.height = buffer->getHeight();
+        info.stride = buffer->getStride() * bytesPerPixel(buffer->getPixelFormat());
 
-        int result = AndroidBitmap_compress(&info, static_cast<int32_t>(outDataspace), base,
+        int result = AndroidBitmap_compress(&info, static_cast<int32_t>(dataspace), base,
                                             ANDROID_BITMAP_COMPRESS_FORMAT_PNG, 100, &fd,
                                             [](void* fdPtr, const void* data, size_t size) -> bool {
                                                 int bytesWritten = write(*static_cast<int*>(fdPtr),
@@ -229,11 +229,11 @@
             notifyMediaScanner(fn);
         }
     } else {
-        uint32_t w = outBuffer->getWidth();
-        uint32_t h = outBuffer->getHeight();
-        uint32_t s = outBuffer->getStride();
-        uint32_t f = outBuffer->getPixelFormat();
-        uint32_t c = dataSpaceToInt(outDataspace);
+        uint32_t w = buffer->getWidth();
+        uint32_t h = buffer->getHeight();
+        uint32_t s = buffer->getStride();
+        uint32_t f = buffer->getPixelFormat();
+        uint32_t c = dataSpaceToInt(dataspace);
 
         write(fd, &w, 4);
         write(fd, &h, 4);
diff --git a/cmds/statsd/Android.bp b/cmds/statsd/Android.bp
index 1579715..88db1d8 100644
--- a/cmds/statsd/Android.bp
+++ b/cmds/statsd/Android.bp
@@ -73,10 +73,10 @@
         "src/HashableDimensionKey.cpp",
         "src/logd/LogEvent.cpp",
         "src/logd/LogEventQueue.cpp",
-        "src/matchers/CombinationLogMatchingTracker.cpp",
+        "src/matchers/CombinationAtomMatchingTracker.cpp",
         "src/matchers/EventMatcherWizard.cpp",
         "src/matchers/matcher_util.cpp",
-        "src/matchers/SimpleLogMatchingTracker.cpp",
+        "src/matchers/SimpleAtomMatchingTracker.cpp",
         "src/metadata_util.cpp",
         "src/metrics/CountMetricProducer.cpp",
         "src/metrics/duration_helper/MaxDurationTracker.cpp",
@@ -85,8 +85,9 @@
         "src/metrics/EventMetricProducer.cpp",
         "src/metrics/GaugeMetricProducer.cpp",
         "src/metrics/MetricProducer.cpp",
-        "src/metrics/metrics_manager_util.cpp",
         "src/metrics/MetricsManager.cpp",
+        "src/metrics/parsing_utils/config_update_utils.cpp",
+        "src/metrics/parsing_utils/metrics_manager_util.cpp",
         "src/metrics/ValueMetricProducer.cpp",
         "src/packages/UidMap.cpp",
         "src/shell/shell_config.proto",
@@ -322,6 +323,8 @@
         "tests/metrics/metrics_test_helper.cpp",
         "tests/metrics/OringDurationTracker_test.cpp",
         "tests/metrics/ValueMetricProducer_test.cpp",
+        "tests/metrics/parsing_utils/config_update_utils_test.cpp",
+        "tests/metrics/parsing_utils/metrics_manager_util_test.cpp",
         "tests/MetricsManager_test.cpp",
         "tests/shell/ShellSubscriber_test.cpp",
         "tests/state/StateTracker_test.cpp",
diff --git a/cmds/statsd/src/StatsLogProcessor.cpp b/cmds/statsd/src/StatsLogProcessor.cpp
index 6327490..7bee4e2 100644
--- a/cmds/statsd/src/StatsLogProcessor.cpp
+++ b/cmds/statsd/src/StatsLogProcessor.cpp
@@ -546,7 +546,8 @@
         }
     } else {
         // Preserve the existing MetricsManager, update necessary components and metadata in place.
-        configValid = it->second->updateConfig(timestampNs, config);
+        configValid = it->second->updateConfig(config, mTimeBaseNs, timestampNs,
+                                               mAnomalyAlarmMonitor, mPeriodicAlarmMonitor);
         if (configValid) {
             // TODO(b/162323476): refresh TTL, ensure init() is handled properly.
             mUidMap->OnConfigUpdated(key);
diff --git a/cmds/statsd/src/atoms.proto b/cmds/statsd/src/atoms.proto
index c95f4c0..e6e22ba 100644
--- a/cmds/statsd/src/atoms.proto
+++ b/cmds/statsd/src/atoms.proto
@@ -485,6 +485,9 @@
         NetworkTetheringReported  network_tethering_reported =
             303 [(module) = "network_tethering"];
         ImeTouchReported ime_touch_reported = 304 [(module) = "sysui"];
+        UIInteractionFrameInfoReported ui_interaction_frame_info_reported =
+            305 [(module) = "framework"];
+        UIActionLatencyReported ui_action_latency_reported = 306 [(module) = "framework"];
 
         // StatsdStats tracks platform atoms with ids upto 500.
         // Update StatsdStats::kMaxPushedAtomId when atom ids here approach that value.
@@ -4004,7 +4007,7 @@
     optional bool is_group_summary = 5;
 
     // The section of the shade that the notification is in.
-    // See NotificationSectionsManager.PriorityBucket.
+    // See SystemUI Notifications.proto.
     enum NotificationSection {
         SECTION_UNKNOWN = 0;
         SECTION_HEADS_UP = 1;
@@ -4012,6 +4015,7 @@
         SECTION_PEOPLE = 3;
         SECTION_ALERTING = 4;
         SECTION_SILENT = 5;
+        SECTION_FOREGROUND_SERVICE = 6;
     }
     optional NotificationSection section = 6;
 }
@@ -4434,15 +4438,12 @@
         UNKNOWN = 0;
         CHIP_VIEWED = 1;
         CHIP_CLICKED = 2;
-        DIALOG_PRIVACY_SETTINGS = 3;
+        reserved 3; // Used only in beta builds, never shipped 
         DIALOG_DISMISS = 4;
         DIALOG_LINE_ITEM = 5;
     }
 
     optional Type type = 1 [(state_field_option).exclusive_state = true];
-
-    // Used if the type is LINE_ITEM
-    optional string package_name = 2;
 }
 
 /**
@@ -5057,6 +5058,54 @@
     optional Result result = 4;
 }
 
+/**
+ * Event to track Jank for various system interactions.
+ *
+ * Logged from:
+ *  frameworks/base/core/java/android/os/aot/FrameTracker.java
+ */
+message UIInteractionFrameInfoReported {
+    enum InteractionType {
+        UNKNOWN = 0;
+        NOTIFICATION_SHADE_SWIPE = 1;
+    }
+
+    optional InteractionType interaction_type = 1;
+
+    // Number of frames rendered during the interaction.
+    optional int64 total_frames = 2;
+
+    // Number of frames that were skipped in rendering during the interaction.
+    optional int64 missed_frames = 3;
+
+    // Maximum time it took to render a single frame during the interaction.
+    optional int64 max_frame_time_nanos = 4;
+}
+
+/**
+ * Event to track various latencies in SystemUI.
+ *
+ * Logged from:
+ *  frameworks/base/core/java/com/android/internal/util/LatencyTracker.java
+ */
+message UIActionLatencyReported {
+    enum ActionType {
+        UNKNOWN = 0;
+        ACTION_EXPAND_PANEL = 1;
+        ACTION_TOGGLE_RECENTS = 2;
+        ACTION_FINGERPRINT_WAKE_AND_UNLOCK = 3;
+        ACTION_CHECK_CREDENTIAL = 4;
+        ACTION_CHECK_CREDENTIAL_UNLOCKED = 5;
+        ACTION_TURN_ON_SCREEN = 6;
+        ACTION_ROTATE_SCREEN = 7;
+        ACTION_FACE_WAKE_AND_UNLOCK = 8;
+    }
+
+    optional ActionType action = 1;
+
+    optional int64 latency_millis = 2;
+}
+
 //////////////////////////////////////////////////////////////////////
 // Pulled atoms below this line //
 //////////////////////////////////////////////////////////////////////
diff --git a/cmds/statsd/src/condition/CombinationConditionTracker.cpp b/cmds/statsd/src/condition/CombinationConditionTracker.cpp
index e9875ba..3b65f82 100644
--- a/cmds/statsd/src/condition/CombinationConditionTracker.cpp
+++ b/cmds/statsd/src/condition/CombinationConditionTracker.cpp
@@ -38,9 +38,23 @@
                                        const vector<sp<ConditionTracker>>& allConditionTrackers,
                                        const unordered_map<int64_t, int>& conditionIdIndexMap,
                                        vector<bool>& stack,
-                                       vector<ConditionState>& initialConditionCache) {
+                                       vector<ConditionState>& conditionCache) {
     VLOG("Combination predicate init() %lld", (long long)mConditionId);
     if (mInitialized) {
+        // All the children are guaranteed to be initialized, but the recursion is needed to
+        // fill the conditionCache properly, since another combination condition or metric
+        // might rely on this. The recursion is needed to compute the current condition.
+
+        // Init is called instead of isConditionMet so that the ConditionKey can be filled with the
+        // default key for sliced conditions, since we do not know all indirect descendants here.
+        for (const int childIndex : mChildren) {
+            if (conditionCache[childIndex] == ConditionState::kNotEvaluated) {
+                allConditionTrackers[childIndex]->init(allConditionConfig, allConditionTrackers,
+                                                       conditionIdIndexMap, stack, conditionCache);
+            }
+        }
+        conditionCache[mIndex] =
+                evaluateCombinationCondition(mChildren, mLogicalOperation, conditionCache);
         return true;
     }
 
@@ -74,9 +88,8 @@
             return false;
         }
 
-        bool initChildSucceeded =
-                childTracker->init(allConditionConfig, allConditionTrackers, conditionIdIndexMap,
-                                   stack, initialConditionCache);
+        bool initChildSucceeded = childTracker->init(allConditionConfig, allConditionTrackers,
+                                                     conditionIdIndexMap, stack, conditionCache);
 
         if (!initChildSucceeded) {
             ALOGW("Child initialization failed %lld ", (long long)child);
@@ -92,14 +105,14 @@
             mUnSlicedChildren.push_back(childIndex);
         }
         mChildren.push_back(childIndex);
-        mTrackerIndex.insert(childTracker->getLogTrackerIndex().begin(),
-                             childTracker->getLogTrackerIndex().end());
+        mTrackerIndex.insert(childTracker->getAtomMatchingTrackerIndex().begin(),
+                             childTracker->getAtomMatchingTrackerIndex().end());
     }
 
-    mUnSlicedPartCondition = evaluateCombinationCondition(mUnSlicedChildren, mLogicalOperation,
-                                                          initialConditionCache);
-    initialConditionCache[mIndex] =
-            evaluateCombinationCondition(mChildren, mLogicalOperation, initialConditionCache);
+    mUnSlicedPartCondition =
+            evaluateCombinationCondition(mUnSlicedChildren, mLogicalOperation, conditionCache);
+    conditionCache[mIndex] =
+            evaluateCombinationCondition(mChildren, mLogicalOperation, conditionCache);
 
     // unmark this node in the recursion stack.
     stack[mIndex] = false;
diff --git a/cmds/statsd/src/condition/CombinationConditionTracker.h b/cmds/statsd/src/condition/CombinationConditionTracker.h
index 39ff0ab..a7fac3d 100644
--- a/cmds/statsd/src/condition/CombinationConditionTracker.h
+++ b/cmds/statsd/src/condition/CombinationConditionTracker.h
@@ -33,7 +33,7 @@
     bool init(const std::vector<Predicate>& allConditionConfig,
               const std::vector<sp<ConditionTracker>>& allConditionTrackers,
               const std::unordered_map<int64_t, int>& conditionIdIndexMap, std::vector<bool>& stack,
-              std::vector<ConditionState>& initialConditionCache) override;
+              std::vector<ConditionState>& conditionCache) override;
 
     void evaluateCondition(const LogEvent& event,
                            const std::vector<MatchingState>& eventMatcherValues,
diff --git a/cmds/statsd/src/condition/ConditionTracker.h b/cmds/statsd/src/condition/ConditionTracker.h
index 62736c8..4e12535 100644
--- a/cmds/statsd/src/condition/ConditionTracker.h
+++ b/cmds/statsd/src/condition/ConditionTracker.h
@@ -18,7 +18,7 @@
 
 #include "condition/condition_util.h"
 #include "frameworks/base/cmds/statsd/src/statsd_config.pb.h"
-#include "matchers/LogMatchingTracker.h"
+#include "matchers/AtomMatchingTracker.h"
 #include "matchers/matcher_util.h"
 
 #include <utils/RefBase.h>
@@ -46,23 +46,25 @@
     // Initialize this ConditionTracker. This initialization is done recursively (DFS). It can also
     // be done in the constructor, but we do it separately because (1) easy to return a bool to
     // indicate whether the initialization is successful. (2) makes unit test easier.
+    // This function can also be called on config updates, in which case it does nothing other than
+    // fill the condition cache with the current condition.
     // allConditionConfig: the list of all Predicate config from statsd_config.
     // allConditionTrackers: the list of all ConditionTrackers (this is needed because we may also
     //                       need to call init() on children conditions)
     // conditionIdIndexMap: the mapping from condition id to its index.
     // stack: a bit map to keep track which nodes have been visited on the stack in the recursion.
-    // initialConditionCache: tracks initial conditions of all ConditionTrackers.
+    // conditionCache: tracks initial conditions of all ConditionTrackers. returns the
+    //                        current condition if called on a config update.
     virtual bool init(const std::vector<Predicate>& allConditionConfig,
                       const std::vector<sp<ConditionTracker>>& allConditionTrackers,
                       const std::unordered_map<int64_t, int>& conditionIdIndexMap,
-                      std::vector<bool>& stack,
-                      std::vector<ConditionState>& initialConditionCache) = 0;
+                      std::vector<bool>& stack, std::vector<ConditionState>& conditionCache) = 0;
 
     // evaluate current condition given the new event.
     // event: the new log event
-    // eventMatcherValues: the results of the LogMatcherTrackers. LogMatcherTrackers always process
-    //                     event before ConditionTrackers, because ConditionTracker depends on
-    //                     LogMatchingTrackers.
+    // eventMatcherValues: the results of the AtomMatchingTrackers. AtomMatchingTrackers always
+    //                     process event before ConditionTrackers, because ConditionTracker depends
+    //                     on AtomMatchingTrackers.
     // mAllConditions: the list of all ConditionTracker
     // conditionCache: the cached non-sliced condition of the ConditionTrackers for this new event.
     // conditionChanged: the bit map to record whether the condition has changed.
@@ -88,8 +90,8 @@
             const bool isPartialLink,
             std::vector<ConditionState>& conditionCache) const = 0;
 
-    // return the list of LogMatchingTracker index that this ConditionTracker uses.
-    virtual const std::set<int>& getLogTrackerIndex() const {
+    // return the list of AtomMatchingTracker index that this ConditionTracker uses.
+    virtual const std::set<int>& getAtomMatchingTrackerIndex() const {
         return mTrackerIndex;
     }
 
@@ -136,7 +138,7 @@
     // if it's properly initialized.
     bool mInitialized;
 
-    // the list of LogMatchingTracker index that this ConditionTracker uses.
+    // the list of AtomMatchingTracker index that this ConditionTracker uses.
     std::set<int> mTrackerIndex;
 
     // This variable is only used for CombinationConditionTrackers.
diff --git a/cmds/statsd/src/condition/SimpleConditionTracker.cpp b/cmds/statsd/src/condition/SimpleConditionTracker.cpp
index efb4d49..f45759b 100644
--- a/cmds/statsd/src/condition/SimpleConditionTracker.cpp
+++ b/cmds/statsd/src/condition/SimpleConditionTracker.cpp
@@ -95,11 +95,14 @@
 bool SimpleConditionTracker::init(const vector<Predicate>& allConditionConfig,
                                   const vector<sp<ConditionTracker>>& allConditionTrackers,
                                   const unordered_map<int64_t, int>& conditionIdIndexMap,
-                                  vector<bool>& stack,
-                                  vector<ConditionState>& initialConditionCache) {
+                                  vector<bool>& stack, vector<ConditionState>& conditionCache) {
     // SimpleConditionTracker does not have dependency on other conditions, thus we just return
     // if the initialization was successful.
-    initialConditionCache[mIndex] = mInitialValue;
+    ConditionKey conditionKey;
+    if (mSliced) {
+        conditionKey[mConditionId] = DEFAULT_DIMENSION_KEY;
+    }
+    isConditionMet(conditionKey, allConditionTrackers, mSliced, conditionCache);
     return mInitialized;
 }
 
diff --git a/cmds/statsd/src/condition/SimpleConditionTracker.h b/cmds/statsd/src/condition/SimpleConditionTracker.h
index ea7f87b..1a9e35e 100644
--- a/cmds/statsd/src/condition/SimpleConditionTracker.h
+++ b/cmds/statsd/src/condition/SimpleConditionTracker.h
@@ -38,7 +38,7 @@
     bool init(const std::vector<Predicate>& allConditionConfig,
               const std::vector<sp<ConditionTracker>>& allConditionTrackers,
               const std::unordered_map<int64_t, int>& conditionIdIndexMap, std::vector<bool>& stack,
-              std::vector<ConditionState>& initialConditionCache) override;
+              std::vector<ConditionState>& conditionCache) override;
 
     void evaluateCondition(const LogEvent& event,
                            const std::vector<MatchingState>& eventMatcherValues,
diff --git a/cmds/statsd/src/hash.h b/cmds/statsd/src/hash.h
index cfe869f..bd6b0cd 100644
--- a/cmds/statsd/src/hash.h
+++ b/cmds/statsd/src/hash.h
@@ -22,6 +22,7 @@
 namespace os {
 namespace statsd {
 
+// Uses murmur2 hashing algorithm.
 extern uint32_t Hash32(const char *data, size_t n, uint32_t seed);
 extern uint64_t Hash64(const char* data, size_t n, uint64_t seed);
 
diff --git a/cmds/statsd/src/matchers/AtomMatchingTracker.h b/cmds/statsd/src/matchers/AtomMatchingTracker.h
new file mode 100644
index 0000000..c138497
--- /dev/null
+++ b/cmds/statsd/src/matchers/AtomMatchingTracker.h
@@ -0,0 +1,118 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef ATOM_MATCHING_TRACKER_H
+#define ATOM_MATCHING_TRACKER_H
+
+#include "frameworks/base/cmds/statsd/src/statsd_config.pb.h"
+#include "logd/LogEvent.h"
+#include "matchers/matcher_util.h"
+
+#include <utils/RefBase.h>
+
+#include <set>
+#include <unordered_map>
+#include <vector>
+
+namespace android {
+namespace os {
+namespace statsd {
+
+class AtomMatchingTracker : public virtual RefBase {
+public:
+    AtomMatchingTracker(const int64_t& id, const int index, const uint64_t protoHash)
+        : mId(id), mIndex(index), mInitialized(false), mProtoHash(protoHash){};
+
+    virtual ~AtomMatchingTracker(){};
+
+    // Initialize this AtomMatchingTracker.
+    // allAtomMatchers: the list of the AtomMatcher proto config. This is needed because we don't
+    //                  store the proto object in memory. We only need it during initilization.
+    // allAtomMatchingTrackers: the list of the AtomMatchingTracker objects. It's a one-to-one
+    //                          mapping with allAtomMatchers. This is needed because the
+    //                          initialization is done recursively for
+    //                          CombinationAtomMatchingTrackers using DFS.
+    // stack: a bit map to record which matcher has been visited on the stack. This is for detecting
+    //        circle dependency.
+    virtual bool init(const std::vector<AtomMatcher>& allAtomMatchers,
+                      const std::vector<sp<AtomMatchingTracker>>& allAtomMatchingTrackers,
+                      const std::unordered_map<int64_t, int>& matcherMap,
+                      std::vector<bool>& stack) = 0;
+
+    // Update appropriate state on config updates. Primarily, all indices need to be updated.
+    // This matcher and all of its children are guaranteed to be preserved across the update.
+    // matcher: the AtomMatcher proto from the config.
+    // index: the index of this matcher in mAllAtomMatchingTrackers.
+    // atomMatchingTrackerMap: map from matcher id to index in mAllAtomMatchingTrackers
+    virtual bool onConfigUpdated(
+            const AtomMatcher& matcher, const int index,
+            const std::unordered_map<int64_t, int>& atomMatchingTrackerMap) = 0;
+
+    // Called when a log event comes.
+    // event: the log event.
+    // allAtomMatchingTrackers: the list of all AtomMatchingTrackers. This is needed because the log
+    //                          processing is done recursively.
+    // matcherResults: The cached results for all matchers for this event. Parent matchers can
+    //                 directly access the children's matching results if they have been evaluated.
+    //                 Otherwise, call children matchers' onLogEvent.
+    virtual void onLogEvent(const LogEvent& event,
+                            const std::vector<sp<AtomMatchingTracker>>& allAtomMatchingTrackers,
+                            std::vector<MatchingState>& matcherResults) = 0;
+
+    // Get the tagIds that this matcher cares about. The combined collection is stored
+    // in MetricMananger, so that we can pass any LogEvents that are not interest of us. It uses
+    // some memory but hopefully it can save us much CPU time when there is flood of events.
+    virtual const std::set<int>& getAtomIds() const {
+        return mAtomIds;
+    }
+
+    int64_t getId() const {
+        return mId;
+    }
+
+    uint64_t getProtoHash() const {
+        return mProtoHash;
+    }
+
+protected:
+    // Name of this matching. We don't really need the name, but it makes log message easy to debug.
+    const int64_t mId;
+
+    // Index of this AtomMatchingTracker in MetricsManager's container.
+    int mIndex;
+
+    // Whether this AtomMatchingTracker has been properly initialized.
+    bool mInitialized;
+
+    // The collection of the event tag ids that this AtomMatchingTracker cares. So we can quickly
+    // return kNotMatched when we receive an event with an id not in the list. This is especially
+    // useful when we have a complex CombinationAtomMatchingTracker.
+    std::set<int> mAtomIds;
+
+    // Hash of the AtomMatcher's proto bytes from StatsdConfig.
+    // Used to determine if the definition of this matcher has changed across a config update.
+    const uint64_t mProtoHash;
+
+    FRIEND_TEST(MetricsManagerTest, TestCreateAtomMatchingTrackerSimple);
+    FRIEND_TEST(MetricsManagerTest, TestCreateAtomMatchingTrackerCombination);
+    FRIEND_TEST(ConfigUpdateTest, TestUpdateMatchers);
+};
+
+}  // namespace statsd
+}  // namespace os
+}  // namespace android
+
+#endif  // ATOM_MATCHING_TRACKER_H
diff --git a/cmds/statsd/src/matchers/CombinationAtomMatchingTracker.cpp b/cmds/statsd/src/matchers/CombinationAtomMatchingTracker.cpp
new file mode 100644
index 0000000..45685ce
--- /dev/null
+++ b/cmds/statsd/src/matchers/CombinationAtomMatchingTracker.cpp
@@ -0,0 +1,140 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "Log.h"
+
+#include "CombinationAtomMatchingTracker.h"
+
+#include "matchers/matcher_util.h"
+
+namespace android {
+namespace os {
+namespace statsd {
+
+using std::set;
+using std::unordered_map;
+using std::vector;
+
+CombinationAtomMatchingTracker::CombinationAtomMatchingTracker(const int64_t& id, const int index,
+                                                               const uint64_t protoHash)
+    : AtomMatchingTracker(id, index, protoHash) {
+}
+
+CombinationAtomMatchingTracker::~CombinationAtomMatchingTracker() {
+}
+
+bool CombinationAtomMatchingTracker::init(
+        const vector<AtomMatcher>& allAtomMatchers,
+        const vector<sp<AtomMatchingTracker>>& allAtomMatchingTrackers,
+        const unordered_map<int64_t, int>& matcherMap, vector<bool>& stack) {
+    if (mInitialized) {
+        return true;
+    }
+
+    // mark this node as visited in the recursion stack.
+    stack[mIndex] = true;
+
+    AtomMatcher_Combination matcher = allAtomMatchers[mIndex].combination();
+
+    // LogicalOperation is missing in the config
+    if (!matcher.has_operation()) {
+        return false;
+    }
+
+    mLogicalOperation = matcher.operation();
+
+    if (mLogicalOperation == LogicalOperation::NOT && matcher.matcher_size() != 1) {
+        return false;
+    }
+
+    for (const auto& child : matcher.matcher()) {
+        auto pair = matcherMap.find(child);
+        if (pair == matcherMap.end()) {
+            ALOGW("Matcher %lld not found in the config", (long long)child);
+            return false;
+        }
+
+        int childIndex = pair->second;
+
+        // if the child is a visited node in the recursion -> circle detected.
+        if (stack[childIndex]) {
+            ALOGE("Circle detected in matcher config");
+            return false;
+        }
+
+        if (!allAtomMatchingTrackers[childIndex]->init(allAtomMatchers, allAtomMatchingTrackers,
+                                                       matcherMap, stack)) {
+            ALOGW("child matcher init failed %lld", (long long)child);
+            return false;
+        }
+
+        mChildren.push_back(childIndex);
+
+        const set<int>& childTagIds = allAtomMatchingTrackers[childIndex]->getAtomIds();
+        mAtomIds.insert(childTagIds.begin(), childTagIds.end());
+    }
+
+    mInitialized = true;
+    // unmark this node in the recursion stack.
+    stack[mIndex] = false;
+    return true;
+}
+
+bool CombinationAtomMatchingTracker::onConfigUpdated(
+        const AtomMatcher& matcher, const int index,
+        const unordered_map<int64_t, int>& atomMatchingTrackerMap) {
+    mIndex = index;
+    mChildren.clear();
+    AtomMatcher_Combination combinationMatcher = matcher.combination();
+    for (const int64_t child : combinationMatcher.matcher()) {
+        const auto& pair = atomMatchingTrackerMap.find(child);
+        if (pair == atomMatchingTrackerMap.end()) {
+            ALOGW("Matcher %lld not found in the config", (long long)child);
+            return false;
+        }
+        mChildren.push_back(pair->second);
+    }
+    return true;
+}
+
+void CombinationAtomMatchingTracker::onLogEvent(
+        const LogEvent& event, const vector<sp<AtomMatchingTracker>>& allAtomMatchingTrackers,
+        vector<MatchingState>& matcherResults) {
+    // this event has been processed.
+    if (matcherResults[mIndex] != MatchingState::kNotComputed) {
+        return;
+    }
+
+    if (mAtomIds.find(event.GetTagId()) == mAtomIds.end()) {
+        matcherResults[mIndex] = MatchingState::kNotMatched;
+        return;
+    }
+
+    // evaluate children matchers if they haven't been evaluated.
+    for (const int childIndex : mChildren) {
+        if (matcherResults[childIndex] == MatchingState::kNotComputed) {
+            const sp<AtomMatchingTracker>& child = allAtomMatchingTrackers[childIndex];
+            child->onLogEvent(event, allAtomMatchingTrackers, matcherResults);
+        }
+    }
+
+    bool matched = combinationMatch(mChildren, mLogicalOperation, matcherResults);
+    matcherResults[mIndex] = matched ? MatchingState::kMatched : MatchingState::kNotMatched;
+}
+
+}  // namespace statsd
+}  // namespace os
+}  // namespace android
diff --git a/cmds/statsd/src/matchers/CombinationAtomMatchingTracker.h b/cmds/statsd/src/matchers/CombinationAtomMatchingTracker.h
new file mode 100644
index 0000000..3160448
--- /dev/null
+++ b/cmds/statsd/src/matchers/CombinationAtomMatchingTracker.h
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#ifndef COMBINATION_ATOM_MATCHING_TRACKER_H
+#define COMBINATION_ATOM_MATCHING_TRACKER_H
+
+#include <unordered_map>
+#include <vector>
+
+#include "AtomMatchingTracker.h"
+#include "frameworks/base/cmds/statsd/src/statsd_config.pb.h"
+
+namespace android {
+namespace os {
+namespace statsd {
+
+// Represents a AtomMatcher_Combination in the StatsdConfig.
+class CombinationAtomMatchingTracker : public AtomMatchingTracker {
+public:
+    CombinationAtomMatchingTracker(const int64_t& id, const int index, const uint64_t protoHash);
+
+    bool init(const std::vector<AtomMatcher>& allAtomMatchers,
+              const std::vector<sp<AtomMatchingTracker>>& allAtomMatchingTrackers,
+              const std::unordered_map<int64_t, int>& matcherMap, std::vector<bool>& stack);
+
+    bool onConfigUpdated(const AtomMatcher& matcher, const int index,
+                         const std::unordered_map<int64_t, int>& atomMatchingTrackerMap) override;
+
+    ~CombinationAtomMatchingTracker();
+
+    void onLogEvent(const LogEvent& event,
+                    const std::vector<sp<AtomMatchingTracker>>& allAtomMatchingTrackers,
+                    std::vector<MatchingState>& matcherResults) override;
+
+private:
+    LogicalOperation mLogicalOperation;
+
+    std::vector<int> mChildren;
+
+    FRIEND_TEST(ConfigUpdateTest, TestUpdateMatchers);
+};
+
+}  // namespace statsd
+}  // namespace os
+}  // namespace android
+#endif  // COMBINATION_ATOM_MATCHING_TRACKER_H
diff --git a/cmds/statsd/src/matchers/CombinationLogMatchingTracker.cpp b/cmds/statsd/src/matchers/CombinationLogMatchingTracker.cpp
deleted file mode 100644
index b94a957..0000000
--- a/cmds/statsd/src/matchers/CombinationLogMatchingTracker.cpp
+++ /dev/null
@@ -1,120 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#include "Log.h"
-
-#include "CombinationLogMatchingTracker.h"
-#include "matchers/matcher_util.h"
-
-namespace android {
-namespace os {
-namespace statsd {
-
-using std::set;
-using std::unordered_map;
-using std::vector;
-
-CombinationLogMatchingTracker::CombinationLogMatchingTracker(const int64_t& id, const int index)
-    : LogMatchingTracker(id, index) {
-}
-
-CombinationLogMatchingTracker::~CombinationLogMatchingTracker() {
-}
-
-bool CombinationLogMatchingTracker::init(const vector<AtomMatcher>& allLogMatchers,
-                                         const vector<sp<LogMatchingTracker>>& allTrackers,
-                                         const unordered_map<int64_t, int>& matcherMap,
-                                         vector<bool>& stack) {
-    if (mInitialized) {
-        return true;
-    }
-
-    // mark this node as visited in the recursion stack.
-    stack[mIndex] = true;
-
-    AtomMatcher_Combination matcher = allLogMatchers[mIndex].combination();
-
-    // LogicalOperation is missing in the config
-    if (!matcher.has_operation()) {
-        return false;
-    }
-
-    mLogicalOperation = matcher.operation();
-
-    if (mLogicalOperation == LogicalOperation::NOT && matcher.matcher_size() != 1) {
-        return false;
-    }
-
-    for (const auto& child : matcher.matcher()) {
-        auto pair = matcherMap.find(child);
-        if (pair == matcherMap.end()) {
-            ALOGW("Matcher %lld not found in the config", (long long)child);
-            return false;
-        }
-
-        int childIndex = pair->second;
-
-        // if the child is a visited node in the recursion -> circle detected.
-        if (stack[childIndex]) {
-            ALOGE("Circle detected in matcher config");
-            return false;
-        }
-
-        if (!allTrackers[childIndex]->init(allLogMatchers, allTrackers, matcherMap, stack)) {
-            ALOGW("child matcher init failed %lld", (long long)child);
-            return false;
-        }
-
-        mChildren.push_back(childIndex);
-
-        const set<int>& childTagIds = allTrackers[childIndex]->getAtomIds();
-        mAtomIds.insert(childTagIds.begin(), childTagIds.end());
-    }
-
-    mInitialized = true;
-    // unmark this node in the recursion stack.
-    stack[mIndex] = false;
-    return true;
-}
-
-void CombinationLogMatchingTracker::onLogEvent(const LogEvent& event,
-                                               const vector<sp<LogMatchingTracker>>& allTrackers,
-                                               vector<MatchingState>& matcherResults) {
-    // this event has been processed.
-    if (matcherResults[mIndex] != MatchingState::kNotComputed) {
-        return;
-    }
-
-    if (mAtomIds.find(event.GetTagId()) == mAtomIds.end()) {
-        matcherResults[mIndex] = MatchingState::kNotMatched;
-        return;
-    }
-
-    // evaluate children matchers if they haven't been evaluated.
-    for (const int childIndex : mChildren) {
-        if (matcherResults[childIndex] == MatchingState::kNotComputed) {
-            const sp<LogMatchingTracker>& child = allTrackers[childIndex];
-            child->onLogEvent(event, allTrackers, matcherResults);
-        }
-    }
-
-    bool matched = combinationMatch(mChildren, mLogicalOperation, matcherResults);
-    matcherResults[mIndex] = matched ? MatchingState::kMatched : MatchingState::kNotMatched;
-}
-
-}  // namespace statsd
-}  // namespace os
-}  // namespace android
diff --git a/cmds/statsd/src/matchers/CombinationLogMatchingTracker.h b/cmds/statsd/src/matchers/CombinationLogMatchingTracker.h
deleted file mode 100644
index 55bc4605..0000000
--- a/cmds/statsd/src/matchers/CombinationLogMatchingTracker.h
+++ /dev/null
@@ -1,53 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-#ifndef COMBINATION_LOG_MATCHING_TRACKER_H
-#define COMBINATION_LOG_MATCHING_TRACKER_H
-
-#include <unordered_map>
-#include <vector>
-#include "LogMatchingTracker.h"
-#include "frameworks/base/cmds/statsd/src/statsd_config.pb.h"
-
-namespace android {
-namespace os {
-namespace statsd {
-
-// Represents a AtomMatcher_Combination in the StatsdConfig.
-class CombinationLogMatchingTracker : public virtual LogMatchingTracker {
-public:
-    CombinationLogMatchingTracker(const int64_t& id, const int index);
-
-    bool init(const std::vector<AtomMatcher>& allLogMatchers,
-              const std::vector<sp<LogMatchingTracker>>& allTrackers,
-              const std::unordered_map<int64_t, int>& matcherMap,
-              std::vector<bool>& stack);
-
-    ~CombinationLogMatchingTracker();
-
-    void onLogEvent(const LogEvent& event,
-                    const std::vector<sp<LogMatchingTracker>>& allTrackers,
-                    std::vector<MatchingState>& matcherResults) override;
-
-private:
-    LogicalOperation mLogicalOperation;
-
-    std::vector<int> mChildren;
-};
-
-}  // namespace statsd
-}  // namespace os
-}  // namespace android
-#endif  // COMBINATION_LOG_MATCHING_TRACKER_H
diff --git a/cmds/statsd/src/matchers/EventMatcherWizard.h b/cmds/statsd/src/matchers/EventMatcherWizard.h
index 57ec2b3..5d780f2 100644
--- a/cmds/statsd/src/matchers/EventMatcherWizard.h
+++ b/cmds/statsd/src/matchers/EventMatcherWizard.h
@@ -16,7 +16,7 @@
 
 #pragma once
 
-#include "LogMatchingTracker.h"
+#include "AtomMatchingTracker.h"
 
 namespace android {
 namespace os {
@@ -25,7 +25,7 @@
 class EventMatcherWizard : public virtual android::RefBase {
 public:
     EventMatcherWizard(){};  // for testing
-    EventMatcherWizard(const std::vector<sp<LogMatchingTracker>>& eventTrackers)
+    EventMatcherWizard(const std::vector<sp<AtomMatchingTracker>>& eventTrackers)
         : mAllEventMatchers(eventTrackers){};
 
     virtual ~EventMatcherWizard(){};
@@ -33,7 +33,7 @@
     MatchingState matchLogEvent(const LogEvent& event, int matcher_index);
 
 private:
-    std::vector<sp<LogMatchingTracker>> mAllEventMatchers;
+    std::vector<sp<AtomMatchingTracker>> mAllEventMatchers;
 };
 
 }  // namespace statsd
diff --git a/cmds/statsd/src/matchers/LogMatchingTracker.h b/cmds/statsd/src/matchers/LogMatchingTracker.h
deleted file mode 100644
index 88ab4e6..0000000
--- a/cmds/statsd/src/matchers/LogMatchingTracker.h
+++ /dev/null
@@ -1,96 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#ifndef LOG_MATCHING_TRACKER_H
-#define LOG_MATCHING_TRACKER_H
-
-#include "frameworks/base/cmds/statsd/src/statsd_config.pb.h"
-#include "logd/LogEvent.h"
-#include "matchers/matcher_util.h"
-
-#include <utils/RefBase.h>
-
-#include <set>
-#include <unordered_map>
-#include <vector>
-
-namespace android {
-namespace os {
-namespace statsd {
-
-class LogMatchingTracker : public virtual RefBase {
-public:
-    LogMatchingTracker(const int64_t& id, const int index)
-        : mId(id), mIndex(index), mInitialized(false){};
-
-    virtual ~LogMatchingTracker(){};
-
-    // Initialize this LogMatchingTracker.
-    // allLogMatchers: the list of the AtomMatcher proto config. This is needed because we don't
-    //                 store the proto object in memory. We only need it during initilization.
-    // allTrackers: the list of the LogMatchingTracker objects. It's a one-to-one mapping with
-    //              allLogMatchers. This is needed because the initialization is done recursively
-    //              for CombinationLogMatchingTrackers using DFS.
-    // stack: a bit map to record which matcher has been visited on the stack. This is for detecting
-    //        circle dependency.
-    virtual bool init(const std::vector<AtomMatcher>& allLogMatchers,
-                      const std::vector<sp<LogMatchingTracker>>& allTrackers,
-                      const std::unordered_map<int64_t, int>& matcherMap,
-                      std::vector<bool>& stack) = 0;
-
-    // Called when a log event comes.
-    // event: the log event.
-    // allTrackers: the list of all LogMatchingTrackers. This is needed because the log processing
-    //              is done recursively.
-    // matcherResults: The cached results for all matchers for this event. Parent matchers can
-    //                 directly access the children's matching results if they have been evaluated.
-    //                 Otherwise, call children matchers' onLogEvent.
-    virtual void onLogEvent(const LogEvent& event,
-                            const std::vector<sp<LogMatchingTracker>>& allTrackers,
-                            std::vector<MatchingState>& matcherResults) = 0;
-
-    // Get the tagIds that this matcher cares about. The combined collection is stored
-    // in MetricMananger, so that we can pass any LogEvents that are not interest of us. It uses
-    // some memory but hopefully it can save us much CPU time when there is flood of events.
-    virtual const std::set<int>& getAtomIds() const {
-        return mAtomIds;
-    }
-
-    const int64_t& getId() const {
-        return mId;
-    }
-
-protected:
-    // Name of this matching. We don't really need the name, but it makes log message easy to debug.
-    const int64_t mId;
-
-    // Index of this LogMatchingTracker in MetricsManager's container.
-    const int mIndex;
-
-    // Whether this LogMatchingTracker has been properly initialized.
-    bool mInitialized;
-
-    // The collection of the event tag ids that this LogMatchingTracker cares. So we can quickly
-    // return kNotMatched when we receive an event with an id not in the list. This is especially
-    // useful when we have a complex CombinationLogMatcherTracker.
-    std::set<int> mAtomIds;
-};
-
-}  // namespace statsd
-}  // namespace os
-}  // namespace android
-
-#endif  // LOG_MATCHING_TRACKER_H
diff --git a/cmds/statsd/src/matchers/SimpleAtomMatchingTracker.cpp b/cmds/statsd/src/matchers/SimpleAtomMatchingTracker.cpp
new file mode 100644
index 0000000..423da5b
--- /dev/null
+++ b/cmds/statsd/src/matchers/SimpleAtomMatchingTracker.cpp
@@ -0,0 +1,81 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define DEBUG false  // STOPSHIP if true
+#include "Log.h"
+
+#include "SimpleAtomMatchingTracker.h"
+
+namespace android {
+namespace os {
+namespace statsd {
+
+using std::unordered_map;
+using std::vector;
+
+SimpleAtomMatchingTracker::SimpleAtomMatchingTracker(const int64_t& id, const int index,
+                                                     const uint64_t protoHash,
+                                                     const SimpleAtomMatcher& matcher,
+                                                     const sp<UidMap>& uidMap)
+    : AtomMatchingTracker(id, index, protoHash), mMatcher(matcher), mUidMap(uidMap) {
+    if (!matcher.has_atom_id()) {
+        mInitialized = false;
+    } else {
+        mAtomIds.insert(matcher.atom_id());
+        mInitialized = true;
+    }
+}
+
+SimpleAtomMatchingTracker::~SimpleAtomMatchingTracker() {
+}
+
+bool SimpleAtomMatchingTracker::init(const vector<AtomMatcher>& allAtomMatchers,
+                                     const vector<sp<AtomMatchingTracker>>& allAtomMatchingTrackers,
+                                     const unordered_map<int64_t, int>& matcherMap,
+                                     vector<bool>& stack) {
+    // no need to do anything.
+    return mInitialized;
+}
+
+bool SimpleAtomMatchingTracker::onConfigUpdated(
+        const AtomMatcher& matcher, const int index,
+        const unordered_map<int64_t, int>& atomMatchingTrackerMap) {
+    mIndex = index;
+    // Do not need to update mMatcher since the matcher must be identical across the update.
+    return mInitialized;
+}
+
+void SimpleAtomMatchingTracker::onLogEvent(
+        const LogEvent& event, const vector<sp<AtomMatchingTracker>>& allAtomMatchingTrackers,
+        vector<MatchingState>& matcherResults) {
+    if (matcherResults[mIndex] != MatchingState::kNotComputed) {
+        VLOG("Matcher %lld already evaluated ", (long long)mId);
+        return;
+    }
+
+    if (mAtomIds.find(event.GetTagId()) == mAtomIds.end()) {
+        matcherResults[mIndex] = MatchingState::kNotMatched;
+        return;
+    }
+
+    bool matched = matchesSimple(mUidMap, mMatcher, event);
+    matcherResults[mIndex] = matched ? MatchingState::kMatched : MatchingState::kNotMatched;
+    VLOG("Stats SimpleAtomMatcher %lld matched? %d", (long long)mId, matched);
+}
+
+}  // namespace statsd
+}  // namespace os
+}  // namespace android
diff --git a/cmds/statsd/src/matchers/SimpleAtomMatchingTracker.h b/cmds/statsd/src/matchers/SimpleAtomMatchingTracker.h
new file mode 100644
index 0000000..b67e6c2
--- /dev/null
+++ b/cmds/statsd/src/matchers/SimpleAtomMatchingTracker.h
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef SIMPLE_ATOM_MATCHING_TRACKER_H
+#define SIMPLE_ATOM_MATCHING_TRACKER_H
+
+#include <unordered_map>
+#include <vector>
+
+#include "AtomMatchingTracker.h"
+#include "frameworks/base/cmds/statsd/src/statsd_config.pb.h"
+#include "packages/UidMap.h"
+
+namespace android {
+namespace os {
+namespace statsd {
+
+class SimpleAtomMatchingTracker : public AtomMatchingTracker {
+public:
+    SimpleAtomMatchingTracker(const int64_t& id, const int index, const uint64_t protoHash,
+                              const SimpleAtomMatcher& matcher, const sp<UidMap>& uidMap);
+
+    ~SimpleAtomMatchingTracker();
+
+    bool init(const std::vector<AtomMatcher>& allAtomMatchers,
+              const std::vector<sp<AtomMatchingTracker>>& allAtomMatchingTrackers,
+              const std::unordered_map<int64_t, int>& matcherMap,
+              std::vector<bool>& stack) override;
+
+    bool onConfigUpdated(const AtomMatcher& matcher, const int index,
+                         const std::unordered_map<int64_t, int>& atomMatchingTrackerMap) override;
+
+    void onLogEvent(const LogEvent& event,
+                    const std::vector<sp<AtomMatchingTracker>>& allAtomMatchingTrackers,
+                    std::vector<MatchingState>& matcherResults) override;
+
+private:
+    const SimpleAtomMatcher mMatcher;
+    const sp<UidMap> mUidMap;
+};
+
+}  // namespace statsd
+}  // namespace os
+}  // namespace android
+#endif  // SIMPLE_ATOM_MATCHING_TRACKER_H
diff --git a/cmds/statsd/src/matchers/SimpleLogMatchingTracker.cpp b/cmds/statsd/src/matchers/SimpleLogMatchingTracker.cpp
deleted file mode 100644
index 082daf5a..0000000
--- a/cmds/statsd/src/matchers/SimpleLogMatchingTracker.cpp
+++ /dev/null
@@ -1,73 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#define DEBUG false  // STOPSHIP if true
-#include "Log.h"
-
-#include "SimpleLogMatchingTracker.h"
-
-namespace android {
-namespace os {
-namespace statsd {
-
-using std::unordered_map;
-using std::vector;
-
-
-SimpleLogMatchingTracker::SimpleLogMatchingTracker(const int64_t& id, const int index,
-                                                   const SimpleAtomMatcher& matcher,
-                                                   const UidMap& uidMap)
-    : LogMatchingTracker(id, index), mMatcher(matcher), mUidMap(uidMap) {
-    if (!matcher.has_atom_id()) {
-        mInitialized = false;
-    } else {
-        mAtomIds.insert(matcher.atom_id());
-        mInitialized = true;
-    }
-}
-
-SimpleLogMatchingTracker::~SimpleLogMatchingTracker() {
-}
-
-bool SimpleLogMatchingTracker::init(const vector<AtomMatcher>& allLogMatchers,
-                                    const vector<sp<LogMatchingTracker>>& allTrackers,
-                                    const unordered_map<int64_t, int>& matcherMap,
-                                    vector<bool>& stack) {
-    // no need to do anything.
-    return mInitialized;
-}
-
-void SimpleLogMatchingTracker::onLogEvent(const LogEvent& event,
-                                          const vector<sp<LogMatchingTracker>>& allTrackers,
-                                          vector<MatchingState>& matcherResults) {
-    if (matcherResults[mIndex] != MatchingState::kNotComputed) {
-        VLOG("Matcher %lld already evaluated ", (long long)mId);
-        return;
-    }
-
-    if (mAtomIds.find(event.GetTagId()) == mAtomIds.end()) {
-        matcherResults[mIndex] = MatchingState::kNotMatched;
-        return;
-    }
-
-    bool matched = matchesSimple(mUidMap, mMatcher, event);
-    matcherResults[mIndex] = matched ? MatchingState::kMatched : MatchingState::kNotMatched;
-    VLOG("Stats SimpleLogMatcher %lld matched? %d", (long long)mId, matched);
-}
-
-}  // namespace statsd
-}  // namespace os
-}  // namespace android
diff --git a/cmds/statsd/src/matchers/SimpleLogMatchingTracker.h b/cmds/statsd/src/matchers/SimpleLogMatchingTracker.h
deleted file mode 100644
index a0f6a88..0000000
--- a/cmds/statsd/src/matchers/SimpleLogMatchingTracker.h
+++ /dev/null
@@ -1,55 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#ifndef SIMPLE_LOG_MATCHING_TRACKER_H
-#define SIMPLE_LOG_MATCHING_TRACKER_H
-
-#include <unordered_map>
-#include <vector>
-#include "LogMatchingTracker.h"
-#include "frameworks/base/cmds/statsd/src/statsd_config.pb.h"
-#include "packages/UidMap.h"
-
-namespace android {
-namespace os {
-namespace statsd {
-
-class SimpleLogMatchingTracker : public virtual LogMatchingTracker {
-public:
-    SimpleLogMatchingTracker(const int64_t& id, const int index,
-                             const SimpleAtomMatcher& matcher,
-                             const UidMap& uidMap);
-
-    ~SimpleLogMatchingTracker();
-
-    bool init(const std::vector<AtomMatcher>& allLogMatchers,
-              const std::vector<sp<LogMatchingTracker>>& allTrackers,
-              const std::unordered_map<int64_t, int>& matcherMap,
-              std::vector<bool>& stack) override;
-
-    void onLogEvent(const LogEvent& event,
-                    const std::vector<sp<LogMatchingTracker>>& allTrackers,
-                    std::vector<MatchingState>& matcherResults) override;
-
-private:
-    const SimpleAtomMatcher mMatcher;
-    const UidMap& mUidMap;
-};
-
-}  // namespace statsd
-}  // namespace os
-}  // namespace android
-#endif  // SIMPLE_LOG_MATCHING_TRACKER_H
diff --git a/cmds/statsd/src/matchers/matcher_util.cpp b/cmds/statsd/src/matchers/matcher_util.cpp
index 2b4c6a3..a7454c5 100644
--- a/cmds/statsd/src/matchers/matcher_util.cpp
+++ b/cmds/statsd/src/matchers/matcher_util.cpp
@@ -17,7 +17,7 @@
 #include "Log.h"
 
 #include "frameworks/base/cmds/statsd/src/statsd_config.pb.h"
-#include "matchers/LogMatchingTracker.h"
+#include "matchers/AtomMatchingTracker.h"
 #include "matchers/matcher_util.h"
 #include "stats_util.h"
 
@@ -81,14 +81,15 @@
     return matched;
 }
 
-bool tryMatchString(const UidMap& uidMap, const FieldValue& fieldValue, const string& str_match) {
+bool tryMatchString(const sp<UidMap>& uidMap, const FieldValue& fieldValue,
+                    const string& str_match) {
     if (isAttributionUidField(fieldValue) || isUidField(fieldValue)) {
         int uid = fieldValue.mValue.int_value;
         auto aidIt = UidMap::sAidToUidMapping.find(str_match);
         if (aidIt != UidMap::sAidToUidMapping.end()) {
             return ((int)aidIt->second) == uid;
         }
-        std::set<string> packageNames = uidMap.getAppNamesFromUid(uid, true /* normalize*/);
+        std::set<string> packageNames = uidMap->getAppNamesFromUid(uid, true /* normalize*/);
         return packageNames.find(str_match) != packageNames.end();
     } else if (fieldValue.mValue.getType() == STRING) {
         return fieldValue.mValue.str_value == str_match;
@@ -96,7 +97,7 @@
     return false;
 }
 
-bool matchesSimple(const UidMap& uidMap, const FieldValueMatcher& matcher,
+bool matchesSimple(const sp<UidMap>& uidMap, const FieldValueMatcher& matcher,
                    const vector<FieldValue>& values, int start, int end, int depth) {
     if (depth > 2) {
         ALOGE("Depth > 3 not supported");
@@ -353,7 +354,7 @@
     }
 }
 
-bool matchesSimple(const UidMap& uidMap, const SimpleAtomMatcher& simpleMatcher,
+bool matchesSimple(const sp<UidMap>& uidMap, const SimpleAtomMatcher& simpleMatcher,
                    const LogEvent& event) {
     if (event.GetTagId() != simpleMatcher.atom_id()) {
         return false;
diff --git a/cmds/statsd/src/matchers/matcher_util.h b/cmds/statsd/src/matchers/matcher_util.h
index 1ab3e87..130b606 100644
--- a/cmds/statsd/src/matchers/matcher_util.h
+++ b/cmds/statsd/src/matchers/matcher_util.h
@@ -36,8 +36,8 @@
 bool combinationMatch(const std::vector<int>& children, const LogicalOperation& operation,
                       const std::vector<MatchingState>& matcherResults);
 
-bool matchesSimple(const UidMap& uidMap,
-    const SimpleAtomMatcher& simpleMatcher, const LogEvent& wrapper);
+bool matchesSimple(const sp<UidMap>& uidMap, const SimpleAtomMatcher& simpleMatcher,
+                   const LogEvent& wrapper);
 
 }  // namespace statsd
 }  // namespace os
diff --git a/cmds/statsd/src/metrics/GaugeMetricProducer.h b/cmds/statsd/src/metrics/GaugeMetricProducer.h
index 2fc772b..ef3a24a 100644
--- a/cmds/statsd/src/metrics/GaugeMetricProducer.h
+++ b/cmds/statsd/src/metrics/GaugeMetricProducer.h
@@ -170,14 +170,14 @@
     // for each slice with the latest value.
     void updateCurrentSlicedBucketForAnomaly();
 
-    // Whitelist of fields to report. Empty means all are reported.
+    // Allowlist of fields to report. Empty means all are reported.
     std::vector<Matcher> mFieldMatchers;
 
     GaugeMetric::SamplingType mSamplingType;
 
     const int64_t mMaxPullDelayNs;
 
-    // apply a whitelist on the original input
+    // apply an allowlist on the original input
     std::shared_ptr<vector<FieldValue>> getGaugeFields(const LogEvent& event);
 
     // Util function to check whether the specified dimension hits the guardrail.
diff --git a/cmds/statsd/src/metrics/MetricsManager.cpp b/cmds/statsd/src/metrics/MetricsManager.cpp
index 189d811..5a52032 100644
--- a/cmds/statsd/src/metrics/MetricsManager.cpp
+++ b/cmds/statsd/src/metrics/MetricsManager.cpp
@@ -24,9 +24,10 @@
 #include "condition/CombinationConditionTracker.h"
 #include "condition/SimpleConditionTracker.h"
 #include "guardrail/StatsdStats.h"
-#include "matchers/CombinationLogMatchingTracker.h"
-#include "matchers/SimpleLogMatchingTracker.h"
-#include "metrics_manager_util.h"
+#include "matchers/CombinationAtomMatchingTracker.h"
+#include "matchers/SimpleAtomMatchingTracker.h"
+#include "parsing_utils/config_update_utils.h"
+#include "parsing_utils/metrics_manager_util.h"
 #include "state/StateManager.h"
 #include "stats_log_util.h"
 #include "stats_util.h"
@@ -77,12 +78,13 @@
     refreshTtl(timeBaseNs);
 
     mConfigValid = initStatsdConfig(
-            key, config, *uidMap, pullerManager, anomalyAlarmMonitor, periodicAlarmMonitor,
-            timeBaseNs, currentTimeNs, mTagIds, mAllAtomMatchers, mAllConditionTrackers,
-            mAllMetricProducers, mAllAnomalyTrackers, mAllPeriodicAlarmTrackers,
-            mConditionToMetricMap, mTrackerToMetricMap, mTrackerToConditionMap,
-            mActivationAtomTrackerToMetricMap, mDeactivationAtomTrackerToMetricMap,
-            mAlertTrackerMap, mMetricIndexesWithActivation, mNoReportMetricIds);
+            key, config, uidMap, pullerManager, anomalyAlarmMonitor, periodicAlarmMonitor,
+            timeBaseNs, currentTimeNs, mTagIds, mAllAtomMatchingTrackers, mAtomMatchingTrackerMap,
+            mAllConditionTrackers, mAllMetricProducers, mAllAnomalyTrackers,
+            mAllPeriodicAlarmTrackers, mConditionToMetricMap, mTrackerToMetricMap,
+            mTrackerToConditionMap, mActivationAtomTrackerToMetricMap,
+            mDeactivationAtomTrackerToMetricMap, mAlertTrackerMap, mMetricIndexesWithActivation,
+            mNoReportMetricIds);
 
     mHashStringsInReport = config.hash_strings_in_metric_report();
     mVersionStringsInReport = config.version_strings_in_metric_report();
@@ -91,7 +93,7 @@
     // Init allowed pushed atom uids.
     if (config.allowed_log_source_size() == 0) {
         mConfigValid = false;
-        ALOGE("Log source whitelist is empty! This config won't get any data. Suggest adding at "
+        ALOGE("Log source allowlist is empty! This config won't get any data. Suggest adding at "
                       "least AID_SYSTEM and AID_STATSD to the allowed_log_source field.");
     } else {
         for (const auto& source : config.allowed_log_source()) {
@@ -153,7 +155,7 @@
     // Guardrail. Reject the config if it's too big.
     if (mAllMetricProducers.size() > StatsdStats::kMaxMetricCountPerConfig ||
         mAllConditionTrackers.size() > StatsdStats::kMaxConditionCountPerConfig ||
-        mAllAtomMatchers.size() > StatsdStats::kMaxMatcherCountPerConfig) {
+        mAllAtomMatchingTrackers.size() > StatsdStats::kMaxMatcherCountPerConfig) {
         ALOGE("This config is too big! Reject!");
         mConfigValid = false;
     }
@@ -173,8 +175,9 @@
 
     // no matter whether this config is valid, log it in the stats.
     StatsdStats::getInstance().noteConfigReceived(
-            key, mAllMetricProducers.size(), mAllConditionTrackers.size(), mAllAtomMatchers.size(),
-            mAllAnomalyTrackers.size(), mAnnotations, mConfigValid);
+            key, mAllMetricProducers.size(), mAllConditionTrackers.size(),
+            mAllAtomMatchingTrackers.size(), mAllAnomalyTrackers.size(), mAnnotations,
+            mConfigValid);
     // Check active
     for (const auto& metric : mAllMetricProducers) {
         if (metric->isActive()) {
@@ -195,7 +198,19 @@
     VLOG("~MetricsManager()");
 }
 
-bool MetricsManager::updateConfig(const int64_t currentTimeNs, const StatsdConfig& config) {
+bool MetricsManager::updateConfig(const StatsdConfig& config, const int64_t timeBaseNs,
+                                  const int64_t currentTimeNs,
+                                  const sp<AlarmMonitor>& anomalyAlarmMonitor,
+                                  const sp<AlarmMonitor>& periodicAlarmMonitor) {
+    vector<sp<AtomMatchingTracker>> newAtomMatchingTrackers;
+    unordered_map<int64_t, int> newAtomMatchingTrackerMap;
+    mTagIds.clear();
+    mConfigValid = updateStatsdConfig(
+            mConfigKey, config, mUidMap, mPullerManager, anomalyAlarmMonitor, periodicAlarmMonitor,
+            timeBaseNs, currentTimeNs, mAllAtomMatchingTrackers, mAtomMatchingTrackerMap, mTagIds,
+            newAtomMatchingTrackers, newAtomMatchingTrackerMap);
+    mAllAtomMatchingTrackers = newAtomMatchingTrackers;
+    mAtomMatchingTrackerMap = newAtomMatchingTrackerMap;
     return mConfigValid;
 }
 
@@ -488,11 +503,12 @@
         return;
     }
 
-    vector<MatchingState> matcherCache(mAllAtomMatchers.size(), MatchingState::kNotComputed);
+    vector<MatchingState> matcherCache(mAllAtomMatchingTrackers.size(),
+                                       MatchingState::kNotComputed);
 
     // Evaluate all atom matchers.
-    for (auto& matcher : mAllAtomMatchers) {
-        matcher->onLogEvent(event, mAllAtomMatchers, matcherCache);
+    for (auto& matcher : mAllAtomMatchingTrackers) {
+        matcher->onLogEvent(event, mAllAtomMatchingTrackers, matcherCache);
     }
 
     // Set of metrics that received an activation cancellation.
@@ -582,10 +598,10 @@
     }
 
     // For matched AtomMatchers, tell relevant metrics that a matched event has come.
-    for (size_t i = 0; i < mAllAtomMatchers.size(); i++) {
+    for (size_t i = 0; i < mAllAtomMatchingTrackers.size(); i++) {
         if (matcherCache[i] == MatchingState::kMatched) {
             StatsdStats::getInstance().noteMatcherMatched(mConfigKey,
-                                                          mAllAtomMatchers[i]->getId());
+                                                          mAllAtomMatchingTrackers[i]->getId());
             auto pair = mTrackerToMetricMap.find(i);
             if (pair != mTrackerToMetricMap.end()) {
                 auto& metricList = pair->second;
diff --git a/cmds/statsd/src/metrics/MetricsManager.h b/cmds/statsd/src/metrics/MetricsManager.h
index 042de29..6f4b2d7 100644
--- a/cmds/statsd/src/metrics/MetricsManager.h
+++ b/cmds/statsd/src/metrics/MetricsManager.h
@@ -25,7 +25,7 @@
 #include "frameworks/base/cmds/statsd/src/statsd_config.pb.h"
 #include "frameworks/base/cmds/statsd/src/statsd_metadata.pb.h"
 #include "logd/LogEvent.h"
-#include "matchers/LogMatchingTracker.h"
+#include "matchers/AtomMatchingTracker.h"
 #include "metrics/MetricProducer.h"
 #include "packages/UidMap.h"
 
@@ -46,7 +46,9 @@
 
     virtual ~MetricsManager();
 
-    bool updateConfig(const int64_t currentTimeNs, const StatsdConfig& config);
+    bool updateConfig(const StatsdConfig& config, const int64_t timeBaseNs,
+                      const int64_t currentTimeNs, const sp<AlarmMonitor>& anomalyAlarmMonitor,
+                      const sp<AlarmMonitor>& periodicAlarmMonitor);
 
     // Return whether the configuration is valid.
     bool isConfigValid() const;
@@ -215,7 +217,7 @@
     // All event tags that are interesting to my metrics.
     std::set<int> mTagIds;
 
-    // We only store the sp of LogMatchingTracker, MetricProducer, and ConditionTracker in
+    // We only store the sp of AtomMatchingTracker, MetricProducer, and ConditionTracker in
     // MetricsManager. There are relationships between them, and the relationships are denoted by
     // index instead of pointers. The reasons for this are: (1) the relationship between them are
     // complicated, so storing index instead of pointers reduces the risk that A holds B's sp, and B
@@ -223,7 +225,7 @@
     // the related results from a cache using the index.
 
     // Hold all the atom matchers from the config.
-    std::vector<sp<LogMatchingTracker>> mAllAtomMatchers;
+    std::vector<sp<AtomMatchingTracker>> mAllAtomMatchingTrackers;
 
     // Hold all the conditions from the config.
     std::vector<sp<ConditionTracker>> mAllConditionTrackers;
@@ -237,23 +239,29 @@
     // Hold all periodic alarm trackers.
     std::vector<sp<AlarmTracker>> mAllPeriodicAlarmTrackers;
 
+    // To make updating configs faster, we map the id of a AtomMatchingTracker, MetricProducer, and
+    // ConditionTracker to its index in the corresponding vector.
+
+    // Maps the id of an atom matcher to its index in mAllAtomMatchingTrackers.
+    std::unordered_map<int64_t, int> mAtomMatchingTrackerMap;
+
     // To make the log processing more efficient, we want to do as much filtering as possible
     // before we go into individual trackers and conditions to match.
 
     // 1st filter: check if the event tag id is in mTagIds.
     // 2nd filter: if it is, we parse the event because there is at least one member is interested.
-    //             then pass to all LogMatchingTrackers (itself also filter events by ids).
-    // 3nd filter: for LogMatchingTrackers that matched this event, we pass this event to the
+    //             then pass to all AtomMatchingTrackers (itself also filter events by ids).
+    // 3nd filter: for AtomMatchingTrackers that matched this event, we pass this event to the
     //             ConditionTrackers and MetricProducers that use this matcher.
     // 4th filter: for ConditionTrackers that changed value due to this event, we pass
     //             new conditions to  metrics that use this condition.
 
     // The following map is initialized from the statsd_config.
 
-    // Maps from the index of the LogMatchingTracker to index of MetricProducer.
+    // Maps from the index of the AtomMatchingTracker to index of MetricProducer.
     std::unordered_map<int, std::vector<int>> mTrackerToMetricMap;
 
-    // Maps from LogMatchingTracker to ConditionTracker
+    // Maps from AtomMatchingTracker to ConditionTracker
     std::unordered_map<int, std::vector<int>> mTrackerToConditionMap;
 
     // Maps from ConditionTracker to MetricProducer
diff --git a/cmds/statsd/src/metrics/parsing_utils/config_update_utils.cpp b/cmds/statsd/src/metrics/parsing_utils/config_update_utils.cpp
new file mode 100644
index 0000000..0983dc0
--- /dev/null
+++ b/cmds/statsd/src/metrics/parsing_utils/config_update_utils.cpp
@@ -0,0 +1,211 @@
+/*
+ * 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.
+ */
+
+#define DEBUG false  // STOPSHIP if true
+
+#include "config_update_utils.h"
+
+#include "external/StatsPullerManager.h"
+#include "hash.h"
+#include "metrics_manager_util.h"
+
+namespace android {
+namespace os {
+namespace statsd {
+
+// Recursive function to determine if a matcher needs to be updated. Populates matcherToUpdate.
+// Returns whether the function was successful or not.
+bool determineMatcherUpdateStatus(const StatsdConfig& config, const int matcherIdx,
+                                  const unordered_map<int64_t, int>& oldAtomMatchingTrackerMap,
+                                  const vector<sp<AtomMatchingTracker>>& oldAtomMatchingTrackers,
+                                  const unordered_map<int64_t, int>& newAtomMatchingTrackerMap,
+                                  vector<UpdateStatus>& matchersToUpdate,
+                                  vector<bool>& cycleTracker) {
+    // Have already examined this matcher.
+    if (matchersToUpdate[matcherIdx] != UPDATE_UNKNOWN) {
+        return true;
+    }
+
+    const AtomMatcher& matcher = config.atom_matcher(matcherIdx);
+    int64_t id = matcher.id();
+    // Check if new matcher.
+    const auto& oldAtomMatchingTrackerIt = oldAtomMatchingTrackerMap.find(id);
+    if (oldAtomMatchingTrackerIt == oldAtomMatchingTrackerMap.end()) {
+        matchersToUpdate[matcherIdx] = UPDATE_REPLACE;
+        return true;
+    }
+
+    // This is an existing matcher. Check if it has changed.
+    string serializedMatcher;
+    if (!matcher.SerializeToString(&serializedMatcher)) {
+        ALOGE("Unable to serialize matcher %lld", (long long)id);
+        return false;
+    }
+    uint64_t newProtoHash = Hash64(serializedMatcher);
+    if (newProtoHash != oldAtomMatchingTrackers[oldAtomMatchingTrackerIt->second]->getProtoHash()) {
+        matchersToUpdate[matcherIdx] = UPDATE_REPLACE;
+        return true;
+    }
+
+    switch (matcher.contents_case()) {
+        case AtomMatcher::ContentsCase::kSimpleAtomMatcher: {
+            matchersToUpdate[matcherIdx] = UPDATE_PRESERVE;
+            return true;
+        }
+        case AtomMatcher::ContentsCase::kCombination: {
+            // Recurse to check if children have changed.
+            cycleTracker[matcherIdx] = true;
+            UpdateStatus status = UPDATE_PRESERVE;
+            for (const int64_t childMatcherId : matcher.combination().matcher()) {
+                const auto& childIt = newAtomMatchingTrackerMap.find(childMatcherId);
+                if (childIt == newAtomMatchingTrackerMap.end()) {
+                    ALOGW("Matcher %lld not found in the config", (long long)childMatcherId);
+                    return false;
+                }
+                const int childIdx = childIt->second;
+                if (cycleTracker[childIdx]) {
+                    ALOGE("Cycle detected in matcher config");
+                    return false;
+                }
+                if (!determineMatcherUpdateStatus(
+                            config, childIdx, oldAtomMatchingTrackerMap, oldAtomMatchingTrackers,
+                            newAtomMatchingTrackerMap, matchersToUpdate, cycleTracker)) {
+                    return false;
+                }
+
+                if (matchersToUpdate[childIdx] == UPDATE_REPLACE) {
+                    status = UPDATE_REPLACE;
+                    break;
+                }
+            }
+            matchersToUpdate[matcherIdx] = status;
+            cycleTracker[matcherIdx] = false;
+            return true;
+        }
+        default: {
+            ALOGE("Matcher \"%lld\" malformed", (long long)id);
+            return false;
+        }
+    }
+    return true;
+}
+
+bool updateAtomTrackers(const StatsdConfig& config, const sp<UidMap>& uidMap,
+                        const unordered_map<int64_t, int>& oldAtomMatchingTrackerMap,
+                        const vector<sp<AtomMatchingTracker>>& oldAtomMatchingTrackers,
+                        set<int>& allTagIds, unordered_map<int64_t, int>& newAtomMatchingTrackerMap,
+                        vector<sp<AtomMatchingTracker>>& newAtomMatchingTrackers) {
+    const int atomMatcherCount = config.atom_matcher_size();
+
+    vector<AtomMatcher> matcherProtos;
+    matcherProtos.reserve(atomMatcherCount);
+    newAtomMatchingTrackers.reserve(atomMatcherCount);
+
+    // Maps matcher id to their position in the config. For fast lookup of dependencies.
+    for (int i = 0; i < atomMatcherCount; i++) {
+        const AtomMatcher& matcher = config.atom_matcher(i);
+        if (newAtomMatchingTrackerMap.find(matcher.id()) != newAtomMatchingTrackerMap.end()) {
+            ALOGE("Duplicate atom matcher found for id %lld", (long long)matcher.id());
+            return false;
+        }
+        newAtomMatchingTrackerMap[matcher.id()] = i;
+        matcherProtos.push_back(matcher);
+    }
+
+    // For combination matchers, we need to determine if any children need to be updated.
+    vector<UpdateStatus> matchersToUpdate(atomMatcherCount, UPDATE_UNKNOWN);
+    vector<bool> cycleTracker(atomMatcherCount, false);
+    for (int i = 0; i < atomMatcherCount; i++) {
+        if (!determineMatcherUpdateStatus(config, i, oldAtomMatchingTrackerMap,
+                                          oldAtomMatchingTrackers, newAtomMatchingTrackerMap,
+                                          matchersToUpdate, cycleTracker)) {
+            return false;
+        }
+    }
+
+    for (int i = 0; i < atomMatcherCount; i++) {
+        const AtomMatcher& matcher = config.atom_matcher(i);
+        const int64_t id = matcher.id();
+        switch (matchersToUpdate[i]) {
+            case UPDATE_PRESERVE: {
+                const auto& oldAtomMatchingTrackerIt = oldAtomMatchingTrackerMap.find(id);
+                if (oldAtomMatchingTrackerIt == oldAtomMatchingTrackerMap.end()) {
+                    ALOGE("Could not find AtomMatcher %lld in the previous config, but expected it "
+                          "to be there",
+                          (long long)id);
+                    return false;
+                }
+                const sp<AtomMatchingTracker>& tracker =
+                        oldAtomMatchingTrackers[oldAtomMatchingTrackerIt->second];
+                if (!tracker->onConfigUpdated(matcherProtos[i], i, newAtomMatchingTrackerMap)) {
+                    ALOGW("Config update failed for matcher %lld", (long long)id);
+                    return false;
+                }
+                newAtomMatchingTrackers.push_back(tracker);
+                break;
+            }
+            case UPDATE_REPLACE: {
+                sp<AtomMatchingTracker> tracker = createAtomMatchingTracker(matcher, i, uidMap);
+                if (tracker == nullptr) {
+                    return false;
+                }
+                newAtomMatchingTrackers.push_back(tracker);
+                break;
+            }
+            default: {
+                ALOGE("Matcher \"%lld\" update state is unknown. This should never happen",
+                      (long long)id);
+                return false;
+            }
+        }
+    }
+
+    std::fill(cycleTracker.begin(), cycleTracker.end(), false);
+    for (auto& matcher : newAtomMatchingTrackers) {
+        if (!matcher->init(matcherProtos, newAtomMatchingTrackers, newAtomMatchingTrackerMap,
+                           cycleTracker)) {
+            return false;
+        }
+        // Collect all the tag ids that are interesting. TagIds exist in leaf nodes only.
+        const set<int>& tagIds = matcher->getAtomIds();
+        allTagIds.insert(tagIds.begin(), tagIds.end());
+    }
+
+    return true;
+}
+
+bool updateStatsdConfig(const ConfigKey& key, const StatsdConfig& config, const sp<UidMap>& uidMap,
+                        const sp<StatsPullerManager>& pullerManager,
+                        const sp<AlarmMonitor>& anomalyAlarmMonitor,
+                        const sp<AlarmMonitor>& periodicAlarmMonitor, const int64_t timeBaseNs,
+                        const int64_t currentTimeNs,
+                        const vector<sp<AtomMatchingTracker>>& oldAtomMatchingTrackers,
+                        const unordered_map<int64_t, int>& oldAtomMatchingTrackerMap,
+                        set<int>& allTagIds,
+                        vector<sp<AtomMatchingTracker>>& newAtomMatchingTrackers,
+                        unordered_map<int64_t, int>& newAtomMatchingTrackerMap) {
+    if (!updateAtomTrackers(config, uidMap, oldAtomMatchingTrackerMap, oldAtomMatchingTrackers,
+                            allTagIds, newAtomMatchingTrackerMap, newAtomMatchingTrackers)) {
+        ALOGE("updateAtomMatchingTrackers failed");
+        return false;
+    }
+
+    return true;
+}
+
+}  // namespace statsd
+}  // namespace os
+}  // namespace android
diff --git a/cmds/statsd/src/metrics/parsing_utils/config_update_utils.h b/cmds/statsd/src/metrics/parsing_utils/config_update_utils.h
new file mode 100644
index 0000000..ae7b216
--- /dev/null
+++ b/cmds/statsd/src/metrics/parsing_utils/config_update_utils.h
@@ -0,0 +1,89 @@
+/*
+ * 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.
+ */
+
+#pragma once
+
+#include <vector>
+
+#include "anomaly/AlarmMonitor.h"
+#include "external/StatsPullerManager.h"
+#include "matchers/AtomMatchingTracker.h"
+
+namespace android {
+namespace os {
+namespace statsd {
+
+// Helper functions for MetricsManager to update itself from a new StatsdConfig.
+// *Note*: only updateStatsdConfig() should be called from outside this file.
+// All other functions are intermediate steps, created to make unit testing easier.
+
+// Possible update states for a component. PRESERVE means we should keep the existing one.
+// REPLACE means we should create a new one, either because it didn't exist or it changed.
+enum UpdateStatus {
+    UPDATE_UNKNOWN = 0,
+    UPDATE_PRESERVE = 1,
+    UPDATE_REPLACE = 2,
+};
+
+// Recursive function to determine if a matcher needs to be updated.
+// input:
+// [config]: the input StatsdConfig
+// [matcherIdx]: the index of the current matcher to be updated
+// [newAtomMatchingTrackerMap]: matcher id to index mapping in the input StatsdConfig
+// [oldAtomMatchingTrackerMap]: matcher id to index mapping in the existing MetricsManager
+// [oldAtomMatchingTrackers]: stores the existing AtomMatchingTrackers
+// output:
+// [matchersToUpdate]: vector of the update status of each matcher. The matcherIdx index will
+//                     be updated from UPDATE_UNKNOWN after this call.
+// [cycleTracker]: intermediate param used during recursion.
+bool determineMatcherUpdateStatus(const StatsdConfig& config, const int matcherIdx,
+                                  const unordered_map<int64_t, int>& oldAtomMatchingTrackerMap,
+                                  const vector<sp<AtomMatchingTracker>>& oldAtomMatchingTrackers,
+                                  const unordered_map<int64_t, int>& newAtomMatchingTrackerMap,
+                                  vector<UpdateStatus>& matchersToUpdate,
+                                  vector<bool>& cycleTracker);
+
+// Updates the AtomMatchingTrackers.
+// input:
+// [config]: the input StatsdConfig
+// [oldAtomMatchingTrackerMap]: existing matcher id to index mapping
+// [oldAtomMatchingTrackers]: stores the existing AtomMatchingTrackers
+// output:
+// [allTagIds]: contains the set of all interesting tag ids to this config.
+// [newAtomMatchingTrackerMap]: new matcher id to index mapping
+// [newAtomMatchers]: stores the new AtomMatchingTrackers
+bool updateAtomTrackers(const StatsdConfig& config, const sp<UidMap>& uidMap,
+                        const unordered_map<int64_t, int>& oldAtomMatchingTrackerMap,
+                        const vector<sp<AtomMatchingTracker>>& oldAtomMatchingTrackers,
+                        set<int>& allTagIds, unordered_map<int64_t, int>& newAtomMatchingTrackerMap,
+                        vector<sp<AtomMatchingTracker>>& newAtomMatchingTrackers);
+
+// Updates the existing MetricsManager from a new StatsdConfig.
+// Parameters are the members of MetricsManager. See MetricsManager for declaration.
+bool updateStatsdConfig(const ConfigKey& key, const StatsdConfig& config, const sp<UidMap>& uidMap,
+                        const sp<StatsPullerManager>& pullerManager,
+                        const sp<AlarmMonitor>& anomalyAlarmMonitor,
+                        const sp<AlarmMonitor>& periodicAlarmMonitor, const int64_t timeBaseNs,
+                        const int64_t currentTimeNs,
+                        const std::vector<sp<AtomMatchingTracker>>& oldAtomMatchingTrackers,
+                        const unordered_map<int64_t, int>& oldAtomMatchingTrackerMap,
+                        std::set<int>& allTagIds,
+                        std::vector<sp<AtomMatchingTracker>>& newAtomMatchingTrackers,
+                        unordered_map<int64_t, int>& newAtomMatchingTrackerMap);
+
+}  // namespace statsd
+}  // namespace os
+}  // namespace android
diff --git a/cmds/statsd/src/metrics/metrics_manager_util.cpp b/cmds/statsd/src/metrics/parsing_utils/metrics_manager_util.cpp
similarity index 76%
rename from cmds/statsd/src/metrics/metrics_manager_util.cpp
rename to cmds/statsd/src/metrics/parsing_utils/metrics_manager_util.cpp
index 8917c36..e40fbdb 100644
--- a/cmds/statsd/src/metrics/metrics_manager_util.cpp
+++ b/cmds/statsd/src/metrics/parsing_utils/metrics_manager_util.cpp
@@ -22,17 +22,18 @@
 #include <inttypes.h>
 
 #include "FieldValue.h"
-#include "MetricProducer.h"
 #include "condition/CombinationConditionTracker.h"
 #include "condition/SimpleConditionTracker.h"
 #include "external/StatsPullerManager.h"
-#include "matchers/CombinationLogMatchingTracker.h"
+#include "hash.h"
+#include "matchers/CombinationAtomMatchingTracker.h"
 #include "matchers/EventMatcherWizard.h"
-#include "matchers/SimpleLogMatchingTracker.h"
+#include "matchers/SimpleAtomMatchingTracker.h"
 #include "metrics/CountMetricProducer.h"
 #include "metrics/DurationMetricProducer.h"
 #include "metrics/EventMetricProducer.h"
 #include "metrics/GaugeMetricProducer.h"
+#include "metrics/MetricProducer.h"
 #include "metrics/ValueMetricProducer.h"
 #include "state/StateManager.h"
 #include "stats_util.h"
@@ -61,18 +62,40 @@
 
 }  // namespace
 
-bool handleMetricWithLogTrackers(const int64_t what, const int metricIndex,
-                                 const bool usedForDimension,
-                                 const vector<sp<LogMatchingTracker>>& allAtomMatchers,
-                                 const unordered_map<int64_t, int>& logTrackerMap,
-                                 unordered_map<int, std::vector<int>>& trackerToMetricMap,
-                                 int& logTrackerIndex) {
-    auto logTrackerIt = logTrackerMap.find(what);
-    if (logTrackerIt == logTrackerMap.end()) {
+sp<AtomMatchingTracker> createAtomMatchingTracker(const AtomMatcher& logMatcher, const int index,
+                                                  const sp<UidMap>& uidMap) {
+    string serializedMatcher;
+    if (!logMatcher.SerializeToString(&serializedMatcher)) {
+        ALOGE("Unable to serialize matcher %lld", (long long)logMatcher.id());
+        return nullptr;
+    }
+    uint64_t protoHash = Hash64(serializedMatcher);
+    switch (logMatcher.contents_case()) {
+        case AtomMatcher::ContentsCase::kSimpleAtomMatcher:
+            return new SimpleAtomMatchingTracker(logMatcher.id(), index, protoHash,
+                                                 logMatcher.simple_atom_matcher(), uidMap);
+            break;
+        case AtomMatcher::ContentsCase::kCombination:
+            return new CombinationAtomMatchingTracker(logMatcher.id(), index, protoHash);
+            break;
+        default:
+            ALOGE("Matcher \"%lld\" malformed", (long long)logMatcher.id());
+            return nullptr;
+    }
+}
+
+bool handleMetricWithAtomMatchingTrackers(
+        const int64_t what, const int metricIndex, const bool usedForDimension,
+        const vector<sp<AtomMatchingTracker>>& allAtomMatchingTrackers,
+        const unordered_map<int64_t, int>& atomMatchingTrackerMap,
+        unordered_map<int, std::vector<int>>& trackerToMetricMap, int& logTrackerIndex) {
+    auto logTrackerIt = atomMatchingTrackerMap.find(what);
+    if (logTrackerIt == atomMatchingTrackerMap.end()) {
         ALOGW("cannot find the AtomMatcher \"%lld\" in config", (long long)what);
         return false;
     }
-    if (usedForDimension && allAtomMatchers[logTrackerIt->second]->getAtomIds().size() > 1) {
+    if (usedForDimension &&
+        allAtomMatchingTrackers[logTrackerIt->second]->getAtomIds().size() > 1) {
         ALOGE("AtomMatcher \"%lld\" has more than one tag ids. When a metric has dimension, "
               "the \"what\" can only about one atom type.",
               (long long)what);
@@ -84,17 +107,17 @@
     return true;
 }
 
-bool handlePullMetricTriggerWithLogTrackers(
+bool handlePullMetricTriggerWithAtomMatchingTrackers(
         const int64_t trigger, const int metricIndex,
-        const vector<sp<LogMatchingTracker>>& allAtomMatchers,
-        const unordered_map<int64_t, int>& logTrackerMap,
+        const vector<sp<AtomMatchingTracker>>& allAtomMatchingTrackers,
+        const unordered_map<int64_t, int>& atomMatchingTrackerMap,
         unordered_map<int, std::vector<int>>& trackerToMetricMap, int& logTrackerIndex) {
-    auto logTrackerIt = logTrackerMap.find(trigger);
-    if (logTrackerIt == logTrackerMap.end()) {
+    auto logTrackerIt = atomMatchingTrackerMap.find(trigger);
+    if (logTrackerIt == atomMatchingTrackerMap.end()) {
         ALOGW("cannot find the AtomMatcher \"%lld\" in config", (long long)trigger);
         return false;
     }
-    if (allAtomMatchers[logTrackerIt->second]->getAtomIds().size() > 1) {
+    if (allAtomMatchingTrackers[logTrackerIt->second]->getAtomIds().size() > 1) {
         ALOGE("AtomMatcher \"%lld\" has more than one tag ids."
               "Trigger can only be one atom type.",
               (long long)trigger);
@@ -125,8 +148,6 @@
             ALOGW("cannot find Predicate \"%lld\" in the config", (long long)link.condition());
             return false;
         }
-        allConditionTrackers[condition_it->second]->setSliced(true);
-        allConditionTrackers[it->second]->setSliced(true);
     }
     conditionIndex = condition_it->second;
 
@@ -184,11 +205,9 @@
 //      to provide the producer with state about its activators and deactivators.
 // Returns false if there are errors.
 bool handleMetricActivation(
-        const StatsdConfig& config,
-        const int64_t metricId,
-        const int metricIndex,
+        const StatsdConfig& config, const int64_t metricId, const int metricIndex,
         const unordered_map<int64_t, int>& metricToActivationMap,
-        const unordered_map<int64_t, int>& logTrackerMap,
+        const unordered_map<int64_t, int>& atomMatchingTrackerMap,
         unordered_map<int, vector<int>>& activationAtomTrackerToMetricMap,
         unordered_map<int, vector<int>>& deactivationAtomTrackerToMetricMap,
         vector<int>& metricsWithActivation,
@@ -204,24 +223,25 @@
     for (int i = 0; i < metricActivation.event_activation_size(); i++) {
         const EventActivation& activation = metricActivation.event_activation(i);
 
-        auto itr = logTrackerMap.find(activation.atom_matcher_id());
-        if (itr == logTrackerMap.end()) {
+        auto itr = atomMatchingTrackerMap.find(activation.atom_matcher_id());
+        if (itr == atomMatchingTrackerMap.end()) {
             ALOGE("Atom matcher not found for event activation.");
             return false;
         }
 
-        ActivationType activationType = (activation.has_activation_type()) ?
-                activation.activation_type() : metricActivation.activation_type();
-        std::shared_ptr<Activation> activationWrapper = std::make_shared<Activation>(
-                activationType, activation.ttl_seconds() * NS_PER_SEC);
+        ActivationType activationType = (activation.has_activation_type())
+                                                ? activation.activation_type()
+                                                : metricActivation.activation_type();
+        std::shared_ptr<Activation> activationWrapper =
+                std::make_shared<Activation>(activationType, activation.ttl_seconds() * NS_PER_SEC);
 
         int atomMatcherIndex = itr->second;
         activationAtomTrackerToMetricMap[atomMatcherIndex].push_back(metricIndex);
         eventActivationMap.emplace(atomMatcherIndex, activationWrapper);
 
         if (activation.has_deactivation_atom_matcher_id()) {
-            itr = logTrackerMap.find(activation.deactivation_atom_matcher_id());
-            if (itr == logTrackerMap.end()) {
+            itr = atomMatchingTrackerMap.find(activation.deactivation_atom_matcher_id());
+            if (itr == atomMatchingTrackerMap.end()) {
                 ALOGE("Atom matcher not found for event deactivation.");
                 return false;
             }
@@ -235,43 +255,35 @@
     return true;
 }
 
-bool initLogTrackers(const StatsdConfig& config, const UidMap& uidMap,
-                     unordered_map<int64_t, int>& logTrackerMap,
-                     vector<sp<LogMatchingTracker>>& allAtomMatchers, set<int>& allTagIds) {
+bool initAtomMatchingTrackers(const StatsdConfig& config, const sp<UidMap>& uidMap,
+                              unordered_map<int64_t, int>& atomMatchingTrackerMap,
+                              vector<sp<AtomMatchingTracker>>& allAtomMatchingTrackers,
+                              set<int>& allTagIds) {
     vector<AtomMatcher> matcherConfigs;
     const int atomMatcherCount = config.atom_matcher_size();
     matcherConfigs.reserve(atomMatcherCount);
-    allAtomMatchers.reserve(atomMatcherCount);
+    allAtomMatchingTrackers.reserve(atomMatcherCount);
 
     for (int i = 0; i < atomMatcherCount; i++) {
         const AtomMatcher& logMatcher = config.atom_matcher(i);
-
-        int index = allAtomMatchers.size();
-        switch (logMatcher.contents_case()) {
-            case AtomMatcher::ContentsCase::kSimpleAtomMatcher:
-                allAtomMatchers.push_back(new SimpleLogMatchingTracker(
-                        logMatcher.id(), index, logMatcher.simple_atom_matcher(), uidMap));
-                break;
-            case AtomMatcher::ContentsCase::kCombination:
-                allAtomMatchers.push_back(
-                        new CombinationLogMatchingTracker(logMatcher.id(), index));
-                break;
-            default:
-                ALOGE("Matcher \"%lld\" malformed", (long long)logMatcher.id());
-                return false;
-                // continue;
+        int index = allAtomMatchingTrackers.size();
+        sp<AtomMatchingTracker> tracker = createAtomMatchingTracker(logMatcher, index, uidMap);
+        if (tracker == nullptr) {
+            return false;
         }
-        if (logTrackerMap.find(logMatcher.id()) != logTrackerMap.end()) {
+        allAtomMatchingTrackers.push_back(tracker);
+        if (atomMatchingTrackerMap.find(logMatcher.id()) != atomMatchingTrackerMap.end()) {
             ALOGE("Duplicate AtomMatcher found!");
             return false;
         }
-        logTrackerMap[logMatcher.id()] = index;
+        atomMatchingTrackerMap[logMatcher.id()] = index;
         matcherConfigs.push_back(logMatcher);
     }
 
-    vector<bool> stackTracker2(allAtomMatchers.size(), false);
-    for (auto& matcher : allAtomMatchers) {
-        if (!matcher->init(matcherConfigs, allAtomMatchers, logTrackerMap, stackTracker2)) {
+    vector<bool> stackTracker2(allAtomMatchingTrackers.size(), false);
+    for (auto& matcher : allAtomMatchingTrackers) {
+        if (!matcher->init(matcherConfigs, allAtomMatchingTrackers, atomMatchingTrackerMap,
+                           stackTracker2)) {
             return false;
         }
         // Collect all the tag ids that are interesting. TagIds exist in leaf nodes only.
@@ -282,7 +294,7 @@
 }
 
 bool initConditions(const ConfigKey& key, const StatsdConfig& config,
-                    const unordered_map<int64_t, int>& logTrackerMap,
+                    const unordered_map<int64_t, int>& atomMatchingTrackerMap,
                     unordered_map<int64_t, int>& conditionTrackerMap,
                     vector<sp<ConditionTracker>>& allConditionTrackers,
                     unordered_map<int, std::vector<int>>& trackerToConditionMap,
@@ -291,8 +303,7 @@
     const int conditionTrackerCount = config.predicate_size();
     conditionConfigs.reserve(conditionTrackerCount);
     allConditionTrackers.reserve(conditionTrackerCount);
-    initialConditionCache.reserve(conditionTrackerCount);
-    std::fill(initialConditionCache.begin(), initialConditionCache.end(), ConditionState::kUnknown);
+    initialConditionCache.assign(conditionTrackerCount, ConditionState::kNotEvaluated);
 
     for (int i = 0; i < conditionTrackerCount; i++) {
         const Predicate& condition = config.predicate(i);
@@ -300,7 +311,8 @@
         switch (condition.contents_case()) {
             case Predicate::ContentsCase::kSimplePredicate: {
                 allConditionTrackers.push_back(new SimpleConditionTracker(
-                        key, condition.id(), index, condition.simple_predicate(), logTrackerMap));
+                        key, condition.id(), index, condition.simple_predicate(),
+                        atomMatchingTrackerMap));
                 break;
             }
             case Predicate::ContentsCase::kCombination: {
@@ -327,7 +339,7 @@
                                     stackTracker, initialConditionCache)) {
             return false;
         }
-        for (const int trackerIndex : conditionTracker->getLogTrackerIndex()) {
+        for (const int trackerIndex : conditionTracker->getAtomMatchingTrackerIndex()) {
             auto& conditionList = trackerToConditionMap[trackerIndex];
             conditionList.push_back(i);
         }
@@ -355,9 +367,9 @@
 
 bool initMetrics(const ConfigKey& key, const StatsdConfig& config, const int64_t timeBaseTimeNs,
                  const int64_t currentTimeNs, const sp<StatsPullerManager>& pullerManager,
-                 const unordered_map<int64_t, int>& logTrackerMap,
+                 const unordered_map<int64_t, int>& atomMatchingTrackerMap,
                  const unordered_map<int64_t, int>& conditionTrackerMap,
-                 const vector<sp<LogMatchingTracker>>& allAtomMatchers,
+                 const vector<sp<AtomMatchingTracker>>& allAtomMatchingTrackers,
                  const unordered_map<int64_t, int>& stateAtomIdMap,
                  const unordered_map<int64_t, unordered_map<int, int64_t>>& allStateGroupMaps,
                  vector<sp<ConditionTracker>>& allConditionTrackers,
@@ -370,7 +382,7 @@
                  unordered_map<int, vector<int>>& deactivationAtomTrackerToMetricMap,
                  vector<int>& metricsWithActivation) {
     sp<ConditionWizard> wizard = new ConditionWizard(allConditionTrackers);
-    sp<EventMatcherWizard> matcherWizard = new EventMatcherWizard(allAtomMatchers);
+    sp<EventMatcherWizard> matcherWizard = new EventMatcherWizard(allAtomMatchingTrackers);
     const int allMetricsCount = config.count_metric_size() + config.duration_metric_size() +
                                 config.event_metric_size() + config.gauge_metric_size() +
                                 config.value_metric_size();
@@ -383,7 +395,7 @@
         const MetricActivation& metricActivation = config.metric_activation(i);
         int64_t metricId = metricActivation.metric_id();
         if (metricToActivationMap.find(metricId) != metricToActivationMap.end()) {
-            ALOGE("Metric %lld has multiple MetricActivations", (long long) metricId);
+            ALOGE("Metric %lld has multiple MetricActivations", (long long)metricId);
             return false;
         }
         metricToActivationMap.insert({metricId, i});
@@ -401,10 +413,10 @@
         int metricIndex = allMetricProducers.size();
         metricMap.insert({metric.id(), metricIndex});
         int trackerIndex;
-        if (!handleMetricWithLogTrackers(metric.what(), metricIndex,
-                                         metric.has_dimensions_in_what(),
-                                         allAtomMatchers, logTrackerMap, trackerToMetricMap,
-                                         trackerIndex)) {
+        if (!handleMetricWithAtomMatchingTrackers(metric.what(), metricIndex,
+                                                  metric.has_dimensions_in_what(),
+                                                  allAtomMatchingTrackers, atomMatchingTrackerMap,
+                                                  trackerToMetricMap, trackerIndex)) {
             return false;
         }
 
@@ -438,10 +450,10 @@
 
         unordered_map<int, shared_ptr<Activation>> eventActivationMap;
         unordered_map<int, vector<shared_ptr<Activation>>> eventDeactivationMap;
-        bool success = handleMetricActivation(config, metric.id(), metricIndex,
-                metricToActivationMap, logTrackerMap, activationAtomTrackerToMetricMap,
-                deactivationAtomTrackerToMetricMap, metricsWithActivation, eventActivationMap,
-                eventDeactivationMap);
+        bool success = handleMetricActivation(
+                config, metric.id(), metricIndex, metricToActivationMap, atomMatchingTrackerMap,
+                activationAtomTrackerToMetricMap, deactivationAtomTrackerToMetricMap,
+                metricsWithActivation, eventActivationMap, eventDeactivationMap);
         if (!success) return false;
 
         sp<MetricProducer> countProducer =
@@ -476,24 +488,27 @@
 
         int trackerIndices[3] = {-1, -1, -1};
         if (!simplePredicate.has_start() ||
-            !handleMetricWithLogTrackers(simplePredicate.start(), metricIndex,
-                                         metric.has_dimensions_in_what(), allAtomMatchers,
-                                         logTrackerMap, trackerToMetricMap, trackerIndices[0])) {
+            !handleMetricWithAtomMatchingTrackers(simplePredicate.start(), metricIndex,
+                                                  metric.has_dimensions_in_what(),
+                                                  allAtomMatchingTrackers, atomMatchingTrackerMap,
+                                                  trackerToMetricMap, trackerIndices[0])) {
             ALOGE("Duration metrics must specify a valid the start event matcher");
             return false;
         }
 
         if (simplePredicate.has_stop() &&
-            !handleMetricWithLogTrackers(simplePredicate.stop(), metricIndex,
-                                         metric.has_dimensions_in_what(), allAtomMatchers,
-                                         logTrackerMap, trackerToMetricMap, trackerIndices[1])) {
+            !handleMetricWithAtomMatchingTrackers(simplePredicate.stop(), metricIndex,
+                                                  metric.has_dimensions_in_what(),
+                                                  allAtomMatchingTrackers, atomMatchingTrackerMap,
+                                                  trackerToMetricMap, trackerIndices[1])) {
             return false;
         }
 
         if (simplePredicate.has_stop_all() &&
-            !handleMetricWithLogTrackers(simplePredicate.stop_all(), metricIndex,
-                                         metric.has_dimensions_in_what(), allAtomMatchers,
-                                         logTrackerMap, trackerToMetricMap, trackerIndices[2])) {
+            !handleMetricWithAtomMatchingTrackers(simplePredicate.stop_all(), metricIndex,
+                                                  metric.has_dimensions_in_what(),
+                                                  allAtomMatchingTrackers, atomMatchingTrackerMap,
+                                                  trackerToMetricMap, trackerIndices[2])) {
             return false;
         }
 
@@ -544,10 +559,10 @@
 
         unordered_map<int, shared_ptr<Activation>> eventActivationMap;
         unordered_map<int, vector<shared_ptr<Activation>>> eventDeactivationMap;
-        bool success = handleMetricActivation(config, metric.id(), metricIndex,
-                metricToActivationMap, logTrackerMap, activationAtomTrackerToMetricMap,
-                deactivationAtomTrackerToMetricMap, metricsWithActivation, eventActivationMap,
-                eventDeactivationMap);
+        bool success = handleMetricActivation(
+                config, metric.id(), metricIndex, metricToActivationMap, atomMatchingTrackerMap,
+                activationAtomTrackerToMetricMap, deactivationAtomTrackerToMetricMap,
+                metricsWithActivation, eventActivationMap, eventDeactivationMap);
         if (!success) return false;
 
         sp<MetricProducer> durationMetric = new DurationMetricProducer(
@@ -569,8 +584,9 @@
             return false;
         }
         int trackerIndex;
-        if (!handleMetricWithLogTrackers(metric.what(), metricIndex, false, allAtomMatchers,
-                                         logTrackerMap, trackerToMetricMap, trackerIndex)) {
+        if (!handleMetricWithAtomMatchingTrackers(metric.what(), metricIndex, false,
+                                                  allAtomMatchingTrackers, atomMatchingTrackerMap,
+                                                  trackerToMetricMap, trackerIndex)) {
             return false;
         }
 
@@ -591,10 +607,10 @@
 
         unordered_map<int, shared_ptr<Activation>> eventActivationMap;
         unordered_map<int, vector<shared_ptr<Activation>>> eventDeactivationMap;
-        bool success = handleMetricActivation(config, metric.id(), metricIndex,
-                metricToActivationMap, logTrackerMap, activationAtomTrackerToMetricMap,
-                deactivationAtomTrackerToMetricMap, metricsWithActivation, eventActivationMap,
-                eventDeactivationMap);
+        bool success = handleMetricActivation(
+                config, metric.id(), metricIndex, metricToActivationMap, atomMatchingTrackerMap,
+                activationAtomTrackerToMetricMap, deactivationAtomTrackerToMetricMap,
+                metricsWithActivation, eventActivationMap, eventDeactivationMap);
         if (!success) return false;
 
         sp<MetricProducer> eventMetric =
@@ -625,14 +641,14 @@
         int metricIndex = allMetricProducers.size();
         metricMap.insert({metric.id(), metricIndex});
         int trackerIndex;
-        if (!handleMetricWithLogTrackers(metric.what(), metricIndex,
-                                         metric.has_dimensions_in_what(),
-                                         allAtomMatchers, logTrackerMap, trackerToMetricMap,
-                                         trackerIndex)) {
+        if (!handleMetricWithAtomMatchingTrackers(metric.what(), metricIndex,
+                                                  metric.has_dimensions_in_what(),
+                                                  allAtomMatchingTrackers, atomMatchingTrackerMap,
+                                                  trackerToMetricMap, trackerIndex)) {
             return false;
         }
 
-        sp<LogMatchingTracker> atomMatcher = allAtomMatchers.at(trackerIndex);
+        sp<AtomMatchingTracker> atomMatcher = allAtomMatchingTrackers.at(trackerIndex);
         // If it is pulled atom, it should be simple matcher with one tagId.
         if (atomMatcher->getAtomIds().size() != 1) {
             return false;
@@ -681,7 +697,7 @@
         unordered_map<int, shared_ptr<Activation>> eventActivationMap;
         unordered_map<int, vector<shared_ptr<Activation>>> eventDeactivationMap;
         bool success = handleMetricActivation(
-                config, metric.id(), metricIndex, metricToActivationMap, logTrackerMap,
+                config, metric.id(), metricIndex, metricToActivationMap, atomMatchingTrackerMap,
                 activationAtomTrackerToMetricMap, deactivationAtomTrackerToMetricMap,
                 metricsWithActivation, eventActivationMap, eventDeactivationMap);
         if (!success) return false;
@@ -717,14 +733,14 @@
         int metricIndex = allMetricProducers.size();
         metricMap.insert({metric.id(), metricIndex});
         int trackerIndex;
-        if (!handleMetricWithLogTrackers(metric.what(), metricIndex,
-                                         metric.has_dimensions_in_what(),
-                                         allAtomMatchers, logTrackerMap, trackerToMetricMap,
-                                         trackerIndex)) {
+        if (!handleMetricWithAtomMatchingTrackers(metric.what(), metricIndex,
+                                                  metric.has_dimensions_in_what(),
+                                                  allAtomMatchingTrackers, atomMatchingTrackerMap,
+                                                  trackerToMetricMap, trackerIndex)) {
             return false;
         }
 
-        sp<LogMatchingTracker> atomMatcher = allAtomMatchers.at(trackerIndex);
+        sp<AtomMatchingTracker> atomMatcher = allAtomMatchingTrackers.at(trackerIndex);
         // For GaugeMetric atom, it should be simple matcher with one tagId.
         if (atomMatcher->getAtomIds().size() != 1) {
             return false;
@@ -743,12 +759,13 @@
             if (metric.sampling_type() != GaugeMetric::FIRST_N_SAMPLES) {
                 return false;
             }
-            if (!handlePullMetricTriggerWithLogTrackers(metric.trigger_event(), metricIndex,
-                                                        allAtomMatchers, logTrackerMap,
-                                                        trackerToMetricMap, triggerTrackerIndex)) {
+            if (!handlePullMetricTriggerWithAtomMatchingTrackers(
+                        metric.trigger_event(), metricIndex, allAtomMatchingTrackers,
+                        atomMatchingTrackerMap, trackerToMetricMap, triggerTrackerIndex)) {
                 return false;
             }
-            sp<LogMatchingTracker> triggerAtomMatcher = allAtomMatchers.at(triggerTrackerIndex);
+            sp<AtomMatchingTracker> triggerAtomMatcher =
+                    allAtomMatchingTrackers.at(triggerTrackerIndex);
             triggerAtomId = *(triggerAtomMatcher->getAtomIds().begin());
         }
 
@@ -775,10 +792,10 @@
 
         unordered_map<int, shared_ptr<Activation>> eventActivationMap;
         unordered_map<int, vector<shared_ptr<Activation>>> eventDeactivationMap;
-        bool success = handleMetricActivation(config, metric.id(), metricIndex,
-                metricToActivationMap, logTrackerMap, activationAtomTrackerToMetricMap,
-                deactivationAtomTrackerToMetricMap, metricsWithActivation, eventActivationMap,
-                eventDeactivationMap);
+        bool success = handleMetricActivation(
+                config, metric.id(), metricIndex, metricToActivationMap, atomMatchingTrackerMap,
+                activationAtomTrackerToMetricMap, deactivationAtomTrackerToMetricMap,
+                metricsWithActivation, eventActivationMap, eventDeactivationMap);
         if (!success) return false;
 
         sp<MetricProducer> gaugeProducer = new GaugeMetricProducer(
@@ -813,8 +830,7 @@
     return true;
 }
 
-bool initAlerts(const StatsdConfig& config,
-                const unordered_map<int64_t, int>& metricProducerMap,
+bool initAlerts(const StatsdConfig& config, const unordered_map<int64_t, int>& metricProducerMap,
                 unordered_map<int64_t, int>& alertTrackerMap,
                 const sp<AlarmMonitor>& anomalyAlarmMonitor,
                 vector<sp<MetricProducer>>& allMetricProducers,
@@ -832,8 +848,8 @@
             return false;
         }
         if (alert.trigger_if_sum_gt() < 0 || alert.num_buckets() <= 0) {
-            ALOGW("invalid alert: threshold=%f num_buckets= %d",
-                  alert.trigger_if_sum_gt(), alert.num_buckets());
+            ALOGW("invalid alert: threshold=%f num_buckets= %d", alert.trigger_if_sum_gt(),
+                  alert.num_buckets());
             return false;
         }
         const int metricIndex = itr->second;
@@ -853,14 +869,13 @@
         }
         if (subscription.subscriber_information_case() ==
             Subscription::SubscriberInformationCase::SUBSCRIBER_INFORMATION_NOT_SET) {
-            ALOGW("subscription \"%lld\" has no subscriber info.\"",
-                (long long)subscription.id());
+            ALOGW("subscription \"%lld\" has no subscriber info.\"", (long long)subscription.id());
             return false;
         }
         const auto& itr = alertTrackerMap.find(subscription.rule_id());
         if (itr == alertTrackerMap.end()) {
             ALOGW("subscription \"%lld\" has unknown rule id: \"%lld\"",
-                (long long)subscription.id(), (long long)subscription.rule_id());
+                  (long long)subscription.id(), (long long)subscription.rule_id());
             return false;
         }
         const int anomalyTrackerIndex = itr->second;
@@ -870,12 +885,11 @@
 }
 
 bool initAlarms(const StatsdConfig& config, const ConfigKey& key,
-                const sp<AlarmMonitor>& periodicAlarmMonitor,
-                const int64_t timeBaseNs, const int64_t currentTimeNs,
-                vector<sp<AlarmTracker>>& allAlarmTrackers) {
+                const sp<AlarmMonitor>& periodicAlarmMonitor, const int64_t timeBaseNs,
+                const int64_t currentTimeNs, vector<sp<AlarmTracker>>& allAlarmTrackers) {
     unordered_map<int64_t, int> alarmTrackerMap;
     int64_t startMillis = timeBaseNs / 1000 / 1000;
-    int64_t currentTimeMillis = currentTimeNs / 1000 /1000;
+    int64_t currentTimeMillis = currentTimeNs / 1000 / 1000;
     for (int i = 0; i < config.alarm_size(); i++) {
         const Alarm& alarm = config.alarm(i);
         if (alarm.offset_millis() <= 0) {
@@ -888,8 +902,7 @@
         }
         alarmTrackerMap.insert(std::make_pair(alarm.id(), allAlarmTrackers.size()));
         allAlarmTrackers.push_back(
-            new AlarmTracker(startMillis, currentTimeMillis,
-                             alarm, key, periodicAlarmMonitor));
+                new AlarmTracker(startMillis, currentTimeMillis, alarm, key, periodicAlarmMonitor));
     }
     for (int i = 0; i < config.subscription_size(); ++i) {
         const Subscription& subscription = config.subscription(i);
@@ -898,14 +911,13 @@
         }
         if (subscription.subscriber_information_case() ==
             Subscription::SubscriberInformationCase::SUBSCRIBER_INFORMATION_NOT_SET) {
-            ALOGW("subscription \"%lld\" has no subscriber info.\"",
-                (long long)subscription.id());
+            ALOGW("subscription \"%lld\" has no subscriber info.\"", (long long)subscription.id());
             return false;
         }
         const auto& itr = alarmTrackerMap.find(subscription.rule_id());
         if (itr == alarmTrackerMap.end()) {
             ALOGW("subscription \"%lld\" has unknown rule id: \"%lld\"",
-                (long long)subscription.id(), (long long)subscription.rule_id());
+                  (long long)subscription.id(), (long long)subscription.rule_id());
             return false;
         }
         const int trackerIndex = itr->second;
@@ -914,12 +926,13 @@
     return true;
 }
 
-bool initStatsdConfig(const ConfigKey& key, const StatsdConfig& config, UidMap& uidMap,
+bool initStatsdConfig(const ConfigKey& key, const StatsdConfig& config, const sp<UidMap>& uidMap,
                       const sp<StatsPullerManager>& pullerManager,
                       const sp<AlarmMonitor>& anomalyAlarmMonitor,
                       const sp<AlarmMonitor>& periodicAlarmMonitor, const int64_t timeBaseNs,
                       const int64_t currentTimeNs, set<int>& allTagIds,
-                      vector<sp<LogMatchingTracker>>& allAtomMatchers,
+                      vector<sp<AtomMatchingTracker>>& allAtomMatchingTrackers,
+                      unordered_map<int64_t, int>& atomMatchingTrackerMap,
                       vector<sp<ConditionTracker>>& allConditionTrackers,
                       vector<sp<MetricProducer>>& allMetricProducers,
                       vector<sp<AnomalyTracker>>& allAnomalyTrackers,
@@ -930,23 +943,22 @@
                       unordered_map<int, std::vector<int>>& activationAtomTrackerToMetricMap,
                       unordered_map<int, std::vector<int>>& deactivationAtomTrackerToMetricMap,
                       unordered_map<int64_t, int>& alertTrackerMap,
-                      vector<int>& metricsWithActivation,
-                      std::set<int64_t>& noReportMetricIds) {
-    unordered_map<int64_t, int> logTrackerMap;
+                      vector<int>& metricsWithActivation, std::set<int64_t>& noReportMetricIds) {
     unordered_map<int64_t, int> conditionTrackerMap;
     vector<ConditionState> initialConditionCache;
     unordered_map<int64_t, int> metricProducerMap;
     unordered_map<int64_t, int> stateAtomIdMap;
     unordered_map<int64_t, unordered_map<int, int64_t>> allStateGroupMaps;
 
-    if (!initLogTrackers(config, uidMap, logTrackerMap, allAtomMatchers, allTagIds)) {
-        ALOGE("initLogMatchingTrackers failed");
+    if (!initAtomMatchingTrackers(config, uidMap, atomMatchingTrackerMap, allAtomMatchingTrackers,
+                                  allTagIds)) {
+        ALOGE("initAtomMatchingTrackers failed");
         return false;
     }
-    VLOG("initLogMatchingTrackers succeed...");
+    VLOG("initAtomMatchingTrackers succeed...");
 
-    if (!initConditions(key, config, logTrackerMap, conditionTrackerMap, allConditionTrackers,
-                        trackerToConditionMap, initialConditionCache)) {
+    if (!initConditions(key, config, atomMatchingTrackerMap, conditionTrackerMap,
+                        allConditionTrackers, trackerToConditionMap, initialConditionCache)) {
         ALOGE("initConditionTrackers failed");
         return false;
     }
@@ -955,12 +967,12 @@
         ALOGE("initStates failed");
         return false;
     }
-    if (!initMetrics(key, config, timeBaseNs, currentTimeNs, pullerManager, logTrackerMap,
-                     conditionTrackerMap, allAtomMatchers, stateAtomIdMap, allStateGroupMaps,
-                     allConditionTrackers, initialConditionCache, allMetricProducers,
-                     conditionToMetricMap, trackerToMetricMap, metricProducerMap, noReportMetricIds,
-                     activationAtomTrackerToMetricMap, deactivationAtomTrackerToMetricMap,
-                     metricsWithActivation)) {
+    if (!initMetrics(key, config, timeBaseNs, currentTimeNs, pullerManager, atomMatchingTrackerMap,
+                     conditionTrackerMap, allAtomMatchingTrackers, stateAtomIdMap,
+                     allStateGroupMaps, allConditionTrackers, initialConditionCache,
+                     allMetricProducers, conditionToMetricMap, trackerToMetricMap,
+                     metricProducerMap, noReportMetricIds, activationAtomTrackerToMetricMap,
+                     deactivationAtomTrackerToMetricMap, metricsWithActivation)) {
         ALOGE("initMetricProducers failed");
         return false;
     }
@@ -969,8 +981,8 @@
         ALOGE("initAlerts failed");
         return false;
     }
-    if (!initAlarms(config, key, periodicAlarmMonitor,
-                    timeBaseNs, currentTimeNs, allPeriodicAlarmTrackers)) {
+    if (!initAlarms(config, key, periodicAlarmMonitor, timeBaseNs, currentTimeNs,
+                    allPeriodicAlarmTrackers)) {
         ALOGE("initAlarms failed");
         return false;
     }
diff --git a/cmds/statsd/src/metrics/metrics_manager_util.h b/cmds/statsd/src/metrics/parsing_utils/metrics_manager_util.h
similarity index 74%
rename from cmds/statsd/src/metrics/metrics_manager_util.h
rename to cmds/statsd/src/metrics/parsing_utils/metrics_manager_util.h
index 96b5c26..4cfd1b0 100644
--- a/cmds/statsd/src/metrics/metrics_manager_util.h
+++ b/cmds/statsd/src/metrics/parsing_utils/metrics_manager_util.h
@@ -20,41 +20,52 @@
 #include <unordered_map>
 #include <vector>
 
-#include "../anomaly/AlarmTracker.h"
-#include "../condition/ConditionTracker.h"
-#include "../external/StatsPullerManager.h"
-#include "../matchers/LogMatchingTracker.h"
-#include "../metrics/MetricProducer.h"
+#include "anomaly/AlarmTracker.h"
+#include "condition/ConditionTracker.h"
+#include "external/StatsPullerManager.h"
+#include "matchers/AtomMatchingTracker.h"
+#include "metrics/MetricProducer.h"
 
 namespace android {
 namespace os {
 namespace statsd {
 
+// Helper functions for creating individual config components from StatsdConfig.
+// Should only be called from metrics_manager_util and config_update_utils.
+
+// Create a AtomMatchingTracker.
+// input:
+// [logMatcher]: the input AtomMatcher from the StatsdConfig
+// [index]: the index of the matcher
+// output:
+// new AtomMatchingTracker, or null if the tracker is unable to be created
+sp<AtomMatchingTracker> createAtomMatchingTracker(const AtomMatcher& logMatcher, const int index,
+                                                  const sp<UidMap>& uidMap);
+
 // Helper functions for MetricsManager to initialize from StatsdConfig.
 // *Note*: only initStatsdConfig() should be called from outside.
 // All other functions are intermediate
 // steps, created to make unit tests easier. And most of the parameters in these
 // functions are temporary objects in the initialization phase.
 
-// Initialize the LogMatchingTrackers.
+// Initialize the AtomMatchingTrackers.
 // input:
 // [key]: the config key that this config belongs to
 // [config]: the input StatsdConfig
 // output:
-// [logTrackerMap]: this map should contain matcher name to index mapping
-// [allAtomMatchers]: should store the sp to all the LogMatchingTracker
+// [atomMatchingTrackerMap]: this map should contain matcher name to index mapping
+// [allAtomMatchingTrackers]: should store the sp to all the AtomMatchingTracker
 // [allTagIds]: contains the set of all interesting tag ids to this config.
-bool initLogTrackers(const StatsdConfig& config,
-                     const UidMap& uidMap,
-                     std::unordered_map<int64_t, int>& logTrackerMap,
-                     std::vector<sp<LogMatchingTracker>>& allAtomMatchers,
-                     std::set<int>& allTagIds);
+bool initAtomMatchingTrackers(const StatsdConfig& config, const sp<UidMap>& uidMap,
+                              std::unordered_map<int64_t, int>& atomMatchingTrackerMap,
+                              std::vector<sp<AtomMatchingTracker>>& allAtomMatchingTrackers,
+                              std::set<int>& allTagIds);
 
 // Initialize ConditionTrackers
 // input:
 // [key]: the config key that this config belongs to
 // [config]: the input config
-// [logTrackerMap]: LogMatchingTracker name to index mapping from previous step.
+// [atomMatchingTrackerMap]: AtomMatchingTracker name to index mapping from previous step.
 // output:
 // [conditionTrackerMap]: this map should contain condition name to index mapping
 // [allConditionTrackers]: stores the sp to all the ConditionTrackers
@@ -62,7 +73,7 @@
 //                        log tracker to condition trackers that use the log tracker
 // [initialConditionCache]: stores the initial conditions for each ConditionTracker
 bool initConditions(const ConfigKey& key, const StatsdConfig& config,
-                    const std::unordered_map<int64_t, int>& logTrackerMap,
+                    const std::unordered_map<int64_t, int>& atomMatchingTrackerMap,
                     std::unordered_map<int64_t, int>& conditionTrackerMap,
                     std::vector<sp<ConditionTracker>>& allConditionTrackers,
                     std::unordered_map<int, std::vector<int>>& trackerToConditionMap,
@@ -85,7 +96,7 @@
 // [key]: the config key that this config belongs to
 // [config]: the input config
 // [timeBaseSec]: start time base for all metrics
-// [logTrackerMap]: LogMatchingTracker name to index mapping from previous step.
+// [atomMatchingTrackerMap]: AtomMatchingTracker name to index mapping from previous step.
 // [conditionTrackerMap]: condition name to index mapping
 // [stateAtomIdMap]: contains the mapping from state ids to atom ids
 // [allStateGroupMaps]: contains the mapping from atom ids and state values to
@@ -97,11 +108,11 @@
 // [trackerToMetricMap]: contains the mapping from log tracker to MetricProducer index.
 bool initMetrics(
         const ConfigKey& key, const StatsdConfig& config, const int64_t timeBaseTimeNs,
-        const int64_t currentTimeNs, UidMap& uidMap, const sp<StatsPullerManager>& pullerManager,
-        const std::unordered_map<int64_t, int>& logTrackerMap,
+        const int64_t currentTimeNs, const sp<StatsPullerManager>& pullerManager,
+        const std::unordered_map<int64_t, int>& atomMatchingTrackerMap,
         const std::unordered_map<int64_t, int>& conditionTrackerMap,
         const std::unordered_map<int, std::vector<MetricConditionLink>>& eventConditionLinks,
-        const vector<sp<LogMatchingTracker>>& allAtomMatchers,
+        const vector<sp<AtomMatchingTracker>>& allAtomMatchingTrackers,
         const unordered_map<int64_t, int>& stateAtomIdMap,
         const unordered_map<int64_t, unordered_map<int, int64_t>>& allStateGroupMaps,
         vector<sp<ConditionTracker>>& allConditionTrackers,
@@ -116,12 +127,13 @@
 
 // Initialize MetricsManager from StatsdConfig.
 // Parameters are the members of MetricsManager. See MetricsManager for declaration.
-bool initStatsdConfig(const ConfigKey& key, const StatsdConfig& config, UidMap& uidMap,
+bool initStatsdConfig(const ConfigKey& key, const StatsdConfig& config, const sp<UidMap>& uidMap,
                       const sp<StatsPullerManager>& pullerManager,
                       const sp<AlarmMonitor>& anomalyAlarmMonitor,
                       const sp<AlarmMonitor>& periodicAlarmMonitor, const int64_t timeBaseNs,
                       const int64_t currentTimeNs, std::set<int>& allTagIds,
-                      std::vector<sp<LogMatchingTracker>>& allAtomMatchers,
+                      std::vector<sp<AtomMatchingTracker>>& allAtomMatchingTrackers,
+                      std::unordered_map<int64_t, int>& atomMatchingTrackerMap,
                       std::vector<sp<ConditionTracker>>& allConditionTrackers,
                       std::vector<sp<MetricProducer>>& allMetricProducers,
                       vector<sp<AnomalyTracker>>& allAnomalyTrackers,
@@ -132,8 +144,7 @@
                       unordered_map<int, std::vector<int>>& activationAtomTrackerToMetricMap,
                       unordered_map<int, std::vector<int>>& deactivationAtomTrackerToMetricMap,
                       std::unordered_map<int64_t, int>& alertTrackerMap,
-                      vector<int>& metricsWithActivation,
-                      std::set<int64_t>& noReportMetricIds);
+                      vector<int>& metricsWithActivation, std::set<int64_t>& noReportMetricIds);
 
 }  // namespace statsd
 }  // namespace os
diff --git a/cmds/statsd/src/shell/ShellSubscriber.cpp b/cmds/statsd/src/shell/ShellSubscriber.cpp
index fd883c2..9d8f0c2 100644
--- a/cmds/statsd/src/shell/ShellSubscriber.cpp
+++ b/cmds/statsd/src/shell/ShellSubscriber.cpp
@@ -191,7 +191,7 @@
     mProto.clear();
     int count = 0;
     for (const auto& event : data) {
-        if (matchesSimple(*mUidMap, matcher, *event)) {
+        if (matchesSimple(mUidMap, matcher, *event)) {
             count++;
             uint64_t atomToken = mProto.start(util::FIELD_TYPE_MESSAGE |
                                               util::FIELD_COUNT_REPEATED | FIELD_ID_ATOM);
@@ -209,7 +209,7 @@
 
     mProto.clear();
     for (const auto& matcher : mSubscriptionInfo->mPushedMatchers) {
-        if (matchesSimple(*mUidMap, matcher, event)) {
+        if (matchesSimple(mUidMap, matcher, event)) {
             uint64_t atomToken = mProto.start(util::FIELD_TYPE_MESSAGE |
                                               util::FIELD_COUNT_REPEATED | FIELD_ID_ATOM);
             event.ToProto(mProto);
diff --git a/cmds/statsd/src/stats_log_util.h b/cmds/statsd/src/stats_log_util.h
index eb65dc6..10e065e 100644
--- a/cmds/statsd/src/stats_log_util.h
+++ b/cmds/statsd/src/stats_log_util.h
@@ -97,7 +97,7 @@
     return message->ParseFromArray(pbBytes.c_str(), pbBytes.size());
 }
 
-// Checks the truncate timestamp annotation as well as the blacklisted range of 300,000 - 304,999.
+// Checks the truncate timestamp annotation as well as the restricted range of 300,000 - 304,999.
 // Returns the truncated timestamp to the nearest 5 minutes if needed.
 int64_t truncateTimestampIfNecessary(const LogEvent& event);
 
diff --git a/cmds/statsd/tests/LogEntryMatcher_test.cpp b/cmds/statsd/tests/LogEntryMatcher_test.cpp
index 6264c07..92cd04f 100644
--- a/cmds/statsd/tests/LogEntryMatcher_test.cpp
+++ b/cmds/statsd/tests/LogEntryMatcher_test.cpp
@@ -110,7 +110,7 @@
 }  // anonymous namespace
 
 TEST(AtomMatcherTest, TestSimpleMatcher) {
-    UidMap uidMap;
+    sp<UidMap> uidMap = new UidMap();
 
     // Set up the matcher
     AtomMatcher matcher;
@@ -129,7 +129,7 @@
 }
 
 TEST(AtomMatcherTest, TestAttributionMatcher) {
-    UidMap uidMap;
+    sp<UidMap> uidMap = new UidMap();
     std::vector<int> attributionUids = {1111, 2222, 3333};
     std::vector<string> attributionTags = {"location1", "location2", "location3"};
 
@@ -204,7 +204,7 @@
             "pkg0");
     EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event));
 
-    uidMap.updateMap(
+    uidMap->updateMap(
             1, {1111, 1111, 2222, 3333, 3333} /* uid list */, {1, 1, 2, 1, 2} /* version list */,
             {android::String16("v1"), android::String16("v1"), android::String16("v2"),
              android::String16("v1"), android::String16("v2")},
@@ -356,8 +356,8 @@
 }
 
 TEST(AtomMatcherTest, TestUidFieldMatcher) {
-    UidMap uidMap;
-    uidMap.updateMap(
+    sp<UidMap> uidMap = new UidMap();
+    uidMap->updateMap(
             1, {1111, 1111, 2222, 3333, 3333} /* uid list */, {1, 1, 2, 1, 2} /* version list */,
             {android::String16("v1"), android::String16("v1"), android::String16("v2"),
              android::String16("v1"), android::String16("v2")},
@@ -392,8 +392,8 @@
 }
 
 TEST(AtomMatcherTest, TestNeqAnyStringMatcher) {
-    UidMap uidMap;
-    uidMap.updateMap(
+    sp<UidMap> uidMap = new UidMap();
+    uidMap->updateMap(
             1, {1111, 1111, 2222, 3333, 3333} /* uid list */, {1, 1, 2, 1, 2} /* version list */,
             {android::String16("v1"), android::String16("v1"), android::String16("v2"),
              android::String16("v1"), android::String16("v2")},
@@ -453,8 +453,8 @@
 }
 
 TEST(AtomMatcherTest, TestEqAnyStringMatcher) {
-    UidMap uidMap;
-    uidMap.updateMap(
+    sp<UidMap> uidMap = new UidMap();
+    uidMap->updateMap(
             1, {1111, 1111, 2222, 3333, 3333} /* uid list */, {1, 1, 2, 1, 2} /* version list */,
             {android::String16("v1"), android::String16("v1"), android::String16("v2"),
              android::String16("v1"), android::String16("v2")},
@@ -517,7 +517,7 @@
 }
 
 TEST(AtomMatcherTest, TestBoolMatcher) {
-    UidMap uidMap;
+    sp<UidMap> uidMap = new UidMap();
     // Set up the matcher
     AtomMatcher matcher;
     auto simpleMatcher = matcher.mutable_simple_atom_matcher();
@@ -550,7 +550,7 @@
 }
 
 TEST(AtomMatcherTest, TestStringMatcher) {
-    UidMap uidMap;
+    sp<UidMap> uidMap = new UidMap();
     // Set up the matcher
     AtomMatcher matcher;
     auto simpleMatcher = matcher.mutable_simple_atom_matcher();
@@ -568,7 +568,7 @@
 }
 
 TEST(AtomMatcherTest, TestMultiFieldsMatcher) {
-    UidMap uidMap;
+    sp<UidMap> uidMap = new UidMap();
     // Set up the matcher
     AtomMatcher matcher;
     auto simpleMatcher = matcher.mutable_simple_atom_matcher();
@@ -597,7 +597,7 @@
 }
 
 TEST(AtomMatcherTest, TestIntComparisonMatcher) {
-    UidMap uidMap;
+    sp<UidMap> uidMap = new UidMap();
     // Set up the matcher
     AtomMatcher matcher;
     auto simpleMatcher = matcher.mutable_simple_atom_matcher();
@@ -654,7 +654,7 @@
 }
 
 TEST(AtomMatcherTest, TestFloatComparisonMatcher) {
-    UidMap uidMap;
+    sp<UidMap> uidMap = new UidMap();
     // Set up the matcher
     AtomMatcher matcher;
     auto simpleMatcher = matcher.mutable_simple_atom_matcher();
diff --git a/cmds/statsd/tests/MetricsManager_test.cpp b/cmds/statsd/tests/MetricsManager_test.cpp
index 6259757..2dd774e 100644
--- a/cmds/statsd/tests/MetricsManager_test.cpp
+++ b/cmds/statsd/tests/MetricsManager_test.cpp
@@ -23,12 +23,12 @@
 #include "frameworks/base/cmds/statsd/src/statsd_config.pb.h"
 #include "metrics/metrics_test_helper.h"
 #include "src/condition/ConditionTracker.h"
-#include "src/matchers/LogMatchingTracker.h"
+#include "src/matchers/AtomMatchingTracker.h"
 #include "src/metrics/CountMetricProducer.h"
 #include "src/metrics/GaugeMetricProducer.h"
 #include "src/metrics/MetricProducer.h"
 #include "src/metrics/ValueMetricProducer.h"
-#include "src/metrics/metrics_manager_util.h"
+#include "src/metrics/parsing_utils/metrics_manager_util.h"
 #include "src/state/StateManager.h"
 #include "statsd_test_util.h"
 
@@ -48,7 +48,6 @@
 
 namespace {
 const ConfigKey kConfigKey(0, 12345);
-const long kAlertId = 3;
 
 const long timeBaseSec = 1000;
 
@@ -90,287 +89,6 @@
     metric->set_bucket(ONE_MINUTE);
     metric->mutable_dimensions_in_what()->set_field(2 /*SCREEN_STATE_CHANGE*/);
     metric->mutable_dimensions_in_what()->add_child()->set_field(1);
-
-    config.add_no_report_metric(3);
-
-    auto alert = config.add_alert();
-    alert->set_id(kAlertId);
-    alert->set_metric_id(3);
-    alert->set_num_buckets(10);
-    alert->set_refractory_period_secs(100);
-    alert->set_trigger_if_sum_gt(100);
-    return config;
-}
-
-StatsdConfig buildCircleMatchers() {
-    StatsdConfig config;
-    config.set_id(12345);
-
-    AtomMatcher* eventMatcher = config.add_atom_matcher();
-    eventMatcher->set_id(StringToId("SCREEN_IS_ON"));
-
-    SimpleAtomMatcher* simpleAtomMatcher = eventMatcher->mutable_simple_atom_matcher();
-    simpleAtomMatcher->set_atom_id(2 /*SCREEN_STATE_CHANGE*/);
-    simpleAtomMatcher->add_field_value_matcher()->set_field(
-            1 /*SCREEN_STATE_CHANGE__DISPLAY_STATE*/);
-    simpleAtomMatcher->mutable_field_value_matcher(0)->set_eq_int(
-            2 /*SCREEN_STATE_CHANGE__DISPLAY_STATE__STATE_ON*/);
-
-    eventMatcher = config.add_atom_matcher();
-    eventMatcher->set_id(StringToId("SCREEN_ON_OR_OFF"));
-
-    AtomMatcher_Combination* combination = eventMatcher->mutable_combination();
-    combination->set_operation(LogicalOperation::OR);
-    combination->add_matcher(StringToId("SCREEN_IS_ON"));
-    // Circle dependency
-    combination->add_matcher(StringToId("SCREEN_ON_OR_OFF"));
-
-    return config;
-}
-
-StatsdConfig buildAlertWithUnknownMetric() {
-    StatsdConfig config;
-    config.set_id(12345);
-
-    AtomMatcher* eventMatcher = config.add_atom_matcher();
-    eventMatcher->set_id(StringToId("SCREEN_IS_ON"));
-
-    CountMetric* metric = config.add_count_metric();
-    metric->set_id(3);
-    metric->set_what(StringToId("SCREEN_IS_ON"));
-    metric->set_bucket(ONE_MINUTE);
-    metric->mutable_dimensions_in_what()->set_field(2 /*SCREEN_STATE_CHANGE*/);
-    metric->mutable_dimensions_in_what()->add_child()->set_field(1);
-
-    auto alert = config.add_alert();
-    alert->set_id(3);
-    alert->set_metric_id(2);
-    alert->set_num_buckets(10);
-    alert->set_refractory_period_secs(100);
-    alert->set_trigger_if_sum_gt(100);
-    return config;
-}
-
-StatsdConfig buildMissingMatchers() {
-    StatsdConfig config;
-    config.set_id(12345);
-
-    AtomMatcher* eventMatcher = config.add_atom_matcher();
-    eventMatcher->set_id(StringToId("SCREEN_IS_ON"));
-
-    SimpleAtomMatcher* simpleAtomMatcher = eventMatcher->mutable_simple_atom_matcher();
-    simpleAtomMatcher->set_atom_id(2 /*SCREEN_STATE_CHANGE*/);
-    simpleAtomMatcher->add_field_value_matcher()->set_field(
-            1 /*SCREEN_STATE_CHANGE__DISPLAY_STATE*/);
-    simpleAtomMatcher->mutable_field_value_matcher(0)->set_eq_int(
-            2 /*SCREEN_STATE_CHANGE__DISPLAY_STATE__STATE_ON*/);
-
-    eventMatcher = config.add_atom_matcher();
-    eventMatcher->set_id(StringToId("SCREEN_ON_OR_OFF"));
-
-    AtomMatcher_Combination* combination = eventMatcher->mutable_combination();
-    combination->set_operation(LogicalOperation::OR);
-    combination->add_matcher(StringToId("SCREEN_IS_ON"));
-    // undefined matcher
-    combination->add_matcher(StringToId("ABC"));
-
-    return config;
-}
-
-StatsdConfig buildMissingPredicate() {
-    StatsdConfig config;
-    config.set_id(12345);
-
-    CountMetric* metric = config.add_count_metric();
-    metric->set_id(3);
-    metric->set_what(StringToId("SCREEN_EVENT"));
-    metric->set_bucket(ONE_MINUTE);
-    metric->set_condition(StringToId("SOME_CONDITION"));
-
-    AtomMatcher* eventMatcher = config.add_atom_matcher();
-    eventMatcher->set_id(StringToId("SCREEN_EVENT"));
-
-    SimpleAtomMatcher* simpleAtomMatcher = eventMatcher->mutable_simple_atom_matcher();
-    simpleAtomMatcher->set_atom_id(2);
-
-    return config;
-}
-
-StatsdConfig buildDimensionMetricsWithMultiTags() {
-    StatsdConfig config;
-    config.set_id(12345);
-
-    AtomMatcher* eventMatcher = config.add_atom_matcher();
-    eventMatcher->set_id(StringToId("BATTERY_VERY_LOW"));
-    SimpleAtomMatcher* simpleAtomMatcher = eventMatcher->mutable_simple_atom_matcher();
-    simpleAtomMatcher->set_atom_id(2);
-
-    eventMatcher = config.add_atom_matcher();
-    eventMatcher->set_id(StringToId("BATTERY_VERY_VERY_LOW"));
-    simpleAtomMatcher = eventMatcher->mutable_simple_atom_matcher();
-    simpleAtomMatcher->set_atom_id(3);
-
-    eventMatcher = config.add_atom_matcher();
-    eventMatcher->set_id(StringToId("BATTERY_LOW"));
-
-    AtomMatcher_Combination* combination = eventMatcher->mutable_combination();
-    combination->set_operation(LogicalOperation::OR);
-    combination->add_matcher(StringToId("BATTERY_VERY_LOW"));
-    combination->add_matcher(StringToId("BATTERY_VERY_VERY_LOW"));
-
-    // Count process state changes, slice by uid, while SCREEN_IS_OFF
-    CountMetric* metric = config.add_count_metric();
-    metric->set_id(3);
-    metric->set_what(StringToId("BATTERY_LOW"));
-    metric->set_bucket(ONE_MINUTE);
-    // This case is interesting. We want to dimension across two atoms.
-    metric->mutable_dimensions_in_what()->add_child()->set_field(1);
-
-    auto alert = config.add_alert();
-    alert->set_id(kAlertId);
-    alert->set_metric_id(3);
-    alert->set_num_buckets(10);
-    alert->set_refractory_period_secs(100);
-    alert->set_trigger_if_sum_gt(100);
-    return config;
-}
-
-StatsdConfig buildCirclePredicates() {
-    StatsdConfig config;
-    config.set_id(12345);
-
-    AtomMatcher* eventMatcher = config.add_atom_matcher();
-    eventMatcher->set_id(StringToId("SCREEN_IS_ON"));
-
-    SimpleAtomMatcher* simpleAtomMatcher = eventMatcher->mutable_simple_atom_matcher();
-    simpleAtomMatcher->set_atom_id(2 /*SCREEN_STATE_CHANGE*/);
-    simpleAtomMatcher->add_field_value_matcher()->set_field(
-            1 /*SCREEN_STATE_CHANGE__DISPLAY_STATE*/);
-    simpleAtomMatcher->mutable_field_value_matcher(0)->set_eq_int(
-            2 /*SCREEN_STATE_CHANGE__DISPLAY_STATE__STATE_ON*/);
-
-    eventMatcher = config.add_atom_matcher();
-    eventMatcher->set_id(StringToId("SCREEN_IS_OFF"));
-
-    simpleAtomMatcher = eventMatcher->mutable_simple_atom_matcher();
-    simpleAtomMatcher->set_atom_id(2 /*SCREEN_STATE_CHANGE*/);
-    simpleAtomMatcher->add_field_value_matcher()->set_field(
-            1 /*SCREEN_STATE_CHANGE__DISPLAY_STATE*/);
-    simpleAtomMatcher->mutable_field_value_matcher(0)->set_eq_int(
-            1 /*SCREEN_STATE_CHANGE__DISPLAY_STATE__STATE_OFF*/);
-
-    auto condition = config.add_predicate();
-    condition->set_id(StringToId("SCREEN_IS_ON"));
-    SimplePredicate* simplePredicate = condition->mutable_simple_predicate();
-    simplePredicate->set_start(StringToId("SCREEN_IS_ON"));
-    simplePredicate->set_stop(StringToId("SCREEN_IS_OFF"));
-
-    condition = config.add_predicate();
-    condition->set_id(StringToId("SCREEN_IS_EITHER_ON_OFF"));
-
-    Predicate_Combination* combination = condition->mutable_combination();
-    combination->set_operation(LogicalOperation::OR);
-    combination->add_predicate(StringToId("SCREEN_IS_ON"));
-    combination->add_predicate(StringToId("SCREEN_IS_EITHER_ON_OFF"));
-
-    return config;
-}
-
-StatsdConfig buildConfigWithDifferentPredicates() {
-    StatsdConfig config;
-    config.set_id(12345);
-
-    auto pulledAtomMatcher =
-            CreateSimpleAtomMatcher("SUBSYSTEM_SLEEP", util::SUBSYSTEM_SLEEP_STATE);
-    *config.add_atom_matcher() = pulledAtomMatcher;
-    auto screenOnAtomMatcher = CreateScreenTurnedOnAtomMatcher();
-    *config.add_atom_matcher() = screenOnAtomMatcher;
-    auto screenOffAtomMatcher = CreateScreenTurnedOffAtomMatcher();
-    *config.add_atom_matcher() = screenOffAtomMatcher;
-    auto batteryNoneAtomMatcher = CreateBatteryStateNoneMatcher();
-    *config.add_atom_matcher() = batteryNoneAtomMatcher;
-    auto batteryUsbAtomMatcher = CreateBatteryStateUsbMatcher();
-    *config.add_atom_matcher() = batteryUsbAtomMatcher;
-
-    // Simple condition with InitialValue set to default (unknown).
-    auto screenOnUnknownPredicate = CreateScreenIsOnPredicate();
-    *config.add_predicate() = screenOnUnknownPredicate;
-
-    // Simple condition with InitialValue set to false.
-    auto screenOnFalsePredicate = config.add_predicate();
-    screenOnFalsePredicate->set_id(StringToId("ScreenIsOnInitialFalse"));
-    SimplePredicate* simpleScreenOnFalsePredicate =
-            screenOnFalsePredicate->mutable_simple_predicate();
-    simpleScreenOnFalsePredicate->set_start(screenOnAtomMatcher.id());
-    simpleScreenOnFalsePredicate->set_stop(screenOffAtomMatcher.id());
-    simpleScreenOnFalsePredicate->set_initial_value(SimplePredicate_InitialValue_FALSE);
-
-    // Simple condition with InitialValue set to false.
-    auto onBatteryFalsePredicate = config.add_predicate();
-    onBatteryFalsePredicate->set_id(StringToId("OnBatteryInitialFalse"));
-    SimplePredicate* simpleOnBatteryFalsePredicate =
-            onBatteryFalsePredicate->mutable_simple_predicate();
-    simpleOnBatteryFalsePredicate->set_start(batteryNoneAtomMatcher.id());
-    simpleOnBatteryFalsePredicate->set_stop(batteryUsbAtomMatcher.id());
-    simpleOnBatteryFalsePredicate->set_initial_value(SimplePredicate_InitialValue_FALSE);
-
-    // Combination condition with both simple condition InitialValues set to false.
-    auto screenOnFalseOnBatteryFalsePredicate = config.add_predicate();
-    screenOnFalseOnBatteryFalsePredicate->set_id(StringToId("ScreenOnFalseOnBatteryFalse"));
-    screenOnFalseOnBatteryFalsePredicate->mutable_combination()->set_operation(
-            LogicalOperation::AND);
-    addPredicateToPredicateCombination(*screenOnFalsePredicate,
-                                       screenOnFalseOnBatteryFalsePredicate);
-    addPredicateToPredicateCombination(*onBatteryFalsePredicate,
-                                       screenOnFalseOnBatteryFalsePredicate);
-
-    // Combination condition with one simple condition InitialValue set to unknown and one set to
-    // false.
-    auto screenOnUnknownOnBatteryFalsePredicate = config.add_predicate();
-    screenOnUnknownOnBatteryFalsePredicate->set_id(StringToId("ScreenOnUnknowneOnBatteryFalse"));
-    screenOnUnknownOnBatteryFalsePredicate->mutable_combination()->set_operation(
-            LogicalOperation::AND);
-    addPredicateToPredicateCombination(screenOnUnknownPredicate,
-                                       screenOnUnknownOnBatteryFalsePredicate);
-    addPredicateToPredicateCombination(*onBatteryFalsePredicate,
-                                       screenOnUnknownOnBatteryFalsePredicate);
-
-    // Simple condition metric with initial value false.
-    ValueMetric* metric1 = config.add_value_metric();
-    metric1->set_id(StringToId("ValueSubsystemSleepWhileScreenOnInitialFalse"));
-    metric1->set_what(pulledAtomMatcher.id());
-    *metric1->mutable_value_field() =
-            CreateDimensions(util::SUBSYSTEM_SLEEP_STATE, {4 /* time sleeping field */});
-    metric1->set_bucket(FIVE_MINUTES);
-    metric1->set_condition(screenOnFalsePredicate->id());
-
-    // Simple condition metric with initial value unknown.
-    ValueMetric* metric2 = config.add_value_metric();
-    metric2->set_id(StringToId("ValueSubsystemSleepWhileScreenOnInitialUnknown"));
-    metric2->set_what(pulledAtomMatcher.id());
-    *metric2->mutable_value_field() =
-            CreateDimensions(util::SUBSYSTEM_SLEEP_STATE, {4 /* time sleeping field */});
-    metric2->set_bucket(FIVE_MINUTES);
-    metric2->set_condition(screenOnUnknownPredicate.id());
-
-    // Combination condition metric with initial values false and false.
-    ValueMetric* metric3 = config.add_value_metric();
-    metric3->set_id(StringToId("ValueSubsystemSleepWhileScreenOnFalseDeviceUnpluggedFalse"));
-    metric3->set_what(pulledAtomMatcher.id());
-    *metric3->mutable_value_field() =
-            CreateDimensions(util::SUBSYSTEM_SLEEP_STATE, {4 /* time sleeping field */});
-    metric3->set_bucket(FIVE_MINUTES);
-    metric3->set_condition(screenOnFalseOnBatteryFalsePredicate->id());
-
-    // Combination condition metric with initial values unknown and false.
-    ValueMetric* metric4 = config.add_value_metric();
-    metric4->set_id(StringToId("ValueSubsystemSleepWhileScreenOnUnknownDeviceUnpluggedFalse"));
-    metric4->set_what(pulledAtomMatcher.id());
-    *metric4->mutable_value_field() =
-            CreateDimensions(util::SUBSYSTEM_SLEEP_STATE, {4 /* time sleeping field */});
-    metric4->set_bucket(FIVE_MINUTES);
-    metric4->set_condition(screenOnUnknownOnBatteryFalsePredicate->id());
-
     return config;
 }
 
@@ -379,274 +97,6 @@
 }
 }  // anonymous namespace
 
-TEST(MetricsManagerTest, TestInitialConditions) {
-    UidMap uidMap;
-    sp<StatsPullerManager> pullerManager = new StatsPullerManager();
-    sp<AlarmMonitor> anomalyAlarmMonitor;
-    sp<AlarmMonitor> periodicAlarmMonitor;
-    StatsdConfig config = buildConfigWithDifferentPredicates();
-    set<int> allTagIds;
-    vector<sp<LogMatchingTracker>> allAtomMatchers;
-    vector<sp<ConditionTracker>> allConditionTrackers;
-    vector<sp<MetricProducer>> allMetricProducers;
-    std::vector<sp<AnomalyTracker>> allAnomalyTrackers;
-    std::vector<sp<AlarmTracker>> allAlarmTrackers;
-    unordered_map<int, std::vector<int>> conditionToMetricMap;
-    unordered_map<int, std::vector<int>> trackerToMetricMap;
-    unordered_map<int, std::vector<int>> trackerToConditionMap;
-    unordered_map<int, std::vector<int>> activationAtomTrackerToMetricMap;
-    unordered_map<int, std::vector<int>> deactivationAtomTrackerToMetricMap;
-    unordered_map<int64_t, int> alertTrackerMap;
-    vector<int> metricsWithActivation;
-    std::set<int64_t> noReportMetricIds;
-
-    EXPECT_TRUE(initStatsdConfig(
-            kConfigKey, config, uidMap, pullerManager, anomalyAlarmMonitor, periodicAlarmMonitor,
-            timeBaseSec, timeBaseSec, allTagIds, allAtomMatchers, allConditionTrackers,
-            allMetricProducers, allAnomalyTrackers, allAlarmTrackers, conditionToMetricMap,
-            trackerToMetricMap, trackerToConditionMap, activationAtomTrackerToMetricMap,
-            deactivationAtomTrackerToMetricMap, alertTrackerMap, metricsWithActivation,
-            noReportMetricIds));
-    ASSERT_EQ(4u, allMetricProducers.size());
-    ASSERT_EQ(5u, allConditionTrackers.size());
-
-    ConditionKey queryKey;
-    vector<ConditionState> conditionCache(5, ConditionState::kNotEvaluated);
-
-    allConditionTrackers[3]->isConditionMet(queryKey, allConditionTrackers, false, conditionCache);
-    allConditionTrackers[4]->isConditionMet(queryKey, allConditionTrackers, false, conditionCache);
-    EXPECT_EQ(ConditionState::kUnknown, conditionCache[0]);
-    EXPECT_EQ(ConditionState::kFalse, conditionCache[1]);
-    EXPECT_EQ(ConditionState::kFalse, conditionCache[2]);
-    EXPECT_EQ(ConditionState::kFalse, conditionCache[3]);
-    EXPECT_EQ(ConditionState::kUnknown, conditionCache[4]);
-
-    EXPECT_EQ(ConditionState::kFalse, allMetricProducers[0]->mCondition);
-    EXPECT_EQ(ConditionState::kUnknown, allMetricProducers[1]->mCondition);
-    EXPECT_EQ(ConditionState::kFalse, allMetricProducers[2]->mCondition);
-    EXPECT_EQ(ConditionState::kUnknown, allMetricProducers[3]->mCondition);
-}
-
-TEST(MetricsManagerTest, TestGoodConfig) {
-    UidMap uidMap;
-    sp<StatsPullerManager> pullerManager = new StatsPullerManager();
-    sp<AlarmMonitor> anomalyAlarmMonitor;
-    sp<AlarmMonitor> periodicAlarmMonitor;
-    StatsdConfig config = buildGoodConfig();
-    set<int> allTagIds;
-    vector<sp<LogMatchingTracker>> allAtomMatchers;
-    vector<sp<ConditionTracker>> allConditionTrackers;
-    vector<sp<MetricProducer>> allMetricProducers;
-    std::vector<sp<AnomalyTracker>> allAnomalyTrackers;
-    std::vector<sp<AlarmTracker>> allAlarmTrackers;
-    unordered_map<int, std::vector<int>> conditionToMetricMap;
-    unordered_map<int, std::vector<int>> trackerToMetricMap;
-    unordered_map<int, std::vector<int>> trackerToConditionMap;
-    unordered_map<int, std::vector<int>> activationAtomTrackerToMetricMap;
-    unordered_map<int, std::vector<int>> deactivationAtomTrackerToMetricMap;
-    unordered_map<int64_t, int> alertTrackerMap;
-    vector<int> metricsWithActivation;
-    std::set<int64_t> noReportMetricIds;
-
-    EXPECT_TRUE(initStatsdConfig(kConfigKey, config, uidMap, pullerManager, anomalyAlarmMonitor,
-                                 periodicAlarmMonitor, timeBaseSec, timeBaseSec, allTagIds,
-                                 allAtomMatchers, allConditionTrackers, allMetricProducers,
-                                 allAnomalyTrackers, allAlarmTrackers, conditionToMetricMap,
-                                 trackerToMetricMap, trackerToConditionMap,
-                                 activationAtomTrackerToMetricMap, deactivationAtomTrackerToMetricMap,
-                                 alertTrackerMap, metricsWithActivation,
-                                 noReportMetricIds));
-    ASSERT_EQ(1u, allMetricProducers.size());
-    ASSERT_EQ(1u, allAnomalyTrackers.size());
-    ASSERT_EQ(1u, noReportMetricIds.size());
-    ASSERT_EQ(1u, alertTrackerMap.size());
-    EXPECT_NE(alertTrackerMap.find(kAlertId), alertTrackerMap.end());
-    EXPECT_EQ(alertTrackerMap.find(kAlertId)->second, 0);
-}
-
-TEST(MetricsManagerTest, TestDimensionMetricsWithMultiTags) {
-    UidMap uidMap;
-    sp<StatsPullerManager> pullerManager = new StatsPullerManager();
-    sp<AlarmMonitor> anomalyAlarmMonitor;
-    sp<AlarmMonitor> periodicAlarmMonitor;
-    StatsdConfig config = buildDimensionMetricsWithMultiTags();
-    set<int> allTagIds;
-    vector<sp<LogMatchingTracker>> allAtomMatchers;
-    vector<sp<ConditionTracker>> allConditionTrackers;
-    vector<sp<MetricProducer>> allMetricProducers;
-    std::vector<sp<AnomalyTracker>> allAnomalyTrackers;
-    std::vector<sp<AlarmTracker>> allAlarmTrackers;
-    unordered_map<int, std::vector<int>> conditionToMetricMap;
-    unordered_map<int, std::vector<int>> trackerToMetricMap;
-    unordered_map<int, std::vector<int>> trackerToConditionMap;
-    unordered_map<int, std::vector<int>> activationAtomTrackerToMetricMap;
-    unordered_map<int, std::vector<int>> deactivationAtomTrackerToMetricMap;
-    unordered_map<int64_t, int> alertTrackerMap;
-    vector<int> metricsWithActivation;
-    std::set<int64_t> noReportMetricIds;
-
-    EXPECT_FALSE(initStatsdConfig(kConfigKey, config, uidMap, pullerManager, anomalyAlarmMonitor,
-                                  periodicAlarmMonitor, timeBaseSec, timeBaseSec, allTagIds,
-                                  allAtomMatchers, allConditionTrackers, allMetricProducers,
-                                  allAnomalyTrackers, allAlarmTrackers, conditionToMetricMap,
-                                  trackerToMetricMap, trackerToConditionMap,
-                                  activationAtomTrackerToMetricMap, deactivationAtomTrackerToMetricMap,
-                                  alertTrackerMap, metricsWithActivation,
-                                  noReportMetricIds));
-}
-
-TEST(MetricsManagerTest, TestCircleLogMatcherDependency) {
-    UidMap uidMap;
-    sp<StatsPullerManager> pullerManager = new StatsPullerManager();
-    sp<AlarmMonitor> anomalyAlarmMonitor;
-    sp<AlarmMonitor> periodicAlarmMonitor;
-    StatsdConfig config = buildCircleMatchers();
-    set<int> allTagIds;
-    vector<sp<LogMatchingTracker>> allAtomMatchers;
-    vector<sp<ConditionTracker>> allConditionTrackers;
-    vector<sp<MetricProducer>> allMetricProducers;
-    std::vector<sp<AnomalyTracker>> allAnomalyTrackers;
-    std::vector<sp<AlarmTracker>> allAlarmTrackers;
-    unordered_map<int, std::vector<int>> conditionToMetricMap;
-    unordered_map<int, std::vector<int>> trackerToMetricMap;
-    unordered_map<int, std::vector<int>> trackerToConditionMap;
-    unordered_map<int, std::vector<int>> activationAtomTrackerToMetricMap;
-    unordered_map<int, std::vector<int>> deactivationAtomTrackerToMetricMap;
-    unordered_map<int64_t, int> alertTrackerMap;
-    vector<int> metricsWithActivation;
-    std::set<int64_t> noReportMetricIds;
-
-    EXPECT_FALSE(initStatsdConfig(kConfigKey, config, uidMap, pullerManager, anomalyAlarmMonitor,
-                                  periodicAlarmMonitor, timeBaseSec, timeBaseSec, allTagIds,
-                                  allAtomMatchers, allConditionTrackers, allMetricProducers,
-                                  allAnomalyTrackers, allAlarmTrackers, conditionToMetricMap,
-                                  trackerToMetricMap, trackerToConditionMap,
-                                  activationAtomTrackerToMetricMap, deactivationAtomTrackerToMetricMap,
-                                  alertTrackerMap, metricsWithActivation,
-                                  noReportMetricIds));
-}
-
-TEST(MetricsManagerTest, TestMissingMatchers) {
-    UidMap uidMap;
-    sp<StatsPullerManager> pullerManager = new StatsPullerManager();
-    sp<AlarmMonitor> anomalyAlarmMonitor;
-    sp<AlarmMonitor> periodicAlarmMonitor;
-    StatsdConfig config = buildMissingMatchers();
-    set<int> allTagIds;
-    vector<sp<LogMatchingTracker>> allAtomMatchers;
-    vector<sp<ConditionTracker>> allConditionTrackers;
-    vector<sp<MetricProducer>> allMetricProducers;
-    std::vector<sp<AnomalyTracker>> allAnomalyTrackers;
-    std::vector<sp<AlarmTracker>> allAlarmTrackers;
-    unordered_map<int, std::vector<int>> conditionToMetricMap;
-    unordered_map<int, std::vector<int>> trackerToMetricMap;
-    unordered_map<int, std::vector<int>> trackerToConditionMap;
-    unordered_map<int, std::vector<int>> activationAtomTrackerToMetricMap;
-    unordered_map<int, std::vector<int>> deactivationAtomTrackerToMetricMap;
-    unordered_map<int64_t, int> alertTrackerMap;
-    vector<int> metricsWithActivation;
-    std::set<int64_t> noReportMetricIds;
-    EXPECT_FALSE(initStatsdConfig(kConfigKey, config, uidMap, pullerManager, anomalyAlarmMonitor,
-                                  periodicAlarmMonitor, timeBaseSec, timeBaseSec, allTagIds,
-                                  allAtomMatchers, allConditionTrackers, allMetricProducers,
-                                  allAnomalyTrackers, allAlarmTrackers, conditionToMetricMap,
-                                  trackerToMetricMap, trackerToConditionMap,
-                                  activationAtomTrackerToMetricMap, deactivationAtomTrackerToMetricMap,
-                                  alertTrackerMap, metricsWithActivation,
-                                  noReportMetricIds));
-}
-
-TEST(MetricsManagerTest, TestMissingPredicate) {
-    UidMap uidMap;
-    sp<StatsPullerManager> pullerManager = new StatsPullerManager();
-    sp<AlarmMonitor> anomalyAlarmMonitor;
-    sp<AlarmMonitor> periodicAlarmMonitor;
-    StatsdConfig config = buildMissingPredicate();
-    set<int> allTagIds;
-    vector<sp<LogMatchingTracker>> allAtomMatchers;
-    vector<sp<ConditionTracker>> allConditionTrackers;
-    vector<sp<MetricProducer>> allMetricProducers;
-    std::vector<sp<AnomalyTracker>> allAnomalyTrackers;
-    std::vector<sp<AlarmTracker>> allAlarmTrackers;
-    unordered_map<int, std::vector<int>> conditionToMetricMap;
-    unordered_map<int, std::vector<int>> trackerToMetricMap;
-    unordered_map<int, std::vector<int>> trackerToConditionMap;
-    unordered_map<int, std::vector<int>> activationAtomTrackerToMetricMap;
-    unordered_map<int, std::vector<int>> deactivationAtomTrackerToMetricMap;
-    unordered_map<int64_t, int> alertTrackerMap;
-    vector<int> metricsWithActivation;
-    std::set<int64_t> noReportMetricIds;
-    EXPECT_FALSE(initStatsdConfig(kConfigKey, config, uidMap, pullerManager, anomalyAlarmMonitor,
-                                  periodicAlarmMonitor, timeBaseSec, timeBaseSec, allTagIds,
-                                  allAtomMatchers, allConditionTrackers, allMetricProducers,
-                                  allAnomalyTrackers, allAlarmTrackers, conditionToMetricMap,
-                                  trackerToMetricMap, trackerToConditionMap,
-                                  activationAtomTrackerToMetricMap, deactivationAtomTrackerToMetricMap,
-                                  alertTrackerMap, metricsWithActivation, noReportMetricIds));
-}
-
-TEST(MetricsManagerTest, TestCirclePredicateDependency) {
-    UidMap uidMap;
-    sp<StatsPullerManager> pullerManager = new StatsPullerManager();
-    sp<AlarmMonitor> anomalyAlarmMonitor;
-    sp<AlarmMonitor> periodicAlarmMonitor;
-    StatsdConfig config = buildCirclePredicates();
-    set<int> allTagIds;
-    vector<sp<LogMatchingTracker>> allAtomMatchers;
-    vector<sp<ConditionTracker>> allConditionTrackers;
-    vector<sp<MetricProducer>> allMetricProducers;
-    std::vector<sp<AnomalyTracker>> allAnomalyTrackers;
-    std::vector<sp<AlarmTracker>> allAlarmTrackers;
-    unordered_map<int, std::vector<int>> conditionToMetricMap;
-    unordered_map<int, std::vector<int>> trackerToMetricMap;
-    unordered_map<int, std::vector<int>> trackerToConditionMap;
-    unordered_map<int, std::vector<int>> activationAtomTrackerToMetricMap;
-    unordered_map<int, std::vector<int>> deactivationAtomTrackerToMetricMap;
-    unordered_map<int64_t, int> alertTrackerMap;
-    vector<int> metricsWithActivation;
-    std::set<int64_t> noReportMetricIds;
-
-    EXPECT_FALSE(initStatsdConfig(kConfigKey, config, uidMap, pullerManager, anomalyAlarmMonitor,
-                                  periodicAlarmMonitor, timeBaseSec, timeBaseSec, allTagIds,
-                                  allAtomMatchers, allConditionTrackers, allMetricProducers,
-                                  allAnomalyTrackers, allAlarmTrackers, conditionToMetricMap,
-                                  trackerToMetricMap, trackerToConditionMap,
-                                  activationAtomTrackerToMetricMap, deactivationAtomTrackerToMetricMap,
-                                  alertTrackerMap, metricsWithActivation,
-                                  noReportMetricIds));
-}
-
-TEST(MetricsManagerTest, testAlertWithUnknownMetric) {
-    UidMap uidMap;
-    sp<StatsPullerManager> pullerManager = new StatsPullerManager();
-    sp<AlarmMonitor> anomalyAlarmMonitor;
-    sp<AlarmMonitor> periodicAlarmMonitor;
-    StatsdConfig config = buildAlertWithUnknownMetric();
-    set<int> allTagIds;
-    vector<sp<LogMatchingTracker>> allAtomMatchers;
-    vector<sp<ConditionTracker>> allConditionTrackers;
-    vector<sp<MetricProducer>> allMetricProducers;
-    std::vector<sp<AnomalyTracker>> allAnomalyTrackers;
-    std::vector<sp<AlarmTracker>> allAlarmTrackers;
-    unordered_map<int, std::vector<int>> conditionToMetricMap;
-    unordered_map<int, std::vector<int>> trackerToMetricMap;
-    unordered_map<int, std::vector<int>> trackerToConditionMap;
-    unordered_map<int, std::vector<int>> activationAtomTrackerToMetricMap;
-    unordered_map<int, std::vector<int>> deactivationAtomTrackerToMetricMap;
-    unordered_map<int64_t, int> alertTrackerMap;
-    vector<int> metricsWithActivation;
-    std::set<int64_t> noReportMetricIds;
-
-    EXPECT_FALSE(initStatsdConfig(kConfigKey, config, uidMap, pullerManager, anomalyAlarmMonitor,
-                                  periodicAlarmMonitor, timeBaseSec, timeBaseSec, allTagIds,
-                                  allAtomMatchers, allConditionTrackers, allMetricProducers,
-                                  allAnomalyTrackers, allAlarmTrackers, conditionToMetricMap,
-                                  trackerToMetricMap, trackerToConditionMap,
-                                  activationAtomTrackerToMetricMap, deactivationAtomTrackerToMetricMap,
-                                  alertTrackerMap, metricsWithActivation,
-                                  noReportMetricIds));
-}
-
 TEST(MetricsManagerTest, TestLogSources) {
     string app1 = "app1";
     set<int32_t> app1Uids = {1111, 11111};
@@ -680,7 +130,7 @@
     sp<AlarmMonitor> anomalyAlarmMonitor;
     sp<AlarmMonitor> periodicAlarmMonitor;
 
-    StatsdConfig config = buildGoodConfig();
+    StatsdConfig config;
     config.add_allowed_log_source("AID_SYSTEM");
     config.add_allowed_log_source(app1);
     config.add_default_pull_packages("AID_SYSTEM");
@@ -744,7 +194,7 @@
     sp<AlarmMonitor> anomalyAlarmMonitor;
     sp<AlarmMonitor> periodicAlarmMonitor;
 
-    StatsdConfig config = buildGoodConfig();
+    StatsdConfig config;
     config.add_whitelisted_atom_ids(3);
     config.add_whitelisted_atom_ids(4);
 
diff --git a/cmds/statsd/tests/StatsLogProcessor_test.cpp b/cmds/statsd/tests/StatsLogProcessor_test.cpp
index 1e6680c..1409b62 100644
--- a/cmds/statsd/tests/StatsLogProcessor_test.cpp
+++ b/cmds/statsd/tests/StatsLogProcessor_test.cpp
@@ -68,7 +68,7 @@
     sp<StatsPullerManager> pullerManager = new StatsPullerManager();
     sp<AlarmMonitor> anomalyAlarmMonitor;
     sp<AlarmMonitor> periodicAlarmMonitor;
-    // Construct the processor with a dummy sendBroadcast function that does nothing.
+    // Construct the processor with a no-op sendBroadcast function that does nothing.
     StatsLogProcessor p(m, pullerManager, anomalyAlarmMonitor, periodicAlarmMonitor, 0,
                         [](const ConfigKey& key) { return true; },
                         [](const int&, const vector<int64_t>&) {return true;});
@@ -896,8 +896,8 @@
     EXPECT_TRUE(metricProducer2->isActive());
 
     int i = 0;
-    for (; i < metricsManager1->mAllAtomMatchers.size(); i++) {
-        if (metricsManager1->mAllAtomMatchers[i]->getId() ==
+    for (; i < metricsManager1->mAllAtomMatchingTrackers.size(); i++) {
+        if (metricsManager1->mAllAtomMatchingTrackers[i]->getId() ==
             metric1ActivationTrigger1->atom_matcher_id()) {
             break;
         }
@@ -908,8 +908,8 @@
     EXPECT_EQ(kNotActive, activation1->state);
 
     i = 0;
-    for (; i < metricsManager1->mAllAtomMatchers.size(); i++) {
-        if (metricsManager1->mAllAtomMatchers[i]->getId() ==
+    for (; i < metricsManager1->mAllAtomMatchingTrackers.size(); i++) {
+        if (metricsManager1->mAllAtomMatchingTrackers[i]->getId() ==
             metric1ActivationTrigger2->atom_matcher_id()) {
             break;
         }
@@ -981,8 +981,8 @@
     EXPECT_TRUE(metricProducer1002->isActive());
 
     i = 0;
-    for (; i < metricsManager1001->mAllAtomMatchers.size(); i++) {
-        if (metricsManager1001->mAllAtomMatchers[i]->getId() ==
+    for (; i < metricsManager1001->mAllAtomMatchingTrackers.size(); i++) {
+        if (metricsManager1001->mAllAtomMatchingTrackers[i]->getId() ==
             metric1ActivationTrigger1->atom_matcher_id()) {
             break;
         }
@@ -993,8 +993,8 @@
     EXPECT_EQ(kNotActive, activation1001_1->state);
 
     i = 0;
-    for (; i < metricsManager1001->mAllAtomMatchers.size(); i++) {
-        if (metricsManager1001->mAllAtomMatchers[i]->getId() ==
+    for (; i < metricsManager1001->mAllAtomMatchingTrackers.size(); i++) {
+        if (metricsManager1001->mAllAtomMatchingTrackers[i]->getId() ==
             metric1ActivationTrigger2->atom_matcher_id()) {
             break;
         }
@@ -1082,8 +1082,8 @@
     EXPECT_TRUE(metricProducerTimeBase3_2->isActive());
 
     i = 0;
-    for (; i < metricsManagerTimeBase3->mAllAtomMatchers.size(); i++) {
-        if (metricsManagerTimeBase3->mAllAtomMatchers[i]->getId() ==
+    for (; i < metricsManagerTimeBase3->mAllAtomMatchingTrackers.size(); i++) {
+        if (metricsManagerTimeBase3->mAllAtomMatchingTrackers[i]->getId() ==
             metric1ActivationTrigger1->atom_matcher_id()) {
             break;
         }
@@ -1094,8 +1094,8 @@
     EXPECT_EQ(kNotActive, activationTimeBase3_1->state);
 
     i = 0;
-    for (; i < metricsManagerTimeBase3->mAllAtomMatchers.size(); i++) {
-        if (metricsManagerTimeBase3->mAllAtomMatchers[i]->getId() ==
+    for (; i < metricsManagerTimeBase3->mAllAtomMatchingTrackers.size(); i++) {
+        if (metricsManagerTimeBase3->mAllAtomMatchingTrackers[i]->getId() ==
             metric1ActivationTrigger2->atom_matcher_id()) {
             break;
         }
@@ -1184,8 +1184,8 @@
     EXPECT_TRUE(metricProducerTimeBase4_2->isActive());
 
     i = 0;
-    for (; i < metricsManagerTimeBase4->mAllAtomMatchers.size(); i++) {
-        if (metricsManagerTimeBase4->mAllAtomMatchers[i]->getId() ==
+    for (; i < metricsManagerTimeBase4->mAllAtomMatchingTrackers.size(); i++) {
+        if (metricsManagerTimeBase4->mAllAtomMatchingTrackers[i]->getId() ==
             metric1ActivationTrigger1->atom_matcher_id()) {
             break;
         }
@@ -1196,8 +1196,8 @@
     EXPECT_EQ(kNotActive, activationTimeBase4_1->state);
 
     i = 0;
-    for (; i < metricsManagerTimeBase4->mAllAtomMatchers.size(); i++) {
-        if (metricsManagerTimeBase4->mAllAtomMatchers[i]->getId() ==
+    for (; i < metricsManagerTimeBase4->mAllAtomMatchingTrackers.size(); i++) {
+        if (metricsManagerTimeBase4->mAllAtomMatchingTrackers[i]->getId() ==
             metric1ActivationTrigger2->atom_matcher_id()) {
             break;
         }
@@ -1585,8 +1585,8 @@
     EXPECT_TRUE(metricProducer3->isActive());
 
     // Check event activations.
-    ASSERT_EQ(metricsManager1->mAllAtomMatchers.size(), 4);
-    EXPECT_EQ(metricsManager1->mAllAtomMatchers[0]->getId(),
+    ASSERT_EQ(metricsManager1->mAllAtomMatchingTrackers.size(), 4);
+    EXPECT_EQ(metricsManager1->mAllAtomMatchingTrackers[0]->getId(),
               metric1ActivationTrigger1->atom_matcher_id());
     const auto& activation1 = metricProducer1->mEventActivationMap.at(0);
     EXPECT_EQ(100 * NS_PER_SEC, activation1->ttl_ns);
@@ -1594,7 +1594,7 @@
     EXPECT_EQ(kNotActive, activation1->state);
     EXPECT_EQ(ACTIVATE_ON_BOOT, activation1->activationType);
 
-    EXPECT_EQ(metricsManager1->mAllAtomMatchers[1]->getId(),
+    EXPECT_EQ(metricsManager1->mAllAtomMatchingTrackers[1]->getId(),
               metric1ActivationTrigger2->atom_matcher_id());
     const auto& activation2 = metricProducer1->mEventActivationMap.at(1);
     EXPECT_EQ(200 * NS_PER_SEC, activation2->ttl_ns);
@@ -1602,7 +1602,7 @@
     EXPECT_EQ(kNotActive, activation2->state);
     EXPECT_EQ(ACTIVATE_IMMEDIATELY, activation2->activationType);
 
-    EXPECT_EQ(metricsManager1->mAllAtomMatchers[2]->getId(),
+    EXPECT_EQ(metricsManager1->mAllAtomMatchingTrackers[2]->getId(),
               metric2ActivationTrigger1->atom_matcher_id());
     const auto& activation3 = metricProducer2->mEventActivationMap.at(2);
     EXPECT_EQ(100 * NS_PER_SEC, activation3->ttl_ns);
@@ -1610,7 +1610,7 @@
     EXPECT_EQ(kNotActive, activation3->state);
     EXPECT_EQ(ACTIVATE_ON_BOOT, activation3->activationType);
 
-    EXPECT_EQ(metricsManager1->mAllAtomMatchers[3]->getId(),
+    EXPECT_EQ(metricsManager1->mAllAtomMatchingTrackers[3]->getId(),
               metric2ActivationTrigger2->atom_matcher_id());
     const auto& activation4 = metricProducer2->mEventActivationMap.at(3);
     EXPECT_EQ(200 * NS_PER_SEC, activation4->ttl_ns);
@@ -1685,8 +1685,8 @@
     // Activation 1 is kActiveOnBoot.
     // Activation 2 and 3 are not active.
     // Activation 4 is active.
-    ASSERT_EQ(metricsManager2->mAllAtomMatchers.size(), 4);
-    EXPECT_EQ(metricsManager2->mAllAtomMatchers[0]->getId(),
+    ASSERT_EQ(metricsManager2->mAllAtomMatchingTrackers.size(), 4);
+    EXPECT_EQ(metricsManager2->mAllAtomMatchingTrackers[0]->getId(),
               metric1ActivationTrigger1->atom_matcher_id());
     const auto& activation1001 = metricProducer1001->mEventActivationMap.at(0);
     EXPECT_EQ(100 * NS_PER_SEC, activation1001->ttl_ns);
@@ -1694,7 +1694,7 @@
     EXPECT_EQ(kActiveOnBoot, activation1001->state);
     EXPECT_EQ(ACTIVATE_ON_BOOT, activation1001->activationType);
 
-    EXPECT_EQ(metricsManager2->mAllAtomMatchers[1]->getId(),
+    EXPECT_EQ(metricsManager2->mAllAtomMatchingTrackers[1]->getId(),
               metric1ActivationTrigger2->atom_matcher_id());
     const auto& activation1002 = metricProducer1001->mEventActivationMap.at(1);
     EXPECT_EQ(200 * NS_PER_SEC, activation1002->ttl_ns);
@@ -1702,7 +1702,7 @@
     EXPECT_EQ(kNotActive, activation1002->state);
     EXPECT_EQ(ACTIVATE_IMMEDIATELY, activation1002->activationType);
 
-    EXPECT_EQ(metricsManager2->mAllAtomMatchers[2]->getId(),
+    EXPECT_EQ(metricsManager2->mAllAtomMatchingTrackers[2]->getId(),
               metric2ActivationTrigger1->atom_matcher_id());
     const auto& activation1003 = metricProducer1002->mEventActivationMap.at(2);
     EXPECT_EQ(100 * NS_PER_SEC, activation1003->ttl_ns);
@@ -1710,7 +1710,7 @@
     EXPECT_EQ(kNotActive, activation1003->state);
     EXPECT_EQ(ACTIVATE_ON_BOOT, activation1003->activationType);
 
-    EXPECT_EQ(metricsManager2->mAllAtomMatchers[3]->getId(),
+    EXPECT_EQ(metricsManager2->mAllAtomMatchingTrackers[3]->getId(),
               metric2ActivationTrigger2->atom_matcher_id());
     const auto& activation1004 = metricProducer1002->mEventActivationMap.at(3);
     EXPECT_EQ(200 * NS_PER_SEC, activation1004->ttl_ns);
diff --git a/cmds/statsd/tests/UidMap_test.cpp b/cmds/statsd/tests/UidMap_test.cpp
index 293e8ed..33bdc64 100644
--- a/cmds/statsd/tests/UidMap_test.cpp
+++ b/cmds/statsd/tests/UidMap_test.cpp
@@ -44,7 +44,7 @@
     sp<StatsPullerManager> pullerManager = new StatsPullerManager();
     sp<AlarmMonitor> anomalyAlarmMonitor;
     sp<AlarmMonitor> subscriberAlarmMonitor;
-    // Construct the processor with a dummy sendBroadcast function that does nothing.
+    // Construct the processor with a no-op sendBroadcast function that does nothing.
     StatsLogProcessor p(
             m, pullerManager, anomalyAlarmMonitor, subscriberAlarmMonitor, 0,
             [](const ConfigKey& key) { return true; },
diff --git a/cmds/statsd/tests/metrics/GaugeMetricProducer_test.cpp b/cmds/statsd/tests/metrics/GaugeMetricProducer_test.cpp
index caea42d..ba919f1 100644
--- a/cmds/statsd/tests/metrics/GaugeMetricProducer_test.cpp
+++ b/cmds/statsd/tests/metrics/GaugeMetricProducer_test.cpp
@@ -23,7 +23,7 @@
 
 #include "logd/LogEvent.h"
 #include "metrics_test_helper.h"
-#include "src/matchers/SimpleLogMatchingTracker.h"
+#include "src/matchers/SimpleAtomMatchingTracker.h"
 #include "src/metrics/MetricProducer.h"
 #include "src/stats_log_util.h"
 #include "stats_event.h"
@@ -47,7 +47,6 @@
 const ConfigKey kConfigKey(0, 12345);
 const int tagId = 1;
 const int64_t metricId = 123;
-const int64_t atomMatcherId = 678;
 const int logEventMatcherIndex = 0;
 const int64_t bucketStartTimeNs = 10 * NS_PER_SEC;
 const int64_t bucketSizeNs = TimeUnitToBucketSizeInMillis(ONE_MINUTE) * 1000000LL;
@@ -94,11 +93,8 @@
 
     sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
 
-    UidMap uidMap;
-    SimpleAtomMatcher atomMatcher;
-    atomMatcher.set_atom_id(tagId);
-    sp<EventMatcherWizard> eventMatcherWizard = new EventMatcherWizard({
-        new SimpleLogMatchingTracker(atomMatcherId, logEventMatcherIndex, atomMatcher, uidMap)});
+    sp<EventMatcherWizard> eventMatcherWizard =
+            createEventMatcherWizard(tagId, logEventMatcherIndex);
 
     sp<MockStatsPullerManager> pullerManager = new StrictMock<MockStatsPullerManager>();
 
@@ -127,12 +123,8 @@
 
     sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
 
-    UidMap uidMap;
-    SimpleAtomMatcher atomMatcher;
-    atomMatcher.set_atom_id(tagId);
     sp<EventMatcherWizard> eventMatcherWizard =
-            new EventMatcherWizard({new SimpleLogMatchingTracker(
-                    atomMatcherId, logEventMatcherIndex, atomMatcher, uidMap)});
+            createEventMatcherWizard(tagId, logEventMatcherIndex);
 
     sp<MockStatsPullerManager> pullerManager = new StrictMock<MockStatsPullerManager>();
     EXPECT_CALL(*pullerManager, RegisterReceiver(tagId, kConfigKey, _, _, _)).WillOnce(Return());
@@ -217,12 +209,8 @@
     sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
     sp<MockStatsPullerManager> pullerManager = new StrictMock<MockStatsPullerManager>();
 
-    UidMap uidMap;
-    SimpleAtomMatcher atomMatcher;
-    atomMatcher.set_atom_id(tagId);
     sp<EventMatcherWizard> eventMatcherWizard =
-            new EventMatcherWizard({new SimpleLogMatchingTracker(
-                    atomMatcherId, logEventMatcherIndex, atomMatcher, uidMap)});
+            createEventMatcherWizard(tagId, logEventMatcherIndex);
 
     GaugeMetricProducer gaugeProducer(kConfigKey, metric, -1 /*-1 meaning no condition*/, {},
                                       wizard, logEventMatcherIndex, eventMatcherWizard,
@@ -301,12 +289,9 @@
 
     sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
 
-    UidMap uidMap;
-    SimpleAtomMatcher atomMatcher;
-    atomMatcher.set_atom_id(tagId);
     sp<EventMatcherWizard> eventMatcherWizard =
-            new EventMatcherWizard({new SimpleLogMatchingTracker(
-                    atomMatcherId, logEventMatcherIndex, atomMatcher, uidMap)});
+            createEventMatcherWizard(tagId, logEventMatcherIndex);
+
     sp<MockStatsPullerManager> pullerManager = new StrictMock<MockStatsPullerManager>();
     EXPECT_CALL(*pullerManager, RegisterReceiver(tagId, kConfigKey, _, _, _)).WillOnce(Return());
     EXPECT_CALL(*pullerManager, UnRegisterReceiver(tagId, kConfigKey, _)).WillOnce(Return());
@@ -378,12 +363,8 @@
 
     sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
 
-    UidMap uidMap;
-    SimpleAtomMatcher atomMatcher;
-    atomMatcher.set_atom_id(tagId);
     sp<EventMatcherWizard> eventMatcherWizard =
-            new EventMatcherWizard({new SimpleLogMatchingTracker(
-                    atomMatcherId, logEventMatcherIndex, atomMatcher, uidMap)});
+            createEventMatcherWizard(tagId, logEventMatcherIndex);
 
     sp<MockStatsPullerManager> pullerManager = new StrictMock<MockStatsPullerManager>();
     EXPECT_CALL(*pullerManager, RegisterReceiver(tagId, kConfigKey, _, _, _)).WillOnce(Return());
@@ -428,12 +409,8 @@
 
     sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
 
-    UidMap uidMap;
-    SimpleAtomMatcher atomMatcher;
-    atomMatcher.set_atom_id(tagId);
     sp<EventMatcherWizard> eventMatcherWizard =
-            new EventMatcherWizard({new SimpleLogMatchingTracker(
-                    atomMatcherId, logEventMatcherIndex, atomMatcher, uidMap)});
+            createEventMatcherWizard(tagId, logEventMatcherIndex);
 
     int64_t conditionChangeNs = bucketStartTimeNs + 8;
 
@@ -502,12 +479,8 @@
     dim->set_field(tagId);
     dim->add_child()->set_field(1);
 
-    UidMap uidMap;
-    SimpleAtomMatcher atomMatcher;
-    atomMatcher.set_atom_id(tagId);
     sp<EventMatcherWizard> eventMatcherWizard =
-            new EventMatcherWizard({new SimpleLogMatchingTracker(
-                    atomMatcherId, logEventMatcherIndex, atomMatcher, uidMap)});
+            createEventMatcherWizard(tagId, logEventMatcherIndex);
 
     sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
     EXPECT_CALL(*wizard, query(_, _, _))
@@ -577,12 +550,8 @@
     gaugeFieldMatcher->set_field(tagId);
     gaugeFieldMatcher->add_child()->set_field(2);
 
-    UidMap uidMap;
-    SimpleAtomMatcher atomMatcher;
-    atomMatcher.set_atom_id(tagId);
     sp<EventMatcherWizard> eventMatcherWizard =
-            new EventMatcherWizard({new SimpleLogMatchingTracker(
-                    atomMatcherId, logEventMatcherIndex, atomMatcher, uidMap)});
+            createEventMatcherWizard(tagId, logEventMatcherIndex);
 
     GaugeMetricProducer gaugeProducer(kConfigKey, metric, -1 /*-1 meaning no condition*/, {},
                                       wizard, logEventMatcherIndex, eventMatcherWizard, tagId, -1,
@@ -657,12 +626,8 @@
 
     sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
 
-    UidMap uidMap;
-    SimpleAtomMatcher atomMatcher;
-    atomMatcher.set_atom_id(tagId);
     sp<EventMatcherWizard> eventMatcherWizard =
-            new EventMatcherWizard({new SimpleLogMatchingTracker(
-                    atomMatcherId, logEventMatcherIndex, atomMatcher, uidMap)});
+            createEventMatcherWizard(tagId, logEventMatcherIndex);
 
     sp<MockStatsPullerManager> pullerManager = new StrictMock<MockStatsPullerManager>();
     EXPECT_CALL(*pullerManager, Pull(tagId, kConfigKey, _, _))
@@ -729,12 +694,8 @@
 
     sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
 
-    UidMap uidMap;
-    SimpleAtomMatcher atomMatcher;
-    atomMatcher.set_atom_id(tagId);
     sp<EventMatcherWizard> eventMatcherWizard =
-            new EventMatcherWizard({new SimpleLogMatchingTracker(
-                    atomMatcherId, logEventMatcherIndex, atomMatcher, uidMap)});
+            createEventMatcherWizard(tagId, logEventMatcherIndex);
 
     sp<MockStatsPullerManager> pullerManager = new StrictMock<MockStatsPullerManager>();
     EXPECT_CALL(*pullerManager, Pull(tagId, kConfigKey, _, _))
@@ -807,12 +768,8 @@
 
     sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
 
-    UidMap uidMap;
-    SimpleAtomMatcher atomMatcher;
-    atomMatcher.set_atom_id(tagId);
     sp<EventMatcherWizard> eventMatcherWizard =
-            new EventMatcherWizard({new SimpleLogMatchingTracker(
-                    atomMatcherId, logEventMatcherIndex, atomMatcher, uidMap)});
+            createEventMatcherWizard(tagId, logEventMatcherIndex);
 
     sp<MockStatsPullerManager> pullerManager = new StrictMock<MockStatsPullerManager>();
     EXPECT_CALL(*pullerManager, Pull(tagId, kConfigKey, bucketStartTimeNs + 3, _))
diff --git a/cmds/statsd/tests/metrics/ValueMetricProducer_test.cpp b/cmds/statsd/tests/metrics/ValueMetricProducer_test.cpp
index 9889250..1000aea 100644
--- a/cmds/statsd/tests/metrics/ValueMetricProducer_test.cpp
+++ b/cmds/statsd/tests/metrics/ValueMetricProducer_test.cpp
@@ -22,7 +22,7 @@
 #include <vector>
 
 #include "metrics_test_helper.h"
-#include "src/matchers/SimpleLogMatchingTracker.h"
+#include "src/matchers/SimpleAtomMatchingTracker.h"
 #include "src/metrics/MetricProducer.h"
 #include "src/stats_log_util.h"
 #include "tests/statsd_test_util.h"
@@ -46,7 +46,6 @@
 const ConfigKey kConfigKey(0, 12345);
 const int tagId = 1;
 const int64_t metricId = 123;
-const int64_t atomMatcherId = 678;
 const int logEventMatcherIndex = 0;
 const int64_t bucketStartTimeNs = 10000000000;
 const int64_t bucketSizeNs = TimeUnitToBucketSizeInMillis(ONE_MINUTE) * 1000000LL;
@@ -99,12 +98,8 @@
 public:
     static sp<ValueMetricProducer> createValueProducerNoConditions(
             sp<MockStatsPullerManager>& pullerManager, ValueMetric& metric) {
-        UidMap uidMap;
-        SimpleAtomMatcher atomMatcher;
-        atomMatcher.set_atom_id(tagId);
         sp<EventMatcherWizard> eventMatcherWizard =
-                new EventMatcherWizard({new SimpleLogMatchingTracker(
-                        atomMatcherId, logEventMatcherIndex, atomMatcher, uidMap)});
+                createEventMatcherWizard(tagId, logEventMatcherIndex);
         sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
         EXPECT_CALL(*pullerManager, RegisterReceiver(tagId, kConfigKey, _, _, _))
                 .WillOnce(Return());
@@ -122,12 +117,8 @@
     static sp<ValueMetricProducer> createValueProducerWithCondition(
             sp<MockStatsPullerManager>& pullerManager, ValueMetric& metric,
             ConditionState conditionAfterFirstBucketPrepared) {
-        UidMap uidMap;
-        SimpleAtomMatcher atomMatcher;
-        atomMatcher.set_atom_id(tagId);
         sp<EventMatcherWizard> eventMatcherWizard =
-                new EventMatcherWizard({new SimpleLogMatchingTracker(
-                        atomMatcherId, logEventMatcherIndex, atomMatcher, uidMap)});
+                createEventMatcherWizard(tagId, logEventMatcherIndex);
         sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
         EXPECT_CALL(*pullerManager, RegisterReceiver(tagId, kConfigKey, _, _, _))
                 .WillOnce(Return());
@@ -147,12 +138,8 @@
             sp<MockStatsPullerManager>& pullerManager, ValueMetric& metric,
             vector<int32_t> slicedStateAtoms,
             unordered_map<int, unordered_map<int, int64_t>> stateGroupMap) {
-        UidMap uidMap;
-        SimpleAtomMatcher atomMatcher;
-        atomMatcher.set_atom_id(tagId);
         sp<EventMatcherWizard> eventMatcherWizard =
-                new EventMatcherWizard({new SimpleLogMatchingTracker(
-                        atomMatcherId, logEventMatcherIndex, atomMatcher, uidMap)});
+                createEventMatcherWizard(tagId, logEventMatcherIndex);
         sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
         EXPECT_CALL(*pullerManager, RegisterReceiver(tagId, kConfigKey, _, _, _))
                 .WillOnce(Return());
@@ -172,12 +159,8 @@
             vector<int32_t> slicedStateAtoms,
             unordered_map<int, unordered_map<int, int64_t>> stateGroupMap,
             ConditionState conditionAfterFirstBucketPrepared) {
-        UidMap uidMap;
-        SimpleAtomMatcher atomMatcher;
-        atomMatcher.set_atom_id(tagId);
         sp<EventMatcherWizard> eventMatcherWizard =
-                new EventMatcherWizard({new SimpleLogMatchingTracker(
-                        atomMatcherId, logEventMatcherIndex, atomMatcher, uidMap)});
+                createEventMatcherWizard(tagId, logEventMatcherIndex);
         sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
         EXPECT_CALL(*pullerManager, RegisterReceiver(tagId, kConfigKey, _, _, _))
                 .WillOnce(Return());
@@ -237,12 +220,8 @@
     ValueMetric metric = ValueMetricProducerTestHelper::createMetric();
 
     int64_t startTimeBase = 11;
-    UidMap uidMap;
-    SimpleAtomMatcher atomMatcher;
-    atomMatcher.set_atom_id(tagId);
     sp<EventMatcherWizard> eventMatcherWizard =
-            new EventMatcherWizard({new SimpleLogMatchingTracker(
-                    atomMatcherId, logEventMatcherIndex, atomMatcher, uidMap)});
+            createEventMatcherWizard(tagId, logEventMatcherIndex);
     sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
     sp<MockStatsPullerManager> pullerManager = new StrictMock<MockStatsPullerManager>();
 
@@ -267,12 +246,8 @@
 TEST(ValueMetricProducerTest, TestFirstBucket) {
     ValueMetric metric = ValueMetricProducerTestHelper::createMetric();
 
-    UidMap uidMap;
-    SimpleAtomMatcher atomMatcher;
-    atomMatcher.set_atom_id(tagId);
     sp<EventMatcherWizard> eventMatcherWizard =
-            new EventMatcherWizard({new SimpleLogMatchingTracker(
-                    atomMatcherId, logEventMatcherIndex, atomMatcher, uidMap)});
+            createEventMatcherWizard(tagId, logEventMatcherIndex);
     sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
     sp<MockStatsPullerManager> pullerManager = new StrictMock<MockStatsPullerManager>();
 
@@ -421,15 +396,11 @@
 TEST(ValueMetricProducerTest, TestPulledEventsWithFiltering) {
     ValueMetric metric = ValueMetricProducerTestHelper::createMetric();
 
-    UidMap uidMap;
-    SimpleAtomMatcher atomMatcher;
-    atomMatcher.set_atom_id(tagId);
-    auto keyValue = atomMatcher.add_field_value_matcher();
-    keyValue->set_field(1);
-    keyValue->set_eq_int(3);
+    FieldValueMatcher fvm;
+    fvm.set_field(1);
+    fvm.set_eq_int(3);
     sp<EventMatcherWizard> eventMatcherWizard =
-            new EventMatcherWizard({new SimpleLogMatchingTracker(
-                    atomMatcherId, logEventMatcherIndex, atomMatcher, uidMap)});
+            createEventMatcherWizard(tagId, logEventMatcherIndex, {fvm});
     sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
     sp<MockStatsPullerManager> pullerManager = new StrictMock<MockStatsPullerManager>();
     EXPECT_CALL(*pullerManager, RegisterReceiver(tagId, kConfigKey, _, _, _)).WillOnce(Return());
@@ -698,12 +669,8 @@
 TEST_P(ValueMetricProducerTest_PartialBucket, TestPushedEvents) {
     ValueMetric metric = ValueMetricProducerTestHelper::createMetric();
 
-    UidMap uidMap;
-    SimpleAtomMatcher atomMatcher;
-    atomMatcher.set_atom_id(tagId);
     sp<EventMatcherWizard> eventMatcherWizard =
-            new EventMatcherWizard({new SimpleLogMatchingTracker(
-                    atomMatcherId, logEventMatcherIndex, atomMatcher, uidMap)});
+            createEventMatcherWizard(tagId, logEventMatcherIndex);
     sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
     sp<MockStatsPullerManager> pullerManager = new StrictMock<MockStatsPullerManager>();
 
@@ -759,12 +726,8 @@
 TEST_P(ValueMetricProducerTest_PartialBucket, TestPulledValue) {
     ValueMetric metric = ValueMetricProducerTestHelper::createMetric();
 
-    UidMap uidMap;
-    SimpleAtomMatcher atomMatcher;
-    atomMatcher.set_atom_id(tagId);
     sp<EventMatcherWizard> eventMatcherWizard =
-            new EventMatcherWizard({new SimpleLogMatchingTracker(
-                    atomMatcherId, logEventMatcherIndex, atomMatcher, uidMap)});
+            createEventMatcherWizard(tagId, logEventMatcherIndex);
     sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
     sp<MockStatsPullerManager> pullerManager = new StrictMock<MockStatsPullerManager>();
     int64_t partialBucketSplitTimeNs = bucket2StartTimeNs + 150;
@@ -820,12 +783,8 @@
     ValueMetric metric = ValueMetricProducerTestHelper::createMetric();
     metric.set_split_bucket_for_app_upgrade(false);
 
-    UidMap uidMap;
-    SimpleAtomMatcher atomMatcher;
-    atomMatcher.set_atom_id(tagId);
     sp<EventMatcherWizard> eventMatcherWizard =
-            new EventMatcherWizard({new SimpleLogMatchingTracker(
-                    atomMatcherId, logEventMatcherIndex, atomMatcher, uidMap)});
+            createEventMatcherWizard(tagId, logEventMatcherIndex);
     sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
     sp<MockStatsPullerManager> pullerManager = new StrictMock<MockStatsPullerManager>();
     EXPECT_CALL(*pullerManager, RegisterReceiver(tagId, kConfigKey, _, _, _)).WillOnce(Return());
@@ -900,12 +859,8 @@
 TEST(ValueMetricProducerTest, TestPushedEventsWithoutCondition) {
     ValueMetric metric = ValueMetricProducerTestHelper::createMetric();
 
-    UidMap uidMap;
-    SimpleAtomMatcher atomMatcher;
-    atomMatcher.set_atom_id(tagId);
     sp<EventMatcherWizard> eventMatcherWizard =
-            new EventMatcherWizard({new SimpleLogMatchingTracker(
-                    atomMatcherId, logEventMatcherIndex, atomMatcher, uidMap)});
+            createEventMatcherWizard(tagId, logEventMatcherIndex);
     sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
     sp<MockStatsPullerManager> pullerManager = new StrictMock<MockStatsPullerManager>();
 
@@ -944,12 +899,8 @@
 TEST(ValueMetricProducerTest, TestPushedEventsWithCondition) {
     ValueMetric metric = ValueMetricProducerTestHelper::createMetric();
 
-    UidMap uidMap;
-    SimpleAtomMatcher atomMatcher;
-    atomMatcher.set_atom_id(tagId);
     sp<EventMatcherWizard> eventMatcherWizard =
-            new EventMatcherWizard({new SimpleLogMatchingTracker(
-                    atomMatcherId, logEventMatcherIndex, atomMatcher, uidMap)});
+            createEventMatcherWizard(tagId, logEventMatcherIndex);
     sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
     sp<MockStatsPullerManager> pullerManager = new StrictMock<MockStatsPullerManager>();
 
@@ -1015,12 +966,8 @@
 
     ValueMetric metric = ValueMetricProducerTestHelper::createMetric();
 
-    UidMap uidMap;
-    SimpleAtomMatcher atomMatcher;
-    atomMatcher.set_atom_id(tagId);
     sp<EventMatcherWizard> eventMatcherWizard =
-            new EventMatcherWizard({new SimpleLogMatchingTracker(
-                    atomMatcherId, logEventMatcherIndex, atomMatcher, uidMap)});
+            createEventMatcherWizard(tagId, logEventMatcherIndex);
     sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
     sp<MockStatsPullerManager> pullerManager = new StrictMock<MockStatsPullerManager>();
 
@@ -1360,12 +1307,8 @@
     ValueMetric metric = ValueMetricProducerTestHelper::createMetric();
     metric.set_aggregation_type(ValueMetric::MIN);
 
-    UidMap uidMap;
-    SimpleAtomMatcher atomMatcher;
-    atomMatcher.set_atom_id(tagId);
     sp<EventMatcherWizard> eventMatcherWizard =
-            new EventMatcherWizard({new SimpleLogMatchingTracker(
-                    atomMatcherId, logEventMatcherIndex, atomMatcher, uidMap)});
+            createEventMatcherWizard(tagId, logEventMatcherIndex);
     sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
     sp<MockStatsPullerManager> pullerManager = new StrictMock<MockStatsPullerManager>();
 
@@ -1404,12 +1347,8 @@
     ValueMetric metric = ValueMetricProducerTestHelper::createMetric();
     metric.set_aggregation_type(ValueMetric::MAX);
 
-    UidMap uidMap;
-    SimpleAtomMatcher atomMatcher;
-    atomMatcher.set_atom_id(tagId);
     sp<EventMatcherWizard> eventMatcherWizard =
-            new EventMatcherWizard({new SimpleLogMatchingTracker(
-                    atomMatcherId, logEventMatcherIndex, atomMatcher, uidMap)});
+            createEventMatcherWizard(tagId, logEventMatcherIndex);
     sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
     sp<MockStatsPullerManager> pullerManager = new StrictMock<MockStatsPullerManager>();
 
@@ -1447,12 +1386,8 @@
     ValueMetric metric = ValueMetricProducerTestHelper::createMetric();
     metric.set_aggregation_type(ValueMetric::AVG);
 
-    UidMap uidMap;
-    SimpleAtomMatcher atomMatcher;
-    atomMatcher.set_atom_id(tagId);
     sp<EventMatcherWizard> eventMatcherWizard =
-            new EventMatcherWizard({new SimpleLogMatchingTracker(
-                    atomMatcherId, logEventMatcherIndex, atomMatcher, uidMap)});
+            createEventMatcherWizard(tagId, logEventMatcherIndex);
     sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
     sp<MockStatsPullerManager> pullerManager = new StrictMock<MockStatsPullerManager>();
 
@@ -1495,12 +1430,8 @@
     ValueMetric metric = ValueMetricProducerTestHelper::createMetric();
     metric.set_aggregation_type(ValueMetric::SUM);
 
-    UidMap uidMap;
-    SimpleAtomMatcher atomMatcher;
-    atomMatcher.set_atom_id(tagId);
     sp<EventMatcherWizard> eventMatcherWizard =
-            new EventMatcherWizard({new SimpleLogMatchingTracker(
-                    atomMatcherId, logEventMatcherIndex, atomMatcher, uidMap)});
+            createEventMatcherWizard(tagId, logEventMatcherIndex);
     sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
     sp<MockStatsPullerManager> pullerManager = new StrictMock<MockStatsPullerManager>();
 
@@ -1539,12 +1470,8 @@
     metric.set_aggregation_type(ValueMetric::MIN);
     metric.set_use_diff(true);
 
-    UidMap uidMap;
-    SimpleAtomMatcher atomMatcher;
-    atomMatcher.set_atom_id(tagId);
     sp<EventMatcherWizard> eventMatcherWizard =
-            new EventMatcherWizard({new SimpleLogMatchingTracker(
-                    atomMatcherId, logEventMatcherIndex, atomMatcher, uidMap)});
+            createEventMatcherWizard(tagId, logEventMatcherIndex);
     sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
     sp<MockStatsPullerManager> pullerManager = new StrictMock<MockStatsPullerManager>();
 
@@ -1611,12 +1538,8 @@
     metric.set_aggregation_type(ValueMetric::MIN);
     metric.set_use_diff(true);
 
-    UidMap uidMap;
-    SimpleAtomMatcher atomMatcher;
-    atomMatcher.set_atom_id(tagId);
     sp<EventMatcherWizard> eventMatcherWizard =
-            new EventMatcherWizard({new SimpleLogMatchingTracker(
-                    atomMatcherId, logEventMatcherIndex, atomMatcher, uidMap)});
+            createEventMatcherWizard(tagId, logEventMatcherIndex);
     sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
     sp<MockStatsPullerManager> pullerManager = new StrictMock<MockStatsPullerManager>();
 
@@ -2140,12 +2063,8 @@
 TEST(ValueMetricProducerTest, TestResetBaseOnPullTooLate) {
     ValueMetric metric = ValueMetricProducerTestHelper::createMetricWithCondition();
 
-    UidMap uidMap;
-    SimpleAtomMatcher atomMatcher;
-    atomMatcher.set_atom_id(tagId);
     sp<EventMatcherWizard> eventMatcherWizard =
-            new EventMatcherWizard({new SimpleLogMatchingTracker(
-                    atomMatcherId, logEventMatcherIndex, atomMatcher, uidMap)});
+            createEventMatcherWizard(tagId, logEventMatcherIndex);
     sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
     sp<MockStatsPullerManager> pullerManager = new StrictMock<MockStatsPullerManager>();
     EXPECT_CALL(*pullerManager, RegisterReceiver(tagId, kConfigKey, _, _, _)).WillOnce(Return());
@@ -2963,12 +2882,8 @@
 TEST(ValueMetricProducerTest, TestPullNeededFastDump) {
     ValueMetric metric = ValueMetricProducerTestHelper::createMetric();
 
-    UidMap uidMap;
-    SimpleAtomMatcher atomMatcher;
-    atomMatcher.set_atom_id(tagId);
     sp<EventMatcherWizard> eventMatcherWizard =
-            new EventMatcherWizard({new SimpleLogMatchingTracker(
-                    atomMatcherId, logEventMatcherIndex, atomMatcher, uidMap)});
+            createEventMatcherWizard(tagId, logEventMatcherIndex);
     sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
     sp<MockStatsPullerManager> pullerManager = new StrictMock<MockStatsPullerManager>();
     EXPECT_CALL(*pullerManager, RegisterReceiver(tagId, kConfigKey, _, _, _)).WillOnce(Return());
@@ -3001,12 +2916,8 @@
 TEST(ValueMetricProducerTest, TestFastDumpWithoutCurrentBucket) {
     ValueMetric metric = ValueMetricProducerTestHelper::createMetric();
 
-    UidMap uidMap;
-    SimpleAtomMatcher atomMatcher;
-    atomMatcher.set_atom_id(tagId);
     sp<EventMatcherWizard> eventMatcherWizard =
-            new EventMatcherWizard({new SimpleLogMatchingTracker(
-                    atomMatcherId, logEventMatcherIndex, atomMatcher, uidMap)});
+            createEventMatcherWizard(tagId, logEventMatcherIndex);
     sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
     sp<MockStatsPullerManager> pullerManager = new StrictMock<MockStatsPullerManager>();
     EXPECT_CALL(*pullerManager, RegisterReceiver(tagId, kConfigKey, _, _, _)).WillOnce(Return());
@@ -3045,12 +2956,8 @@
 TEST(ValueMetricProducerTest, TestPullNeededNoTimeConstraints) {
     ValueMetric metric = ValueMetricProducerTestHelper::createMetric();
 
-    UidMap uidMap;
-    SimpleAtomMatcher atomMatcher;
-    atomMatcher.set_atom_id(tagId);
     sp<EventMatcherWizard> eventMatcherWizard =
-            new EventMatcherWizard({new SimpleLogMatchingTracker(
-                    atomMatcherId, logEventMatcherIndex, atomMatcher, uidMap)});
+            createEventMatcherWizard(tagId, logEventMatcherIndex);
     sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
     sp<MockStatsPullerManager> pullerManager = new StrictMock<MockStatsPullerManager>();
     EXPECT_CALL(*pullerManager, RegisterReceiver(tagId, kConfigKey, _, _, _)).WillOnce(Return());
diff --git a/cmds/statsd/tests/metrics/parsing_utils/config_update_utils_test.cpp b/cmds/statsd/tests/metrics/parsing_utils/config_update_utils_test.cpp
new file mode 100644
index 0000000..8c698eb
--- /dev/null
+++ b/cmds/statsd/tests/metrics/parsing_utils/config_update_utils_test.cpp
@@ -0,0 +1,416 @@
+// 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.
+
+#include "src/metrics/parsing_utils/config_update_utils.h"
+
+#include <gtest/gtest.h>
+#include <private/android_filesystem_config.h>
+#include <stdio.h>
+
+#include <set>
+#include <unordered_map>
+#include <vector>
+
+#include "frameworks/base/cmds/statsd/src/statsd_config.pb.h"
+#include "src/matchers/CombinationAtomMatchingTracker.h"
+#include "src/metrics/parsing_utils/metrics_manager_util.h"
+#include "tests/statsd_test_util.h"
+
+using namespace testing;
+using android::sp;
+using android::os::statsd::Predicate;
+using std::map;
+using std::set;
+using std::unordered_map;
+using std::vector;
+
+#ifdef __ANDROID__
+
+namespace android {
+namespace os {
+namespace statsd {
+
+namespace {
+
+ConfigKey key(123, 456);
+const int64_t timeBaseNs = 1000;
+sp<UidMap> uidMap = new UidMap();
+sp<StatsPullerManager> pullerManager = new StatsPullerManager();
+sp<AlarmMonitor> anomalyAlarmMonitor;
+sp<AlarmMonitor> periodicAlarmMonitor;
+set<int> allTagIds;
+vector<sp<AtomMatchingTracker>> oldAtomMatchingTrackers;
+unordered_map<int64_t, int> oldAtomMatchingTrackerMap;
+vector<sp<ConditionTracker>> oldConditionTrackers;
+vector<sp<MetricProducer>> oldMetricProducers;
+std::vector<sp<AnomalyTracker>> oldAnomalyTrackers;
+std::vector<sp<AlarmTracker>> oldAlarmTrackers;
+unordered_map<int, std::vector<int>> conditionToMetricMap;
+unordered_map<int, std::vector<int>> trackerToMetricMap;
+unordered_map<int, std::vector<int>> trackerToConditionMap;
+unordered_map<int, std::vector<int>> activationAtomTrackerToMetricMap;
+unordered_map<int, std::vector<int>> deactivationAtomTrackerToMetricMap;
+unordered_map<int64_t, int> alertTrackerMap;
+vector<int> metricsWithActivation;
+std::set<int64_t> noReportMetricIds;
+
+class ConfigUpdateTest : public ::testing::Test {
+public:
+    ConfigUpdateTest() {
+    }
+
+    void SetUp() override {
+        allTagIds.clear();
+        oldAtomMatchingTrackers.clear();
+        oldAtomMatchingTrackerMap.clear();
+        oldConditionTrackers.clear();
+        oldMetricProducers.clear();
+        oldAnomalyTrackers.clear();
+        oldAlarmTrackers.clear();
+        conditionToMetricMap.clear();
+        trackerToMetricMap.clear();
+        trackerToConditionMap.clear();
+        activationAtomTrackerToMetricMap.clear();
+        deactivationAtomTrackerToMetricMap.clear();
+        alertTrackerMap.clear();
+        metricsWithActivation.clear();
+        noReportMetricIds.clear();
+    }
+};
+
+bool initConfig(const StatsdConfig& config) {
+    return initStatsdConfig(
+            key, config, uidMap, pullerManager, anomalyAlarmMonitor, periodicAlarmMonitor,
+            timeBaseNs, timeBaseNs, allTagIds, oldAtomMatchingTrackers, oldAtomMatchingTrackerMap,
+            oldConditionTrackers, oldMetricProducers, oldAnomalyTrackers, oldAlarmTrackers,
+            conditionToMetricMap, trackerToMetricMap, trackerToConditionMap,
+            activationAtomTrackerToMetricMap, deactivationAtomTrackerToMetricMap, alertTrackerMap,
+            metricsWithActivation, noReportMetricIds);
+}
+
+}  // anonymous namespace
+
+TEST_F(ConfigUpdateTest, TestSimpleMatcherPreserve) {
+    StatsdConfig config;
+    AtomMatcher matcher = CreateSimpleAtomMatcher("TEST", /*atom=*/10);
+    int64_t matcherId = matcher.id();
+    *config.add_atom_matcher() = matcher;
+
+    // Create an initial config.
+    EXPECT_TRUE(initConfig(config));
+
+    vector<UpdateStatus> matchersToUpdate(1, UPDATE_UNKNOWN);
+    vector<bool> cycleTracker(1, false);
+    unordered_map<int64_t, int> newAtomMatchingTrackerMap;
+    newAtomMatchingTrackerMap[matcherId] = 0;
+    EXPECT_TRUE(determineMatcherUpdateStatus(config, 0, oldAtomMatchingTrackerMap,
+                                             oldAtomMatchingTrackers, newAtomMatchingTrackerMap,
+                                             matchersToUpdate, cycleTracker));
+    EXPECT_EQ(matchersToUpdate[0], UPDATE_PRESERVE);
+}
+
+TEST_F(ConfigUpdateTest, TestSimpleMatcherReplace) {
+    StatsdConfig config;
+    AtomMatcher matcher = CreateSimpleAtomMatcher("TEST", /*atom=*/10);
+    *config.add_atom_matcher() = matcher;
+
+    EXPECT_TRUE(initConfig(config));
+
+    StatsdConfig newConfig;
+    // Same id, different atom, so should be replaced.
+    AtomMatcher newMatcher = CreateSimpleAtomMatcher("TEST", /*atom=*/11);
+    int64_t matcherId = newMatcher.id();
+    EXPECT_EQ(matcherId, matcher.id());
+    *newConfig.add_atom_matcher() = newMatcher;
+
+    vector<UpdateStatus> matchersToUpdate(1, UPDATE_UNKNOWN);
+    vector<bool> cycleTracker(1, false);
+    unordered_map<int64_t, int> newAtomMatchingTrackerMap;
+    newAtomMatchingTrackerMap[matcherId] = 0;
+    EXPECT_TRUE(determineMatcherUpdateStatus(newConfig, 0, oldAtomMatchingTrackerMap,
+                                             oldAtomMatchingTrackers, newAtomMatchingTrackerMap,
+                                             matchersToUpdate, cycleTracker));
+    EXPECT_EQ(matchersToUpdate[0], UPDATE_REPLACE);
+}
+
+TEST_F(ConfigUpdateTest, TestCombinationMatcherPreserve) {
+    StatsdConfig config;
+    AtomMatcher matcher1 = CreateSimpleAtomMatcher("TEST1", /*atom=*/10);
+    int64_t matcher1Id = matcher1.id();
+    *config.add_atom_matcher() = matcher1;
+
+    AtomMatcher matcher2 = CreateSimpleAtomMatcher("TEST2", /*atom=*/11);
+    *config.add_atom_matcher() = matcher2;
+    int64_t matcher2Id = matcher2.id();
+
+    AtomMatcher matcher3;
+    matcher3.set_id(StringToId("TEST3"));
+    AtomMatcher_Combination* combination = matcher3.mutable_combination();
+    combination->set_operation(LogicalOperation::OR);
+    combination->add_matcher(matcher1Id);
+    combination->add_matcher(matcher2Id);
+    int64_t matcher3Id = matcher3.id();
+    *config.add_atom_matcher() = matcher3;
+
+    EXPECT_TRUE(initConfig(config));
+
+    StatsdConfig newConfig;
+    unordered_map<int64_t, int> newAtomMatchingTrackerMap;
+    // Same matchers, different order, all should be preserved.
+    *newConfig.add_atom_matcher() = matcher2;
+    newAtomMatchingTrackerMap[matcher2Id] = 0;
+    *newConfig.add_atom_matcher() = matcher3;
+    newAtomMatchingTrackerMap[matcher3Id] = 1;
+    *newConfig.add_atom_matcher() = matcher1;
+    newAtomMatchingTrackerMap[matcher1Id] = 2;
+
+    vector<UpdateStatus> matchersToUpdate(3, UPDATE_UNKNOWN);
+    vector<bool> cycleTracker(3, false);
+    // Only update the combination. It should recurse the two child matchers and preserve all 3.
+    EXPECT_TRUE(determineMatcherUpdateStatus(newConfig, 1, oldAtomMatchingTrackerMap,
+                                             oldAtomMatchingTrackers, newAtomMatchingTrackerMap,
+                                             matchersToUpdate, cycleTracker));
+    EXPECT_EQ(matchersToUpdate[0], UPDATE_PRESERVE);
+    EXPECT_EQ(matchersToUpdate[1], UPDATE_PRESERVE);
+    EXPECT_EQ(matchersToUpdate[2], UPDATE_PRESERVE);
+}
+
+TEST_F(ConfigUpdateTest, TestCombinationMatcherReplace) {
+    StatsdConfig config;
+    AtomMatcher matcher1 = CreateSimpleAtomMatcher("TEST1", /*atom=*/10);
+    int64_t matcher1Id = matcher1.id();
+    *config.add_atom_matcher() = matcher1;
+
+    AtomMatcher matcher2 = CreateSimpleAtomMatcher("TEST2", /*atom=*/11);
+    *config.add_atom_matcher() = matcher2;
+    int64_t matcher2Id = matcher2.id();
+
+    AtomMatcher matcher3;
+    matcher3.set_id(StringToId("TEST3"));
+    AtomMatcher_Combination* combination = matcher3.mutable_combination();
+    combination->set_operation(LogicalOperation::OR);
+    combination->add_matcher(matcher1Id);
+    combination->add_matcher(matcher2Id);
+    int64_t matcher3Id = matcher3.id();
+    *config.add_atom_matcher() = matcher3;
+
+    EXPECT_TRUE(initConfig(config));
+
+    // Change the logical operation of the combination matcher, causing a replacement.
+    matcher3.mutable_combination()->set_operation(LogicalOperation::AND);
+
+    StatsdConfig newConfig;
+    unordered_map<int64_t, int> newAtomMatchingTrackerMap;
+    *newConfig.add_atom_matcher() = matcher2;
+    newAtomMatchingTrackerMap[matcher2Id] = 0;
+    *newConfig.add_atom_matcher() = matcher3;
+    newAtomMatchingTrackerMap[matcher3Id] = 1;
+    *newConfig.add_atom_matcher() = matcher1;
+    newAtomMatchingTrackerMap[matcher1Id] = 2;
+
+    vector<UpdateStatus> matchersToUpdate(3, UPDATE_UNKNOWN);
+    vector<bool> cycleTracker(3, false);
+    // Only update the combination. The simple matchers should not be evaluated.
+    EXPECT_TRUE(determineMatcherUpdateStatus(newConfig, 1, oldAtomMatchingTrackerMap,
+                                             oldAtomMatchingTrackers, newAtomMatchingTrackerMap,
+                                             matchersToUpdate, cycleTracker));
+    EXPECT_EQ(matchersToUpdate[0], UPDATE_UNKNOWN);
+    EXPECT_EQ(matchersToUpdate[1], UPDATE_REPLACE);
+    EXPECT_EQ(matchersToUpdate[2], UPDATE_UNKNOWN);
+}
+
+TEST_F(ConfigUpdateTest, TestCombinationMatcherDepsChange) {
+    StatsdConfig config;
+    AtomMatcher matcher1 = CreateSimpleAtomMatcher("TEST1", /*atom=*/10);
+    int64_t matcher1Id = matcher1.id();
+    *config.add_atom_matcher() = matcher1;
+
+    AtomMatcher matcher2 = CreateSimpleAtomMatcher("TEST2", /*atom=*/11);
+    *config.add_atom_matcher() = matcher2;
+    int64_t matcher2Id = matcher2.id();
+
+    AtomMatcher matcher3;
+    matcher3.set_id(StringToId("TEST3"));
+    AtomMatcher_Combination* combination = matcher3.mutable_combination();
+    combination->set_operation(LogicalOperation::OR);
+    combination->add_matcher(matcher1Id);
+    combination->add_matcher(matcher2Id);
+    int64_t matcher3Id = matcher3.id();
+    *config.add_atom_matcher() = matcher3;
+
+    EXPECT_TRUE(initConfig(config));
+
+    // Change a dependency of matcher 3.
+    matcher2.mutable_simple_atom_matcher()->set_atom_id(12);
+
+    StatsdConfig newConfig;
+    unordered_map<int64_t, int> newAtomMatchingTrackerMap;
+    *newConfig.add_atom_matcher() = matcher2;
+    newAtomMatchingTrackerMap[matcher2Id] = 0;
+    *newConfig.add_atom_matcher() = matcher3;
+    newAtomMatchingTrackerMap[matcher3Id] = 1;
+    *newConfig.add_atom_matcher() = matcher1;
+    newAtomMatchingTrackerMap[matcher1Id] = 2;
+
+    vector<UpdateStatus> matchersToUpdate(3, UPDATE_UNKNOWN);
+    vector<bool> cycleTracker(3, false);
+    // Only update the combination.
+    EXPECT_TRUE(determineMatcherUpdateStatus(newConfig, 1, oldAtomMatchingTrackerMap,
+                                             oldAtomMatchingTrackers, newAtomMatchingTrackerMap,
+                                             matchersToUpdate, cycleTracker));
+    // Matcher 2 and matcher3 must be reevaluated. Matcher 1 might, but does not need to be.
+    EXPECT_EQ(matchersToUpdate[0], UPDATE_REPLACE);
+    EXPECT_EQ(matchersToUpdate[1], UPDATE_REPLACE);
+}
+
+TEST_F(ConfigUpdateTest, TestUpdateMatchers) {
+    StatsdConfig config;
+    // Will be preserved.
+    AtomMatcher simple1 = CreateSimpleAtomMatcher("SIMPLE1", /*atom=*/10);
+    int64_t simple1Id = simple1.id();
+    *config.add_atom_matcher() = simple1;
+
+    // Will be replaced.
+    AtomMatcher simple2 = CreateSimpleAtomMatcher("SIMPLE2", /*atom=*/11);
+    *config.add_atom_matcher() = simple2;
+    int64_t simple2Id = simple2.id();
+
+    // Will be removed.
+    AtomMatcher simple3 = CreateSimpleAtomMatcher("SIMPLE3", /*atom=*/12);
+    *config.add_atom_matcher() = simple3;
+    int64_t simple3Id = simple3.id();
+
+    // Will be preserved.
+    AtomMatcher combination1;
+    combination1.set_id(StringToId("combination1"));
+    AtomMatcher_Combination* combination = combination1.mutable_combination();
+    combination->set_operation(LogicalOperation::NOT);
+    combination->add_matcher(simple1Id);
+    int64_t combination1Id = combination1.id();
+    *config.add_atom_matcher() = combination1;
+
+    // Will be replaced since it depends on simple2.
+    AtomMatcher combination2;
+    combination2.set_id(StringToId("combination2"));
+    combination = combination2.mutable_combination();
+    combination->set_operation(LogicalOperation::AND);
+    combination->add_matcher(simple1Id);
+    combination->add_matcher(simple2Id);
+    int64_t combination2Id = combination2.id();
+    *config.add_atom_matcher() = combination2;
+
+    EXPECT_TRUE(initConfig(config));
+
+    // Change simple2, causing simple2 and combination2 to be replaced.
+    simple2.mutable_simple_atom_matcher()->set_atom_id(111);
+
+    // 2 new matchers: simple4 and combination3:
+    AtomMatcher simple4 = CreateSimpleAtomMatcher("SIMPLE4", /*atom=*/13);
+    int64_t simple4Id = simple4.id();
+
+    AtomMatcher combination3;
+    combination3.set_id(StringToId("combination3"));
+    combination = combination3.mutable_combination();
+    combination->set_operation(LogicalOperation::AND);
+    combination->add_matcher(simple4Id);
+    combination->add_matcher(simple2Id);
+    int64_t combination3Id = combination3.id();
+
+    StatsdConfig newConfig;
+    *newConfig.add_atom_matcher() = combination3;
+    *newConfig.add_atom_matcher() = simple2;
+    *newConfig.add_atom_matcher() = combination2;
+    *newConfig.add_atom_matcher() = simple1;
+    *newConfig.add_atom_matcher() = simple4;
+    *newConfig.add_atom_matcher() = combination1;
+
+    set<int> newTagIds;
+    unordered_map<int64_t, int> newAtomMatchingTrackerMap;
+    vector<sp<AtomMatchingTracker>> newAtomMatchingTrackers;
+    EXPECT_TRUE(updateAtomTrackers(newConfig, uidMap, oldAtomMatchingTrackerMap,
+                                   oldAtomMatchingTrackers, newTagIds, newAtomMatchingTrackerMap,
+                                   newAtomMatchingTrackers));
+
+    ASSERT_EQ(newTagIds.size(), 3);
+    EXPECT_EQ(newTagIds.count(10), 1);
+    EXPECT_EQ(newTagIds.count(111), 1);
+    EXPECT_EQ(newTagIds.count(13), 1);
+
+    ASSERT_EQ(newAtomMatchingTrackerMap.size(), 6);
+    EXPECT_EQ(newAtomMatchingTrackerMap.at(combination3Id), 0);
+    EXPECT_EQ(newAtomMatchingTrackerMap.at(simple2Id), 1);
+    EXPECT_EQ(newAtomMatchingTrackerMap.at(combination2Id), 2);
+    EXPECT_EQ(newAtomMatchingTrackerMap.at(simple1Id), 3);
+    EXPECT_EQ(newAtomMatchingTrackerMap.at(simple4Id), 4);
+    EXPECT_EQ(newAtomMatchingTrackerMap.at(combination1Id), 5);
+
+    ASSERT_EQ(newAtomMatchingTrackers.size(), 6);
+    // Make sure all atom matchers are initialized:
+    for (const sp<AtomMatchingTracker>& tracker : newAtomMatchingTrackers) {
+        EXPECT_TRUE(tracker->mInitialized);
+    }
+    // Make sure preserved atom matchers are the same.
+    EXPECT_EQ(oldAtomMatchingTrackers[oldAtomMatchingTrackerMap.at(simple1Id)],
+              newAtomMatchingTrackers[newAtomMatchingTrackerMap.at(simple1Id)]);
+    EXPECT_EQ(oldAtomMatchingTrackers[oldAtomMatchingTrackerMap.at(combination1Id)],
+              newAtomMatchingTrackers[newAtomMatchingTrackerMap.at(combination1Id)]);
+    // Make sure replaced matchers are different.
+    EXPECT_NE(oldAtomMatchingTrackers[oldAtomMatchingTrackerMap.at(simple2Id)],
+              newAtomMatchingTrackers[newAtomMatchingTrackerMap.at(simple2Id)]);
+    EXPECT_NE(oldAtomMatchingTrackers[oldAtomMatchingTrackerMap.at(combination2Id)],
+              newAtomMatchingTrackers[newAtomMatchingTrackerMap.at(combination2Id)]);
+
+    // Validation, make sure the matchers have the proper ids/indices. Could do more checks here.
+    EXPECT_EQ(newAtomMatchingTrackers[0]->getId(), combination3Id);
+    EXPECT_EQ(newAtomMatchingTrackers[0]->mIndex, 0);
+    EXPECT_EQ(newAtomMatchingTrackers[1]->getId(), simple2Id);
+    EXPECT_EQ(newAtomMatchingTrackers[1]->mIndex, 1);
+    EXPECT_EQ(newAtomMatchingTrackers[2]->getId(), combination2Id);
+    EXPECT_EQ(newAtomMatchingTrackers[2]->mIndex, 2);
+    EXPECT_EQ(newAtomMatchingTrackers[3]->getId(), simple1Id);
+    EXPECT_EQ(newAtomMatchingTrackers[3]->mIndex, 3);
+    EXPECT_EQ(newAtomMatchingTrackers[4]->getId(), simple4Id);
+    EXPECT_EQ(newAtomMatchingTrackers[4]->mIndex, 4);
+    EXPECT_EQ(newAtomMatchingTrackers[5]->getId(), combination1Id);
+    EXPECT_EQ(newAtomMatchingTrackers[5]->mIndex, 5);
+
+    // Verify child indices of Combination Matchers are correct.
+    CombinationAtomMatchingTracker* combinationTracker1 =
+            static_cast<CombinationAtomMatchingTracker*>(newAtomMatchingTrackers[5].get());
+    vector<int>* childMatchers = &combinationTracker1->mChildren;
+    EXPECT_EQ(childMatchers->size(), 1);
+    EXPECT_NE(std::find(childMatchers->begin(), childMatchers->end(), 3), childMatchers->end());
+
+    CombinationAtomMatchingTracker* combinationTracker2 =
+            static_cast<CombinationAtomMatchingTracker*>(newAtomMatchingTrackers[2].get());
+    childMatchers = &combinationTracker2->mChildren;
+    EXPECT_EQ(childMatchers->size(), 2);
+    EXPECT_NE(std::find(childMatchers->begin(), childMatchers->end(), 1), childMatchers->end());
+    EXPECT_NE(std::find(childMatchers->begin(), childMatchers->end(), 3), childMatchers->end());
+
+    CombinationAtomMatchingTracker* combinationTracker3 =
+            static_cast<CombinationAtomMatchingTracker*>(newAtomMatchingTrackers[0].get());
+    childMatchers = &combinationTracker3->mChildren;
+    EXPECT_EQ(childMatchers->size(), 2);
+    EXPECT_NE(std::find(childMatchers->begin(), childMatchers->end(), 1), childMatchers->end());
+    EXPECT_NE(std::find(childMatchers->begin(), childMatchers->end(), 4), childMatchers->end());
+}
+
+}  // namespace statsd
+}  // namespace os
+}  // namespace android
+
+#else
+GTEST_LOG_(INFO) << "This test does nothing.\n";
+#endif
diff --git a/cmds/statsd/tests/metrics/parsing_utils/metrics_manager_util_test.cpp b/cmds/statsd/tests/metrics/parsing_utils/metrics_manager_util_test.cpp
new file mode 100644
index 0000000..d6db4c1
--- /dev/null
+++ b/cmds/statsd/tests/metrics/parsing_utils/metrics_manager_util_test.cpp
@@ -0,0 +1,708 @@
+// 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.
+
+#include "src/metrics/parsing_utils/metrics_manager_util.h"
+
+#include <gtest/gtest.h>
+#include <private/android_filesystem_config.h>
+#include <stdio.h>
+
+#include <set>
+#include <unordered_map>
+#include <vector>
+
+#include "frameworks/base/cmds/statsd/src/statsd_config.pb.h"
+#include "src/condition/ConditionTracker.h"
+#include "src/matchers/AtomMatchingTracker.h"
+#include "src/metrics/CountMetricProducer.h"
+#include "src/metrics/GaugeMetricProducer.h"
+#include "src/metrics/MetricProducer.h"
+#include "src/metrics/ValueMetricProducer.h"
+#include "src/state/StateManager.h"
+#include "tests/metrics/metrics_test_helper.h"
+#include "tests/statsd_test_util.h"
+
+using namespace testing;
+using android::sp;
+using android::os::statsd::Predicate;
+using std::map;
+using std::set;
+using std::unordered_map;
+using std::vector;
+
+#ifdef __ANDROID__
+
+namespace android {
+namespace os {
+namespace statsd {
+
+namespace {
+const ConfigKey kConfigKey(0, 12345);
+const long kAlertId = 3;
+
+const long timeBaseSec = 1000;
+
+StatsdConfig buildGoodConfig() {
+    StatsdConfig config;
+    config.set_id(12345);
+
+    AtomMatcher* eventMatcher = config.add_atom_matcher();
+    eventMatcher->set_id(StringToId("SCREEN_IS_ON"));
+
+    SimpleAtomMatcher* simpleAtomMatcher = eventMatcher->mutable_simple_atom_matcher();
+    simpleAtomMatcher->set_atom_id(2 /*SCREEN_STATE_CHANGE*/);
+    simpleAtomMatcher->add_field_value_matcher()->set_field(
+            1 /*SCREEN_STATE_CHANGE__DISPLAY_STATE*/);
+    simpleAtomMatcher->mutable_field_value_matcher(0)->set_eq_int(
+            2 /*SCREEN_STATE_CHANGE__DISPLAY_STATE__STATE_ON*/);
+
+    eventMatcher = config.add_atom_matcher();
+    eventMatcher->set_id(StringToId("SCREEN_IS_OFF"));
+
+    simpleAtomMatcher = eventMatcher->mutable_simple_atom_matcher();
+    simpleAtomMatcher->set_atom_id(2 /*SCREEN_STATE_CHANGE*/);
+    simpleAtomMatcher->add_field_value_matcher()->set_field(
+            1 /*SCREEN_STATE_CHANGE__DISPLAY_STATE*/);
+    simpleAtomMatcher->mutable_field_value_matcher(0)->set_eq_int(
+            1 /*SCREEN_STATE_CHANGE__DISPLAY_STATE__STATE_OFF*/);
+
+    eventMatcher = config.add_atom_matcher();
+    eventMatcher->set_id(StringToId("SCREEN_ON_OR_OFF"));
+
+    AtomMatcher_Combination* combination = eventMatcher->mutable_combination();
+    combination->set_operation(LogicalOperation::OR);
+    combination->add_matcher(StringToId("SCREEN_IS_ON"));
+    combination->add_matcher(StringToId("SCREEN_IS_OFF"));
+
+    CountMetric* metric = config.add_count_metric();
+    metric->set_id(3);
+    metric->set_what(StringToId("SCREEN_IS_ON"));
+    metric->set_bucket(ONE_MINUTE);
+    metric->mutable_dimensions_in_what()->set_field(2 /*SCREEN_STATE_CHANGE*/);
+    metric->mutable_dimensions_in_what()->add_child()->set_field(1);
+
+    config.add_no_report_metric(3);
+
+    auto alert = config.add_alert();
+    alert->set_id(kAlertId);
+    alert->set_metric_id(3);
+    alert->set_num_buckets(10);
+    alert->set_refractory_period_secs(100);
+    alert->set_trigger_if_sum_gt(100);
+    return config;
+}
+
+StatsdConfig buildCircleMatchers() {
+    StatsdConfig config;
+    config.set_id(12345);
+
+    AtomMatcher* eventMatcher = config.add_atom_matcher();
+    eventMatcher->set_id(StringToId("SCREEN_IS_ON"));
+
+    SimpleAtomMatcher* simpleAtomMatcher = eventMatcher->mutable_simple_atom_matcher();
+    simpleAtomMatcher->set_atom_id(2 /*SCREEN_STATE_CHANGE*/);
+    simpleAtomMatcher->add_field_value_matcher()->set_field(
+            1 /*SCREEN_STATE_CHANGE__DISPLAY_STATE*/);
+    simpleAtomMatcher->mutable_field_value_matcher(0)->set_eq_int(
+            2 /*SCREEN_STATE_CHANGE__DISPLAY_STATE__STATE_ON*/);
+
+    eventMatcher = config.add_atom_matcher();
+    eventMatcher->set_id(StringToId("SCREEN_ON_OR_OFF"));
+
+    AtomMatcher_Combination* combination = eventMatcher->mutable_combination();
+    combination->set_operation(LogicalOperation::OR);
+    combination->add_matcher(StringToId("SCREEN_IS_ON"));
+    // Circle dependency
+    combination->add_matcher(StringToId("SCREEN_ON_OR_OFF"));
+
+    return config;
+}
+
+StatsdConfig buildAlertWithUnknownMetric() {
+    StatsdConfig config;
+    config.set_id(12345);
+
+    AtomMatcher* eventMatcher = config.add_atom_matcher();
+    eventMatcher->set_id(StringToId("SCREEN_IS_ON"));
+
+    CountMetric* metric = config.add_count_metric();
+    metric->set_id(3);
+    metric->set_what(StringToId("SCREEN_IS_ON"));
+    metric->set_bucket(ONE_MINUTE);
+    metric->mutable_dimensions_in_what()->set_field(2 /*SCREEN_STATE_CHANGE*/);
+    metric->mutable_dimensions_in_what()->add_child()->set_field(1);
+
+    auto alert = config.add_alert();
+    alert->set_id(3);
+    alert->set_metric_id(2);
+    alert->set_num_buckets(10);
+    alert->set_refractory_period_secs(100);
+    alert->set_trigger_if_sum_gt(100);
+    return config;
+}
+
+StatsdConfig buildMissingMatchers() {
+    StatsdConfig config;
+    config.set_id(12345);
+
+    AtomMatcher* eventMatcher = config.add_atom_matcher();
+    eventMatcher->set_id(StringToId("SCREEN_IS_ON"));
+
+    SimpleAtomMatcher* simpleAtomMatcher = eventMatcher->mutable_simple_atom_matcher();
+    simpleAtomMatcher->set_atom_id(2 /*SCREEN_STATE_CHANGE*/);
+    simpleAtomMatcher->add_field_value_matcher()->set_field(
+            1 /*SCREEN_STATE_CHANGE__DISPLAY_STATE*/);
+    simpleAtomMatcher->mutable_field_value_matcher(0)->set_eq_int(
+            2 /*SCREEN_STATE_CHANGE__DISPLAY_STATE__STATE_ON*/);
+
+    eventMatcher = config.add_atom_matcher();
+    eventMatcher->set_id(StringToId("SCREEN_ON_OR_OFF"));
+
+    AtomMatcher_Combination* combination = eventMatcher->mutable_combination();
+    combination->set_operation(LogicalOperation::OR);
+    combination->add_matcher(StringToId("SCREEN_IS_ON"));
+    // undefined matcher
+    combination->add_matcher(StringToId("ABC"));
+
+    return config;
+}
+
+StatsdConfig buildMissingPredicate() {
+    StatsdConfig config;
+    config.set_id(12345);
+
+    CountMetric* metric = config.add_count_metric();
+    metric->set_id(3);
+    metric->set_what(StringToId("SCREEN_EVENT"));
+    metric->set_bucket(ONE_MINUTE);
+    metric->set_condition(StringToId("SOME_CONDITION"));
+
+    AtomMatcher* eventMatcher = config.add_atom_matcher();
+    eventMatcher->set_id(StringToId("SCREEN_EVENT"));
+
+    SimpleAtomMatcher* simpleAtomMatcher = eventMatcher->mutable_simple_atom_matcher();
+    simpleAtomMatcher->set_atom_id(2);
+
+    return config;
+}
+
+StatsdConfig buildDimensionMetricsWithMultiTags() {
+    StatsdConfig config;
+    config.set_id(12345);
+
+    AtomMatcher* eventMatcher = config.add_atom_matcher();
+    eventMatcher->set_id(StringToId("BATTERY_VERY_LOW"));
+    SimpleAtomMatcher* simpleAtomMatcher = eventMatcher->mutable_simple_atom_matcher();
+    simpleAtomMatcher->set_atom_id(2);
+
+    eventMatcher = config.add_atom_matcher();
+    eventMatcher->set_id(StringToId("BATTERY_VERY_VERY_LOW"));
+    simpleAtomMatcher = eventMatcher->mutable_simple_atom_matcher();
+    simpleAtomMatcher->set_atom_id(3);
+
+    eventMatcher = config.add_atom_matcher();
+    eventMatcher->set_id(StringToId("BATTERY_LOW"));
+
+    AtomMatcher_Combination* combination = eventMatcher->mutable_combination();
+    combination->set_operation(LogicalOperation::OR);
+    combination->add_matcher(StringToId("BATTERY_VERY_LOW"));
+    combination->add_matcher(StringToId("BATTERY_VERY_VERY_LOW"));
+
+    // Count process state changes, slice by uid, while SCREEN_IS_OFF
+    CountMetric* metric = config.add_count_metric();
+    metric->set_id(3);
+    metric->set_what(StringToId("BATTERY_LOW"));
+    metric->set_bucket(ONE_MINUTE);
+    // This case is interesting. We want to dimension across two atoms.
+    metric->mutable_dimensions_in_what()->add_child()->set_field(1);
+
+    auto alert = config.add_alert();
+    alert->set_id(kAlertId);
+    alert->set_metric_id(3);
+    alert->set_num_buckets(10);
+    alert->set_refractory_period_secs(100);
+    alert->set_trigger_if_sum_gt(100);
+    return config;
+}
+
+StatsdConfig buildCirclePredicates() {
+    StatsdConfig config;
+    config.set_id(12345);
+
+    AtomMatcher* eventMatcher = config.add_atom_matcher();
+    eventMatcher->set_id(StringToId("SCREEN_IS_ON"));
+
+    SimpleAtomMatcher* simpleAtomMatcher = eventMatcher->mutable_simple_atom_matcher();
+    simpleAtomMatcher->set_atom_id(2 /*SCREEN_STATE_CHANGE*/);
+    simpleAtomMatcher->add_field_value_matcher()->set_field(
+            1 /*SCREEN_STATE_CHANGE__DISPLAY_STATE*/);
+    simpleAtomMatcher->mutable_field_value_matcher(0)->set_eq_int(
+            2 /*SCREEN_STATE_CHANGE__DISPLAY_STATE__STATE_ON*/);
+
+    eventMatcher = config.add_atom_matcher();
+    eventMatcher->set_id(StringToId("SCREEN_IS_OFF"));
+
+    simpleAtomMatcher = eventMatcher->mutable_simple_atom_matcher();
+    simpleAtomMatcher->set_atom_id(2 /*SCREEN_STATE_CHANGE*/);
+    simpleAtomMatcher->add_field_value_matcher()->set_field(
+            1 /*SCREEN_STATE_CHANGE__DISPLAY_STATE*/);
+    simpleAtomMatcher->mutable_field_value_matcher(0)->set_eq_int(
+            1 /*SCREEN_STATE_CHANGE__DISPLAY_STATE__STATE_OFF*/);
+
+    auto condition = config.add_predicate();
+    condition->set_id(StringToId("SCREEN_IS_ON"));
+    SimplePredicate* simplePredicate = condition->mutable_simple_predicate();
+    simplePredicate->set_start(StringToId("SCREEN_IS_ON"));
+    simplePredicate->set_stop(StringToId("SCREEN_IS_OFF"));
+
+    condition = config.add_predicate();
+    condition->set_id(StringToId("SCREEN_IS_EITHER_ON_OFF"));
+
+    Predicate_Combination* combination = condition->mutable_combination();
+    combination->set_operation(LogicalOperation::OR);
+    combination->add_predicate(StringToId("SCREEN_IS_ON"));
+    combination->add_predicate(StringToId("SCREEN_IS_EITHER_ON_OFF"));
+
+    return config;
+}
+
+StatsdConfig buildConfigWithDifferentPredicates() {
+    StatsdConfig config;
+    config.set_id(12345);
+
+    auto pulledAtomMatcher =
+            CreateSimpleAtomMatcher("SUBSYSTEM_SLEEP", util::SUBSYSTEM_SLEEP_STATE);
+    *config.add_atom_matcher() = pulledAtomMatcher;
+    auto screenOnAtomMatcher = CreateScreenTurnedOnAtomMatcher();
+    *config.add_atom_matcher() = screenOnAtomMatcher;
+    auto screenOffAtomMatcher = CreateScreenTurnedOffAtomMatcher();
+    *config.add_atom_matcher() = screenOffAtomMatcher;
+    auto batteryNoneAtomMatcher = CreateBatteryStateNoneMatcher();
+    *config.add_atom_matcher() = batteryNoneAtomMatcher;
+    auto batteryUsbAtomMatcher = CreateBatteryStateUsbMatcher();
+    *config.add_atom_matcher() = batteryUsbAtomMatcher;
+
+    // Simple condition with InitialValue set to default (unknown).
+    auto screenOnUnknownPredicate = CreateScreenIsOnPredicate();
+    *config.add_predicate() = screenOnUnknownPredicate;
+
+    // Simple condition with InitialValue set to false.
+    auto screenOnFalsePredicate = config.add_predicate();
+    screenOnFalsePredicate->set_id(StringToId("ScreenIsOnInitialFalse"));
+    SimplePredicate* simpleScreenOnFalsePredicate =
+            screenOnFalsePredicate->mutable_simple_predicate();
+    simpleScreenOnFalsePredicate->set_start(screenOnAtomMatcher.id());
+    simpleScreenOnFalsePredicate->set_stop(screenOffAtomMatcher.id());
+    simpleScreenOnFalsePredicate->set_initial_value(SimplePredicate_InitialValue_FALSE);
+
+    // Simple condition with InitialValue set to false.
+    auto onBatteryFalsePredicate = config.add_predicate();
+    onBatteryFalsePredicate->set_id(StringToId("OnBatteryInitialFalse"));
+    SimplePredicate* simpleOnBatteryFalsePredicate =
+            onBatteryFalsePredicate->mutable_simple_predicate();
+    simpleOnBatteryFalsePredicate->set_start(batteryNoneAtomMatcher.id());
+    simpleOnBatteryFalsePredicate->set_stop(batteryUsbAtomMatcher.id());
+    simpleOnBatteryFalsePredicate->set_initial_value(SimplePredicate_InitialValue_FALSE);
+
+    // Combination condition with both simple condition InitialValues set to false.
+    auto screenOnFalseOnBatteryFalsePredicate = config.add_predicate();
+    screenOnFalseOnBatteryFalsePredicate->set_id(StringToId("ScreenOnFalseOnBatteryFalse"));
+    screenOnFalseOnBatteryFalsePredicate->mutable_combination()->set_operation(
+            LogicalOperation::AND);
+    addPredicateToPredicateCombination(*screenOnFalsePredicate,
+                                       screenOnFalseOnBatteryFalsePredicate);
+    addPredicateToPredicateCombination(*onBatteryFalsePredicate,
+                                       screenOnFalseOnBatteryFalsePredicate);
+
+    // Combination condition with one simple condition InitialValue set to unknown and one set to
+    // false.
+    auto screenOnUnknownOnBatteryFalsePredicate = config.add_predicate();
+    screenOnUnknownOnBatteryFalsePredicate->set_id(StringToId("ScreenOnUnknowneOnBatteryFalse"));
+    screenOnUnknownOnBatteryFalsePredicate->mutable_combination()->set_operation(
+            LogicalOperation::AND);
+    addPredicateToPredicateCombination(screenOnUnknownPredicate,
+                                       screenOnUnknownOnBatteryFalsePredicate);
+    addPredicateToPredicateCombination(*onBatteryFalsePredicate,
+                                       screenOnUnknownOnBatteryFalsePredicate);
+
+    // Simple condition metric with initial value false.
+    ValueMetric* metric1 = config.add_value_metric();
+    metric1->set_id(StringToId("ValueSubsystemSleepWhileScreenOnInitialFalse"));
+    metric1->set_what(pulledAtomMatcher.id());
+    *metric1->mutable_value_field() =
+            CreateDimensions(util::SUBSYSTEM_SLEEP_STATE, {4 /* time sleeping field */});
+    metric1->set_bucket(FIVE_MINUTES);
+    metric1->set_condition(screenOnFalsePredicate->id());
+
+    // Simple condition metric with initial value unknown.
+    ValueMetric* metric2 = config.add_value_metric();
+    metric2->set_id(StringToId("ValueSubsystemSleepWhileScreenOnInitialUnknown"));
+    metric2->set_what(pulledAtomMatcher.id());
+    *metric2->mutable_value_field() =
+            CreateDimensions(util::SUBSYSTEM_SLEEP_STATE, {4 /* time sleeping field */});
+    metric2->set_bucket(FIVE_MINUTES);
+    metric2->set_condition(screenOnUnknownPredicate.id());
+
+    // Combination condition metric with initial values false and false.
+    ValueMetric* metric3 = config.add_value_metric();
+    metric3->set_id(StringToId("ValueSubsystemSleepWhileScreenOnFalseDeviceUnpluggedFalse"));
+    metric3->set_what(pulledAtomMatcher.id());
+    *metric3->mutable_value_field() =
+            CreateDimensions(util::SUBSYSTEM_SLEEP_STATE, {4 /* time sleeping field */});
+    metric3->set_bucket(FIVE_MINUTES);
+    metric3->set_condition(screenOnFalseOnBatteryFalsePredicate->id());
+
+    // Combination condition metric with initial values unknown and false.
+    ValueMetric* metric4 = config.add_value_metric();
+    metric4->set_id(StringToId("ValueSubsystemSleepWhileScreenOnUnknownDeviceUnpluggedFalse"));
+    metric4->set_what(pulledAtomMatcher.id());
+    *metric4->mutable_value_field() =
+            CreateDimensions(util::SUBSYSTEM_SLEEP_STATE, {4 /* time sleeping field */});
+    metric4->set_bucket(FIVE_MINUTES);
+    metric4->set_condition(screenOnUnknownOnBatteryFalsePredicate->id());
+
+    return config;
+}
+}  // anonymous namespace
+
+TEST(MetricsManagerTest, TestInitialConditions) {
+    sp<UidMap> uidMap = new UidMap();
+    sp<StatsPullerManager> pullerManager = new StatsPullerManager();
+    sp<AlarmMonitor> anomalyAlarmMonitor;
+    sp<AlarmMonitor> periodicAlarmMonitor;
+    StatsdConfig config = buildConfigWithDifferentPredicates();
+    set<int> allTagIds;
+    vector<sp<AtomMatchingTracker>> allAtomMatchingTrackers;
+    unordered_map<int64_t, int> logTrackerMap;
+    vector<sp<ConditionTracker>> allConditionTrackers;
+    vector<sp<MetricProducer>> allMetricProducers;
+    std::vector<sp<AnomalyTracker>> allAnomalyTrackers;
+    std::vector<sp<AlarmTracker>> allAlarmTrackers;
+    unordered_map<int, std::vector<int>> conditionToMetricMap;
+    unordered_map<int, std::vector<int>> trackerToMetricMap;
+    unordered_map<int, std::vector<int>> trackerToConditionMap;
+    unordered_map<int, std::vector<int>> activationAtomTrackerToMetricMap;
+    unordered_map<int, std::vector<int>> deactivationAtomTrackerToMetricMap;
+    unordered_map<int64_t, int> alertTrackerMap;
+    vector<int> metricsWithActivation;
+    std::set<int64_t> noReportMetricIds;
+
+    EXPECT_TRUE(initStatsdConfig(
+            kConfigKey, config, uidMap, pullerManager, anomalyAlarmMonitor, periodicAlarmMonitor,
+            timeBaseSec, timeBaseSec, allTagIds, allAtomMatchingTrackers, logTrackerMap,
+            allConditionTrackers, allMetricProducers, allAnomalyTrackers, allAlarmTrackers,
+            conditionToMetricMap, trackerToMetricMap, trackerToConditionMap,
+            activationAtomTrackerToMetricMap, deactivationAtomTrackerToMetricMap, alertTrackerMap,
+            metricsWithActivation, noReportMetricIds));
+    ASSERT_EQ(4u, allMetricProducers.size());
+    ASSERT_EQ(5u, allConditionTrackers.size());
+
+    ConditionKey queryKey;
+    vector<ConditionState> conditionCache(5, ConditionState::kNotEvaluated);
+
+    allConditionTrackers[3]->isConditionMet(queryKey, allConditionTrackers, false, conditionCache);
+    allConditionTrackers[4]->isConditionMet(queryKey, allConditionTrackers, false, conditionCache);
+    EXPECT_EQ(ConditionState::kUnknown, conditionCache[0]);
+    EXPECT_EQ(ConditionState::kFalse, conditionCache[1]);
+    EXPECT_EQ(ConditionState::kFalse, conditionCache[2]);
+    EXPECT_EQ(ConditionState::kFalse, conditionCache[3]);
+    EXPECT_EQ(ConditionState::kUnknown, conditionCache[4]);
+
+    EXPECT_EQ(ConditionState::kFalse, allMetricProducers[0]->mCondition);
+    EXPECT_EQ(ConditionState::kUnknown, allMetricProducers[1]->mCondition);
+    EXPECT_EQ(ConditionState::kFalse, allMetricProducers[2]->mCondition);
+    EXPECT_EQ(ConditionState::kUnknown, allMetricProducers[3]->mCondition);
+}
+
+TEST(MetricsManagerTest, TestGoodConfig) {
+    sp<UidMap> uidMap = new UidMap();
+    sp<StatsPullerManager> pullerManager = new StatsPullerManager();
+    sp<AlarmMonitor> anomalyAlarmMonitor;
+    sp<AlarmMonitor> periodicAlarmMonitor;
+    StatsdConfig config = buildGoodConfig();
+    set<int> allTagIds;
+    vector<sp<AtomMatchingTracker>> allAtomMatchingTrackers;
+    unordered_map<int64_t, int> logTrackerMap;
+    vector<sp<ConditionTracker>> allConditionTrackers;
+    vector<sp<MetricProducer>> allMetricProducers;
+    std::vector<sp<AnomalyTracker>> allAnomalyTrackers;
+    std::vector<sp<AlarmTracker>> allAlarmTrackers;
+    unordered_map<int, std::vector<int>> conditionToMetricMap;
+    unordered_map<int, std::vector<int>> trackerToMetricMap;
+    unordered_map<int, std::vector<int>> trackerToConditionMap;
+    unordered_map<int, std::vector<int>> activationAtomTrackerToMetricMap;
+    unordered_map<int, std::vector<int>> deactivationAtomTrackerToMetricMap;
+    unordered_map<int64_t, int> alertTrackerMap;
+    vector<int> metricsWithActivation;
+    std::set<int64_t> noReportMetricIds;
+
+    EXPECT_TRUE(initStatsdConfig(
+            kConfigKey, config, uidMap, pullerManager, anomalyAlarmMonitor, periodicAlarmMonitor,
+            timeBaseSec, timeBaseSec, allTagIds, allAtomMatchingTrackers, logTrackerMap,
+            allConditionTrackers, allMetricProducers, allAnomalyTrackers, allAlarmTrackers,
+            conditionToMetricMap, trackerToMetricMap, trackerToConditionMap,
+            activationAtomTrackerToMetricMap, deactivationAtomTrackerToMetricMap, alertTrackerMap,
+            metricsWithActivation, noReportMetricIds));
+    ASSERT_EQ(1u, allMetricProducers.size());
+    ASSERT_EQ(1u, allAnomalyTrackers.size());
+    ASSERT_EQ(1u, noReportMetricIds.size());
+    ASSERT_EQ(1u, alertTrackerMap.size());
+    EXPECT_NE(alertTrackerMap.find(kAlertId), alertTrackerMap.end());
+    EXPECT_EQ(alertTrackerMap.find(kAlertId)->second, 0);
+}
+
+TEST(MetricsManagerTest, TestDimensionMetricsWithMultiTags) {
+    sp<UidMap> uidMap = new UidMap();
+    sp<StatsPullerManager> pullerManager = new StatsPullerManager();
+    sp<AlarmMonitor> anomalyAlarmMonitor;
+    sp<AlarmMonitor> periodicAlarmMonitor;
+    StatsdConfig config = buildDimensionMetricsWithMultiTags();
+    set<int> allTagIds;
+    vector<sp<AtomMatchingTracker>> allAtomMatchingTrackers;
+    unordered_map<int64_t, int> logTrackerMap;
+    vector<sp<ConditionTracker>> allConditionTrackers;
+    vector<sp<MetricProducer>> allMetricProducers;
+    std::vector<sp<AnomalyTracker>> allAnomalyTrackers;
+    std::vector<sp<AlarmTracker>> allAlarmTrackers;
+    unordered_map<int, std::vector<int>> conditionToMetricMap;
+    unordered_map<int, std::vector<int>> trackerToMetricMap;
+    unordered_map<int, std::vector<int>> trackerToConditionMap;
+    unordered_map<int, std::vector<int>> activationAtomTrackerToMetricMap;
+    unordered_map<int, std::vector<int>> deactivationAtomTrackerToMetricMap;
+    unordered_map<int64_t, int> alertTrackerMap;
+    vector<int> metricsWithActivation;
+    std::set<int64_t> noReportMetricIds;
+
+    EXPECT_FALSE(initStatsdConfig(
+            kConfigKey, config, uidMap, pullerManager, anomalyAlarmMonitor, periodicAlarmMonitor,
+            timeBaseSec, timeBaseSec, allTagIds, allAtomMatchingTrackers, logTrackerMap,
+            allConditionTrackers, allMetricProducers, allAnomalyTrackers, allAlarmTrackers,
+            conditionToMetricMap, trackerToMetricMap, trackerToConditionMap,
+            activationAtomTrackerToMetricMap, deactivationAtomTrackerToMetricMap, alertTrackerMap,
+            metricsWithActivation, noReportMetricIds));
+}
+
+TEST(MetricsManagerTest, TestCircleLogMatcherDependency) {
+    sp<UidMap> uidMap = new UidMap();
+    sp<StatsPullerManager> pullerManager = new StatsPullerManager();
+    sp<AlarmMonitor> anomalyAlarmMonitor;
+    sp<AlarmMonitor> periodicAlarmMonitor;
+    StatsdConfig config = buildCircleMatchers();
+    set<int> allTagIds;
+    vector<sp<AtomMatchingTracker>> allAtomMatchingTrackers;
+    unordered_map<int64_t, int> logTrackerMap;
+    vector<sp<ConditionTracker>> allConditionTrackers;
+    vector<sp<MetricProducer>> allMetricProducers;
+    std::vector<sp<AnomalyTracker>> allAnomalyTrackers;
+    std::vector<sp<AlarmTracker>> allAlarmTrackers;
+    unordered_map<int, std::vector<int>> conditionToMetricMap;
+    unordered_map<int, std::vector<int>> trackerToMetricMap;
+    unordered_map<int, std::vector<int>> trackerToConditionMap;
+    unordered_map<int, std::vector<int>> activationAtomTrackerToMetricMap;
+    unordered_map<int, std::vector<int>> deactivationAtomTrackerToMetricMap;
+    unordered_map<int64_t, int> alertTrackerMap;
+    vector<int> metricsWithActivation;
+    std::set<int64_t> noReportMetricIds;
+
+    EXPECT_FALSE(initStatsdConfig(
+            kConfigKey, config, uidMap, pullerManager, anomalyAlarmMonitor, periodicAlarmMonitor,
+            timeBaseSec, timeBaseSec, allTagIds, allAtomMatchingTrackers, logTrackerMap,
+            allConditionTrackers, allMetricProducers, allAnomalyTrackers, allAlarmTrackers,
+            conditionToMetricMap, trackerToMetricMap, trackerToConditionMap,
+            activationAtomTrackerToMetricMap, deactivationAtomTrackerToMetricMap, alertTrackerMap,
+            metricsWithActivation, noReportMetricIds));
+}
+
+TEST(MetricsManagerTest, TestMissingMatchers) {
+    sp<UidMap> uidMap = new UidMap();
+    sp<StatsPullerManager> pullerManager = new StatsPullerManager();
+    sp<AlarmMonitor> anomalyAlarmMonitor;
+    sp<AlarmMonitor> periodicAlarmMonitor;
+    StatsdConfig config = buildMissingMatchers();
+    set<int> allTagIds;
+    vector<sp<AtomMatchingTracker>> allAtomMatchingTrackers;
+    unordered_map<int64_t, int> logTrackerMap;
+    vector<sp<ConditionTracker>> allConditionTrackers;
+    vector<sp<MetricProducer>> allMetricProducers;
+    std::vector<sp<AnomalyTracker>> allAnomalyTrackers;
+    std::vector<sp<AlarmTracker>> allAlarmTrackers;
+    unordered_map<int, std::vector<int>> conditionToMetricMap;
+    unordered_map<int, std::vector<int>> trackerToMetricMap;
+    unordered_map<int, std::vector<int>> trackerToConditionMap;
+    unordered_map<int, std::vector<int>> activationAtomTrackerToMetricMap;
+    unordered_map<int, std::vector<int>> deactivationAtomTrackerToMetricMap;
+    unordered_map<int64_t, int> alertTrackerMap;
+    vector<int> metricsWithActivation;
+    std::set<int64_t> noReportMetricIds;
+    EXPECT_FALSE(initStatsdConfig(
+            kConfigKey, config, uidMap, pullerManager, anomalyAlarmMonitor, periodicAlarmMonitor,
+            timeBaseSec, timeBaseSec, allTagIds, allAtomMatchingTrackers, logTrackerMap,
+            allConditionTrackers, allMetricProducers, allAnomalyTrackers, allAlarmTrackers,
+            conditionToMetricMap, trackerToMetricMap, trackerToConditionMap,
+            activationAtomTrackerToMetricMap, deactivationAtomTrackerToMetricMap, alertTrackerMap,
+            metricsWithActivation, noReportMetricIds));
+}
+
+TEST(MetricsManagerTest, TestMissingPredicate) {
+    sp<UidMap> uidMap = new UidMap();
+    sp<StatsPullerManager> pullerManager = new StatsPullerManager();
+    sp<AlarmMonitor> anomalyAlarmMonitor;
+    sp<AlarmMonitor> periodicAlarmMonitor;
+    StatsdConfig config = buildMissingPredicate();
+    set<int> allTagIds;
+    vector<sp<AtomMatchingTracker>> allAtomMatchingTrackers;
+    unordered_map<int64_t, int> logTrackerMap;
+    vector<sp<ConditionTracker>> allConditionTrackers;
+    vector<sp<MetricProducer>> allMetricProducers;
+    std::vector<sp<AnomalyTracker>> allAnomalyTrackers;
+    std::vector<sp<AlarmTracker>> allAlarmTrackers;
+    unordered_map<int, std::vector<int>> conditionToMetricMap;
+    unordered_map<int, std::vector<int>> trackerToMetricMap;
+    unordered_map<int, std::vector<int>> trackerToConditionMap;
+    unordered_map<int, std::vector<int>> activationAtomTrackerToMetricMap;
+    unordered_map<int, std::vector<int>> deactivationAtomTrackerToMetricMap;
+    unordered_map<int64_t, int> alertTrackerMap;
+    vector<int> metricsWithActivation;
+    std::set<int64_t> noReportMetricIds;
+    EXPECT_FALSE(initStatsdConfig(
+            kConfigKey, config, uidMap, pullerManager, anomalyAlarmMonitor, periodicAlarmMonitor,
+            timeBaseSec, timeBaseSec, allTagIds, allAtomMatchingTrackers, logTrackerMap,
+            allConditionTrackers, allMetricProducers, allAnomalyTrackers, allAlarmTrackers,
+            conditionToMetricMap, trackerToMetricMap, trackerToConditionMap,
+            activationAtomTrackerToMetricMap, deactivationAtomTrackerToMetricMap, alertTrackerMap,
+            metricsWithActivation, noReportMetricIds));
+}
+
+TEST(MetricsManagerTest, TestCirclePredicateDependency) {
+    sp<UidMap> uidMap = new UidMap();
+    sp<StatsPullerManager> pullerManager = new StatsPullerManager();
+    sp<AlarmMonitor> anomalyAlarmMonitor;
+    sp<AlarmMonitor> periodicAlarmMonitor;
+    StatsdConfig config = buildCirclePredicates();
+    set<int> allTagIds;
+    vector<sp<AtomMatchingTracker>> allAtomMatchingTrackers;
+    unordered_map<int64_t, int> logTrackerMap;
+    vector<sp<ConditionTracker>> allConditionTrackers;
+    vector<sp<MetricProducer>> allMetricProducers;
+    std::vector<sp<AnomalyTracker>> allAnomalyTrackers;
+    std::vector<sp<AlarmTracker>> allAlarmTrackers;
+    unordered_map<int, std::vector<int>> conditionToMetricMap;
+    unordered_map<int, std::vector<int>> trackerToMetricMap;
+    unordered_map<int, std::vector<int>> trackerToConditionMap;
+    unordered_map<int, std::vector<int>> activationAtomTrackerToMetricMap;
+    unordered_map<int, std::vector<int>> deactivationAtomTrackerToMetricMap;
+    unordered_map<int64_t, int> alertTrackerMap;
+    vector<int> metricsWithActivation;
+    std::set<int64_t> noReportMetricIds;
+
+    EXPECT_FALSE(initStatsdConfig(
+            kConfigKey, config, uidMap, pullerManager, anomalyAlarmMonitor, periodicAlarmMonitor,
+            timeBaseSec, timeBaseSec, allTagIds, allAtomMatchingTrackers, logTrackerMap,
+            allConditionTrackers, allMetricProducers, allAnomalyTrackers, allAlarmTrackers,
+            conditionToMetricMap, trackerToMetricMap, trackerToConditionMap,
+            activationAtomTrackerToMetricMap, deactivationAtomTrackerToMetricMap, alertTrackerMap,
+            metricsWithActivation, noReportMetricIds));
+}
+
+TEST(MetricsManagerTest, testAlertWithUnknownMetric) {
+    sp<UidMap> uidMap = new UidMap();
+    sp<StatsPullerManager> pullerManager = new StatsPullerManager();
+    sp<AlarmMonitor> anomalyAlarmMonitor;
+    sp<AlarmMonitor> periodicAlarmMonitor;
+    StatsdConfig config = buildAlertWithUnknownMetric();
+    set<int> allTagIds;
+    vector<sp<AtomMatchingTracker>> allAtomMatchingTrackers;
+    unordered_map<int64_t, int> logTrackerMap;
+    vector<sp<ConditionTracker>> allConditionTrackers;
+    vector<sp<MetricProducer>> allMetricProducers;
+    std::vector<sp<AnomalyTracker>> allAnomalyTrackers;
+    std::vector<sp<AlarmTracker>> allAlarmTrackers;
+    unordered_map<int, std::vector<int>> conditionToMetricMap;
+    unordered_map<int, std::vector<int>> trackerToMetricMap;
+    unordered_map<int, std::vector<int>> trackerToConditionMap;
+    unordered_map<int, std::vector<int>> activationAtomTrackerToMetricMap;
+    unordered_map<int, std::vector<int>> deactivationAtomTrackerToMetricMap;
+    unordered_map<int64_t, int> alertTrackerMap;
+    vector<int> metricsWithActivation;
+    std::set<int64_t> noReportMetricIds;
+
+    EXPECT_FALSE(initStatsdConfig(
+            kConfigKey, config, uidMap, pullerManager, anomalyAlarmMonitor, periodicAlarmMonitor,
+            timeBaseSec, timeBaseSec, allTagIds, allAtomMatchingTrackers, logTrackerMap,
+            allConditionTrackers, allMetricProducers, allAnomalyTrackers, allAlarmTrackers,
+            conditionToMetricMap, trackerToMetricMap, trackerToConditionMap,
+            activationAtomTrackerToMetricMap, deactivationAtomTrackerToMetricMap, alertTrackerMap,
+            metricsWithActivation, noReportMetricIds));
+}
+
+TEST(MetricsManagerTest, TestCreateAtomMatchingTrackerInvalidMatcher) {
+    sp<UidMap> uidMap = new UidMap();
+    AtomMatcher matcher;
+    matcher.set_id(21);
+    EXPECT_EQ(createAtomMatchingTracker(matcher, 0, uidMap), nullptr);
+}
+
+TEST(MetricsManagerTest, TestCreateAtomMatchingTrackerSimple) {
+    int index = 1;
+    int64_t id = 123;
+    sp<UidMap> uidMap = new UidMap();
+    AtomMatcher matcher;
+    matcher.set_id(id);
+    SimpleAtomMatcher* simpleAtomMatcher = matcher.mutable_simple_atom_matcher();
+    simpleAtomMatcher->set_atom_id(util::SCREEN_STATE_CHANGED);
+    simpleAtomMatcher->add_field_value_matcher()->set_field(
+            1 /*SCREEN_STATE_CHANGE__DISPLAY_STATE*/);
+    simpleAtomMatcher->mutable_field_value_matcher(0)->set_eq_int(
+            android::view::DisplayStateEnum::DISPLAY_STATE_ON);
+
+    sp<AtomMatchingTracker> tracker = createAtomMatchingTracker(matcher, index, uidMap);
+    EXPECT_NE(tracker, nullptr);
+
+    EXPECT_TRUE(tracker->mInitialized);
+    EXPECT_EQ(tracker->getId(), id);
+    EXPECT_EQ(tracker->mIndex, index);
+    const set<int>& atomIds = tracker->getAtomIds();
+    ASSERT_EQ(atomIds.size(), 1);
+    EXPECT_EQ(atomIds.count(util::SCREEN_STATE_CHANGED), 1);
+}
+
+TEST(MetricsManagerTest, TestCreateAtomMatchingTrackerCombination) {
+    int index = 1;
+    int64_t id = 123;
+    sp<UidMap> uidMap = new UidMap();
+    AtomMatcher matcher;
+    matcher.set_id(id);
+    AtomMatcher_Combination* combination = matcher.mutable_combination();
+    combination->set_operation(LogicalOperation::OR);
+    combination->add_matcher(123);
+    combination->add_matcher(223);
+
+    sp<AtomMatchingTracker> tracker = createAtomMatchingTracker(matcher, index, uidMap);
+    EXPECT_NE(tracker, nullptr);
+
+    // Combination matchers need to be initialized first.
+    EXPECT_FALSE(tracker->mInitialized);
+    EXPECT_EQ(tracker->getId(), id);
+    EXPECT_EQ(tracker->mIndex, index);
+    const set<int>& atomIds = tracker->getAtomIds();
+    ASSERT_EQ(atomIds.size(), 0);
+}
+
+}  // namespace statsd
+}  // namespace os
+}  // namespace android
+
+#else
+GTEST_LOG_(INFO) << "This test does nothing.\n";
+#endif
diff --git a/cmds/statsd/tests/statsd_test_util.cpp b/cmds/statsd/tests/statsd_test_util.cpp
index cee8372..1761d5d 100644
--- a/cmds/statsd/tests/statsd_test_util.cpp
+++ b/cmds/statsd/tests/statsd_test_util.cpp
@@ -15,6 +15,8 @@
 #include "statsd_test_util.h"
 
 #include <aidl/android/util/StatsEventParcel.h>
+
+#include "matchers/SimpleAtomMatchingTracker.h"
 #include "stats_event.h"
 
 using aidl::android::util::StatsEventParcel;
@@ -996,6 +998,20 @@
     return static_cast<int64_t>(std::hash<std::string>()(str));
 }
 
+sp<EventMatcherWizard> createEventMatcherWizard(
+        int tagId, int matcherIndex, const vector<FieldValueMatcher>& fieldValueMatchers) {
+    sp<UidMap> uidMap = new UidMap();
+    SimpleAtomMatcher atomMatcher;
+    atomMatcher.set_atom_id(tagId);
+    for (const FieldValueMatcher& fvm : fieldValueMatchers) {
+        *atomMatcher.add_field_value_matcher() = fvm;
+    }
+    uint64_t matcherHash = 0x12345678;
+    int64_t matcherId = 678;
+    return new EventMatcherWizard({new SimpleAtomMatchingTracker(
+            matcherId, matcherIndex, matcherHash, atomMatcher, uidMap)});
+}
+
 void ValidateWakelockAttributionUidAndTagDimension(const DimensionsValue& value, const int atomId,
                                                    const int uid, const string& tag) {
     EXPECT_EQ(value.field(), atomId);
diff --git a/cmds/statsd/tests/statsd_test_util.h b/cmds/statsd/tests/statsd_test_util.h
index 3dcf4ec..1220019 100644
--- a/cmds/statsd/tests/statsd_test_util.h
+++ b/cmds/statsd/tests/statsd_test_util.h
@@ -25,6 +25,7 @@
 #include "src/StatsLogProcessor.h"
 #include "src/hash.h"
 #include "src/logd/LogEvent.h"
+#include "src/matchers/EventMatcherWizard.h"
 #include "src/packages/UidMap.h"
 #include "src/stats_log_util.h"
 #include "stats_event.h"
@@ -335,6 +336,9 @@
 
 int64_t StringToId(const string& str);
 
+sp<EventMatcherWizard> createEventMatcherWizard(
+        int tagId, int matcherIndex, const std::vector<FieldValueMatcher>& fieldValueMatchers = {});
+
 void ValidateWakelockAttributionUidAndTagDimension(const DimensionsValue& value, const int atomId,
                                                    const int uid, const string& tag);
 void ValidateUidDimension(const DimensionsValue& value, int node_idx, int atomId, int uid);
diff --git a/cmds/uiautomator/library/Android.bp b/cmds/uiautomator/library/Android.bp
index 04b00cb..14b74da 100644
--- a/cmds/uiautomator/library/Android.bp
+++ b/cmds/uiautomator/library/Android.bp
@@ -54,7 +54,6 @@
     ],
     installable: false,
     custom_template: "droiddoc-templates-sdk",
-    create_stubs: false,
 }
 
 java_library_static {
@@ -66,6 +65,7 @@
         "android.test.runner",
         "junit",
     ],
+    java_version: "1.8",
 }
 
 java_library_static {
diff --git a/cmds/uinput/Android.bp b/cmds/uinput/Android.bp
new file mode 100644
index 0000000..0d7fed2
--- /dev/null
+++ b/cmds/uinput/Android.bp
@@ -0,0 +1,18 @@
+// Copyright 2020 The Android Open Source Project
+//
+
+java_binary {
+    name: "uinput",
+    wrapper: "uinput",
+    srcs: ["**/*.java",
+           ":uinputcommand_aidl"
+    ],
+    required: ["libuinputcommand_jni"],
+}
+
+filegroup {
+    name: "uinputcommand_aidl",
+    srcs: [
+        "src/com/android/commands/uinput/InputAbsInfo.aidl",
+    ],
+}
\ No newline at end of file
diff --git a/cmds/uinput/MODULE_LICENSE_APACHE2 b/cmds/uinput/MODULE_LICENSE_APACHE2
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/cmds/uinput/MODULE_LICENSE_APACHE2
diff --git a/cmds/uinput/NOTICE b/cmds/uinput/NOTICE
new file mode 100644
index 0000000..c5b1efa
--- /dev/null
+++ b/cmds/uinput/NOTICE
@@ -0,0 +1,190 @@
+
+   Copyright (c) 2005-2008, The Android Open Source Project
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+   you may not use this file except in compliance with the License.
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+
+
+                                 Apache License
+                           Version 2.0, January 2004
+                        http://www.apache.org/licenses/
+
+   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+   1. Definitions.
+
+      "License" shall mean the terms and conditions for use, reproduction,
+      and distribution as defined by Sections 1 through 9 of this document.
+
+      "Licensor" shall mean the copyright owner or entity authorized by
+      the copyright owner that is granting the License.
+
+      "Legal Entity" shall mean the union of the acting entity and all
+      other entities that control, are controlled by, or are under common
+      control with that entity. For the purposes of this definition,
+      "control" means (i) the power, direct or indirect, to cause the
+      direction or management of such entity, whether by contract or
+      otherwise, or (ii) ownership of fifty percent (50%) or more of the
+      outstanding shares, or (iii) beneficial ownership of such entity.
+
+      "You" (or "Your") shall mean an individual or Legal Entity
+      exercising permissions granted by this License.
+
+      "Source" form shall mean the preferred form for making modifications,
+      including but not limited to software source code, documentation
+      source, and configuration files.
+
+      "Object" form shall mean any form resulting from mechanical
+      transformation or translation of a Source form, including but
+      not limited to compiled object code, generated documentation,
+      and conversions to other media types.
+
+      "Work" shall mean the work of authorship, whether in Source or
+      Object form, made available under the License, as indicated by a
+      copyright notice that is included in or attached to the work
+      (an example is provided in the Appendix below).
+
+      "Derivative Works" shall mean any work, whether in Source or Object
+      form, that is based on (or derived from) the Work and for which the
+      editorial revisions, annotations, elaborations, or other modifications
+      represent, as a whole, an original work of authorship. For the purposes
+      of this License, Derivative Works shall not include works that remain
+      separable from, or merely link (or bind by name) to the interfaces of,
+      the Work and Derivative Works thereof.
+
+      "Contribution" shall mean any work of authorship, including
+      the original version of the Work and any modifications or additions
+      to that Work or Derivative Works thereof, that is intentionally
+      submitted to Licensor for inclusion in the Work by the copyright owner
+      or by an individual or Legal Entity authorized to submit on behalf of
+      the copyright owner. For the purposes of this definition, "submitted"
+      means any form of electronic, verbal, or written communication sent
+      to the Licensor or its representatives, including but not limited to
+      communication on electronic mailing lists, source code control systems,
+      and issue tracking systems that are managed by, or on behalf of, the
+      Licensor for the purpose of discussing and improving the Work, but
+      excluding communication that is conspicuously marked or otherwise
+      designated in writing by the copyright owner as "Not a Contribution."
+
+      "Contributor" shall mean Licensor and any individual or Legal Entity
+      on behalf of whom a Contribution has been received by Licensor and
+      subsequently incorporated within the Work.
+
+   2. Grant of Copyright License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      copyright license to reproduce, prepare Derivative Works of,
+      publicly display, publicly perform, sublicense, and distribute the
+      Work and such Derivative Works in Source or Object form.
+
+   3. Grant of Patent License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      (except as stated in this section) patent license to make, have made,
+      use, offer to sell, sell, import, and otherwise transfer the Work,
+      where such license applies only to those patent claims licensable
+      by such Contributor that are necessarily infringed by their
+      Contribution(s) alone or by combination of their Contribution(s)
+      with the Work to which such Contribution(s) was submitted. If You
+      institute patent litigation against any entity (including a
+      cross-claim or counterclaim in a lawsuit) alleging that the Work
+      or a Contribution incorporated within the Work constitutes direct
+      or contributory patent infringement, then any patent licenses
+      granted to You under this License for that Work shall terminate
+      as of the date such litigation is filed.
+
+   4. Redistribution. You may reproduce and distribute copies of the
+      Work or Derivative Works thereof in any medium, with or without
+      modifications, and in Source or Object form, provided that You
+      meet the following conditions:
+
+      (a) You must give any other recipients of the Work or
+          Derivative Works a copy of this License; and
+
+      (b) You must cause any modified files to carry prominent notices
+          stating that You changed the files; and
+
+      (c) You must retain, in the Source form of any Derivative Works
+          that You distribute, all copyright, patent, trademark, and
+          attribution notices from the Source form of the Work,
+          excluding those notices that do not pertain to any part of
+          the Derivative Works; and
+
+      (d) If the Work includes a "NOTICE" text file as part of its
+          distribution, then any Derivative Works that You distribute must
+          include a readable copy of the attribution notices contained
+          within such NOTICE file, excluding those notices that do not
+          pertain to any part of the Derivative Works, in at least one
+          of the following places: within a NOTICE text file distributed
+          as part of the Derivative Works; within the Source form or
+          documentation, if provided along with the Derivative Works; or,
+          within a display generated by the Derivative Works, if and
+          wherever such third-party notices normally appear. The contents
+          of the NOTICE file are for informational purposes only and
+          do not modify the License. You may add Your own attribution
+          notices within Derivative Works that You distribute, alongside
+          or as an addendum to the NOTICE text from the Work, provided
+          that such additional attribution notices cannot be construed
+          as modifying the License.
+
+      You may add Your own copyright statement to Your modifications and
+      may provide additional or different license terms and conditions
+      for use, reproduction, or distribution of Your modifications, or
+      for any such Derivative Works as a whole, provided Your use,
+      reproduction, and distribution of the Work otherwise complies with
+      the conditions stated in this License.
+
+   5. Submission of Contributions. Unless You explicitly state otherwise,
+      any Contribution intentionally submitted for inclusion in the Work
+      by You to the Licensor shall be under the terms and conditions of
+      this License, without any additional terms or conditions.
+      Notwithstanding the above, nothing herein shall supersede or modify
+      the terms of any separate license agreement you may have executed
+      with Licensor regarding such Contributions.
+
+   6. Trademarks. This License does not grant permission to use the trade
+      names, trademarks, service marks, or product names of the Licensor,
+      except as required for reasonable and customary use in describing the
+      origin of the Work and reproducing the content of the NOTICE file.
+
+   7. Disclaimer of Warranty. Unless required by applicable law or
+      agreed to in writing, Licensor provides the Work (and each
+      Contributor provides its Contributions) on an "AS IS" BASIS,
+      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+      implied, including, without limitation, any warranties or conditions
+      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+      PARTICULAR PURPOSE. You are solely responsible for determining the
+      appropriateness of using or redistributing the Work and assume any
+      risks associated with Your exercise of permissions under this License.
+
+   8. Limitation of Liability. In no event and under no legal theory,
+      whether in tort (including negligence), contract, or otherwise,
+      unless required by applicable law (such as deliberate and grossly
+      negligent acts) or agreed to in writing, shall any Contributor be
+      liable to You for damages, including any direct, indirect, special,
+      incidental, or consequential damages of any character arising as a
+      result of this License or out of the use or inability to use the
+      Work (including but not limited to damages for loss of goodwill,
+      work stoppage, computer failure or malfunction, or any and all
+      other commercial damages or losses), even if such Contributor
+      has been advised of the possibility of such damages.
+
+   9. Accepting Warranty or Additional Liability. While redistributing
+      the Work or Derivative Works thereof, You may choose to offer,
+      and charge a fee for, acceptance of support, warranty, indemnity,
+      or other liability obligations and/or rights consistent with this
+      License. However, in accepting such obligations, You may act only
+      on Your own behalf and on Your sole responsibility, not on behalf
+      of any other Contributor, and only if You agree to indemnify,
+      defend, and hold each Contributor harmless for any liability
+      incurred by, or claims asserted against, such Contributor by reason
+      of your accepting any such warranty or additional liability.
+
+   END OF TERMS AND CONDITIONS
+
diff --git a/cmds/uinput/README.md b/cmds/uinput/README.md
new file mode 100644
index 0000000..47e1dad
--- /dev/null
+++ b/cmds/uinput/README.md
@@ -0,0 +1,166 @@
+# Usage
+##  Two options to use the uinput command:
+### 1. Interactive through stdin:
+type `uinput -` into the terminal, then type/paste commands to send to the binary.
+Use Ctrl+D to signal end of stream to the binary (EOF).
+
+This mode can be also used from an app to send uinput events.
+For an example, see the cts test case at: [InputTestCase.java][2]
+
+When using another program to control uinput in interactive mode, registering a
+new input device (for example, a bluetooth joystick) should be the first step.
+After the device is added, you need to wait for the _onInputDeviceAdded_
+(see [InputDeviceListener][1]) notification before issuing commands
+to the device.
+Failure to do so will cause missed events and inconsistent behavior.
+
+### 2. Using a file as an input:
+type `uinput <filename>`, and the file will be used an an input to the binary.
+You must add a sufficient delay after a "register" command to ensure device
+is ready. The interactive mode is the recommended method of communicating
+with the uinput binary.
+
+All of the input commands should be in pseudo-JSON format as documented below.
+See examples [here][3].
+
+The file can have multiple commands one after the other (which is not strictly
+legal JSON format, as this would imply multiple root elements).
+
+## Command description
+
+1. `register`
+Register a new uinput device
+
+| Field         | Type          | Description                |
+|:-------------:|:-------------:|:-------------------------- |
+| id            | integer       | Device id                  |
+| command       | string        | Must be set to "register"  |
+| name          | string        | Device name                |
+| vid           | 16-bit integer| Vendor id                  |
+| pid           | 16-bit integer| Product id                 |
+| bus           | string        | Bus that device should use |
+| configuration | int array     | uinput device configuration|
+| ff_effects_max| integer       | ff_effects_max value       |
+| abs_info      | array         | ABS axes information       |
+
+Device ID is used for matching the subsequent commands to a specific device
+to avoid ambiguity when multiple devices are registered.
+
+Device bus is used to determine how the uinput device is connected to the host.
+The options are "usb" and "bluetooth".
+
+Device configuration is used to configure uinput device.  "type" field provides the UI_SET_*
+control code, and data is a vector of control values to be sent to uinput device, depends on
+the control code.
+
+| Field         | Type          | Description                |
+|:-------------:|:-------------:|:-------------------------- |
+| type          | integer       | UI_SET_ control type       |
+| data          | int array     | control values             |
+
+Device ff_effects_max must be provided if FFBIT is set.
+
+Device abs_info fields are provided to set the device axes information. It is an array of below
+objects:
+| Field         | Type          | Description                |
+|:-------------:|:-------------:|:-------------------------- |
+| code          | integer       | Axis code                  |
+| info          | object        | ABS information object     |
+
+ABS information object is defined as below:
+| Field         | Type          | Description                |
+|:-------------:|:-------------:|:-------------------------- |
+| value         | integer       | Latest reported value      |
+| minimum       | integer       | Minimum value for the axis |
+| maximum       | integer       | Maximum value for the axis |
+| fuzz          | integer       | fuzz value for noise filter|
+| flat          | integer       | values to be discarded     |
+| resolution    | integer       | resolution of axis         |
+
+See [struct input_absinfo][4]) definitions.
+
+Example:
+```json
+
+{
+  "id": 1,
+  "command": "register",
+  "name": "Keyboard (Test)",
+  "vid": 0x18d2,
+  "pid": 0x2c42,
+  "bus": "usb",
+  "configuration":[
+        {"type":100, "data":[1, 21]},  // UI_SET_EVBIT : EV_KEY and EV_FF
+        {"type":101, "data":[11, 2, 3, 4]},   // UI_SET_KEYBIT : KEY_0 KEY_1 KEY_2 KEY_3
+        {"type":107, "data":[80]}    //  UI_SET_FFBIT : FF_RUMBLE
+  ],
+  "ff_effects_max" : 1,
+  "abs_info": [
+        {"code":1, "info": {"value":20, "minimum":-255,
+                            "maximum":255, "fuzz":0, "flat":0, "resolution":1}
+        },
+        {"code":8, "info": {"value":-50, "minimum":-255,
+                            "maximum":255, "fuzz":0, "flat":0, "resolution":1}
+        }
+  ]
+}
+
+```
+2. `delay`
+Add a delay to command processing
+
+| Field         | Type          | Description                |
+|:-------------:|:-------------:|:-------------------------- |
+| id            | integer       | Device id                  |
+| command       | string        | Must be set to "delay"     |
+| duration      | integer       | Delay in milliseconds      |
+
+Example:
+```json
+{
+  "id": 1,
+  "command": "delay",
+  "duration": 10
+}
+```
+
+3. `inject`
+Send an array of uinput event packets [type, code, value] to the uinput device
+
+| Field         | Type          | Description                |
+|:-------------:|:-------------:|:-------------------------- |
+| id            | integer       | Device id                  |
+| command       | string        | Must be set to "inject"    |
+| events        | integer array | events to inject           |
+
+The "events" parameter is an array of integers, encapsulates evdev input_event type, code and value,
+see the example below.
+
+Example:
+```json
+{
+  "id": 1,
+  "command": "inject",
+  "events": [0x01, 0xb,  0x1,   // EV_KEY, KEY_0, DOWN
+             0x00, 0x00, 0x00,  // EV_SYN, SYN_REPORT, 0
+             0x01, 0x0b, 0x00,  // EV_KEY, KEY_0, UP
+             0x00, 0x00, 0x00,  // EV_SYN, SYN_REPORT, 0
+             0x01, 0x2,  0x1,   // EV_KEY, KEY_1, DOWN
+             0x00, 0x00, 0x01,  // EV_SYN, SYN_REPORT, 0
+             0x01, 0x02, 0x00,  // EV_KEY, KEY_1, UP
+             0x00, 0x00, 0x01   // EV_SYN, SYN_REPORT, 0
+            ]
+}
+```
+
+### Notes
+1. As soon as EOF is reached (either in interactive mode, or in file mode),
+the device that was created will be unregistered. There is no
+explicit command for unregistering a device.
+2. The `getevent` utility can used to print out the key events
+for debugging purposes.
+
+[1]: https://developer.android.com/reference/android/hardware/input/InputManager.InputDeviceListener.html
+[2]: ../../../../cts/tests/tests/hardware/src/android/hardware/input/cts/tests/InputTestCase.java
+[3]: ../../../../cts/tests/tests/hardware/res/raw/
+[4]: ../../../../bionic/libc/kernel/uapi/linux/input.h
diff --git a/cmds/uinput/jni/Android.bp b/cmds/uinput/jni/Android.bp
new file mode 100644
index 0000000..199bbbd
--- /dev/null
+++ b/cmds/uinput/jni/Android.bp
@@ -0,0 +1,23 @@
+cc_library_shared {
+    name: "libuinputcommand_jni",
+
+    srcs: [
+        "com_android_commands_uinput_Device.cpp",
+        ":uinputcommand_aidl",
+    ],
+
+    shared_libs: [
+        "libandroid",
+        "libandroid_runtime_lazy",
+        "libbase",
+        "libbinder",
+        "liblog",
+        "libnativehelper",
+    ],
+
+    cflags: [
+        "-Wall",
+        "-Wextra",
+        "-Werror",
+    ],
+}
diff --git a/cmds/uinput/jni/com_android_commands_uinput_Device.cpp b/cmds/uinput/jni/com_android_commands_uinput_Device.cpp
new file mode 100644
index 0000000..06fa2aa
--- /dev/null
+++ b/cmds/uinput/jni/com_android_commands_uinput_Device.cpp
@@ -0,0 +1,351 @@
+/*
+ * 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.
+ */
+
+#define LOG_TAG "UinputCommandDevice"
+
+#include <linux/uinput.h>
+
+#include <fcntl.h>
+#include <inttypes.h>
+#include <time.h>
+#include <unistd.h>
+#include <algorithm>
+#include <array>
+#include <cstdio>
+#include <cstring>
+#include <iterator>
+#include <memory>
+#include <vector>
+
+#include <android/looper.h>
+#include <android_os_Parcel.h>
+#include <jni.h>
+#include <log/log.h>
+#include <nativehelper/JNIHelp.h>
+#include <nativehelper/ScopedLocalRef.h>
+#include <nativehelper/ScopedPrimitiveArray.h>
+#include <nativehelper/ScopedUtfChars.h>
+
+#include <android-base/stringprintf.h>
+
+#include "com_android_commands_uinput_Device.h"
+
+namespace android {
+namespace uinput {
+
+using src::com::android::commands::uinput::InputAbsInfo;
+
+static constexpr const char* UINPUT_PATH = "/dev/uinput";
+
+static struct {
+    jmethodID onDeviceConfigure;
+    jmethodID onDeviceVibrating;
+    jmethodID onDeviceError;
+} gDeviceCallbackClassInfo;
+
+static void checkAndClearException(JNIEnv* env, const char* methodName) {
+    if (env->ExceptionCheck()) {
+        ALOGE("An exception was thrown by callback '%s'.", methodName);
+        env->ExceptionClear();
+    }
+}
+
+DeviceCallback::DeviceCallback(JNIEnv* env, jobject callback)
+      : mCallbackObject(env->NewGlobalRef(callback)) {
+    env->GetJavaVM(&mJavaVM);
+}
+
+DeviceCallback::~DeviceCallback() {
+    JNIEnv* env = getJNIEnv();
+    env->DeleteGlobalRef(mCallbackObject);
+}
+
+void DeviceCallback::onDeviceError() {
+    JNIEnv* env = getJNIEnv();
+    env->CallVoidMethod(mCallbackObject, gDeviceCallbackClassInfo.onDeviceError);
+    checkAndClearException(env, "onDeviceError");
+}
+
+void DeviceCallback::onDeviceConfigure(int handle) {
+    JNIEnv* env = getJNIEnv();
+    env->CallVoidMethod(mCallbackObject, gDeviceCallbackClassInfo.onDeviceConfigure, handle);
+    checkAndClearException(env, "onDeviceConfigure");
+}
+
+void DeviceCallback::onDeviceVibrating(int value) {
+    JNIEnv* env = getJNIEnv();
+    env->CallVoidMethod(mCallbackObject, gDeviceCallbackClassInfo.onDeviceVibrating, value);
+    checkAndClearException(env, "onDeviceVibrating");
+}
+
+JNIEnv* DeviceCallback::getJNIEnv() {
+    JNIEnv* env;
+    mJavaVM->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION_1_6);
+    return env;
+}
+
+std::unique_ptr<UinputDevice> UinputDevice::open(int32_t id, const char* name, int32_t vid,
+                                                 int32_t pid, uint16_t bus, uint32_t ffEffectsMax,
+                                                 std::unique_ptr<DeviceCallback> callback) {
+    android::base::unique_fd fd(::open(UINPUT_PATH, O_RDWR | O_NONBLOCK | O_CLOEXEC));
+    if (!fd.ok()) {
+        ALOGE("Failed to open uinput: %s", strerror(errno));
+        return nullptr;
+    }
+
+    int32_t version;
+    ::ioctl(fd, UI_GET_VERSION, &version);
+    if (version < 5) {
+        ALOGE("Kernel version %d older than 5 is not supported", version);
+        return nullptr;
+    }
+
+    struct uinput_setup setupDescriptor;
+    memset(&setupDescriptor, 0, sizeof(setupDescriptor));
+    strlcpy(setupDescriptor.name, name, UINPUT_MAX_NAME_SIZE);
+    setupDescriptor.id.version = 1;
+    setupDescriptor.id.bustype = bus;
+    setupDescriptor.id.vendor = vid;
+    setupDescriptor.id.product = pid;
+    setupDescriptor.ff_effects_max = ffEffectsMax;
+
+    // Request device configuration.
+    callback->onDeviceConfigure(fd.get());
+
+    // register the input device
+    if (::ioctl(fd, UI_DEV_SETUP, &setupDescriptor)) {
+        ALOGE("UI_DEV_SETUP ioctl failed on fd %d: %s.", fd.get(), strerror(errno));
+        return nullptr;
+    }
+
+    if (::ioctl(fd, UI_DEV_CREATE) != 0) {
+        ALOGE("Unable to create uinput device: %s.", strerror(errno));
+        return nullptr;
+    }
+
+    // using 'new' to access non-public constructor
+    return std::unique_ptr<UinputDevice>(new UinputDevice(id, std::move(fd), std::move(callback)));
+}
+
+UinputDevice::UinputDevice(int32_t id, android::base::unique_fd fd,
+                           std::unique_ptr<DeviceCallback> callback)
+      : mId(id), mFd(std::move(fd)), mDeviceCallback(std::move(callback)) {
+    ALooper* aLooper = ALooper_forThread();
+    if (aLooper == nullptr) {
+        ALOGE("Could not get ALooper, ALooper_forThread returned NULL");
+        aLooper = ALooper_prepare(ALOOPER_PREPARE_ALLOW_NON_CALLBACKS);
+    }
+    ALooper_addFd(
+            aLooper, mFd, 0, ALOOPER_EVENT_INPUT,
+            [](int, int events, void* data) {
+                UinputDevice* d = reinterpret_cast<UinputDevice*>(data);
+                return d->handleEvents(events);
+            },
+            reinterpret_cast<void*>(this));
+    ALOGI("uinput device %d created: version = %d, fd = %d", mId, UINPUT_VERSION, mFd.get());
+}
+
+UinputDevice::~UinputDevice() {
+    ::ioctl(mFd, UI_DEV_DESTROY);
+}
+
+void UinputDevice::injectEvent(uint16_t type, uint16_t code, int32_t value) {
+    struct input_event event = {};
+    event.type = type;
+    event.code = code;
+    event.value = value;
+    timespec ts;
+    clock_gettime(CLOCK_MONOTONIC, &ts);
+    TIMESPEC_TO_TIMEVAL(&event.time, &ts);
+
+    if (::write(mFd, &event, sizeof(input_event)) < 0) {
+        ALOGE("Could not write event %" PRIu16 " %" PRIu16 " with value %" PRId32 " : %s", type,
+              code, value, strerror(errno));
+    }
+}
+
+int UinputDevice::handleEvents(int events) {
+    if (events & (ALOOPER_EVENT_ERROR | ALOOPER_EVENT_HANGUP)) {
+        ALOGE("uinput node was closed or an error occurred. events=0x%x", events);
+        mDeviceCallback->onDeviceError();
+        return 0;
+    }
+    struct input_event ev;
+    ssize_t ret = ::read(mFd, &ev, sizeof(ev));
+    if (ret < 0) {
+        ALOGE("Failed to read from uinput node: %s", strerror(errno));
+        mDeviceCallback->onDeviceError();
+        return 0;
+    }
+
+    switch (ev.type) {
+        case EV_UINPUT: {
+            if (ev.code == UI_FF_UPLOAD) {
+                struct uinput_ff_upload ff_upload;
+                ff_upload.request_id = ev.value;
+                ::ioctl(mFd, UI_BEGIN_FF_UPLOAD, &ff_upload);
+                ff_upload.retval = 0;
+                ::ioctl(mFd, UI_END_FF_UPLOAD, &ff_upload);
+            } else if (ev.code == UI_FF_ERASE) {
+                struct uinput_ff_erase ff_erase;
+                ff_erase.request_id = ev.value;
+                ::ioctl(mFd, UI_BEGIN_FF_ERASE, &ff_erase);
+                ff_erase.retval = 0;
+                ::ioctl(mFd, UI_END_FF_ERASE, &ff_erase);
+            }
+            break;
+        }
+        case EV_FF: {
+            ALOGI("EV_FF effect = %d value = %d", ev.code, ev.value);
+            mDeviceCallback->onDeviceVibrating(ev.value);
+            break;
+        }
+        default: {
+            ALOGI("Unhandled event type: %" PRIu32, ev.type);
+            break;
+        }
+    }
+
+    return 1;
+}
+
+} // namespace uinput
+
+std::vector<int32_t> toVector(JNIEnv* env, jintArray javaArray) {
+    std::vector<int32_t> data;
+    if (javaArray == nullptr) {
+        return data;
+    }
+
+    ScopedIntArrayRO scopedArray(env, javaArray);
+    size_t size = scopedArray.size();
+    data.reserve(size);
+    for (size_t i = 0; i < size; i++) {
+        data.push_back(static_cast<int32_t>(scopedArray[i]));
+    }
+    return data;
+}
+
+static jlong openUinputDevice(JNIEnv* env, jclass /* clazz */, jstring rawName, jint id, jint vid,
+                              jint pid, jint bus, jint ffEffectsMax, jobject callback) {
+    ScopedUtfChars name(env, rawName);
+    if (name.c_str() == nullptr) {
+        return 0;
+    }
+
+    std::unique_ptr<uinput::DeviceCallback> cb =
+            std::make_unique<uinput::DeviceCallback>(env, callback);
+
+    std::unique_ptr<uinput::UinputDevice> d =
+            uinput::UinputDevice::open(id, name.c_str(), vid, pid, bus, ffEffectsMax,
+                                       std::move(cb));
+    return reinterpret_cast<jlong>(d.release());
+}
+
+static void closeUinputDevice(JNIEnv* /* env */, jclass /* clazz */, jlong ptr) {
+    uinput::UinputDevice* d = reinterpret_cast<uinput::UinputDevice*>(ptr);
+    if (d != nullptr) {
+        delete d;
+    }
+}
+
+static void injectEvent(JNIEnv* /* env */, jclass /* clazz */, jlong ptr, jint type, jint code,
+                        jint value) {
+    uinput::UinputDevice* d = reinterpret_cast<uinput::UinputDevice*>(ptr);
+    if (d != nullptr) {
+        d->injectEvent(static_cast<uint16_t>(type), static_cast<uint16_t>(code),
+                       static_cast<int32_t>(value));
+    } else {
+        ALOGE("Could not inject event, Device* is null!");
+    }
+}
+
+static void configure(JNIEnv* env, jclass /* clazz */, jint handle, jint code,
+                      jintArray rawConfigs) {
+    std::vector<int32_t> configs = toVector(env, rawConfigs);
+    // Configure uinput device, with user specified code and value.
+    for (auto& config : configs) {
+        ::ioctl(static_cast<int>(handle), _IOW(UINPUT_IOCTL_BASE, code, int), config);
+    }
+}
+
+static void setAbsInfo(JNIEnv* env, jclass /* clazz */, jint handle, jint axisCode,
+                       jobject infoObj) {
+    Parcel* parcel = parcelForJavaObject(env, infoObj);
+    uinput::InputAbsInfo info;
+
+    info.readFromParcel(parcel);
+
+    struct uinput_abs_setup absSetup;
+    absSetup.code = axisCode;
+    absSetup.absinfo.maximum = info.maximum;
+    absSetup.absinfo.minimum = info.minimum;
+    absSetup.absinfo.value = info.value;
+    absSetup.absinfo.fuzz = info.fuzz;
+    absSetup.absinfo.flat = info.flat;
+    absSetup.absinfo.resolution = info.resolution;
+
+    ::ioctl(static_cast<int>(handle), UI_ABS_SETUP, &absSetup);
+}
+
+static JNINativeMethod sMethods[] = {
+        {"nativeOpenUinputDevice",
+         "(Ljava/lang/String;IIIII"
+         "Lcom/android/commands/uinput/Device$DeviceCallback;)J",
+         reinterpret_cast<void*>(openUinputDevice)},
+        {"nativeInjectEvent", "(JIII)V", reinterpret_cast<void*>(injectEvent)},
+        {"nativeConfigure", "(II[I)V", reinterpret_cast<void*>(configure)},
+        {"nativeSetAbsInfo", "(IILandroid/os/Parcel;)V", reinterpret_cast<void*>(setAbsInfo)},
+        {"nativeCloseUinputDevice", "(J)V", reinterpret_cast<void*>(closeUinputDevice)},
+};
+
+int register_com_android_commands_uinput_Device(JNIEnv* env) {
+    jclass clazz = env->FindClass("com/android/commands/uinput/Device$DeviceCallback");
+    if (clazz == nullptr) {
+        ALOGE("Unable to find class 'DeviceCallback'");
+        return JNI_ERR;
+    }
+
+    uinput::gDeviceCallbackClassInfo.onDeviceConfigure =
+            env->GetMethodID(clazz, "onDeviceConfigure", "(I)V");
+    uinput::gDeviceCallbackClassInfo.onDeviceVibrating =
+            env->GetMethodID(clazz, "onDeviceVibrating", "(I)V");
+    uinput::gDeviceCallbackClassInfo.onDeviceError =
+            env->GetMethodID(clazz, "onDeviceError", "()V");
+    if (uinput::gDeviceCallbackClassInfo.onDeviceConfigure == nullptr ||
+        uinput::gDeviceCallbackClassInfo.onDeviceError == nullptr ||
+        uinput::gDeviceCallbackClassInfo.onDeviceVibrating == nullptr) {
+        ALOGE("Unable to obtain onDeviceConfigure or onDeviceError or onDeviceVibrating methods");
+        return JNI_ERR;
+    }
+    return jniRegisterNativeMethods(env, "com/android/commands/uinput/Device", sMethods,
+                                    NELEM(sMethods));
+}
+
+} // namespace android
+
+jint JNI_OnLoad(JavaVM* jvm, void*) {
+    JNIEnv* env = nullptr;
+    if (jvm->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION_1_6)) {
+        return JNI_ERR;
+    }
+
+    if (android::register_com_android_commands_uinput_Device(env) < 0) {
+        return JNI_ERR;
+    }
+
+    return JNI_VERSION_1_6;
+}
diff --git a/cmds/uinput/jni/com_android_commands_uinput_Device.h b/cmds/uinput/jni/com_android_commands_uinput_Device.h
new file mode 100644
index 0000000..5a9a06c
--- /dev/null
+++ b/cmds/uinput/jni/com_android_commands_uinput_Device.h
@@ -0,0 +1,67 @@
+/*
+ * 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.
+ */
+
+#include <memory>
+#include <vector>
+
+#include <jni.h>
+#include <linux/input.h>
+
+#include <android-base/unique_fd.h>
+#include "src/com/android/commands/uinput/InputAbsInfo.h"
+
+namespace android {
+namespace uinput {
+
+class DeviceCallback {
+public:
+    DeviceCallback(JNIEnv* env, jobject callback);
+    ~DeviceCallback();
+
+    void onDeviceOpen();
+    void onDeviceGetReport(uint32_t requestId, uint8_t reportId);
+    void onDeviceOutput(const std::vector<uint8_t>& data);
+    void onDeviceConfigure(int handle);
+    void onDeviceVibrating(int value);
+    void onDeviceError();
+
+private:
+    JNIEnv* getJNIEnv();
+    jobject mCallbackObject;
+    JavaVM* mJavaVM;
+};
+
+class UinputDevice {
+public:
+    static std::unique_ptr<UinputDevice> open(int32_t id, const char* name, int32_t vid,
+                                              int32_t pid, uint16_t bus, uint32_t ff_effects_max,
+                                              std::unique_ptr<DeviceCallback> callback);
+
+    virtual ~UinputDevice();
+
+    void injectEvent(uint16_t type, uint16_t code, int32_t value);
+    int handleEvents(int events);
+
+private:
+    UinputDevice(int32_t id, android::base::unique_fd fd, std::unique_ptr<DeviceCallback> callback);
+
+    int32_t mId;
+    android::base::unique_fd mFd;
+    std::unique_ptr<DeviceCallback> mDeviceCallback;
+};
+
+} // namespace uinput
+} // namespace android
diff --git a/cmds/uinput/src/com/android/commands/uinput/Device.java b/cmds/uinput/src/com/android/commands/uinput/Device.java
new file mode 100644
index 0000000..62bee7b
--- /dev/null
+++ b/cmds/uinput/src/com/android/commands/uinput/Device.java
@@ -0,0 +1,232 @@
+/*
+ * 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.commands.uinput;
+
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.Looper;
+import android.os.Message;
+import android.os.Parcel;
+import android.os.SystemClock;
+import android.util.Log;
+import android.util.SparseArray;
+
+import com.android.internal.os.SomeArgs;
+
+import org.json.JSONException;
+import org.json.JSONObject;
+
+import java.io.IOException;
+import java.io.OutputStream;
+
+import src.com.android.commands.uinput.InputAbsInfo;
+
+/**
+ * Device class defines uinput device interfaces of device operations, for device open, close,
+ * configuration, events injection.
+ */
+public class Device {
+    private static final String TAG = "UinputDevice";
+
+    private static final int MSG_OPEN_UINPUT_DEVICE = 1;
+    private static final int MSG_CLOSE_UINPUT_DEVICE = 2;
+    private static final int MSG_INJECT_EVENT = 3;
+
+    private final int mId;
+    private final HandlerThread mThread;
+    private final DeviceHandler mHandler;
+    // mConfiguration is sparse array of ioctl code and array of values.
+    private final SparseArray<int[]> mConfiguration;
+    private final SparseArray<InputAbsInfo> mAbsInfo;
+    private final OutputStream mOutputStream;
+    private final Object mCond = new Object();
+    private long mTimeToSend;
+
+    static {
+        System.loadLibrary("uinputcommand_jni");
+    }
+
+    private static native long nativeOpenUinputDevice(String name, int id, int vid, int pid,
+            int bus, int ffEffectsMax, DeviceCallback callback);
+    private static native void nativeCloseUinputDevice(long ptr);
+    private static native void nativeInjectEvent(long ptr, int type, int code, int value);
+    private static native void nativeConfigure(int handle, int code, int[] configs);
+    private static native void nativeSetAbsInfo(int handle, int axisCode, Parcel axisParcel);
+
+    public Device(int id, String name, int vid, int pid, int bus,
+            SparseArray<int[]> configuration, int ffEffectsMax,
+            SparseArray<InputAbsInfo> absInfo) {
+        mId = id;
+        mThread = new HandlerThread("UinputDeviceHandler");
+        mThread.start();
+        mHandler = new DeviceHandler(mThread.getLooper());
+        mConfiguration = configuration;
+        mAbsInfo = absInfo;
+        mOutputStream = System.out;
+        SomeArgs args = SomeArgs.obtain();
+        args.argi1 = id;
+        args.argi2 = vid;
+        args.argi3 = pid;
+        args.argi4 = bus;
+        args.argi5 = ffEffectsMax;
+        if (name != null) {
+            args.arg1 = name;
+        } else {
+            args.arg1 = id + ":" + vid + ":" + pid;
+        }
+
+        mHandler.obtainMessage(MSG_OPEN_UINPUT_DEVICE, args).sendToTarget();
+        mTimeToSend = SystemClock.uptimeMillis();
+    }
+
+    /**
+     * Inject uinput events to device
+     *
+     * @param events  Array of raw uinput events.
+     */
+    public void injectEvent(int[] events) {
+        // if two messages are sent at identical time, they will be processed in order received
+        Message msg = mHandler.obtainMessage(MSG_INJECT_EVENT, events);
+        mHandler.sendMessageAtTime(msg, mTimeToSend);
+    }
+
+    /**
+     * Impose a delay to the device for execution.
+     *
+     * @param delay  Time to delay in unit of milliseconds.
+     */
+    public void addDelay(int delay) {
+        mTimeToSend = Math.max(SystemClock.uptimeMillis(), mTimeToSend) + delay;
+    }
+
+    /**
+     * Close an uinput device.
+     *
+     */
+    public void close() {
+        Message msg = mHandler.obtainMessage(MSG_CLOSE_UINPUT_DEVICE);
+        mHandler.sendMessageAtTime(msg, Math.max(SystemClock.uptimeMillis(), mTimeToSend) + 1);
+        try {
+            synchronized (mCond) {
+                mCond.wait();
+            }
+        } catch (InterruptedException ignore) {
+        }
+    }
+
+    private class DeviceHandler extends Handler {
+        private long mPtr;
+        private int mBarrierToken;
+
+        DeviceHandler(Looper looper) {
+            super(looper);
+        }
+
+        @Override
+        public void handleMessage(Message msg) {
+            switch (msg.what) {
+                case MSG_OPEN_UINPUT_DEVICE:
+                    SomeArgs args = (SomeArgs) msg.obj;
+                    mPtr = nativeOpenUinputDevice((String) args.arg1, args.argi1, args.argi2,
+                            args.argi3, args.argi4, args.argi5,
+                            new DeviceCallback());
+                    break;
+                case MSG_INJECT_EVENT:
+                    if (mPtr != 0) {
+                        int[] events = (int[]) msg.obj;
+                        for (int pos = 0; pos + 2 < events.length; pos += 3) {
+                            nativeInjectEvent(mPtr, events[pos], events[pos + 1], events[pos + 2]);
+                        }
+                    }
+                    break;
+                case MSG_CLOSE_UINPUT_DEVICE:
+                    if (mPtr != 0) {
+                        nativeCloseUinputDevice(mPtr);
+                        getLooper().quitSafely();
+                        mPtr = 0;
+                    } else {
+                        Log.e(TAG, "Tried to close already closed device.");
+                    }
+                    Log.i(TAG, "Device closed.");
+                    synchronized (mCond) {
+                        mCond.notify();
+                    }
+                    break;
+                default:
+                    throw new IllegalArgumentException("Unknown device message");
+            }
+        }
+
+        public void pauseEvents() {
+            mBarrierToken = getLooper().myQueue().postSyncBarrier();
+        }
+
+        public void resumeEvents() {
+            getLooper().myQueue().removeSyncBarrier(mBarrierToken);
+            mBarrierToken = 0;
+        }
+    }
+
+    private class DeviceCallback {
+        public void onDeviceOpen() {
+            mHandler.resumeEvents();
+        }
+
+        public void onDeviceConfigure(int handle) {
+            for (int i = 0; i < mConfiguration.size(); i++) {
+                int key = mConfiguration.keyAt(i);
+                int[] data = mConfiguration.get(key);
+                nativeConfigure(handle, key, data);
+            }
+
+            if (mAbsInfo != null) {
+                for (int i = 0; i < mAbsInfo.size(); i++) {
+                    int key = mAbsInfo.keyAt(i);
+                    InputAbsInfo info = mAbsInfo.get(key);
+                    Parcel parcel = Parcel.obtain();
+                    info.writeToParcel(parcel, 0);
+                    parcel.setDataPosition(0);
+                    nativeSetAbsInfo(handle, key, parcel);
+                }
+            }
+        }
+
+        public void onDeviceVibrating(int value) {
+            JSONObject json = new JSONObject();
+            try {
+                json.put("reason", "vibrating");
+                json.put("id", mId);
+                json.put("status", value);
+            } catch (JSONException e) {
+                throw new RuntimeException("Could not create JSON object ", e);
+            }
+            try {
+                mOutputStream.write(json.toString().getBytes());
+                mOutputStream.flush();
+            } catch (IOException e) {
+                throw new RuntimeException(e);
+            }
+        }
+
+        public void onDeviceError() {
+            Log.e(TAG, "Device error occurred, closing /dev/uinput");
+            Message msg = mHandler.obtainMessage(MSG_CLOSE_UINPUT_DEVICE);
+            msg.setAsynchronous(true);
+            msg.sendToTarget();
+        }
+    }
+}
diff --git a/cmds/uinput/src/com/android/commands/uinput/Event.java b/cmds/uinput/src/com/android/commands/uinput/Event.java
new file mode 100644
index 0000000..c4ba050
--- /dev/null
+++ b/cmds/uinput/src/com/android/commands/uinput/Event.java
@@ -0,0 +1,454 @@
+/*
+ * 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.commands.uinput;
+
+import android.util.JsonReader;
+import android.util.JsonToken;
+import android.util.Log;
+import android.util.SparseArray;
+
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.util.ArrayList;
+import java.util.Arrays;
+
+import src.com.android.commands.uinput.InputAbsInfo;
+
+/**
+ * An event is a JSON file defined action event to instruct uinput to perform a command like
+ * device registration or uinput events injection.
+ */
+public class Event {
+    private static final String TAG = "UinputEvent";
+
+    public static final String COMMAND_REGISTER = "register";
+    public static final String COMMAND_DELAY = "delay";
+    public static final String COMMAND_INJECT = "inject";
+    private static final int ABS_CNT = 64;
+
+    // These constants come from "include/uapi/linux/input.h" in the kernel
+    enum Bus {
+        USB(0x03), BLUETOOTH(0x05);
+        private final int mValue;
+
+        Bus(int value) {
+            mValue = value;
+        }
+
+        int getValue() {
+            return mValue;
+        }
+    }
+
+    private int mId;
+    private String mCommand;
+    private String mName;
+    private int mVid;
+    private int mPid;
+    private Bus mBus;
+    private int[] mInjections;
+    private SparseArray<int[]> mConfiguration;
+    private int mDuration;
+    private int mFfEffectsMax = 0;
+    private SparseArray<InputAbsInfo> mAbsInfo;
+
+    public int getId() {
+        return mId;
+    }
+
+    public String getCommand() {
+        return mCommand;
+    }
+
+    public String getName() {
+        return mName;
+    }
+
+    public int getVendorId() {
+        return mVid;
+    }
+
+    public int getProductId() {
+        return mPid;
+    }
+
+    public int getBus() {
+        return mBus.getValue();
+    }
+
+    public int[] getInjections() {
+        return mInjections;
+    }
+
+    public SparseArray<int[]> getConfiguration() {
+        return mConfiguration;
+    }
+
+    public int getDuration() {
+        return mDuration;
+    }
+
+    public int getFfEffectsMax() {
+        return mFfEffectsMax;
+    }
+
+    public SparseArray<InputAbsInfo>  getAbsInfo() {
+        return mAbsInfo;
+    }
+
+    /**
+     * Convert an event to String.
+     */
+    public String toString() {
+        return "Event{id=" + mId
+            + ", command=" + mCommand
+            + ", name=" + mName
+            + ", vid=" + mVid
+            + ", pid=" + mPid
+            + ", bus=" + mBus
+            + ", events=" + Arrays.toString(mInjections)
+            + ", configuration=" + mConfiguration
+            + ", duration=" + mDuration
+            + ", ff_effects_max=" + mFfEffectsMax
+            + "}";
+    }
+
+    private static class Builder {
+        private Event mEvent;
+
+        Builder() {
+            mEvent = new Event();
+        }
+
+        public void setId(int id) {
+            mEvent.mId = id;
+        }
+
+        private void setCommand(String command) {
+            mEvent.mCommand = command;
+        }
+
+        public void setName(String name) {
+            mEvent.mName = name;
+        }
+
+        public void setInjections(int[] events) {
+            mEvent.mInjections = events;
+        }
+
+        public void setConfiguration(SparseArray<int[]> configuration) {
+            mEvent.mConfiguration = configuration;
+        }
+
+        public void setVid(int vid) {
+            mEvent.mVid = vid;
+        }
+
+        public void setPid(int pid) {
+            mEvent.mPid = pid;
+        }
+
+        public void setBus(Bus bus) {
+            mEvent.mBus = bus;
+        }
+
+        public void setDuration(int duration) {
+            mEvent.mDuration = duration;
+        }
+
+        public void setFfEffectsMax(int ffEffectsMax) {
+            mEvent.mFfEffectsMax = ffEffectsMax;
+        }
+
+        public void setAbsInfo(SparseArray<InputAbsInfo> absInfo) {
+            mEvent.mAbsInfo = absInfo;
+        }
+
+        public Event build() {
+            if (mEvent.mId == -1) {
+                throw new IllegalStateException("No event id");
+            } else if (mEvent.mCommand == null) {
+                throw new IllegalStateException("Event does not contain a command");
+            }
+            if (COMMAND_REGISTER.equals(mEvent.mCommand)) {
+                if (mEvent.mConfiguration == null) {
+                    throw new IllegalStateException(
+                            "Device registration is missing configuration");
+                }
+            } else if (COMMAND_DELAY.equals(mEvent.mCommand)) {
+                if (mEvent.mDuration <= 0) {
+                    throw new IllegalStateException("Delay has missing or invalid duration");
+                }
+            } else if (COMMAND_INJECT.equals(mEvent.mCommand)) {
+                if (mEvent.mInjections  == null) {
+                    throw new IllegalStateException("Inject command is missing injection data");
+                }
+            } else {
+                throw new IllegalStateException("Unknown command " + mEvent.mCommand);
+            }
+            return mEvent;
+        }
+    }
+
+    /**
+     *  A class that parses the JSON event format from an input stream to build device events.
+     */
+    public static class Reader {
+        private JsonReader mReader;
+
+        public Reader(InputStreamReader in) {
+            mReader = new JsonReader(in);
+            mReader.setLenient(true);
+        }
+
+        /**
+         * Get next event entry from JSON file reader.
+         */
+        public Event getNextEvent() throws IOException {
+            Event e = null;
+            while (e == null && mReader.peek() != JsonToken.END_DOCUMENT) {
+                Event.Builder eb = new Event.Builder();
+                try {
+                    mReader.beginObject();
+                    while (mReader.hasNext()) {
+                        String name = mReader.nextName();
+                        switch (name) {
+                            case "id":
+                                eb.setId(readInt());
+                                break;
+                            case "command":
+                                eb.setCommand(mReader.nextString());
+                                break;
+                            case "name":
+                                eb.setName(mReader.nextString());
+                                break;
+                            case "vid":
+                                eb.setVid(readInt());
+                                break;
+                            case "pid":
+                                eb.setPid(readInt());
+                                break;
+                            case "bus":
+                                eb.setBus(readBus());
+                                break;
+                            case "events":
+                                int[] injections = readIntList().stream()
+                                            .mapToInt(Integer::intValue).toArray();
+                                eb.setInjections(injections);
+                                break;
+                            case "configuration":
+                                eb.setConfiguration(readConfiguration());
+                                break;
+                            case "ff_effects_max":
+                                eb.setFfEffectsMax(readInt());
+                                break;
+                            case "abs_info":
+                                eb.setAbsInfo(readAbsInfoArray());
+                                break;
+                            case "duration":
+                                eb.setDuration(readInt());
+                                break;
+                            default:
+                                mReader.skipValue();
+                        }
+                    }
+                    mReader.endObject();
+                } catch (IllegalStateException ex) {
+                    error("Error reading in object, ignoring.", ex);
+                    consumeRemainingElements();
+                    mReader.endObject();
+                    continue;
+                }
+                e = eb.build();
+            }
+
+            return e;
+        }
+
+        private ArrayList<Integer> readIntList() throws IOException {
+            ArrayList<Integer> data = new ArrayList<Integer>();
+            try {
+                mReader.beginArray();
+                while (mReader.hasNext()) {
+                    data.add(Integer.decode(mReader.nextString()));
+                }
+                mReader.endArray();
+            } catch (IllegalStateException | NumberFormatException e) {
+                consumeRemainingElements();
+                mReader.endArray();
+                throw new IllegalStateException("Encountered malformed data.", e);
+            }
+            return data;
+        }
+
+        private byte[] readData() throws IOException {
+            ArrayList<Integer> data = readIntList();
+            byte[] rawData = new byte[data.size()];
+            for (int i = 0; i < data.size(); i++) {
+                int d = data.get(i);
+                if ((d & 0xFF) != d) {
+                    throw new IllegalStateException("Invalid data, all values must be byte-sized");
+                }
+                rawData[i] = (byte) d;
+            }
+            return rawData;
+        }
+
+        private int readInt() throws IOException {
+            String val = mReader.nextString();
+            return Integer.decode(val);
+        }
+
+        private Bus readBus() throws IOException {
+            String val = mReader.nextString();
+            return Bus.valueOf(val.toUpperCase());
+        }
+
+        private SparseArray<int[]> readConfiguration()
+                throws IllegalStateException, IOException {
+            SparseArray<int[]> configuration = new SparseArray<>();
+            try {
+                mReader.beginArray();
+                while (mReader.hasNext()) {
+                    int type = 0;
+                    int[] data = null;
+                    mReader.beginObject();
+                    while (mReader.hasNext()) {
+                        String name = mReader.nextName();
+                        switch (name) {
+                            case "type":
+                                type = readInt();
+                                break;
+                            case "data":
+                                data = readIntList().stream()
+                                            .mapToInt(Integer::intValue).toArray();
+                                break;
+                            default:
+                                consumeRemainingElements();
+                                mReader.endObject();
+                                throw new IllegalStateException(
+                                        "Invalid key in device configuration: " + name);
+                        }
+                    }
+                    mReader.endObject();
+                    if (data != null) {
+                        configuration.put(type, data);
+                    }
+                }
+                mReader.endArray();
+            } catch (IllegalStateException | NumberFormatException e) {
+                consumeRemainingElements();
+                mReader.endArray();
+                throw new IllegalStateException("Encountered malformed data.", e);
+            }
+            return configuration;
+        }
+
+        private InputAbsInfo readAbsInfo() throws IllegalStateException, IOException {
+            InputAbsInfo absInfo = new InputAbsInfo();
+            try {
+                mReader.beginObject();
+                while (mReader.hasNext()) {
+                    String name = mReader.nextName();
+                    switch (name) {
+                        case "value":
+                            absInfo.value = readInt();
+                            break;
+                        case "minimum":
+                            absInfo.minimum = readInt();
+                            break;
+                        case "maximum":
+                            absInfo.maximum = readInt();
+                            break;
+                        case "fuzz":
+                            absInfo.fuzz = readInt();
+                            break;
+                        case "flat":
+                            absInfo.flat = readInt();
+                            break;
+                        case "resolution":
+                            absInfo.resolution = readInt();
+                            break;
+                        default:
+                            consumeRemainingElements();
+                            mReader.endObject();
+                            throw new IllegalStateException("Invalid key in abs info: " + name);
+                    }
+                }
+                mReader.endObject();
+            } catch (IllegalStateException | NumberFormatException e) {
+                consumeRemainingElements();
+                mReader.endObject();
+                throw new IllegalStateException("Encountered malformed data.", e);
+            }
+            return absInfo;
+        }
+
+        private SparseArray<InputAbsInfo> readAbsInfoArray()
+                throws IllegalStateException, IOException {
+            SparseArray<InputAbsInfo> infoArray = new SparseArray<>();
+            try {
+                mReader.beginArray();
+                while (mReader.hasNext()) {
+                    int type = 0;
+                    InputAbsInfo absInfo = null;
+                    mReader.beginObject();
+                    while (mReader.hasNext()) {
+                        String name = mReader.nextName();
+                        switch (name) {
+                            case "code":
+                                type = readInt();
+                                break;
+                            case "info":
+                                absInfo = readAbsInfo();
+                                break;
+                            default:
+                                consumeRemainingElements();
+                                mReader.endObject();
+                                throw new IllegalStateException("Invalid key in abs info array: "
+                                        + name);
+                        }
+                    }
+                    mReader.endObject();
+                    if (absInfo != null) {
+                        infoArray.put(type, absInfo);
+                    }
+                }
+                mReader.endArray();
+            } catch (IllegalStateException | NumberFormatException e) {
+                consumeRemainingElements();
+                mReader.endArray();
+                throw new IllegalStateException("Encountered malformed data.", e);
+            }
+            return infoArray;
+        }
+
+        private void consumeRemainingElements() throws IOException {
+            while (mReader.hasNext()) {
+                mReader.skipValue();
+            }
+        }
+    }
+
+    private static void error(String msg, Exception e) {
+        System.out.println(msg);
+        Log.e(TAG, msg);
+        if (e != null) {
+            Log.e(TAG, Log.getStackTraceString(e));
+        }
+    }
+}
diff --git a/cmds/uinput/src/com/android/commands/uinput/InputAbsInfo.aidl b/cmds/uinput/src/com/android/commands/uinput/InputAbsInfo.aidl
new file mode 100644
index 0000000..88c57f2
--- /dev/null
+++ b/cmds/uinput/src/com/android/commands/uinput/InputAbsInfo.aidl
@@ -0,0 +1,26 @@
+/*
+**
+** Copyright 2020, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+**     http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+package src.com.android.commands.uinput;
+
+parcelable InputAbsInfo {
+    int value;
+    int minimum;
+    int maximum;
+    int fuzz;
+    int flat;
+    int resolution;
+}
diff --git a/cmds/uinput/src/com/android/commands/uinput/Uinput.java b/cmds/uinput/src/com/android/commands/uinput/Uinput.java
new file mode 100644
index 0000000..f7601a2
--- /dev/null
+++ b/cmds/uinput/src/com/android/commands/uinput/Uinput.java
@@ -0,0 +1,140 @@
+/*
+ * 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.commands.uinput;
+
+import android.util.Log;
+import android.util.SparseArray;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.UnsupportedEncodingException;
+
+/**
+ * Uinput class encapsulates execution of "uinput" command. It parses the provided input stream
+ * parameters as JSON file format, extract event entries and perform commands of event entries.
+ * Uinput device will be created when performing registration command and used to inject events.
+ */
+public class Uinput {
+    private static final String TAG = "UINPUT";
+
+    private final Event.Reader mReader;
+    private final SparseArray<Device> mDevices;
+
+    private static void usage() {
+        error("Usage: uinput [FILE]");
+    }
+
+    /**
+     * Commandline "uinput" binary main entry
+     */
+    public static void main(String[] args) {
+        if (args.length != 1) {
+            usage();
+            System.exit(1);
+        }
+
+        InputStream stream = null;
+        try {
+            if (args[0].equals("-")) {
+                stream = System.in;
+            } else {
+                File f = new File(args[0]);
+                stream = new FileInputStream(f);
+            }
+            (new Uinput(stream)).run();
+        } catch (Exception e) {
+            error("Uinput injection failed.", e);
+            System.exit(1);
+        } finally {
+            try {
+                stream.close();
+            } catch (IOException e) {
+            }
+        }
+    }
+
+    private Uinput(InputStream in) {
+        mDevices = new SparseArray<Device>();
+        try {
+            mReader = new Event.Reader(new InputStreamReader(in, "UTF-8"));
+        } catch (UnsupportedEncodingException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    private void run() {
+        try {
+            Event e = null;
+            while ((e = mReader.getNextEvent()) != null) {
+                process(e);
+            }
+        } catch (IOException ex) {
+            error("Error reading in events.", ex);
+        }
+
+        for (int i = 0; i < mDevices.size(); i++) {
+            mDevices.valueAt(i).close();
+        }
+    }
+
+    private void process(Event e) {
+        final int index = mDevices.indexOfKey(e.getId());
+        if (index >= 0) {
+            Device d = mDevices.valueAt(index);
+            if (Event.COMMAND_DELAY.equals(e.getCommand())) {
+                d.addDelay(e.getDuration());
+            } else if (Event.COMMAND_INJECT.equals(e.getCommand())) {
+                d.injectEvent(e.getInjections());
+            } else {
+                if (Event.COMMAND_REGISTER.equals(e.getCommand())) {
+                    error("Device id=" + e.getId() + " is already registered. Ignoring event.");
+                } else {
+                    error("Unknown command \"" + e.getCommand() + "\". Ignoring event.");
+                }
+            }
+        } else if (Event.COMMAND_REGISTER.equals(e.getCommand())) {
+            registerDevice(e);
+        } else {
+            Log.e(TAG, "Unknown device id specified. Ignoring event.");
+        }
+    }
+
+    private void registerDevice(Event e) {
+        if (!Event.COMMAND_REGISTER.equals(e.getCommand())) {
+            throw new IllegalStateException(
+                    "Tried to send command \"" + e.getCommand() + "\" to an unregistered device!");
+        }
+        int id = e.getId();
+        Device d = new Device(id, e.getName(), e.getVendorId(), e.getProductId(), e.getBus(),
+                e.getConfiguration(), e.getFfEffectsMax(), e.getAbsInfo());
+        mDevices.append(id, d);
+    }
+
+    private static void error(String msg) {
+        error(msg, null);
+    }
+
+    private static void error(String msg, Exception e) {
+        Log.e(TAG, msg);
+        if (e != null) {
+            Log.e(TAG, Log.getStackTraceString(e));
+        }
+    }
+}
diff --git a/cmds/uinput/uinput b/cmds/uinput/uinput
new file mode 100755
index 0000000..ab2770e
--- /dev/null
+++ b/cmds/uinput/uinput
@@ -0,0 +1,9 @@
+#!/system/bin/sh
+
+# Preload the native portion libuinputcommand_jni.so to bypass the dependency
+# checks in the Java classloader, which prohibit dependencies that aren't
+# listed in system/core/rootdir/etc/public.libraries.android.txt.
+export LD_PRELOAD=libuinputcommand_jni.so
+
+export CLASSPATH=/system/framework/uinput.jar
+exec app_process /system/bin com.android.commands.uinput.Uinput "$@"
diff --git a/config/boot-image-profile.txt b/config/boot-image-profile.txt
index ab2f42b..f43bd2b 100644
--- a/config/boot-image-profile.txt
+++ b/config/boot-image-profile.txt
@@ -33629,6 +33629,35 @@
 HSPLjava/math/BigInteger;->abs()Ljava/math/BigInteger;
 HSPLjava/math/BigInteger;->add(Ljava/math/BigInteger;)Ljava/math/BigInteger;
 HSPLjava/math/BigInteger;->bitLength()I
+#Temporary manual additions to avoid slowing tests down too much
+#Carefully positioned for clean merge
+HSPLjava/math/BigInteger;->add([IJ)[I
+HSPLjava/math/BigInteger;->add([I[I)[I
+HSPLjava/math/BigInteger;->subtract([IJ)[I
+HSPLjava/math/BigInteger;->subtract([I[I)[I
+HSPLjava/math/BigInteger;->jacobiSymbol(ILjava/math/BigInteger;)I
+HSPLjava/math/BigInteger;->lucasLehmerSequence(ILjava/math/BigInteger;Ljava/math/BigInteger;)Ljava/math/BigInteger;
+HSPLjava/math/BigInteger;->multiplyToLen([II[II[I)[I
+HSPLjava/math/MutableBigInteger;->add(Ljava/math/MutableBigInteger;)V
+HSPLjava/math/MutableBigInteger;->addShifted(Ljava/math/MutableBigInteger;)V
+HSPLjava/math/MutableBigInteger;->subtract(Ljava/math/MutableBigInteger;)V
+HSPLjava/math/MutableBigInteger;->difference(Ljava/math/MutableBigInteger;)V
+HSPLjava/math/MutableBigInteger;->divideKnuth(Ljava/math/MutableBigInteger;Ljava/math/MutableBigInteger;Z)Ljava/math/MutableBigInteger;
+HSPLjava/math/MutableBigInteger;->divideMagnitude(Ljava/math/MutableBigInteger;Ljava/math/MutableBigInteger;Z)Ljava/math/MutableBigInteger;
+HSPLjava/math/MutableBigInteger;->divideLongMagnitude(JLjava/math/MutableBigInteger;)Ljava/math/MutableBigInteger;
+HSPLjava/math/MutableBigInteger;->divideOneWord(ILjava/math/MutableBigInteger;)I
+HSPLjava/math/MutableBigInteger;->divadd([I[II)I
+HSPLjava/math/MutableBigInteger;->mulsub([I[IIII)I
+HSPLjava/math/MutableBigInteger;->mulsubBorrow([I[IIII)I
+HSPLjava/math/MutableBigInteger;->copyAndShift([III[III)V
+HSPLjava/math/MutableBigInteger;->multiply(Ljava/math/MutableBigInteger;Ljava/math/MutableBigInteger;)V
+HSPLjava/math/MutableBigInteger;->mul(ILjava/math/MutableBigInteger;)V
+HSPLjava/math/MutableBigInteger;->multiply(ILjava/math/MutableBigInteger;)V
+HSPLjava/math/MutableBigInteger;->primitiveRightShift(I)I
+HSPLjava/math/MutableBigInteger;->primitiveLeftShift(I)I
+HSPLjava/math/MutableBigInteger;->binaryGCD(Ljava/math/MutableBigInteger;)Ljava/math/MutableBigInteger;
+HSPLjava/math/MutableBigInteger;->binaryGCD(II)I
+#End of maual additions
 HSPLjava/math/BigInteger;->compareTo(Ljava/math/BigInteger;)I
 HSPLjava/math/BigInteger;->divide(Ljava/math/BigInteger;)Ljava/math/BigInteger;
 HSPLjava/math/BigInteger;->divideAndRemainder(Ljava/math/BigInteger;)[Ljava/math/BigInteger;
diff --git a/config/hiddenapi-force-blacklist.txt b/config/hiddenapi-force-blocked.txt
similarity index 100%
rename from config/hiddenapi-force-blacklist.txt
rename to config/hiddenapi-force-blocked.txt
diff --git a/config/hiddenapi-greylist-max-o.txt b/config/hiddenapi-max-target-o.txt
similarity index 100%
rename from config/hiddenapi-greylist-max-o.txt
rename to config/hiddenapi-max-target-o.txt
diff --git a/config/hiddenapi-greylist-max-p.txt b/config/hiddenapi-max-target-p.txt
similarity index 100%
rename from config/hiddenapi-greylist-max-p.txt
rename to config/hiddenapi-max-target-p.txt
diff --git a/config/hiddenapi-greylist-max-q.txt b/config/hiddenapi-max-target-q.txt
similarity index 100%
rename from config/hiddenapi-greylist-max-q.txt
rename to config/hiddenapi-max-target-q.txt
diff --git a/config/hiddenapi-greylist-packages.txt b/config/hiddenapi-unsupported-packages.txt
similarity index 100%
rename from config/hiddenapi-greylist-packages.txt
rename to config/hiddenapi-unsupported-packages.txt
diff --git a/config/hiddenapi-greylist.txt b/config/hiddenapi-unsupported.txt
similarity index 100%
rename from config/hiddenapi-greylist.txt
rename to config/hiddenapi-unsupported.txt
diff --git a/config/preloaded-classes b/config/preloaded-classes
index e43c7d4..f56656b 100644
--- a/config/preloaded-classes
+++ b/config/preloaded-classes
@@ -3709,9 +3709,9 @@
 android.media.IRingtonePlayer$Stub$Proxy
 android.media.IRingtonePlayer$Stub
 android.media.IRingtonePlayer
-android.media.IStrategyPreferredDeviceDispatcher$Stub$Proxy
-android.media.IStrategyPreferredDeviceDispatcher$Stub
-android.media.IStrategyPreferredDeviceDispatcher
+android.media.IStrategyPreferredDevicesDispatcher$Stub$Proxy
+android.media.IStrategyPreferredDevicesDispatcher$Stub
+android.media.IStrategyPreferredDevicesDispatcher
 android.media.IVolumeController$Stub$Proxy
 android.media.IVolumeController$Stub
 android.media.IVolumeController
diff --git a/core/java/android/accessibilityservice/AccessibilityServiceInfo.java b/core/java/android/accessibilityservice/AccessibilityServiceInfo.java
index ca22bf4..d334de6 100644
--- a/core/java/android/accessibilityservice/AccessibilityServiceInfo.java
+++ b/core/java/android/accessibilityservice/AccessibilityServiceInfo.java
@@ -364,6 +364,18 @@
      */
     public static final int FLAG_REQUEST_MULTI_FINGER_GESTURES = 0x0001000;
 
+    /**
+     * This flag requests that when when {@link #FLAG_REQUEST_MULTI_FINGER_GESTURES} is enabled,
+     * two-finger passthrough gestures are re-enabled. Two-finger swipe gestures are not detected,
+     * but instead passed through as one-finger gestures. In addition, three-finger swipes from the
+     * bottom of the screen are not detected, and instead are passed through unchanged. If {@link
+     * #FLAG_REQUEST_MULTI_FINGER_GESTURES} is disabled this flag has no effect.
+     *
+     * @see #FLAG_REQUEST_TOUCH_EXPLORATION_MODE
+     * @hide
+     */
+    public static final int FLAG_REQUEST_2_FINGER_PASSTHROUGH = 0x0002000;
+
     /** {@hide} */
     public static final int FLAG_FORCE_DIRECT_BOOT_AWARE = 0x00010000;
 
@@ -624,6 +636,7 @@
                     0);
             flags = asAttributes.getInt(
                     com.android.internal.R.styleable.AccessibilityService_accessibilityFlags, 0);
+            flags |= FLAG_REQUEST_2_FINGER_PASSTHROUGH;
             mSettingsActivityName = asAttributes.getString(
                     com.android.internal.R.styleable.AccessibilityService_settingsActivity);
             if (asAttributes.getBoolean(com.android.internal.R.styleable
@@ -1261,6 +1274,8 @@
                 return "FLAG_SERVICE_HANDLES_DOUBLE_TAP";
             case FLAG_REQUEST_MULTI_FINGER_GESTURES:
                 return "FLAG_REQUEST_MULTI_FINGER_GESTURES";
+            case FLAG_REQUEST_2_FINGER_PASSTHROUGH:
+                return "FLAG_REQUEST_2_FINGER_PASSTHROUGH";
             case FLAG_REQUEST_ENHANCED_WEB_ACCESSIBILITY:
                 return "FLAG_REQUEST_ENHANCED_WEB_ACCESSIBILITY";
             case FLAG_REPORT_VIEW_IDS:
diff --git a/core/java/android/animation/AnimatorSet.java b/core/java/android/animation/AnimatorSet.java
index a9239b4..bc8db02 100644
--- a/core/java/android/animation/AnimatorSet.java
+++ b/core/java/android/animation/AnimatorSet.java
@@ -183,7 +183,7 @@
 
     // This is to work around a bug in b/34736819. This needs to be removed once app team
     // fixes their side.
-    private AnimatorListenerAdapter mAnimationEndingListener = new AnimatorListenerAdapter() {
+    private AnimatorListenerAdapter mAnimationEndListener = new AnimatorListenerAdapter() {
         @Override
         public void onAnimationEnd(Animator animation) {
             if (mNodeMap.get(animation) == null) {
@@ -1186,7 +1186,7 @@
     }
 
     private void startAnimation() {
-        addAnimationEndingListener();
+        addAnimationEndListener();
 
         // Register animation callback
         addAnimationCallback(0);
@@ -1243,15 +1243,15 @@
 
     // This is to work around the issue in b/34736819, as the old behavior in AnimatorSet had
     // masked a real bug in play movies. TODO: remove this and below once the root cause is fixed.
-    private void addAnimationEndingListener() {
+    private void addAnimationEndListener() {
         for (int i = 1; i < mNodes.size(); i++) {
-            mNodes.get(i).mAnimation.addListener(mAnimationEndingListener);
+            mNodes.get(i).mAnimation.addListener(mAnimationEndListener);
         }
     }
 
-    private void removeAnimationEndingListener() {
+    private void removeAnimationEndListener() {
         for (int i = 1; i < mNodes.size(); i++) {
-            mNodes.get(i).mAnimation.removeListener(mAnimationEndingListener);
+            mNodes.get(i).mAnimation.removeListener(mAnimationEndListener);
         }
     }
 
@@ -1301,7 +1301,7 @@
                 tmpListeners.get(i).onAnimationEnd(this, mReversing);
             }
         }
-        removeAnimationEndingListener();
+        removeAnimationEndListener();
         mSelfPulse = true;
         mReversing = false;
     }
@@ -1346,7 +1346,7 @@
         anim.mNodeMap = new ArrayMap<Animator, Node>();
         anim.mNodes = new ArrayList<Node>(nodeCount);
         anim.mEvents = new ArrayList<AnimationEvent>();
-        anim.mAnimationEndingListener = new AnimatorListenerAdapter() {
+        anim.mAnimationEndListener = new AnimatorListenerAdapter() {
             @Override
             public void onAnimationEnd(Animator animation) {
                 if (anim.mNodeMap.get(animation) == null) {
@@ -1369,7 +1369,7 @@
             final Node node = mNodes.get(n);
             Node nodeClone = node.clone();
             // Remove the old internal listener from the cloned child
-            nodeClone.mAnimation.removeListener(mAnimationEndingListener);
+            nodeClone.mAnimation.removeListener(mAnimationEndListener);
             clonesMap.put(node, nodeClone);
             anim.mNodes.add(nodeClone);
             anim.mNodeMap.put(nodeClone.mAnimation, nodeClone);
diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java
index 4a982dd..5c4951e 100644
--- a/core/java/android/app/Activity.java
+++ b/core/java/android/app/Activity.java
@@ -35,6 +35,7 @@
 import android.annotation.StyleRes;
 import android.annotation.SystemApi;
 import android.annotation.TestApi;
+import android.annotation.UiContext;
 import android.app.VoiceInteractor.Request;
 import android.app.admin.DevicePolicyManager;
 import android.app.assist.AssistContent;
@@ -726,6 +727,7 @@
  * upload, independent of whether the original activity is paused, stopped,
  * or finished.
  */
+@UiContext
 public class Activity extends ContextThemeWrapper
         implements LayoutInflater.Factory2,
         Window.Callback, KeyEvent.Callback,
@@ -2878,7 +2880,7 @@
      * Return the number of actions that will be displayed in the picture-in-picture UI when the
      * user interacts with the activity currently in picture-in-picture mode. This number may change
      * if the global configuration changes (ie. if the device is plugged into an external display),
-     * but will always be larger than three.
+     * but will always be at least three.
      */
     public int getMaxNumPictureInPictureActions() {
         try {
@@ -3825,6 +3827,12 @@
         } catch (RemoteException e) {
             finishAfterTransition();
         }
+
+        // Activity was launched when user tapped a link in the Autofill Save UI - Save UI must
+        // be restored now.
+        if (mIntent != null && mIntent.hasExtra(AutofillManager.EXTRA_RESTORE_SESSION_TOKEN)) {
+            restoreAutofillSaveUi();
+        }
     }
 
     /**
diff --git a/core/java/android/app/ActivityManagerInternal.java b/core/java/android/app/ActivityManagerInternal.java
index 7fe567b..1f8cf8a 100644
--- a/core/java/android/app/ActivityManagerInternal.java
+++ b/core/java/android/app/ActivityManagerInternal.java
@@ -52,23 +52,14 @@
      * if in the same profile group.
      * Otherwise, {@link android.Manifest.permission#INTERACT_ACROSS_USERS_FULL} is required.
      */
-    public static final int ALLOW_NON_FULL_IN_PROFILE_OR_FULL = 1;
+    public static final int ALLOW_NON_FULL_IN_PROFILE = 1;
     public static final int ALLOW_FULL_ONLY = 2;
     /**
      * Allows access to a caller with {@link android.Manifest.permission#INTERACT_ACROSS_PROFILES}
      * or {@link android.Manifest.permission#INTERACT_ACROSS_USERS} if in the same profile group.
      * Otherwise, {@link android.Manifest.permission#INTERACT_ACROSS_USERS_FULL} is required.
      */
-    public static final int ALLOW_ACROSS_PROFILES_IN_PROFILE_OR_FULL = 3;
-    /**
-     * Requires {@link android.Manifest.permission#INTERACT_ACROSS_PROFILES},
-     * {@link android.Manifest.permission#INTERACT_ACROSS_USERS}, or
-     * {@link android.Manifest.permission#INTERACT_ACROSS_USERS_FULL} if in same profile group,
-     * otherwise {@link android.Manifest.permission#INTERACT_ACROSS_USERS} or
-     * {@link android.Manifest.permission#INTERACT_ACROSS_USERS_FULL}. (so this is an extension
-     * to {@link #ALLOW_NON_FULL})
-     */
-    public static final int ALLOW_ACROSS_PROFILES_IN_PROFILE_OR_NON_FULL = 4;
+    public static final int ALLOW_ALL_PROFILE_PERMISSIONS_IN_PROFILE = 3;
 
     /**
      * Verify that calling app has access to the given provider.
@@ -350,7 +341,8 @@
     /** @see com.android.server.am.ActivityManagerService#monitor */
     public abstract void monitor();
 
-    /** Input dispatch timeout to a window, start the ANR process. */
+    /** Input dispatch timeout to a window, start the ANR process. Return the timeout extension,
+     * in milliseconds, or 0 to abort dispatch. */
     public abstract long inputDispatchingTimedOut(int pid, boolean aboveSystem, String reason);
     public abstract boolean inputDispatchingTimedOut(Object proc, String activityShortComponentName,
             ApplicationInfo aInfo, String parentShortComponentName, Object parentProc,
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index 9b13d25..f6b0453 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -37,6 +37,7 @@
 import android.app.assist.AssistContent;
 import android.app.assist.AssistStructure;
 import android.app.backup.BackupAgent;
+import android.app.backup.BackupManager;
 import android.app.servertransaction.ActivityLifecycleItem;
 import android.app.servertransaction.ActivityLifecycleItem.LifecycleState;
 import android.app.servertransaction.ActivityRelaunchItem;
@@ -289,6 +290,8 @@
 
     private final Object mNetworkPolicyLock = new Object();
 
+    private static final String DEFAULT_FULL_BACKUP_AGENT = "android.app.backup.FullBackupAgent";
+
     /**
      * Denotes the sequence number of the process state change for which the main thread needs
      * to block until the network rules are updated for it.
@@ -421,9 +424,14 @@
         final String authority;
         final int userId;
 
+        @GuardedBy("mLock")
+        ContentProviderHolder mHolder; // Temp holder to be used between notifier and waiter
+        Object mLock; // The lock to be used to get notified when the provider is ready
+
         public ProviderKey(String authority, int userId) {
             this.authority = authority;
             this.userId = userId;
+            this.mLock = new Object();
         }
 
         @Override
@@ -437,7 +445,11 @@
 
         @Override
         public int hashCode() {
-            return ((authority != null) ? authority.hashCode() : 0) ^ userId;
+            return hashCode(authority, userId);
+        }
+
+        public static int hashCode(final String auth, final int userIdent) {
+            return ((auth != null) ? auth.hashCode() : 0) ^ userIdent;
         }
     }
 
@@ -458,9 +470,8 @@
     // Mitigation for b/74523247: Used to serialize calls to AM.getContentProvider().
     // Note we never removes items from this map but that's okay because there are only so many
     // users and so many authorities.
-    // TODO Remove it once we move CPR.wait() from AMS to the client side.
-    @GuardedBy("mGetProviderLocks")
-    final ArrayMap<ProviderKey, Object> mGetProviderLocks = new ArrayMap<>();
+    @GuardedBy("mGetProviderKeys")
+    final SparseArray<ProviderKey> mGetProviderKeys = new SparseArray<>();
 
     final ArrayMap<Activity, ArrayList<OnActivityPausedListener>> mOnPauseListeners
         = new ArrayMap<Activity, ArrayList<OnActivityPausedListener>>();
@@ -737,6 +748,7 @@
         CompatibilityInfo compatInfo;
         int backupMode;
         int userId;
+        int operationType;
         public String toString() {
             return "CreateBackupAgentData{appInfo=" + appInfo
                     + " backupAgent=" + appInfo.backupAgentName
@@ -957,12 +969,13 @@
         }
 
         public final void scheduleCreateBackupAgent(ApplicationInfo app,
-                CompatibilityInfo compatInfo, int backupMode, int userId) {
+                CompatibilityInfo compatInfo, int backupMode, int userId, int operationType) {
             CreateBackupAgentData d = new CreateBackupAgentData();
             d.appInfo = app;
             d.compatInfo = compatInfo;
             d.backupMode = backupMode;
             d.userId = userId;
+            d.operationType = operationType;
 
             sendMessage(H.CREATE_BACKUP_AGENT, d);
         }
@@ -1751,6 +1764,16 @@
                     ActivityThread.this, activityToken, actionId, arguments,
                     cancellationSignal, resultCallback));
         }
+
+        @Override
+        public void notifyContentProviderPublishStatus(@NonNull ContentProviderHolder holder,
+                @NonNull String auth, int userId, boolean published) {
+            final ProviderKey key = getGetProviderKey(auth, userId);
+            synchronized (key.mLock) {
+                key.mHolder = holder;
+                key.mLock.notifyAll();
+            }
+        }
     }
 
     private @NonNull SafeCancellationTransport createSafeCancellationTransport(
@@ -2219,9 +2242,9 @@
      * Resources if one has already been created.
      */
     Resources getTopLevelResources(String resDir, String[] splitResDirs, String[] overlayDirs,
-            String[] libDirs, int displayId, LoadedApk pkgInfo) {
+            String[] libDirs, LoadedApk pkgInfo) {
         return mResourcesManager.getResources(null, resDir, splitResDirs, overlayDirs, libDirs,
-                displayId, null, pkgInfo.getCompatibilityInfo(), pkgInfo.getClassLoader(), null);
+                null, null, pkgInfo.getCompatibilityInfo(), pkgInfo.getClassLoader(), null);
     }
 
     @UnsupportedAppUsage
@@ -4075,12 +4098,7 @@
             return;
         }
 
-        String classname = data.appInfo.backupAgentName;
-        // full backup operation but no app-supplied agent?  use the default implementation
-        if (classname == null && (data.backupMode == ApplicationThreadConstants.BACKUP_MODE_FULL
-                || data.backupMode == ApplicationThreadConstants.BACKUP_MODE_RESTORE_FULL)) {
-            classname = "android.app.backup.FullBackupAgent";
-        }
+        String classname = getBackupAgentName(data);
 
         try {
             IBinder binder = null;
@@ -4104,7 +4122,7 @@
                     context.setOuterContext(agent);
                     agent.attach(context);
 
-                    agent.onCreate(UserHandle.of(data.userId));
+                    agent.onCreate(UserHandle.of(data.userId), data.operationType);
                     binder = agent.onBind();
                     backupAgents.put(packageName, agent);
                 } catch (Exception e) {
@@ -4132,6 +4150,23 @@
         }
     }
 
+    private String getBackupAgentName(CreateBackupAgentData data) {
+        String agentName = data.appInfo.backupAgentName;
+        if (!UserHandle.isCore(data.appInfo.uid)
+                && data.operationType == BackupManager.OperationType.MIGRATION) {
+            // If this is a migration, use the default backup agent regardless of the app's
+            // preferences.
+            agentName = DEFAULT_FULL_BACKUP_AGENT;
+        } else {
+            // full backup operation but no app-supplied agent?  use the default implementation
+            if (agentName == null && (data.backupMode == ApplicationThreadConstants.BACKUP_MODE_FULL
+                    || data.backupMode == ApplicationThreadConstants.BACKUP_MODE_RESTORE_FULL)) {
+                agentName = DEFAULT_FULL_BACKUP_AGENT;
+            }
+        }
+        return agentName;
+    }
+
     // Tear down a BackupAgent
     private void handleDestroyBackupAgent(CreateBackupAgentData data) {
         if (DEBUG_BACKUP) Slog.v(TAG, "handleDestroyBackupAgent: " + data);
@@ -5111,6 +5146,7 @@
                 }
             }
             r.setState(ON_DESTROY);
+            mLastReportedWindowingMode.remove(r.activity.getActivityToken());
         }
         schedulePurgeIdler();
         // updatePendingActivityConfiguration() reads from mActivities to update
@@ -5353,16 +5389,8 @@
             throw e.rethrowFromSystemServer();
         }
 
-        // Save the current windowing mode to be restored and compared to the new configuration's
-        // windowing mode (needed because we update the last reported windowing mode when launching
-        // an activity and we can't tell inside performLaunchActivity whether we are relaunching)
-        final int oldWindowingMode = mLastReportedWindowingMode.getOrDefault(
-                r.activity.getActivityToken(), WINDOWING_MODE_UNDEFINED);
         handleRelaunchActivityInner(r, configChanges, tmp.pendingResults, tmp.pendingIntents,
                 pendingActions, tmp.startsNotResumed, tmp.overrideConfig, "handleRelaunchActivity");
-        mLastReportedWindowingMode.put(r.activity.getActivityToken(), oldWindowingMode);
-        handleWindowingModeChangeIfNeeded(r.activity, r.activity.mCurrentConfig);
-
         if (pendingActions != null) {
             // Only report a successful relaunch to WindowManager.
             pendingActions.setReportRelaunchToWindowManager(true);
@@ -5628,10 +5656,6 @@
             throw new IllegalArgumentException("Activity token not set. Is the activity attached?");
         }
 
-        // multi-window / pip mode changes, if any, should be sent before the configuration change
-        // callback, see also PinnedStackTests#testConfigurationChangeOrderDuringTransition
-        handleWindowingModeChangeIfNeeded(activity, newConfig);
-
         final boolean movedToDifferentDisplay = isDifferentDisplay(activity, displayId);
         boolean shouldReportChange = false;
         if (activity.mCurrentConfig == null) {
@@ -5668,8 +5692,7 @@
         // many places.
         final Configuration finalOverrideConfig = createNewConfigAndUpdateIfNotNull(
                 amOverrideConfig, contextThemeWrapperOverrideConfig);
-        mResourcesManager.updateResourcesForActivity(activityToken, finalOverrideConfig,
-                displayId, movedToDifferentDisplay);
+        mResourcesManager.updateResourcesForActivity(activityToken, finalOverrideConfig, displayId);
 
         activity.mConfigChangeFlags = 0;
         activity.mCurrentConfig = new Configuration(newConfig);
@@ -5685,6 +5708,11 @@
         }
 
         if (shouldReportChange) {
+            // multi-window / pip mode changes, if any, should be sent before the configuration
+            // change callback, see also
+            // PinnedStackTests#testConfigurationChangeOrderDuringTransition
+            handleWindowingModeChangeIfNeeded(activity, newConfig);
+
             activity.mCalled = false;
             activity.onConfigurationChanged(configToReport);
             if (!activity.mCalled) {
@@ -5985,6 +6013,11 @@
             r.mPendingOverrideConfig = null;
         }
 
+        if (displayId == INVALID_DISPLAY) {
+            // If INVALID_DISPLAY is passed assume that the activity should keep its current
+            // display.
+            displayId = r.activity.getDisplayId();
+        }
         final boolean movedToDifferentDisplay = isDifferentDisplay(r.activity, displayId);
         if (r.overrideConfig != null && !r.overrideConfig.isOtherSeqNewer(overrideConfig)
                 && !movedToDifferentDisplay) {
@@ -6796,13 +6829,33 @@
         // provider since it might take a long time to run and it could also potentially
         // be re-entrant in the case where the provider is in the same process.
         ContentProviderHolder holder = null;
+        final ProviderKey key = getGetProviderKey(auth, userId);
         try {
-            synchronized (getGetProviderLock(auth, userId)) {
+            synchronized (key) {
                 holder = ActivityManager.getService().getContentProvider(
                         getApplicationThread(), c.getOpPackageName(), auth, userId, stable);
+                // If the returned holder is non-null but its provider is null and it's not
+                // local, we'll need to wait for the publishing of the provider.
+                if (holder != null && holder.provider == null && !holder.mLocal) {
+                    synchronized (key.mLock) {
+                        key.mLock.wait(ContentResolver.CONTENT_PROVIDER_READY_TIMEOUT_MILLIS);
+                        holder = key.mHolder;
+                    }
+                    if (holder != null && holder.provider == null) {
+                        // probably timed out
+                        holder = null;
+                    }
+                }
             }
         } catch (RemoteException ex) {
             throw ex.rethrowFromSystemServer();
+        } catch (InterruptedException e) {
+            holder = null;
+        } finally {
+            // Clear the holder from the key since the key itself is never cleared.
+            synchronized (key.mLock) {
+                key.mHolder = null;
+            }
         }
         if (holder == null) {
             if (UserManager.get(c).isUserUnlocked(userId)) {
@@ -6820,13 +6873,13 @@
         return holder.provider;
     }
 
-    private Object getGetProviderLock(String auth, int userId) {
-        final ProviderKey key = new ProviderKey(auth, userId);
-        synchronized (mGetProviderLocks) {
-            Object lock = mGetProviderLocks.get(key);
+    private ProviderKey getGetProviderKey(String auth, int userId) {
+        final int key = ProviderKey.hashCode(auth, userId);
+        synchronized (mGetProviderKeys) {
+            ProviderKey lock = mGetProviderKeys.get(key);
             if (lock == null) {
-                lock = key;
-                mGetProviderLocks.put(key, lock);
+                lock = new ProviderKey(auth, userId);
+                mGetProviderKeys.put(key, lock);
             }
             return lock;
         }
diff --git a/core/java/android/app/ActivityView.java b/core/java/android/app/ActivityView.java
index 98a23f2..3cb6293 100644
--- a/core/java/android/app/ActivityView.java
+++ b/core/java/android/app/ActivityView.java
@@ -105,7 +105,8 @@
     public ActivityView(
             @NonNull Context context, @NonNull AttributeSet attrs, int defStyle,
             boolean singleTaskInstance, boolean usePublicVirtualDisplay) {
-        this(context, attrs, defStyle, singleTaskInstance, usePublicVirtualDisplay, false);
+        this(context, attrs, defStyle, singleTaskInstance, usePublicVirtualDisplay,
+                false /* disableSurfaceViewBackgroundLayer */);
     }
 
     /** @hide */
@@ -113,12 +114,22 @@
             @NonNull Context context, @NonNull AttributeSet attrs, int defStyle,
             boolean singleTaskInstance, boolean usePublicVirtualDisplay,
             boolean disableSurfaceViewBackgroundLayer) {
+        this(context, attrs, defStyle, singleTaskInstance, usePublicVirtualDisplay,
+                disableSurfaceViewBackgroundLayer, false /* useTrustedDisplay */);
+    }
+
+    // TODO(b/162901735): Refactor ActivityView with Builder
+    /** @hide */
+    public ActivityView(
+            @NonNull Context context, @NonNull AttributeSet attrs, int defStyle,
+            boolean singleTaskInstance, boolean usePublicVirtualDisplay,
+            boolean disableSurfaceViewBackgroundLayer, boolean useTrustedDisplay) {
         super(context, attrs, defStyle);
         if (useTaskOrganizer()) {
             mTaskEmbedder = new TaskOrganizerTaskEmbedder(context, this);
         } else {
             mTaskEmbedder = new VirtualDisplayTaskEmbedder(context, this, singleTaskInstance,
-                    usePublicVirtualDisplay);
+                    usePublicVirtualDisplay, useTrustedDisplay);
         }
         mSurfaceView = new SurfaceView(context, null, 0, 0, disableSurfaceViewBackgroundLayer);
         // Since ActivityView#getAlpha has been overridden, we should use parent class's alpha
diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java
index b40dd00..0055711 100644
--- a/core/java/android/app/AppOpsManager.java
+++ b/core/java/android/app/AppOpsManager.java
@@ -1145,9 +1145,24 @@
     /** @hide */
     public static final int OP_NO_ISOLATED_STORAGE = AppProtoEnums.APP_OP_NO_ISOLATED_STORAGE;
 
+    /**
+     * Phone call is using microphone
+     *
+     * @hide
+     */
+    // TODO: Add as AppProtoEnums
+    public static final int OP_PHONE_CALL_MICROPHONE = 100;
+    /**
+     * Phone call is using camera
+     *
+     * @hide
+     */
+    // TODO: Add as AppProtoEnums
+    public static final int OP_PHONE_CALL_CAMERA = 101;
+
     /** @hide */
     @UnsupportedAppUsage
-    public static final int _NUM_OP = 100;
+    public static final int _NUM_OP = 102;
 
     /** Access to coarse location information. */
     public static final String OPSTR_COARSE_LOCATION = "android:coarse_location";
@@ -1469,6 +1484,19 @@
     @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
     public static final String OPSTR_NO_ISOLATED_STORAGE = "android:no_isolated_storage";
 
+    /**
+     * Phone call is using microphone
+     *
+     * @hide
+     */
+    public static final String OPSTR_PHONE_CALL_MICROPHONE = "android:phone_call_microphone";
+    /**
+     * Phone call is using camera
+     *
+     * @hide
+     */
+    public static final String OPSTR_PHONE_CALL_CAMERA = "android:phone_call_camera";
+
     /** {@link #sAppOpsToNote} not initialized yet for this op */
     private static final byte SHOULD_COLLECT_NOTE_OP_NOT_INITIALIZED = 0;
     /** Should not collect noting of this app-op in {@link #sAppOpsToNote} */
@@ -1658,6 +1686,8 @@
             OP_AUTO_REVOKE_PERMISSIONS_IF_UNUSED, //AUTO_REVOKE_PERMISSIONS_IF_UNUSED
             OP_AUTO_REVOKE_MANAGED_BY_INSTALLER, //OP_AUTO_REVOKE_MANAGED_BY_INSTALLER
             OP_NO_ISOLATED_STORAGE,             // NO_ISOLATED_STORAGE
+            OP_PHONE_CALL_MICROPHONE,           // OP_PHONE_CALL_MICROPHONE
+            OP_PHONE_CALL_CAMERA,               // OP_PHONE_CALL_CAMERA
     };
 
     /**
@@ -1764,6 +1794,8 @@
             OPSTR_AUTO_REVOKE_PERMISSIONS_IF_UNUSED,
             OPSTR_AUTO_REVOKE_MANAGED_BY_INSTALLER,
             OPSTR_NO_ISOLATED_STORAGE,
+            OPSTR_PHONE_CALL_MICROPHONE,
+            OPSTR_PHONE_CALL_CAMERA,
     };
 
     /**
@@ -1871,6 +1903,8 @@
             "AUTO_REVOKE_PERMISSIONS_IF_UNUSED",
             "AUTO_REVOKE_MANAGED_BY_INSTALLER",
             "NO_ISOLATED_STORAGE",
+            "PHONE_CALL_MICROPHONE",
+            "PHONE_CALL_CAMERA",
     };
 
     /**
@@ -1979,6 +2013,8 @@
             null, // no permission for OP_AUTO_REVOKE_PERMISSIONS_IF_UNUSED
             null, // no permission for OP_AUTO_REVOKE_MANAGED_BY_INSTALLER
             null, // no permission for OP_NO_ISOLATED_STORAGE
+            null, // no permission for OP_PHONE_CALL_MICROPHONE
+            null, // no permission for OP_PHONE_CALL_CAMERA
     };
 
     /**
@@ -2087,6 +2123,8 @@
             null, // AUTO_REVOKE_PERMISSIONS_IF_UNUSED
             null, // AUTO_REVOKE_MANAGED_BY_INSTALLER
             null, // NO_ISOLATED_STORAGE
+            null, // PHONE_CALL_MICROPHONE
+            null, // PHONE_CALL_MICROPHONE
     };
 
     /**
@@ -2194,6 +2232,8 @@
             null, // AUTO_REVOKE_PERMISSIONS_IF_UNUSED
             null, // AUTO_REVOKE_MANAGED_BY_INSTALLER
             null, // NO_ISOLATED_STORAGE
+            null, // PHONE_CALL_MICROPHONE
+            null, // PHONE_CALL_CAMERA
     };
 
     /**
@@ -2300,6 +2340,8 @@
             AppOpsManager.MODE_DEFAULT, // OP_AUTO_REVOKE_PERMISSIONS_IF_UNUSED
             AppOpsManager.MODE_ALLOWED, // OP_AUTO_REVOKE_MANAGED_BY_INSTALLER
             AppOpsManager.MODE_ERRORED, // OP_NO_ISOLATED_STORAGE
+            AppOpsManager.MODE_ALLOWED, // PHONE_CALL_MICROPHONE
+            AppOpsManager.MODE_ALLOWED, // PHONE_CALL_CAMERA
     };
 
     /**
@@ -2410,6 +2452,8 @@
             false, // AUTO_REVOKE_PERMISSIONS_IF_UNUSED
             false, // AUTO_REVOKE_MANAGED_BY_INSTALLER
             true, // NO_ISOLATED_STORAGE
+            false, // PHONE_CALL_MICROPHONE
+            false, // PHONE_CALL_CAMERA
     };
 
     /**
@@ -2657,8 +2701,10 @@
      * @hide
      */
     // TODO: this should probably be @SystemApi as well
-    public static @NonNull String toReceiverId(@NonNull Object obj) {
-        if (obj instanceof PendingIntent) {
+    public static @NonNull String toReceiverId(@Nullable Object obj) {
+        if (obj == null) {
+            return "null";
+        } else if (obj instanceof PendingIntent) {
             return toReceiverId((PendingIntent) obj);
         } else {
             return obj.getClass().getName() + "@" + System.identityHashCode(obj);
diff --git a/core/java/android/app/ApplicationLoaders.java b/core/java/android/app/ApplicationLoaders.java
index 15237be..08cd0b3 100644
--- a/core/java/android/app/ApplicationLoaders.java
+++ b/core/java/android/app/ApplicationLoaders.java
@@ -243,8 +243,8 @@
 
         // cached must be built and loaded in the same environment
         if (!sharedLibrariesEquals(sharedLibraries, cached.sharedLibraries)) {
-            Log.w(TAG, "Unexpected environment for cached library: (" + sharedLibraries + "|"
-                    + cached.sharedLibraries + ")");
+            Log.w(TAG, "Unexpected environment loading cached library " + zip + " (real|cached): ("
+                    + sharedLibraries + "|" + cached.sharedLibraries + ")");
             return null;
         }
 
diff --git a/core/java/android/app/ApplicationPackageManager.java b/core/java/android/app/ApplicationPackageManager.java
index dedd870..340d5a1 100644
--- a/core/java/android/app/ApplicationPackageManager.java
+++ b/core/java/android/app/ApplicationPackageManager.java
@@ -96,7 +96,6 @@
 import android.util.DebugUtils;
 import android.util.LauncherIcons;
 import android.util.Log;
-import android.view.Display;
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.Immutable;
@@ -684,8 +683,7 @@
 
     @Override
     public int checkPermission(String permName, String pkgName) {
-        return PermissionManager
-                .checkPackageNamePermission(permName, pkgName, getUserId());
+        return PermissionManager.checkPackageNamePermission(permName, pkgName, getUserId());
     }
 
     @Override
@@ -1749,7 +1747,7 @@
         final Resources r = mContext.mMainThread.getTopLevelResources(
                     sameUid ? app.sourceDir : app.publicSourceDir,
                     sameUid ? app.splitSourceDirs : app.splitPublicSourceDirs,
-                    app.resourceDirs, app.sharedLibraryFiles, Display.DEFAULT_DISPLAY,
+                    app.resourceDirs, app.sharedLibraryFiles,
                     mContext.mPackageInfo);
         if (r != null) {
             return r;
diff --git a/core/java/android/app/ContentProviderHolder.java b/core/java/android/app/ContentProviderHolder.java
index 3d74583..e330a30 100644
--- a/core/java/android/app/ContentProviderHolder.java
+++ b/core/java/android/app/ContentProviderHolder.java
@@ -39,6 +39,11 @@
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
     public boolean noReleaseNeeded;
 
+    /**
+     * Whether the provider here is a local provider or not.
+     */
+    public boolean mLocal;
+
     @UnsupportedAppUsage
     public ContentProviderHolder(ProviderInfo _info) {
         info = _info;
@@ -59,6 +64,7 @@
         }
         dest.writeStrongBinder(connection);
         dest.writeInt(noReleaseNeeded ? 1 : 0);
+        dest.writeInt(mLocal ? 1 : 0);
     }
 
     public static final @android.annotation.NonNull Parcelable.Creator<ContentProviderHolder> CREATOR
@@ -81,5 +87,6 @@
                 source.readStrongBinder());
         connection = source.readStrongBinder();
         noReleaseNeeded = source.readInt() != 0;
+        mLocal = source.readInt() != 0;
     }
-}
\ No newline at end of file
+}
diff --git a/core/java/android/app/ContextImpl.java b/core/java/android/app/ContextImpl.java
index 9613e58..cee607f 100644
--- a/core/java/android/app/ContextImpl.java
+++ b/core/java/android/app/ContextImpl.java
@@ -187,6 +187,16 @@
     private static final String XATTR_INODE_CODE_CACHE = "user.inode_code_cache";
 
     /**
+     * Special intent extra that critical system apps can use to hide the notification for a
+     * foreground service. This extra should be placed in the intent passed into {@link
+     * #startForegroundService(Intent)}.
+     *
+     * @hide
+     */
+    private static final String EXTRA_HIDDEN_FOREGROUND_SERVICE =
+            "android.intent.extra.HIDDEN_FOREGROUND_SERVICE";
+
+    /**
      * Map from package name, to preference name, to cached preferences.
      */
     @GuardedBy("ContextImpl.class")
@@ -227,6 +237,15 @@
     private @NonNull Resources mResources;
     private @Nullable Display mDisplay; // may be null if invalid display or not initialized yet.
 
+    /**
+     * If set to {@code true} the resources for this context will be configured for mDisplay which
+     * will override the display configuration inherited from {@link #mToken} (or the global
+     * configuration if mToken is null). Typically set for display contexts and contexts derived
+     * from display contexts where changes to the activity display and the global configuration
+     * display should not impact their resources.
+     */
+    private boolean mForceDisplayOverrideInResources;
+
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
     private final int mFlags;
 
@@ -1624,8 +1643,9 @@
         }
         try {
             final Intent intent = ActivityManager.getService().registerReceiverWithFeature(
-                    mMainThread.getApplicationThread(), mBasePackageName, getAttributionTag(), rd,
-                    filter, broadcastPermission, userId, flags);
+                    mMainThread.getApplicationThread(), mBasePackageName, getAttributionTag(),
+                    AppOpsManager.toReceiverId(receiver), rd, filter, broadcastPermission, userId,
+                    flags);
             if (intent != null) {
                 intent.setExtrasClassLoader(getClassLoader());
                 intent.prepareToEnterProcess();
@@ -1697,9 +1717,12 @@
         try {
             validateServiceIntent(service);
             service.prepareToLeaveProcess(this);
+            final boolean hideForegroundNotification = requireForeground
+                    && service.getBooleanExtra(EXTRA_HIDDEN_FOREGROUND_SERVICE, false);
             ComponentName cn = ActivityManager.getService().startService(
                     mMainThread.getApplicationThread(), service,
                     service.resolveTypeIfNeeded(getContentResolver()), requireForeground,
+                    hideForegroundNotification,
                     getOpPackageName(), getAttributionTag(), user.getIdentifier());
             if (cn != null) {
                 if (cn.getPackageName().equals("!")) {
@@ -2245,8 +2268,8 @@
     }
 
     private static Resources createResources(IBinder activityToken, LoadedApk pi, String splitName,
-            int displayId, Configuration overrideConfig, CompatibilityInfo compatInfo,
-            List<ResourcesLoader> resourcesLoader) {
+            @Nullable Integer overrideDisplayId, Configuration overrideConfig,
+            CompatibilityInfo compatInfo, List<ResourcesLoader> resourcesLoader) {
         final String[] splitResDirs;
         final ClassLoader classLoader;
         try {
@@ -2260,7 +2283,7 @@
                 splitResDirs,
                 pi.getOverlayDirs(),
                 pi.getApplicationInfo().sharedLibraryFiles,
-                displayId,
+                overrideDisplayId,
                 overrideConfig,
                 compatInfo,
                 classLoader,
@@ -2277,8 +2300,10 @@
                     new UserHandle(UserHandle.getUserId(application.uid)), flags, null, null);
 
             final int displayId = getDisplayId();
+            final Integer overrideDisplayId = mForceDisplayOverrideInResources
+                    ? displayId : null;
 
-            c.setResources(createResources(mToken, pi, null, displayId, null,
+            c.setResources(createResources(mToken, pi, null, overrideDisplayId, null,
                     getDisplayAdjustments(displayId).getCompatibilityInfo(), null));
             if (c.mResources != null) {
                 return c;
@@ -2312,8 +2337,10 @@
                     mToken, user, flags, null, null);
 
             final int displayId = getDisplayId();
+            final Integer overrideDisplayId = mForceDisplayOverrideInResources
+                    ? displayId : null;
 
-            c.setResources(createResources(mToken, pi, null, displayId, null,
+            c.setResources(createResources(mToken, pi, null, overrideDisplayId, null,
                     getDisplayAdjustments(displayId).getCompatibilityInfo(), null));
             if (c.mResources != null) {
                 return c;
@@ -2347,15 +2374,13 @@
         final ContextImpl context = new ContextImpl(this, mMainThread, mPackageInfo,
                 mAttributionTag, splitName, mToken, mUser, mFlags, classLoader, null);
 
-        final int displayId = getDisplayId();
-
         context.setResources(ResourcesManager.getInstance().getResources(
                 mToken,
                 mPackageInfo.getResDir(),
                 paths,
                 mPackageInfo.getOverlayDirs(),
                 mPackageInfo.getApplicationInfo().sharedLibraryFiles,
-                displayId,
+                mForceDisplayOverrideInResources ? getDisplayId() : null,
                 null,
                 mPackageInfo.getCompatibilityInfo(),
                 classLoader,
@@ -2369,12 +2394,23 @@
             throw new IllegalArgumentException("overrideConfiguration must not be null");
         }
 
+        if (mForceDisplayOverrideInResources) {
+            // Ensure the resources display metrics are adjusted to match the display this context
+            // is based on.
+            Configuration displayAdjustedConfig = new Configuration();
+            displayAdjustedConfig.setTo(mDisplay.getDisplayAdjustments().getConfiguration(),
+                    ActivityInfo.CONFIG_WINDOW_CONFIGURATION, 1);
+            displayAdjustedConfig.updateFrom(overrideConfiguration);
+            overrideConfiguration = displayAdjustedConfig;
+        }
+
         ContextImpl context = new ContextImpl(this, mMainThread, mPackageInfo, mAttributionTag,
                 mSplitName, mToken, mUser, mFlags, mClassLoader, null);
 
         final int displayId = getDisplayId();
-
-        context.setResources(createResources(mToken, mPackageInfo, mSplitName, displayId,
+        final Integer overrideDisplayId = mForceDisplayOverrideInResources
+                ? displayId : null;
+        context.setResources(createResources(mToken, mPackageInfo, mSplitName, overrideDisplayId,
                 overrideConfiguration, getDisplayAdjustments(displayId).getCompatibilityInfo(),
                 mResources.getLoaders()));
         context.mIsUiContext = isUiContext() || isOuterUiContext();
@@ -2392,11 +2428,20 @@
 
         final int displayId = display.getDisplayId();
 
+        // Ensure the resources display metrics are adjusted to match the provided display.
+        Configuration overrideConfig = new Configuration();
+        overrideConfig.setTo(display.getDisplayAdjustments().getConfiguration(),
+                ActivityInfo.CONFIG_WINDOW_CONFIGURATION, 1);
+
         context.setResources(createResources(mToken, mPackageInfo, mSplitName, displayId,
-                null, getDisplayAdjustments(displayId).getCompatibilityInfo(),
+                overrideConfig, display.getDisplayAdjustments().getCompatibilityInfo(),
                 mResources.getLoaders()));
         context.mDisplay = display;
         context.mIsAssociatedWithDisplay = true;
+        // Display contexts and any context derived from a display context should always override
+        // the display that would otherwise be inherited from mToken (or the global configuration if
+        // mToken is null).
+        context.mForceDisplayOverrideInResources = true;
         return context;
     }
 
@@ -2414,8 +2459,10 @@
         ContextImpl context = new ContextImpl(this, mMainThread, mPackageInfo, mAttributionTag,
                 mSplitName, token, mUser, mFlags, mClassLoader, null);
         context.mIsUiContext = true;
-
         context.mIsAssociatedWithDisplay = true;
+        // Window contexts receive configurations directly from the server and as such do not
+        // need to override their display in ResourcesManager.
+        context.mForceDisplayOverrideInResources = false;
         return context;
     }
 
@@ -2758,6 +2805,7 @@
             mDisplay = container.mDisplay;
             mIsAssociatedWithDisplay = container.mIsAssociatedWithDisplay;
             mIsSystemOrSystemUiContext = container.mIsSystemOrSystemUiContext;
+            mForceDisplayOverrideInResources = container.mForceDisplayOverrideInResources;
         } else {
             mBasePackageName = packageInfo.mPackageName;
             ApplicationInfo ainfo = packageInfo.getApplicationInfo();
diff --git a/core/java/android/app/Dialog.java b/core/java/android/app/Dialog.java
index 5e05506..9833ed6 100644
--- a/core/java/android/app/Dialog.java
+++ b/core/java/android/app/Dialog.java
@@ -24,6 +24,7 @@
 import android.annotation.Nullable;
 import android.annotation.StringRes;
 import android.annotation.StyleRes;
+import android.annotation.UiContext;
 import android.compat.annotation.UnsupportedAppUsage;
 import android.content.ComponentName;
 import android.content.Context;
@@ -101,6 +102,7 @@
     private final WindowManager mWindowManager;
 
     @UnsupportedAppUsage
+    @UiContext
     final Context mContext;
     @UnsupportedAppUsage
     final Window mWindow;
@@ -158,7 +160,7 @@
      * @param context the context in which the dialog should run
      * @see android.R.styleable#Theme_dialogTheme
      */
-    public Dialog(@NonNull Context context) {
+    public Dialog(@UiContext @NonNull Context context) {
         this(context, 0, true);
     }
 
@@ -177,11 +179,12 @@
      * @param themeResId a style resource describing the theme to use for the
      *              window, or {@code 0} to use the default dialog theme
      */
-    public Dialog(@NonNull Context context, @StyleRes int themeResId) {
+    public Dialog(@UiContext @NonNull Context context, @StyleRes int themeResId) {
         this(context, themeResId, true);
     }
 
-    Dialog(@NonNull Context context, @StyleRes int themeResId, boolean createContextThemeWrapper) {
+    Dialog(@UiContext @NonNull Context context, @StyleRes int themeResId,
+            boolean createContextThemeWrapper) {
         if (createContextThemeWrapper) {
             if (themeResId == Resources.ID_NULL) {
                 final TypedValue outValue = new TypedValue();
@@ -222,7 +225,7 @@
         mCancelMessage = cancelCallback;
     }
 
-    protected Dialog(@NonNull Context context, boolean cancelable,
+    protected Dialog(@UiContext @NonNull Context context, boolean cancelable,
             @Nullable OnCancelListener cancelListener) {
         this(context);
         mCancelable = cancelable;
@@ -234,7 +237,9 @@
      * 
      * @return Context The Context used by the Dialog.
      */
-    public final @NonNull Context getContext() {
+    @UiContext
+    @NonNull
+    public final Context getContext() {
         return mContext;
     }
 
diff --git a/core/java/android/app/IActivityManager.aidl b/core/java/android/app/IActivityManager.aidl
index 3b6a7b8..0a47248 100644
--- a/core/java/android/app/IActivityManager.aidl
+++ b/core/java/android/app/IActivityManager.aidl
@@ -123,8 +123,8 @@
             in IIntentReceiver receiver, in IntentFilter filter,
             in String requiredPermission, int userId, int flags);
     Intent registerReceiverWithFeature(in IApplicationThread caller, in String callerPackage,
-            in String callingFeatureId, in IIntentReceiver receiver, in IntentFilter filter,
-            in String requiredPermission, int userId, int flags);
+            in String callingFeatureId, in String receiverId, in IIntentReceiver receiver,
+            in IntentFilter filter, in String requiredPermission, int userId, int flags);
     @UnsupportedAppUsage
     void unregisterReceiver(in IIntentReceiver receiver);
     /** @deprecated Use {@link #broadcastIntentWithFeature} instead */
@@ -156,7 +156,8 @@
     boolean refContentProvider(in IBinder connection, int stableDelta, int unstableDelta);
     PendingIntent getRunningServiceControlPanel(in ComponentName service);
     ComponentName startService(in IApplicationThread caller, in Intent service,
-            in String resolvedType, boolean requireForeground, in String callingPackage,
+            in String resolvedType, boolean requireForeground,
+            boolean hideForegroundNotification, in String callingPackage,
             in String callingFeatureId, int userId);
     @UnsupportedAppUsage
     int stopService(in IApplicationThread caller, in Intent service,
@@ -288,7 +289,8 @@
     void stopAppSwitches();
     @UnsupportedAppUsage
     void resumeAppSwitches();
-    boolean bindBackupAgent(in String packageName, int backupRestoreMode, int targetUserId);
+    boolean bindBackupAgent(in String packageName, int backupRestoreMode, int targetUserId,
+            int operationType);
     void backupAgentCreated(in String packageName, in IBinder agent, int userId);
     void unbindBackupAgent(in ApplicationInfo appInfo);
     int getUidForIntentSender(in IIntentSender sender);
@@ -460,7 +462,7 @@
     @UnsupportedAppUsage
     Rect getTaskBounds(int taskId);
     @UnsupportedAppUsage
-    boolean setProcessMemoryTrimLevel(in String process, int uid, int level);
+    boolean setProcessMemoryTrimLevel(in String process, int userId, int level);
 
 
     // Start of L transactions
diff --git a/core/java/android/app/IApplicationThread.aidl b/core/java/android/app/IApplicationThread.aidl
index 6e9157e..dc9918a 100644
--- a/core/java/android/app/IApplicationThread.aidl
+++ b/core/java/android/app/IApplicationThread.aidl
@@ -16,6 +16,7 @@
 
 package android.app;
 
+import android.app.ContentProviderHolder;
 import android.app.IInstrumentationWatcher;
 import android.app.IUiAutomationConnection;
 import android.app.ProfilerInfo;
@@ -95,7 +96,7 @@
     void profilerControl(boolean start, in ProfilerInfo profilerInfo, int profileType);
     void setSchedulingGroup(int group);
     void scheduleCreateBackupAgent(in ApplicationInfo app, in CompatibilityInfo compatInfo,
-            int backupMode, int userId);
+            int backupMode, int userId, int operationType);
     void scheduleDestroyBackupAgent(in ApplicationInfo app,
             in CompatibilityInfo compatInfo, int userId);
     void scheduleOnNewActivityOptions(IBinder token, in Bundle options);
@@ -147,4 +148,6 @@
     void performDirectAction(IBinder activityToken, String actionId,
             in Bundle arguments, in RemoteCallback cancellationCallback,
             in RemoteCallback resultCallback);
+    void notifyContentProviderPublishStatus(in ContentProviderHolder holder, String auth,
+            int userId, boolean published);
 }
diff --git a/core/java/android/app/IUiAutomationConnection.aidl b/core/java/android/app/IUiAutomationConnection.aidl
index 8c3180b..4c9e400 100644
--- a/core/java/android/app/IUiAutomationConnection.aidl
+++ b/core/java/android/app/IUiAutomationConnection.aidl
@@ -39,7 +39,7 @@
     boolean injectInputEvent(in InputEvent event, boolean sync);
     void syncInputTransactions();
     boolean setRotation(int rotation);
-    Bitmap takeScreenshot(in Rect crop, int rotation);
+    Bitmap takeScreenshot(in Rect crop);
     boolean clearWindowContentFrameStats(int windowId);
     WindowContentFrameStats getWindowContentFrameStats(int windowId);
     void clearWindowAnimationFrameStats();
diff --git a/core/java/android/app/LoadedApk.java b/core/java/android/app/LoadedApk.java
index aa6a08b..202b615 100644
--- a/core/java/android/app/LoadedApk.java
+++ b/core/java/android/app/LoadedApk.java
@@ -56,7 +56,6 @@
 import android.util.Log;
 import android.util.Slog;
 import android.util.SparseArray;
-import android.view.Display;
 import android.view.DisplayAdjustments;
 
 import com.android.internal.util.ArrayUtils;
@@ -367,7 +366,7 @@
 
                 mResources = ResourcesManager.getInstance().getResources(null, mResDir,
                         splitPaths, mOverlayDirs, mApplicationInfo.sharedLibraryFiles,
-                        Display.DEFAULT_DISPLAY, null, getCompatibilityInfo(),
+                        null, null, getCompatibilityInfo(),
                         getClassLoader(), mApplication == null ? null
                                 : mApplication.getResources().getLoaders());
             }
@@ -1231,7 +1230,7 @@
 
             mResources = ResourcesManager.getInstance().getResources(null, mResDir,
                     splitPaths, mOverlayDirs, mApplicationInfo.sharedLibraryFiles,
-                    Display.DEFAULT_DISPLAY, null, getCompatibilityInfo(),
+                    null, null, getCompatibilityInfo(),
                     getClassLoader(), null);
         }
         return mResources;
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java
index 68e6561..6737972 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -1492,6 +1492,7 @@
         private boolean mAllowGeneratedReplies = true;
         private final @SemanticAction int mSemanticAction;
         private final boolean mIsContextual;
+        private boolean mAuthenticationRequired;
 
         /**
          * Small icon representing the action.
@@ -1528,6 +1529,7 @@
             mAllowGeneratedReplies = in.readInt() == 1;
             mSemanticAction = in.readInt();
             mIsContextual = in.readInt() == 1;
+            mAuthenticationRequired = in.readInt() == 1;
         }
 
         /**
@@ -1536,13 +1538,14 @@
         @Deprecated
         public Action(int icon, CharSequence title, PendingIntent intent) {
             this(Icon.createWithResource("", icon), title, intent, new Bundle(), null, true,
-                    SEMANTIC_ACTION_NONE, false /* isContextual */);
+                    SEMANTIC_ACTION_NONE, false /* isContextual */, false /* requireAuth */);
         }
 
         /** Keep in sync with {@link Notification.Action.Builder#Builder(Action)}! */
         private Action(Icon icon, CharSequence title, PendingIntent intent, Bundle extras,
                 RemoteInput[] remoteInputs, boolean allowGeneratedReplies,
-                       @SemanticAction int semanticAction, boolean isContextual) {
+                @SemanticAction int semanticAction, boolean isContextual,
+                boolean requireAuth) {
             this.mIcon = icon;
             if (icon != null && icon.getType() == Icon.TYPE_RESOURCE) {
                 this.icon = icon.getResId();
@@ -1554,6 +1557,7 @@
             this.mAllowGeneratedReplies = allowGeneratedReplies;
             this.mSemanticAction = semanticAction;
             this.mIsContextual = isContextual;
+            this.mAuthenticationRequired = requireAuth;
         }
 
         /**
@@ -1624,6 +1628,17 @@
         }
 
         /**
+         * Returns whether the OS should only send this action's {@link PendingIntent} on an
+         * unlocked device.
+         *
+         * If the device is locked when the action is invoked, the OS should show the keyguard and
+         * require successful authentication before invoking the intent.
+         */
+        public boolean isAuthenticationRequired() {
+            return mAuthenticationRequired;
+        }
+
+        /**
          * Builder class for {@link Action} objects.
          */
         public static final class Builder {
@@ -1635,6 +1650,7 @@
             @Nullable private ArrayList<RemoteInput> mRemoteInputs;
             private @SemanticAction int mSemanticAction;
             private boolean mIsContextual;
+            private boolean mAuthenticationRequired;
 
             /**
              * Construct a new builder for {@link Action} object.
@@ -1654,7 +1670,7 @@
              * @param intent the {@link PendingIntent} to fire when users trigger this action
              */
             public Builder(Icon icon, CharSequence title, PendingIntent intent) {
-                this(icon, title, intent, new Bundle(), null, true, SEMANTIC_ACTION_NONE);
+                this(icon, title, intent, new Bundle(), null, true, SEMANTIC_ACTION_NONE, false);
             }
 
             /**
@@ -1665,23 +1681,25 @@
             public Builder(Action action) {
                 this(action.getIcon(), action.title, action.actionIntent,
                         new Bundle(action.mExtras), action.getRemoteInputs(),
-                        action.getAllowGeneratedReplies(), action.getSemanticAction());
+                        action.getAllowGeneratedReplies(), action.getSemanticAction(),
+                        action.isAuthenticationRequired());
             }
 
             private Builder(@Nullable Icon icon, @Nullable CharSequence title,
                     @Nullable PendingIntent intent, @NonNull Bundle extras,
                     @Nullable RemoteInput[] remoteInputs, boolean allowGeneratedReplies,
-                    @SemanticAction int semanticAction) {
+                    @SemanticAction int semanticAction, boolean authRequired) {
                 mIcon = icon;
                 mTitle = title;
                 mIntent = intent;
                 mExtras = extras;
                 if (remoteInputs != null) {
-                    mRemoteInputs = new ArrayList<RemoteInput>(remoteInputs.length);
+                    mRemoteInputs = new ArrayList<>(remoteInputs.length);
                     Collections.addAll(mRemoteInputs, remoteInputs);
                 }
                 mAllowGeneratedReplies = allowGeneratedReplies;
                 mSemanticAction = semanticAction;
+                mAuthenticationRequired = authRequired;
             }
 
             /**
@@ -1776,6 +1794,21 @@
             }
 
             /**
+             * Sets whether the OS should only send this action's {@link PendingIntent} on an
+             * unlocked device.
+             *
+             * If this is true and the device is locked when the action is invoked, the OS will
+             * show the keyguard and require successful authentication before invoking the intent.
+             * If this is false and the device is locked, the OS will decide whether authentication
+             * should be required.
+             */
+            @NonNull
+            public Builder setAuthenticationRequired(boolean authenticationRequired) {
+                mAuthenticationRequired = authenticationRequired;
+                return this;
+            }
+
+            /**
              * Throws an NPE if we are building a contextual action missing one of the fields
              * necessary to display the action.
              */
@@ -1827,7 +1860,8 @@
                 RemoteInput[] textInputsArr = textInputs.isEmpty()
                         ? null : textInputs.toArray(new RemoteInput[textInputs.size()]);
                 return new Action(mIcon, mTitle, mIntent, mExtras, textInputsArr,
-                        mAllowGeneratedReplies, mSemanticAction, mIsContextual);
+                        mAllowGeneratedReplies, mSemanticAction, mIsContextual,
+                        mAuthenticationRequired);
             }
         }
 
@@ -1841,7 +1875,8 @@
                     getRemoteInputs(),
                     getAllowGeneratedReplies(),
                     getSemanticAction(),
-                    isContextual());
+                    isContextual(),
+                    isAuthenticationRequired());
         }
 
         @Override
@@ -1870,6 +1905,7 @@
             out.writeInt(mAllowGeneratedReplies ? 1 : 0);
             out.writeInt(mSemanticAction);
             out.writeInt(mIsContextual ? 1 : 0);
+            out.writeInt(mAuthenticationRequired ? 1 : 0);
         }
 
         public static final @android.annotation.NonNull Parcelable.Creator<Action> CREATOR =
@@ -5151,19 +5187,11 @@
             bindHeaderChronometerAndTime(contentView, p);
             bindProfileBadge(contentView, p);
             bindAlertedIcon(contentView, p);
-            bindActivePermissions(contentView, p);
             bindFeedbackIcon(contentView, p);
             bindExpandButton(contentView, p);
             mN.mUsesStandardHeader = true;
         }
 
-        private void bindActivePermissions(RemoteViews contentView, StandardTemplateParams p) {
-            int color = getNeutralColor(p);
-            contentView.setDrawableTint(R.id.camera, false, color, PorterDuff.Mode.SRC_ATOP);
-            contentView.setDrawableTint(R.id.mic, false, color, PorterDuff.Mode.SRC_ATOP);
-            contentView.setDrawableTint(R.id.overlay, false, color, PorterDuff.Mode.SRC_ATOP);
-        }
-
         private void bindFeedbackIcon(RemoteViews contentView, StandardTemplateParams p) {
             int color = getNeutralColor(p);
             contentView.setDrawableTint(R.id.feedback, false, color, PorterDuff.Mode.SRC_ATOP);
diff --git a/core/java/android/app/NotificationManager.java b/core/java/android/app/NotificationManager.java
index 8ee995d..0627bc8 100644
--- a/core/java/android/app/NotificationManager.java
+++ b/core/java/android/app/NotificationManager.java
@@ -282,6 +282,16 @@
             = "android.app.action.INTERRUPTION_FILTER_CHANGED";
 
     /**
+     * Intent that is broadcast when the state of
+     * {@link #hasEnabledNotificationListener(String, UserHandle)} changes.
+     * @hide
+     */
+    @SdkConstant(SdkConstant.SdkConstantType.BROADCAST_INTENT_ACTION)
+    @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+    public static final String ACTION_NOTIFICATION_LISTENER_ENABLED_CHANGED =
+            "android.app.action.NOTIFICATION_LISTENER_ENABLED_CHANGED";
+
+    /**
      * Intent that is broadcast when the state of getCurrentInterruptionFilter() changes.
      * @hide
      */
diff --git a/core/java/android/app/PropertyInvalidatedCache.java b/core/java/android/app/PropertyInvalidatedCache.java
index bca6f39..6c6c04e 100644
--- a/core/java/android/app/PropertyInvalidatedCache.java
+++ b/core/java/android/app/PropertyInvalidatedCache.java
@@ -215,7 +215,7 @@
     private long mMisses = 0;
 
     @GuardedBy("mLock")
-    private long mMissDisabled[] = new long[]{ 0, 0, 0 };
+    private long mSkips[] = new long[]{ 0, 0, 0 };
 
     @GuardedBy("mLock")
     private long mMissOverflow = 0;
@@ -223,6 +223,9 @@
     @GuardedBy("mLock")
     private long mHighWaterMark = 0;
 
+    @GuardedBy("mLock")
+    private long mClears = 0;
+
     // Most invalidation is done in a static context, so the counters need to be accessible.
     @GuardedBy("sCorkLock")
     private static final HashMap<String, Long> sInvalidates = new HashMap<>();
@@ -273,6 +276,13 @@
      */
     private volatile SystemProperties.Handle mPropertyHandle;
 
+    /**
+     * The name by which this cache is known.  This should normally be the
+     * binder call that is being cached, but the constructors default it to
+     * the property name.
+     */
+    private final String mCacheName;
+
     @GuardedBy("mLock")
     private final LinkedHashMap<Query, Result> mCache;
 
@@ -297,9 +307,23 @@
      *
      * @param maxEntries Maximum number of entries to cache; LRU discard
      * @param propertyName Name of the system property holding the cache invalidation nonce
+     * Defaults the cache name to the property name.
      */
     public PropertyInvalidatedCache(int maxEntries, @NonNull String propertyName) {
+        this(maxEntries, propertyName, propertyName);
+    }
+
+    /**
+     * Make a new property invalidated cache.
+     *
+     * @param maxEntries Maximum number of entries to cache; LRU discard
+     * @param propertyName Name of the system property holding the cache invalidation nonce
+     * @param cacheName Name of this cache in debug and dumpsys
+     */
+    public PropertyInvalidatedCache(int maxEntries, @NonNull String propertyName,
+            @NonNull String cacheName) {
         mPropertyName = propertyName;
+        mCacheName = cacheName;
         mMaxEntries = maxEntries;
         mCache = new LinkedHashMap<Query, Result>(
             2 /* start small */,
@@ -320,7 +344,6 @@
             };
         synchronized (sCorkLock) {
             sCaches.put(this, null);
-            sInvalidates.put(propertyName, (long) 0);
         }
     }
 
@@ -333,6 +356,7 @@
                 Log.d(TAG, "clearing cache for " + mPropertyName);
             }
             mCache.clear();
+            mClears++;
         }
     }
 
@@ -389,7 +413,7 @@
     public final void disableLocal() {
         synchronized (mLock) {
             mDisabled = true;
-            mCache.clear();
+            clear();
         }
     }
 
@@ -413,7 +437,7 @@
                     // Do not bother collecting statistics if the cache is
                     // locally disabled.
                     synchronized (mLock) {
-                        mMissDisabled[(int) currentNonce]++;
+                        mSkips[(int) currentNonce]++;
                     }
                 }
 
@@ -439,7 +463,7 @@
                             cacheName(), mCache.size(),
                             mLastSeenNonce, currentNonce));
                     }
-                    mCache.clear();
+                    clear();
                     mLastSeenNonce = currentNonce;
                     cachedResult = null;
                 }
@@ -704,9 +728,13 @@
      * It's better to use explicit cork and uncork pairs that tighly surround big batches of
      * invalidations, but it's not always practical to tell where these invalidation batches
      * might occur. AutoCorker's time-based corking is a decent alternative.
+     *
+     * The auto-cork delay is configurable but it should not be too long.  The purpose of
+     * the delay is to minimize the number of times a server writes to the system property
+     * when invalidating the cache.  One write every 50ms does not hurt system performance.
      */
     public static final class AutoCorker {
-        public static final int DEFAULT_AUTO_CORK_DELAY_MS = 2000;
+        public static final int DEFAULT_AUTO_CORK_DELAY_MS = 50;
 
         private final String mPropertyName;
         private final int mAutoCorkDelayMs;
@@ -742,12 +770,16 @@
                 boolean alreadyQueued = mUncorkDeadlineMs >= 0;
                 if (DEBUG) {
                     Log.w(TAG, String.format(
-                                    "autoCork mUncorkDeadlineMs=%s", mUncorkDeadlineMs));
+                            "autoCork %s mUncorkDeadlineMs=%s", mPropertyName,
+                            mUncorkDeadlineMs));
                 }
                 mUncorkDeadlineMs = SystemClock.uptimeMillis() + mAutoCorkDelayMs;
                 if (!alreadyQueued) {
                     getHandlerLocked().sendEmptyMessageAtTime(0, mUncorkDeadlineMs);
                     PropertyInvalidatedCache.corkInvalidations(mPropertyName);
+                } else {
+                    final long count = sCorkedInvalidates.getOrDefault(mPropertyName, (long) 0);
+                    sCorkedInvalidates.put(mPropertyName, count + 1);
                 }
             }
         }
@@ -756,7 +788,8 @@
             synchronized (mLock) {
                 if (DEBUG) {
                     Log.w(TAG, String.format(
-                                    "handleMsesage mUncorkDeadlineMs=%s", mUncorkDeadlineMs));
+                            "handleMsesage %s mUncorkDeadlineMs=%s",
+                            mPropertyName, mUncorkDeadlineMs));
                 }
 
                 if (mUncorkDeadlineMs < 0) {
@@ -816,7 +849,7 @@
      * method is public so clients can use it.
      */
     public String cacheName() {
-        return mPropertyName;
+        return mCacheName;
     }
 
     /**
@@ -864,16 +897,20 @@
         }
 
         synchronized (mLock) {
-            pw.println(String.format("  Cache Property Name: %s", cacheName()));
-            pw.println(String.format("    Hits: %d, Misses: %d, Invalidates: %d, Overflows: %d",
-                    mHits, mMisses, invalidateCount, mMissOverflow));
-            pw.println(String.format("    Miss-corked: %d, Miss-unset: %d, Miss-other: %d," +
-                    " CorkedInvalidates: %d",
-                    mMissDisabled[NONCE_CORKED], mMissDisabled[NONCE_UNSET],
-                    mMissDisabled[NONCE_DISABLED], corkedInvalidates));
-            pw.println(String.format("    Last Observed Nonce: %d", mLastSeenNonce));
-            pw.println(String.format("    Current Size: %d, Max Size: %d, HW Mark: %d",
-                    mCache.size(), mMaxEntries, mHighWaterMark));
+            pw.println(String.format("  Cache Name: %s", cacheName()));
+            pw.println(String.format("    Property: %s", mPropertyName));
+            final long skips = mSkips[NONCE_CORKED] + mSkips[NONCE_UNSET] + mSkips[NONCE_DISABLED];
+            pw.println(String.format("    Hits: %d, Misses: %d, Skips: %d, Clears: %d",
+                    mHits, mMisses, skips, mClears));
+            pw.println(String.format("    Skip-corked: %d, Skip-unset: %d, Skip-other: %d",
+                    mSkips[NONCE_CORKED], mSkips[NONCE_UNSET],
+                    mSkips[NONCE_DISABLED]));
+            pw.println(String.format(
+                    "    Nonce: 0x%016x, Invalidates: %d, CorkedInvalidates: %d",
+                    mLastSeenNonce, invalidateCount, corkedInvalidates));
+            pw.println(String.format(
+                    "    Current Size: %d, Max Size: %d, HW Mark: %d, Overflows: %d",
+                    mCache.size(), mMaxEntries, mHighWaterMark, mMissOverflow));
             pw.println(String.format("    Enabled: %s", mDisabled ? "false" : "true"));
 
             Set<Map.Entry<Query, Result>> cacheEntries = mCache.entrySet();
diff --git a/core/java/android/app/ResourcesManager.java b/core/java/android/app/ResourcesManager.java
index 7477899..7cd3fca 100644
--- a/core/java/android/app/ResourcesManager.java
+++ b/core/java/android/app/ResourcesManager.java
@@ -17,6 +17,8 @@
 package android.app;
 
 import static android.app.ActivityThread.DEBUG_CONFIGURATION;
+import static android.view.Display.DEFAULT_DISPLAY;
+import static android.view.Display.INVALID_DISPLAY;
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -39,7 +41,6 @@
 import android.util.ArrayMap;
 import android.util.DisplayMetrics;
 import android.util.Log;
-import android.util.LruCache;
 import android.util.Pair;
 import android.util.Slog;
 import android.view.Display;
@@ -62,7 +63,7 @@
 import java.util.Objects;
 import java.util.WeakHashMap;
 import java.util.function.Consumer;
-import java.util.function.Predicate;
+import java.util.function.Function;
 
 /** @hide */
 public class ResourcesManager {
@@ -84,6 +85,12 @@
     private final Configuration mResConfiguration = new Configuration();
 
     /**
+     * The display upon which all Resources are based. Activity, window token, and display context
+     * resources apply their overrides to this display id.
+     */
+    private int mResDisplayId = DEFAULT_DISPLAY;
+
+    /**
      * A mapping of ResourceImpls and their configurations. These are heavy weight objects
      * which should be reused as much as possible.
      */
@@ -129,35 +136,107 @@
         }
     }
 
-    private static final boolean ENABLE_APK_ASSETS_CACHE = false;
-
     /**
-     * The ApkAssets we are caching and intend to hold strong references to.
+     * Loads {@link ApkAssets} and caches them to prevent their garbage collection while the
+     * instance is alive and reachable.
      */
-    private final LruCache<ApkKey, ApkAssets> mLoadedApkAssets =
-            (ENABLE_APK_ASSETS_CACHE) ? new LruCache<>(3) : null;
+    private class ApkAssetsSupplier {
+        final ArrayMap<ApkKey, ApkAssets> mLocalCache = new ArrayMap<>();
+
+        /**
+         * Retrieves the {@link ApkAssets} corresponding to the specified key, caches the ApkAssets
+         * within this instance, and inserts the loaded ApkAssets into the {@link #mCachedApkAssets}
+         * cache.
+         */
+        ApkAssets load(final ApkKey apkKey) throws IOException {
+            ApkAssets apkAssets = mLocalCache.get(apkKey);
+            if (apkAssets == null) {
+                apkAssets = loadApkAssets(apkKey);
+                mLocalCache.put(apkKey, apkAssets);
+            }
+            return apkAssets;
+        }
+    }
 
     /**
-     * The ApkAssets that are being referenced in the wild that we can reuse, even if they aren't
-     * in our LRU cache. Bonus resources :)
+     * The ApkAssets that are being referenced in the wild that we can reuse.
      */
     private final ArrayMap<ApkKey, WeakReference<ApkAssets>> mCachedApkAssets = new ArrayMap<>();
 
     /**
-     * Resources and base configuration override associated with an Activity.
+     * Class containing the base configuration override and set of resources associated with an
+     * Activity or {@link WindowContext}.
      */
     private static class ActivityResources {
-        @UnsupportedAppUsage
-        private ActivityResources() {
-        }
+        /**
+         * Override config to apply to all resources associated with the token this instance is
+         * based on.
+         *
+         * @see #activityResources
+         * @see #getResources(IBinder, String, String[], String[], String[], Integer, Configuration,
+         * CompatibilityInfo, ClassLoader, List)
+         */
         public final Configuration overrideConfig = new Configuration();
-        public final ArrayList<WeakReference<Resources>> activityResources = new ArrayList<>();
-        final ReferenceQueue<Resources> activityResourcesQueue = new ReferenceQueue<>();
+
+        /**
+         * The display to apply to all resources associated with the token this instance is based
+         * on.
+         */
+        public int overrideDisplayId;
+
+        /** List of {@link ActivityResource} associated with the token this instance is based on. */
+        public final ArrayList<ActivityResource> activityResources = new ArrayList<>();
+
+        public final ReferenceQueue<Resources> activityResourcesQueue = new ReferenceQueue<>();
+
+        @UnsupportedAppUsage
+        private ActivityResources() {}
+
+        /** Returns the number of live resource references within {@code activityResources}. */
+        public int countLiveReferences() {
+            int count = 0;
+            for (int i = 0; i < activityResources.size(); i++) {
+                WeakReference<Resources> resources = activityResources.get(i).resources;
+                if (resources != null && resources.get() != null) {
+                    count++;
+                }
+            }
+            return count;
+        }
     }
 
     /**
-     * Each Activity may has a base override configuration that is applied to each Resources object,
-     * which in turn may have their own override configuration specified.
+     * Contains a resource derived from an {@link Activity} or {@link WindowContext} and information
+     * about how this resource expects its configuration to differ from the token's.
+     *
+     * @see ActivityResources
+     */
+    // TODO: Ideally this class should be called something token related, like TokenBasedResource.
+    private static class ActivityResource {
+        /**
+         * The override configuration applied on top of the token's override config for this
+         * resource.
+         */
+        public final Configuration overrideConfig = new Configuration();
+
+        /**
+         * If non-null this resource expects its configuration to override the display from the
+         * token's configuration.
+         *
+         * @see #applyDisplayMetricsToConfiguration(DisplayMetrics, Configuration)
+         */
+        @Nullable
+        public Integer overrideDisplayId;
+
+        @Nullable
+        public WeakReference<Resources> resources;
+
+        private ActivityResource() {}
+    }
+
+    /**
+     * Each Activity or WindowToken may has a base override configuration that is applied to each
+     * Resources object, which in turn may have their own override configuration specified.
      */
     @UnsupportedAppUsage
     private final WeakHashMap<IBinder, ActivityResources> mActivityResourceReferences =
@@ -228,9 +307,9 @@
         }
     }
 
-    DisplayMetrics getDisplayMetrics() {
-        return getDisplayMetrics(Display.DEFAULT_DISPLAY,
-                DisplayAdjustments.DEFAULT_DISPLAY_ADJUSTMENTS);
+    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
+    public DisplayMetrics getDisplayMetrics() {
+        return getDisplayMetrics(mResDisplayId, DisplayAdjustments.DEFAULT_DISPLAY_ADJUSTMENTS);
     }
 
     /**
@@ -248,8 +327,8 @@
         return dm;
     }
 
-    private static void applyNonDefaultDisplayMetricsToConfiguration(
-            @NonNull DisplayMetrics dm, @NonNull Configuration config) {
+    private static void applyDisplayMetricsToConfiguration(@NonNull DisplayMetrics dm,
+            @NonNull Configuration config) {
         config.touchscreen = Configuration.TOUCHSCREEN_NOTOUCH;
         config.densityDpi = dm.densityDpi;
         config.screenWidthDp = (int) (dm.widthPixels / dm.density);
@@ -337,49 +416,78 @@
         return "/data/resource-cache/" + path.substring(1).replace('/', '@') + "@idmap";
     }
 
-    private @NonNull ApkAssets loadApkAssets(String path, boolean sharedLib, boolean overlay)
-            throws IOException {
-        final ApkKey newKey = new ApkKey(path, sharedLib, overlay);
-        ApkAssets apkAssets = null;
-        if (mLoadedApkAssets != null) {
-            apkAssets = mLoadedApkAssets.get(newKey);
-            if (apkAssets != null && apkAssets.isUpToDate()) {
-                return apkAssets;
-            }
-        }
+    private @NonNull ApkAssets loadApkAssets(@NonNull final ApkKey key) throws IOException {
+        ApkAssets apkAssets;
 
         // Optimistically check if this ApkAssets exists somewhere else.
-        final WeakReference<ApkAssets> apkAssetsRef = mCachedApkAssets.get(newKey);
-        if (apkAssetsRef != null) {
-            apkAssets = apkAssetsRef.get();
-            if (apkAssets != null && apkAssets.isUpToDate()) {
-                if (mLoadedApkAssets != null) {
-                    mLoadedApkAssets.put(newKey, apkAssets);
+        synchronized (this) {
+            final WeakReference<ApkAssets> apkAssetsRef = mCachedApkAssets.get(key);
+            if (apkAssetsRef != null) {
+                apkAssets = apkAssetsRef.get();
+                if (apkAssets != null && apkAssets.isUpToDate()) {
+                    return apkAssets;
+                } else {
+                    // Clean up the reference.
+                    mCachedApkAssets.remove(key);
                 }
-
-                return apkAssets;
-            } else {
-                // Clean up the reference.
-                mCachedApkAssets.remove(newKey);
             }
         }
 
         // We must load this from disk.
-        if (overlay) {
-            apkAssets = ApkAssets.loadOverlayFromPath(overlayPathToIdmapPath(path), 0 /*flags*/);
+        if (key.overlay) {
+            apkAssets = ApkAssets.loadOverlayFromPath(overlayPathToIdmapPath(key.path),
+                    0 /*flags*/);
         } else {
-            apkAssets = ApkAssets.loadFromPath(path, sharedLib ? ApkAssets.PROPERTY_DYNAMIC : 0);
+            apkAssets = ApkAssets.loadFromPath(key.path,
+                    key.sharedLib ? ApkAssets.PROPERTY_DYNAMIC : 0);
         }
 
-        if (mLoadedApkAssets != null) {
-            mLoadedApkAssets.put(newKey, apkAssets);
+        synchronized (this) {
+            mCachedApkAssets.put(key, new WeakReference<>(apkAssets));
         }
 
-        mCachedApkAssets.put(newKey, new WeakReference<>(apkAssets));
         return apkAssets;
     }
 
     /**
+     * Retrieves a list of apk keys representing the ApkAssets that should be loaded for
+     * AssetManagers mapped to the {@param key}.
+     */
+    private static @NonNull ArrayList<ApkKey> extractApkKeys(@NonNull final ResourcesKey key) {
+        final ArrayList<ApkKey> apkKeys = new ArrayList<>();
+
+        // resDir can be null if the 'android' package is creating a new Resources object.
+        // This is fine, since each AssetManager automatically loads the 'android' package
+        // already.
+        if (key.mResDir != null) {
+            apkKeys.add(new ApkKey(key.mResDir, false /*sharedLib*/, false /*overlay*/));
+        }
+
+        if (key.mSplitResDirs != null) {
+            for (final String splitResDir : key.mSplitResDirs) {
+                apkKeys.add(new ApkKey(splitResDir, false /*sharedLib*/, false /*overlay*/));
+            }
+        }
+
+        if (key.mLibDirs != null) {
+            for (final String libDir : key.mLibDirs) {
+                // Avoid opening files we know do not have resources, like code-only .jar files.
+                if (libDir.endsWith(".apk")) {
+                    apkKeys.add(new ApkKey(libDir, true /*sharedLib*/, false /*overlay*/));
+                }
+            }
+        }
+
+        if (key.mOverlayDirs != null) {
+            for (final String idmapPath : key.mOverlayDirs) {
+                apkKeys.add(new ApkKey(idmapPath, false /*sharedLib*/, true /*overlay*/));
+            }
+        }
+
+        return apkKeys;
+    }
+
+    /**
      * Creates an AssetManager from the paths within the ResourcesKey.
      *
      * This can be overridden in tests so as to avoid creating a real AssetManager with
@@ -390,64 +498,38 @@
     @VisibleForTesting
     @UnsupportedAppUsage
     protected @Nullable AssetManager createAssetManager(@NonNull final ResourcesKey key) {
+        return createAssetManager(key, /* apkSupplier */ null);
+    }
+
+    /**
+     * Variant of {@link #createAssetManager(ResourcesKey)} that attempts to load ApkAssets
+     * from an {@link ApkAssetsSupplier} if non-null; otherwise ApkAssets are loaded using
+     * {@link #loadApkAssets(ApkKey)}.
+     */
+    private @Nullable AssetManager createAssetManager(@NonNull final ResourcesKey key,
+            @Nullable ApkAssetsSupplier apkSupplier) {
         final AssetManager.Builder builder = new AssetManager.Builder();
 
-        // resDir can be null if the 'android' package is creating a new Resources object.
-        // This is fine, since each AssetManager automatically loads the 'android' package
-        // already.
-        if (key.mResDir != null) {
+        final ArrayList<ApkKey> apkKeys = extractApkKeys(key);
+        for (int i = 0, n = apkKeys.size(); i < n; i++) {
+            final ApkKey apkKey = apkKeys.get(i);
             try {
-                builder.addApkAssets(loadApkAssets(key.mResDir, false /*sharedLib*/,
-                        false /*overlay*/));
+                builder.addApkAssets(
+                        (apkSupplier != null) ? apkSupplier.load(apkKey) : loadApkAssets(apkKey));
             } catch (IOException e) {
-                Log.e(TAG, "failed to add asset path " + key.mResDir);
-                return null;
-            }
-        }
-
-        if (key.mSplitResDirs != null) {
-            for (final String splitResDir : key.mSplitResDirs) {
-                try {
-                    builder.addApkAssets(loadApkAssets(splitResDir, false /*sharedLib*/,
-                            false /*overlay*/));
-                } catch (IOException e) {
-                    Log.e(TAG, "failed to add split asset path " + splitResDir);
+                if (apkKey.overlay) {
+                    Log.w(TAG, String.format("failed to add overlay path '%s'", apkKey.path), e);
+                } else if (apkKey.sharedLib) {
+                    Log.w(TAG, String.format(
+                            "asset path '%s' does not exist or contains no resources",
+                            apkKey.path), e);
+                } else {
+                    Log.e(TAG, String.format("failed to add asset path '%s'", apkKey.path), e);
                     return null;
                 }
             }
         }
 
-        if (key.mLibDirs != null) {
-            for (final String libDir : key.mLibDirs) {
-                if (libDir.endsWith(".apk")) {
-                    // Avoid opening files we know do not have resources,
-                    // like code-only .jar files.
-                    try {
-                        builder.addApkAssets(loadApkAssets(libDir, true /*sharedLib*/,
-                                false /*overlay*/));
-                    } catch (IOException e) {
-                        Log.w(TAG, "Asset path '" + libDir +
-                                "' does not exist or contains no resources.");
-
-                        // continue.
-                    }
-                }
-            }
-        }
-
-        if (key.mOverlayDirs != null) {
-            for (final String idmapPath : key.mOverlayDirs) {
-                try {
-                    builder.addApkAssets(loadApkAssets(idmapPath, false /*sharedLib*/,
-                            true /*overlay*/));
-                } catch (IOException e) {
-                    Log.w(TAG, "failed to add overlay path " + idmapPath);
-
-                    // continue.
-                }
-            }
-        }
-
         if (key.mLoaders != null) {
             for (final ResourcesLoader loader : key.mLoaders) {
                 builder.addLoader(loader);
@@ -480,24 +562,6 @@
 
             pw.println("ResourcesManager:");
             pw.increaseIndent();
-            if (mLoadedApkAssets != null) {
-                pw.print("cached apks: total=");
-                pw.print(mLoadedApkAssets.size());
-                pw.print(" created=");
-                pw.print(mLoadedApkAssets.createCount());
-                pw.print(" evicted=");
-                pw.print(mLoadedApkAssets.evictionCount());
-                pw.print(" hit=");
-                pw.print(mLoadedApkAssets.hitCount());
-                pw.print(" miss=");
-                pw.print(mLoadedApkAssets.missCount());
-                pw.print(" max=");
-                pw.print(mLoadedApkAssets.maxSize());
-            } else {
-                pw.print("cached apks: 0 [cache disabled]");
-            }
-            pw.println();
-
             pw.print("total apks: ");
             pw.println(countLiveReferences(mCachedApkAssets.values()));
 
@@ -505,7 +569,7 @@
 
             int references = countLiveReferences(mResourceReferences);
             for (ActivityResources activityResources : mActivityResourceReferences.values()) {
-                references += countLiveReferences(activityResources.activityResources);
+                references += activityResources.countLiveReferences();
             }
             pw.println(references);
 
@@ -514,37 +578,36 @@
         }
     }
 
-    private Configuration generateConfig(@NonNull ResourcesKey key, @NonNull DisplayMetrics dm) {
+    private Configuration generateConfig(@NonNull ResourcesKey key) {
         Configuration config;
-        final boolean isDefaultDisplay = (key.mDisplayId == Display.DEFAULT_DISPLAY);
         final boolean hasOverrideConfig = key.hasOverrideConfiguration();
-        if (!isDefaultDisplay || hasOverrideConfig) {
+        if (hasOverrideConfig) {
             config = new Configuration(getConfiguration());
-            if (!isDefaultDisplay) {
-                applyNonDefaultDisplayMetricsToConfiguration(dm, config);
-            }
-            if (hasOverrideConfig) {
-                config.updateFrom(key.mOverrideConfiguration);
-                if (DEBUG) Slog.v(TAG, "Applied overrideConfig=" + key.mOverrideConfiguration);
-            }
+            config.updateFrom(key.mOverrideConfiguration);
+            if (DEBUG) Slog.v(TAG, "Applied overrideConfig=" + key.mOverrideConfiguration);
         } else {
             config = getConfiguration();
         }
         return config;
     }
 
-    private @Nullable ResourcesImpl createResourcesImpl(@NonNull ResourcesKey key) {
-        final DisplayAdjustments daj = new DisplayAdjustments(key.mOverrideConfiguration);
-        daj.setCompatibilityInfo(key.mCompatInfo);
+    private int generateDisplayId(@NonNull ResourcesKey key) {
+        return key.mDisplayId != INVALID_DISPLAY ? key.mDisplayId : mResDisplayId;
+    }
 
-        final AssetManager assets = createAssetManager(key);
+    private @Nullable ResourcesImpl createResourcesImpl(@NonNull ResourcesKey key,
+            @Nullable ApkAssetsSupplier apkSupplier) {
+        final AssetManager assets = createAssetManager(key, apkSupplier);
         if (assets == null) {
             return null;
         }
 
-        final DisplayMetrics dm = getDisplayMetrics(key.mDisplayId, daj);
-        final Configuration config = generateConfig(key, dm);
-        final ResourcesImpl impl = new ResourcesImpl(assets, dm, config, daj);
+        final DisplayAdjustments daj = new DisplayAdjustments(key.mOverrideConfiguration);
+        daj.setCompatibilityInfo(key.mCompatInfo);
+
+        final Configuration config = generateConfig(key);
+        final DisplayMetrics displayMetrics = getDisplayMetrics(generateDisplayId(key), daj);
+        final ResourcesImpl impl = new ResourcesImpl(assets, displayMetrics, config, daj);
 
         if (DEBUG) {
             Slog.d(TAG, "- creating impl=" + impl + " with key: " + key);
@@ -575,9 +638,18 @@
      */
     private @Nullable ResourcesImpl findOrCreateResourcesImplForKeyLocked(
             @NonNull ResourcesKey key) {
+        return findOrCreateResourcesImplForKeyLocked(key, /* apkSupplier */ null);
+    }
+
+    /**
+     * Variant of {@link #findOrCreateResourcesImplForKeyLocked(ResourcesKey)} that attempts to
+     * load ApkAssets from a {@link ApkAssetsSupplier} when creating a new ResourcesImpl.
+     */
+    private @Nullable ResourcesImpl findOrCreateResourcesImplForKeyLocked(
+            @NonNull ResourcesKey key, @Nullable ApkAssetsSupplier apkSupplier) {
         ResourcesImpl impl = findResourcesImplForKeyLocked(key);
         if (impl == null) {
-            impl = createResourcesImpl(key);
+            impl = createResourcesImpl(key, apkSupplier);
             if (impl != null) {
                 mResourceImpls.put(key, new WeakReference<>(impl));
             }
@@ -645,8 +717,8 @@
 
         final int size = activityResources.activityResources.size();
         for (int index = 0; index < size; index++) {
-            WeakReference<Resources> ref = activityResources.activityResources.get(index);
-            Resources resources = ref.get();
+            ActivityResource activityResource = activityResources.activityResources.get(index);
+            Resources resources = activityResource.resources.get();
             ResourcesKey key = resources == null ? null : findKeyForResourceImplLocked(
                     resources.getImpl());
 
@@ -660,20 +732,28 @@
         return null;
     }
 
-    private @NonNull Resources createResourcesForActivityLocked(@NonNull IBinder activityToken,
+    @NonNull
+    private Resources createResourcesForActivityLocked(@NonNull IBinder activityToken,
+            @NonNull Configuration initialOverrideConfig, @Nullable Integer overrideDisplayId,
             @NonNull ClassLoader classLoader, @NonNull ResourcesImpl impl,
             @NonNull CompatibilityInfo compatInfo) {
         final ActivityResources activityResources = getOrCreateActivityResourcesStructLocked(
                 activityToken);
         cleanupReferences(activityResources.activityResources,
-                activityResources.activityResourcesQueue);
+                activityResources.activityResourcesQueue,
+                (r) -> r.resources);
 
         Resources resources = compatInfo.needsCompatResources() ? new CompatResources(classLoader)
                 : new Resources(classLoader);
         resources.setImpl(impl);
         resources.setCallbacks(mUpdateCallbacks);
-        activityResources.activityResources.add(
-                new WeakReference<>(resources, activityResources.activityResourcesQueue));
+
+        ActivityResource activityResource = new ActivityResource();
+        activityResource.resources = new WeakReference<>(resources,
+                activityResources.activityResourcesQueue);
+        activityResource.overrideConfig.setTo(initialOverrideConfig);
+        activityResource.overrideDisplayId = overrideDisplayId;
+        activityResources.activityResources.add(activityResource);
         if (DEBUG) {
             Slog.d(TAG, "- creating new ref=" + resources);
             Slog.d(TAG, "- setting ref=" + resources + " with impl=" + impl);
@@ -699,7 +779,7 @@
 
     /**
      * Creates base resources for a binder token. Calls to
-     * {@link #getResources(IBinder, String, String[], String[], String[], int, Configuration,
+     * {@link #getResources(IBinder, String, String[], String[], String[], Integer, Configuration,
      * CompatibilityInfo, ClassLoader, List)} with the same binder token will have their override
      * configurations merged with the one specified here.
      *
@@ -736,7 +816,7 @@
                     overlayDirs,
                     libDirs,
                     displayId,
-                    overrideConfig != null ? new Configuration(overrideConfig) : null, // Copy
+                    overrideConfig,
                     compatInfo,
                     loaders == null ? null : loaders.toArray(new ResourcesLoader[0]));
             classLoader = classLoader != null ? classLoader : ClassLoader.getSystemClassLoader();
@@ -752,10 +832,7 @@
             }
 
             // Update any existing Activity Resources references.
-            updateResourcesForActivity(token, overrideConfig, displayId,
-                    false /* movedToDifferentDisplay */);
-
-            rebaseKeyForActivity(token, key);
+            updateResourcesForActivity(token, overrideConfig, displayId);
 
             synchronized (this) {
                 Resources resources = findResourcesForActivityLocked(token, key,
@@ -766,7 +843,9 @@
             }
 
             // Now request an actual Resources object.
-            return createResources(token, key, classLoader);
+            return createResourcesForActivity(token, key,
+                    /* initialOverrideConfig */ Configuration.EMPTY, /* overrideDisplayId */ null,
+                    classLoader, /* apkSupplier */ null);
         } finally {
             Trace.traceEnd(Trace.TRACE_TAG_RESOURCES);
         }
@@ -774,45 +853,129 @@
 
     /**
      * Rebases a key's override config on top of the Activity's base override.
+     *
+     * @param activityToken the token the supplied {@code key} is derived from.
+     * @param key the key to rebase
+     * @param overridesActivityDisplay whether this key is overriding the display from the token
      */
-    private void rebaseKeyForActivity(IBinder activityToken, ResourcesKey key) {
+    private void rebaseKeyForActivity(IBinder activityToken, ResourcesKey key,
+            boolean overridesActivityDisplay) {
         synchronized (this) {
             final ActivityResources activityResources =
                     getOrCreateActivityResourcesStructLocked(activityToken);
 
-            // Rebase the key's override config on top of the Activity's base override.
-            if (key.hasOverrideConfiguration()
-                    && !activityResources.overrideConfig.equals(Configuration.EMPTY)) {
-                final Configuration temp = new Configuration(activityResources.overrideConfig);
-                temp.updateFrom(key.mOverrideConfiguration);
-                key.mOverrideConfiguration.setTo(temp);
+            if (key.mDisplayId == INVALID_DISPLAY) {
+                key.mDisplayId = activityResources.overrideDisplayId;
             }
+
+            Configuration config;
+            if (key.hasOverrideConfiguration()) {
+                config = new Configuration(activityResources.overrideConfig);
+                config.updateFrom(key.mOverrideConfiguration);
+            } else {
+                config = activityResources.overrideConfig;
+            }
+
+            if (overridesActivityDisplay
+                    && key.mOverrideConfiguration.windowConfiguration.getAppBounds() == null) {
+                if (!key.hasOverrideConfiguration()) {
+                    // Make a copy to handle the case where the override config is set to defaults.
+                    config = new Configuration(config);
+                }
+
+                // If this key is overriding the display from the token and the key's
+                // window config app bounds is null we need to explicitly override this to
+                // ensure the display adjustments are as expected.
+                config.windowConfiguration.setAppBounds(null);
+            }
+
+            key.mOverrideConfiguration.setTo(config);
         }
     }
 
     /**
+     * Rebases a key's override config with display metrics of the {@code overrideDisplay} paired
+     * with the {code displayAdjustments}.
+     *
+     * @see #applyDisplayMetricsToConfiguration(DisplayMetrics, Configuration)
+     */
+    private void rebaseKeyForDisplay(ResourcesKey key, int overrideDisplay) {
+        final Configuration temp = new Configuration();
+
+        DisplayAdjustments daj = new DisplayAdjustments(key.mOverrideConfiguration);
+        daj.setCompatibilityInfo(key.mCompatInfo);
+
+        final DisplayMetrics dm = getDisplayMetrics(overrideDisplay, daj);
+        applyDisplayMetricsToConfiguration(dm, temp);
+
+        if (key.hasOverrideConfiguration()) {
+            temp.updateFrom(key.mOverrideConfiguration);
+        }
+        key.mOverrideConfiguration.setTo(temp);
+    }
+
+    /**
      * Check WeakReferences and remove any dead references so they don't pile up.
      */
     private static <T> void cleanupReferences(ArrayList<WeakReference<T>> references,
             ReferenceQueue<T> referenceQueue) {
-        Reference<? extends T> enduedRef = referenceQueue.poll();
-        if (enduedRef == null) {
+        cleanupReferences(references, referenceQueue, Function.identity());
+    }
+
+    /**
+     * Check WeakReferences and remove any dead references so they don't pile up.
+     */
+    private static <C, T> void cleanupReferences(ArrayList<C> referenceContainers,
+            ReferenceQueue<T> referenceQueue, Function<C, WeakReference<T>> unwrappingFunction) {
+        Reference<? extends T> enqueuedRef = referenceQueue.poll();
+        if (enqueuedRef == null) {
             return;
         }
 
         final HashSet<Reference<? extends T>> deadReferences = new HashSet<>();
-        for (; enduedRef != null; enduedRef = referenceQueue.poll()) {
-            deadReferences.add(enduedRef);
+        for (; enqueuedRef != null; enqueuedRef = referenceQueue.poll()) {
+            deadReferences.add(enqueuedRef);
         }
 
-        ArrayUtils.unstableRemoveIf(references,
-                (ref) -> ref == null || deadReferences.contains(ref));
+        ArrayUtils.unstableRemoveIf(referenceContainers, (refContainer) -> {
+            WeakReference<T> ref = unwrappingFunction.apply(refContainer);
+            return ref == null || deadReferences.contains(ref);
+        });
+    }
+
+    /**
+     * Creates an {@link ApkAssetsSupplier} and loads all the ApkAssets required by the {@param key}
+     * into the supplier. This should be done while the lock is not held to prevent performing I/O
+     * while holding the lock.
+     */
+    private @NonNull ApkAssetsSupplier createApkAssetsSupplierNotLocked(@NonNull ResourcesKey key) {
+        Trace.traceBegin(Trace.TRACE_TAG_RESOURCES,
+                "ResourcesManager#createApkAssetsSupplierNotLocked");
+        try {
+            if (DEBUG && Thread.holdsLock(this)) {
+                Slog.w(TAG, "Calling thread " + Thread.currentThread().getName()
+                    + " is holding mLock", new Throwable());
+            }
+
+            final ApkAssetsSupplier supplier = new ApkAssetsSupplier();
+            final ArrayList<ApkKey> apkKeys = extractApkKeys(key);
+            for (int i = 0, n = apkKeys.size(); i < n; i++) {
+                final ApkKey apkKey = apkKeys.get(i);
+                try {
+                    supplier.load(apkKey);
+                } catch (IOException e) {
+                    Log.w(TAG, String.format("failed to preload asset path '%s'", apkKey.path), e);
+                }
+            }
+            return supplier;
+        } finally {
+            Trace.traceEnd(Trace.TRACE_TAG_RESOURCES);
+        }
     }
 
     /**
      * Creates a Resources object set with a ResourcesImpl object matching the given key.
      *
-     * @param activityToken The Activity this Resources object should be associated with.
      * @param key The key describing the parameters of the ResourcesImpl object.
      * @param classLoader The classloader to use for the Resources object.
      *                    If null, {@link ClassLoader#getSystemClassLoader()} is used.
@@ -820,8 +983,30 @@
      *         {@link #applyConfigurationToResourcesLocked(Configuration, CompatibilityInfo)}
      *         is called.
      */
-    private @Nullable Resources createResources(@Nullable IBinder activityToken,
-            @NonNull ResourcesKey key, @NonNull ClassLoader classLoader) {
+    @Nullable
+    private Resources createResources(@NonNull ResourcesKey key, @NonNull ClassLoader classLoader,
+            @Nullable ApkAssetsSupplier apkSupplier) {
+        synchronized (this) {
+            if (DEBUG) {
+                Throwable here = new Throwable();
+                here.fillInStackTrace();
+                Slog.w(TAG, "!! Create resources for key=" + key, here);
+            }
+
+            ResourcesImpl resourcesImpl = findOrCreateResourcesImplForKeyLocked(key, apkSupplier);
+            if (resourcesImpl == null) {
+                return null;
+            }
+
+            return createResourcesLocked(classLoader, resourcesImpl, key.mCompatInfo);
+        }
+    }
+
+    @Nullable
+    private Resources createResourcesForActivity(@NonNull IBinder activityToken,
+            @NonNull ResourcesKey key, @NonNull Configuration initialOverrideConfig,
+            @Nullable Integer overrideDisplayId, @NonNull ClassLoader classLoader,
+            @Nullable ApkAssetsSupplier apkSupplier) {
         synchronized (this) {
             if (DEBUG) {
                 Throwable here = new Throwable();
@@ -829,17 +1014,13 @@
                 Slog.w(TAG, "!! Get resources for activity=" + activityToken + " key=" + key, here);
             }
 
-            ResourcesImpl resourcesImpl = findOrCreateResourcesImplForKeyLocked(key);
+            ResourcesImpl resourcesImpl = findOrCreateResourcesImplForKeyLocked(key, apkSupplier);
             if (resourcesImpl == null) {
                 return null;
             }
 
-            if (activityToken != null) {
-                return createResourcesForActivityLocked(activityToken, classLoader,
-                        resourcesImpl, key.mCompatInfo);
-            } else {
-                return createResourcesLocked(classLoader, resourcesImpl, key.mCompatInfo);
-            }
+            return createResourcesForActivityLocked(activityToken, initialOverrideConfig,
+                    overrideDisplayId, classLoader, resourcesImpl, key.mCompatInfo);
         }
     }
 
@@ -860,7 +1041,10 @@
      * @param splitResDirs An array of split resource paths. Can be null.
      * @param overlayDirs An array of overlay paths. Can be null.
      * @param libDirs An array of resource library paths. Can be null.
-     * @param displayId The ID of the display for which to create the resources.
+     * @param overrideDisplayId The ID of the display for which the returned Resources should be
+     * based. This will cause display-based configuration properties to override those of the base
+     * Resources for the {@code activityToken}, or the global configuration if {@code activityToken}
+     * is null.
      * @param overrideConfig The configuration to apply on top of the base configuration. Can be
      * null. Mostly used with Activities that are in multi-window which may override width and
      * height properties from the base config.
@@ -870,13 +1054,14 @@
      * {@link ClassLoader#getSystemClassLoader()} is used.
      * @return a Resources object from which to access resources.
      */
-    public @Nullable Resources getResources(
+    @Nullable
+    public Resources getResources(
             @Nullable IBinder activityToken,
             @Nullable String resDir,
             @Nullable String[] splitResDirs,
             @Nullable String[] overlayDirs,
             @Nullable String[] libDirs,
-            int displayId,
+            @Nullable Integer overrideDisplayId,
             @Nullable Configuration overrideConfig,
             @NonNull CompatibilityInfo compatInfo,
             @Nullable ClassLoader classLoader,
@@ -888,17 +1073,30 @@
                     splitResDirs,
                     overlayDirs,
                     libDirs,
-                    displayId,
-                    overrideConfig != null ? new Configuration(overrideConfig) : null, // Copy
+                    overrideDisplayId != null ? overrideDisplayId : INVALID_DISPLAY,
+                    overrideConfig,
                     compatInfo,
                     loaders == null ? null : loaders.toArray(new ResourcesLoader[0]));
             classLoader = classLoader != null ? classLoader : ClassLoader.getSystemClassLoader();
 
-            if (activityToken != null) {
-                rebaseKeyForActivity(activityToken, key);
+            // Preload the ApkAssets required by the key to prevent performing heavy I/O while the
+            // ResourcesManager lock is held.
+            final ApkAssetsSupplier assetsSupplier = createApkAssetsSupplierNotLocked(key);
+
+            if (overrideDisplayId != null) {
+                rebaseKeyForDisplay(key, overrideDisplayId);
             }
 
-            return createResources(activityToken, key, classLoader);
+            Resources resources;
+            if (activityToken != null) {
+                Configuration initialOverrideConfig = new Configuration(key.mOverrideConfiguration);
+                rebaseKeyForActivity(activityToken, key, overrideDisplayId != null);
+                resources = createResourcesForActivity(activityToken, key, initialOverrideConfig,
+                        overrideDisplayId, classLoader, assetsSupplier);
+            } else {
+                resources = createResources(key, classLoader, assetsSupplier);
+            }
+            return resources;
         } finally {
             Trace.traceEnd(Trace.TRACE_TAG_RESOURCES);
         }
@@ -907,24 +1105,26 @@
     /**
      * Updates an Activity's Resources object with overrideConfig. The Resources object
      * that was previously returned by {@link #getResources(IBinder, String, String[], String[],
-     * String[], int, Configuration, CompatibilityInfo, ClassLoader, List)} is still valid and will
-     * have the updated configuration.
+     * String[], Integer, Configuration, CompatibilityInfo, ClassLoader, List)} is still valid and
+     * will have the updated configuration.
      *
      * @param activityToken The Activity token.
      * @param overrideConfig The configuration override to update.
      * @param displayId Id of the display where activity currently resides.
-     * @param movedToDifferentDisplay Indicates if the activity was moved to different display.
      */
     public void updateResourcesForActivity(@NonNull IBinder activityToken,
-            @Nullable Configuration overrideConfig, int displayId,
-            boolean movedToDifferentDisplay) {
+            @Nullable Configuration overrideConfig, int displayId) {
         try {
             Trace.traceBegin(Trace.TRACE_TAG_RESOURCES,
                     "ResourcesManager#updateResourcesForActivity");
+            if (displayId == INVALID_DISPLAY) {
+                throw new IllegalArgumentException("displayId can not be INVALID_DISPLAY");
+            }
             synchronized (this) {
                 final ActivityResources activityResources =
                         getOrCreateActivityResourcesStructLocked(activityToken);
 
+                boolean movedToDifferentDisplay = activityResources.overrideDisplayId != displayId;
                 if (Objects.equals(activityResources.overrideConfig, overrideConfig)
                         && !movedToDifferentDisplay) {
                     // They are the same and no change of display id, no work to do.
@@ -941,6 +1141,8 @@
                 } else {
                     activityResources.overrideConfig.unset();
                 }
+                // Update the Activity's override display id.
+                activityResources.overrideDisplayId = displayId;
 
                 if (DEBUG) {
                     Throwable here = new Throwable();
@@ -958,18 +1160,26 @@
                 // Rebase each Resources associated with this Activity.
                 final int refCount = activityResources.activityResources.size();
                 for (int i = 0; i < refCount; i++) {
-                    final WeakReference<Resources> weakResRef =
+                    final ActivityResource activityResource =
                             activityResources.activityResources.get(i);
 
-                    final Resources resources = weakResRef.get();
+                    final Resources resources = activityResource.resources.get();
                     if (resources == null) {
                         continue;
                     }
 
-                    final ResourcesKey newKey = rebaseActivityOverrideConfig(resources, oldConfig,
+                    final ResourcesKey newKey = rebaseActivityOverrideConfig(activityResource,
                             overrideConfig, displayId);
-                    if (newKey != null) {
-                        updateActivityResources(resources, newKey, false);
+                    if (newKey == null) {
+                        continue;
+                    }
+
+                    final ResourcesImpl resourcesImpl =
+                            findOrCreateResourcesImplForKeyLocked(newKey);
+                    if (resourcesImpl != null && resourcesImpl != resources.getImpl()) {
+                        // Set the ResourcesImpl, updating it for all users of this Resources
+                        // object.
+                        resources.setImpl(resourcesImpl);
                     }
                 }
             }
@@ -983,9 +1193,13 @@
      * that an Activity's Resources should be set to.
      */
     @Nullable
-    private ResourcesKey rebaseActivityOverrideConfig(@NonNull Resources resources,
-            @NonNull Configuration oldOverrideConfig, @Nullable Configuration newOverrideConfig,
-            int displayId) {
+    private ResourcesKey rebaseActivityOverrideConfig(@NonNull ActivityResource activityResource,
+            @Nullable Configuration newOverrideConfig, int displayId) {
+        final Resources resources = activityResource.resources.get();
+        if (resources == null) {
+            return null;
+        }
+
         // Extract the ResourcesKey that was last used to create the Resources for this
         // activity.
         final ResourcesKey oldKey = findKeyForResourceImplLocked(resources.getImpl());
@@ -1001,16 +1215,33 @@
             rebasedOverrideConfig.setTo(newOverrideConfig);
         }
 
-        final boolean hadOverrideConfig = !oldOverrideConfig.equals(Configuration.EMPTY);
-        if (hadOverrideConfig && oldKey.hasOverrideConfiguration()) {
-            // Generate a delta between the old base Activity override configuration and
-            // the actual final override configuration that was used to figure out the
-            // real delta this Resources object wanted.
-            Configuration overrideOverrideConfig = Configuration.generateDelta(
-                    oldOverrideConfig, oldKey.mOverrideConfiguration);
-            rebasedOverrideConfig.updateFrom(overrideOverrideConfig);
+        final Integer overrideDisplayId = activityResource.overrideDisplayId;
+        if (overrideDisplayId != null) {
+            DisplayAdjustments displayAdjustments = new DisplayAdjustments(rebasedOverrideConfig);
+            displayAdjustments.getConfiguration().setTo(activityResource.overrideConfig);
+            displayAdjustments.setCompatibilityInfo(oldKey.mCompatInfo);
+
+            DisplayMetrics dm = getDisplayMetrics(overrideDisplayId, displayAdjustments);
+            applyDisplayMetricsToConfiguration(dm, rebasedOverrideConfig);
         }
 
+        final boolean hasOverrideConfig =
+                !activityResource.overrideConfig.equals(Configuration.EMPTY);
+        if (hasOverrideConfig) {
+            rebasedOverrideConfig.updateFrom(activityResource.overrideConfig);
+        }
+
+        if (activityResource.overrideDisplayId != null
+                && activityResource.overrideConfig.windowConfiguration.getAppBounds() == null) {
+            // If this activity resource is overriding the display from the token and the key's
+            // window config app bounds is null we need to explicitly override this to
+            // ensure the display adjustments are as expected.
+            rebasedOverrideConfig.windowConfiguration.setAppBounds(null);
+        }
+
+        // Ensure the new key keeps the expected override display instead of the new token display.
+        displayId = overrideDisplayId != null ? overrideDisplayId : displayId;
+
         // Create the new ResourcesKey with the rebased override config.
         final ResourcesKey newKey = new ResourcesKey(oldKey.mResDir,
                 oldKey.mSplitResDirs, oldKey.mOverlayDirs, oldKey.mLibDirs,
@@ -1024,24 +1255,6 @@
         return newKey;
     }
 
-    private void updateActivityResources(Resources resources, ResourcesKey newKey,
-            boolean hasLoader) {
-        final ResourcesImpl resourcesImpl;
-
-        if (hasLoader) {
-            // Loaders always get new Impls because they cannot be shared
-            resourcesImpl = createResourcesImpl(newKey);
-        } else {
-            resourcesImpl = findOrCreateResourcesImplForKeyLocked(newKey);
-        }
-
-        if (resourcesImpl != null && resourcesImpl != resources.getImpl()) {
-            // Set the ResourcesImpl, updating it for all users of this Resources
-            // object.
-            resources.setImpl(resourcesImpl);
-        }
-    }
-
     public final boolean applyConfigurationToResources(@NonNull Configuration config,
             @Nullable CompatibilityInfo compat) {
         synchronized(this) {
@@ -1060,12 +1273,11 @@
                         + mResConfiguration.seq + ", newSeq=" + config.seq);
                 return false;
             }
-            int changes = mResConfiguration.updateFrom(config);
+
             // Things might have changed in display manager, so clear the cached displays.
             mAdjustedDisplays.clear();
 
-            DisplayMetrics defaultDisplayMetrics = getDisplayMetrics();
-
+            int changes = mResConfiguration.updateFrom(config);
             if (compat != null && (mResCompatibilityInfo == null ||
                     !mResCompatibilityInfo.equals(compat))) {
                 mResCompatibilityInfo = compat;
@@ -1074,10 +1286,10 @@
                         | ActivityInfo.CONFIG_SMALLEST_SCREEN_SIZE;
             }
 
-            Resources.updateSystemConfiguration(config, defaultDisplayMetrics, compat);
+            DisplayMetrics displayMetrics = getDisplayMetrics();
+            Resources.updateSystemConfiguration(config, displayMetrics, compat);
 
             ApplicationPackageManager.configurationChanged();
-            //Slog.i(TAG, "Configuration changed in " + currentPackageName());
 
             Configuration tmpConfig = new Configuration();
 
@@ -1107,11 +1319,7 @@
         }
 
         tmpConfig.setTo(config);
-
-        // Apply the override configuration before setting the display adjustments to ensure that
-        // the process config does not override activity display adjustments.
-        final boolean hasOverrideConfiguration = key.hasOverrideConfiguration();
-        if (hasOverrideConfiguration) {
+        if (key.hasOverrideConfiguration()) {
             tmpConfig.updateFrom(key.mOverrideConfiguration);
         }
 
@@ -1123,22 +1331,8 @@
             daj = new DisplayAdjustments(daj);
             daj.setCompatibilityInfo(compat);
         }
-
-        final int displayId = key.mDisplayId;
-        if (displayId == Display.DEFAULT_DISPLAY) {
-            daj.setConfiguration(tmpConfig);
-        }
-        DisplayMetrics dm = getDisplayMetrics(displayId, daj);
-        if (displayId != Display.DEFAULT_DISPLAY) {
-            applyNonDefaultDisplayMetricsToConfiguration(dm, tmpConfig);
-
-            // Re-apply the override configuration to ensure that configuration contexts based on
-            // a display context (ex: createDisplayContext().createConfigurationContext()) have the
-            // correct override.
-            if (hasOverrideConfiguration) {
-                tmpConfig.updateFrom(key.mOverrideConfiguration);
-            }
-        }
+        daj.setConfiguration(tmpConfig);
+        DisplayMetrics dm = getDisplayMetrics(generateDisplayId(key), daj);
 
         resourcesImpl.updateConfiguration(tmpConfig, dm, compat);
     }
@@ -1275,8 +1469,10 @@
         for (ActivityResources activityResources : mActivityResourceReferences.values()) {
             final int resCount = activityResources.activityResources.size();
             for (int i = 0; i < resCount; i++) {
-                final WeakReference<Resources> ref = activityResources.activityResources.get(i);
-                final Resources r = ref != null ? ref.get() : null;
+                final ActivityResource activityResource =
+                        activityResources.activityResources.get(i);
+                final Resources r = activityResource != null
+                        ? activityResource.resources.get() : null;
                 if (r != null) {
                     final ResourcesKey key = updatedResourceKeys.get(r.getImpl());
                     if (key != null) {
@@ -1308,10 +1504,16 @@
             if (tokenResources == null) {
                 return false;
             }
-            final ArrayList<WeakReference<Resources>> resourcesRefs =
-                    tokenResources.activityResources;
+            final ArrayList<ActivityResource> resourcesRefs = tokenResources.activityResources;
             for (int i = resourcesRefs.size() - 1; i >= 0; i--) {
-                final Resources res = resourcesRefs.get(i).get();
+                final ActivityResource activityResource = resourcesRefs.get(i);
+                if (activityResource.overrideDisplayId != null) {
+                    // This resource overrides the display of the token so we should not be
+                    // modifying its display adjustments here.
+                    continue;
+                }
+
+                final Resources res = activityResource.resources.get();
                 if (res != null) {
                     res.overrideDisplayAdjustments(override);
                     handled = true;
diff --git a/core/java/android/app/TEST_MAPPING b/core/java/android/app/TEST_MAPPING
index fe509de..4139b2f 100644
--- a/core/java/android/app/TEST_MAPPING
+++ b/core/java/android/app/TEST_MAPPING
@@ -65,6 +65,33 @@
         {
             "name": "CtsInstantAppTests",
             "file_patterns": ["(/|^)InstantAppResolve[^/]*"]
+        },
+        {
+            "name": "CtsAutoFillServiceTestCases",
+            "options": [
+                {
+                    "include-filter": "android.autofillservice.cts.PreSimpleSaveActivityTest"
+                },
+                {
+                    "exclude-annotation": "androidx.test.filters.FlakyTest"
+                }
+            ],
+            "file_patterns": ["(/|^)Activity.java"]
+        },
+        {
+            "name": "CtsAutoFillServiceTestCases",
+            "options": [
+                {
+                    "include-filter": "android.autofillservice.cts.SimpleSaveActivityTest"
+                },
+                {
+                    "exclude-annotation": "androidx.test.filters.FlakyTest"
+                },
+                {
+                    "exclude-annotation": "android.platform.test.annotations.AppModeFull"
+                }
+            ],
+            "file_patterns": ["(/|^)Activity.java"]
         }
     ],
     "postsubmit": [
diff --git a/core/java/android/app/UiAutomation.java b/core/java/android/app/UiAutomation.java
index e0951bf..109205f 100644
--- a/core/java/android/app/UiAutomation.java
+++ b/core/java/android/app/UiAutomation.java
@@ -903,7 +903,7 @@
         try {
             // Calling out without a lock held.
             screenShot = mUiAutomationConnection.takeScreenshot(
-                    new Rect(0, 0, displaySize.x, displaySize.y), rotation);
+                    new Rect(0, 0, displaySize.x, displaySize.y));
             if (screenShot == null) {
                 return null;
             }
diff --git a/core/java/android/app/UiAutomationConnection.java b/core/java/android/app/UiAutomationConnection.java
index ce51dba..70d5201 100644
--- a/core/java/android/app/UiAutomationConnection.java
+++ b/core/java/android/app/UiAutomationConnection.java
@@ -180,7 +180,7 @@
     }
 
     @Override
-    public Bitmap takeScreenshot(Rect crop, int rotation) {
+    public Bitmap takeScreenshot(Rect crop) {
         synchronized (mLock) {
             throwIfCalledByNotTrustedUidLocked();
             throwIfShutdownLocked();
@@ -190,7 +190,15 @@
         try {
             int width = crop.width();
             int height = crop.height();
-            return SurfaceControl.screenshot(crop, width, height, rotation);
+            final IBinder displayToken = SurfaceControl.getInternalDisplayToken();
+            final SurfaceControl.DisplayCaptureArgs captureArgs =
+                    new SurfaceControl.DisplayCaptureArgs.Builder(displayToken)
+                            .setSourceCrop(crop)
+                            .setSize(width, height)
+                            .build();
+            final SurfaceControl.ScreenshotHardwareBuffer screenshotBuffer =
+                    SurfaceControl.captureDisplay(captureArgs);
+            return screenshotBuffer == null ? null : screenshotBuffer.asBitmap();
         } finally {
             Binder.restoreCallingIdentity(identity);
         }
diff --git a/core/java/android/app/UiModeManager.java b/core/java/android/app/UiModeManager.java
index 7c6eff1..06d1b74 100644
--- a/core/java/android/app/UiModeManager.java
+++ b/core/java/android/app/UiModeManager.java
@@ -510,6 +510,9 @@
     }
 
     /**
+     * Activating night mode for the current user
+     *
+     * @return {@code true} if the change is successful
      * @hide
      */
     public boolean setNightModeActivated(boolean active) {
diff --git a/core/java/android/app/WallpaperManager.java b/core/java/android/app/WallpaperManager.java
index c5d343d..0a80ccc 100644
--- a/core/java/android/app/WallpaperManager.java
+++ b/core/java/android/app/WallpaperManager.java
@@ -26,6 +26,7 @@
 import android.annotation.SystemApi;
 import android.annotation.SystemService;
 import android.annotation.TestApi;
+import android.annotation.UiContext;
 import android.compat.annotation.UnsupportedAppUsage;
 import android.content.ComponentName;
 import android.content.ContentResolver;
@@ -213,7 +214,6 @@
     private static final Object sSync = new Object[0];
     @UnsupportedAppUsage
     private static Globals sGlobals;
-
     private final Context mContext;
     private final boolean mWcgEnabled;
     private final ColorManagementProxy mCmProxy;
@@ -539,7 +539,8 @@
         }
     }
 
-    /*package*/ WallpaperManager(IWallpaperManager service, Context context, Handler handler) {
+    /*package*/ WallpaperManager(IWallpaperManager service, @UiContext Context context,
+            Handler handler) {
         mContext = context;
         if (service != null) {
             initGlobals(service, context.getMainLooper());
diff --git a/core/java/android/app/WindowContext.java b/core/java/android/app/WindowContext.java
index cb416c9..5f72bac 100644
--- a/core/java/android/app/WindowContext.java
+++ b/core/java/android/app/WindowContext.java
@@ -20,6 +20,7 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.annotation.UiContext;
 import android.content.Context;
 import android.content.ContextWrapper;
 import android.os.Bundle;
@@ -42,6 +43,7 @@
  * @see Context#createWindowContext(int, Bundle)
  * @hide
  */
+@UiContext
 public class WindowContext extends ContextWrapper {
     private final WindowManagerImpl mWindowManager;
     private final IWindowManager mWms;
diff --git a/core/java/android/app/WindowTokenClient.java b/core/java/android/app/WindowTokenClient.java
index b5d1039..9092ef36 100644
--- a/core/java/android/app/WindowTokenClient.java
+++ b/core/java/android/app/WindowTokenClient.java
@@ -71,8 +71,7 @@
         final boolean configChanged = config.diff(newConfig) != 0;
         if (displayChanged || configChanged) {
             // TODO(ag/9789103): update resource manager logic to track non-activity tokens
-            mResourcesManager.updateResourcesForActivity(this, newConfig, newDisplayId,
-                    displayChanged);
+            mResourcesManager.updateResourcesForActivity(this, newConfig, newDisplayId);
         }
         if (displayChanged) {
             context.updateDisplay(newDisplayId);
diff --git a/core/java/android/app/backup/BackupAgent.java b/core/java/android/app/backup/BackupAgent.java
index a789169..056cfc7 100644
--- a/core/java/android/app/backup/BackupAgent.java
+++ b/core/java/android/app/backup/BackupAgent.java
@@ -19,6 +19,7 @@
 import android.annotation.Nullable;
 import android.app.IBackupAgent;
 import android.app.QueuedWork;
+import android.app.backup.BackupManager.OperationType;
 import android.app.backup.FullBackup.BackupScheme.PathWithRequiredFlags;
 import android.content.Context;
 import android.content.ContextWrapper;
@@ -38,6 +39,8 @@
 import android.util.ArraySet;
 import android.util.Log;
 
+import com.android.internal.annotations.VisibleForTesting;
+
 import libcore.io.IoUtils;
 
 import org.xmlpull.v1.XmlPullParserException;
@@ -50,6 +53,7 @@
 import java.util.LinkedList;
 import java.util.List;
 import java.util.Map;
+import java.util.Objects;
 import java.util.Set;
 import java.util.concurrent.CountDownLatch;
 
@@ -129,6 +133,7 @@
 public abstract class BackupAgent extends ContextWrapper {
     private static final String TAG = "BackupAgent";
     private static final boolean DEBUG = false;
+    private static final int DEFAULT_OPERATION_TYPE = OperationType.BACKUP;
 
     /** @hide */
     public static final int RESULT_SUCCESS = 0;
@@ -186,6 +191,9 @@
     Handler mHandler = null;
 
     @Nullable private UserHandle mUser;
+     // This field is written from the main thread (in onCreate), and read in a Binder thread (in
+     // onFullBackup that is called from system_server via Binder).
+    @OperationType private volatile int mOperationType = DEFAULT_OPERATION_TYPE;
 
     Handler getHandler() {
         if (mHandler == null) {
@@ -229,6 +237,13 @@
     }
 
     /**
+     * @hide
+     */
+    public void onCreate(UserHandle user) {
+        onCreate(user, DEFAULT_OPERATION_TYPE);
+    }
+
+    /**
      * Provided as a convenience for agent implementations that need an opportunity
      * to do one-time initialization before the actual backup or restore operation
      * is begun with information about the calling user.
@@ -236,10 +251,11 @@
      *
      * @hide
      */
-    public void onCreate(UserHandle user) {
+    public void onCreate(UserHandle user, @OperationType int operationType) {
         onCreate();
 
         mUser = user;
+        mOperationType = operationType;
     }
 
     /**
@@ -386,16 +402,13 @@
      */
     public void onFullBackup(FullBackupDataOutput data) throws IOException {
         FullBackup.BackupScheme backupScheme = FullBackup.getBackupScheme(this);
-        if (!backupScheme.isFullBackupContentEnabled()) {
+        if (!isDeviceToDeviceMigration() && !backupScheme.isFullBackupContentEnabled()) {
             return;
         }
 
-        Map<String, Set<PathWithRequiredFlags>> manifestIncludeMap;
-        ArraySet<PathWithRequiredFlags> manifestExcludeSet;
+        IncludeExcludeRules includeExcludeRules;
         try {
-            manifestIncludeMap =
-                    backupScheme.maybeParseAndGetCanonicalIncludePaths();
-            manifestExcludeSet = backupScheme.maybeParseAndGetCanonicalExcludePaths();
+            includeExcludeRules = getIncludeExcludeRules(backupScheme);
         } catch (IOException | XmlPullParserException e) {
             if (Log.isLoggable(FullBackup.TAG_XML_PARSER, Log.VERBOSE)) {
                 Log.v(FullBackup.TAG_XML_PARSER,
@@ -404,6 +417,10 @@
             }
             return;
         }
+        Map<String, Set<PathWithRequiredFlags>> manifestIncludeMap
+                = includeExcludeRules.getIncludeMap();
+        Set<PathWithRequiredFlags> manifestExcludeSet
+                = includeExcludeRules.getExcludeSet();
 
         final String packageName = getPackageName();
         final ApplicationInfo appInfo = getApplicationInfo();
@@ -413,24 +430,18 @@
         final Context ceContext = createCredentialProtectedStorageContext();
         final String rootDir = ceContext.getDataDir().getCanonicalPath();
         final String filesDir = ceContext.getFilesDir().getCanonicalPath();
-        final String noBackupDir = ceContext.getNoBackupFilesDir().getCanonicalPath();
         final String databaseDir = ceContext.getDatabasePath("foo").getParentFile()
                 .getCanonicalPath();
         final String sharedPrefsDir = ceContext.getSharedPreferencesPath("foo").getParentFile()
                 .getCanonicalPath();
-        final String cacheDir = ceContext.getCacheDir().getCanonicalPath();
-        final String codeCacheDir = ceContext.getCodeCacheDir().getCanonicalPath();
 
         final Context deContext = createDeviceProtectedStorageContext();
         final String deviceRootDir = deContext.getDataDir().getCanonicalPath();
         final String deviceFilesDir = deContext.getFilesDir().getCanonicalPath();
-        final String deviceNoBackupDir = deContext.getNoBackupFilesDir().getCanonicalPath();
         final String deviceDatabaseDir = deContext.getDatabasePath("foo").getParentFile()
                 .getCanonicalPath();
         final String deviceSharedPrefsDir = deContext.getSharedPreferencesPath("foo")
                 .getParentFile().getCanonicalPath();
-        final String deviceCacheDir = deContext.getCacheDir().getCanonicalPath();
-        final String deviceCodeCacheDir = deContext.getCodeCacheDir().getCanonicalPath();
 
         final String libDir = (appInfo.nativeLibraryDir != null)
                 ? new File(appInfo.nativeLibraryDir).getCanonicalPath()
@@ -443,33 +454,36 @@
 
         // Add the directories we always exclude.
         traversalExcludeSet.add(filesDir);
-        traversalExcludeSet.add(noBackupDir);
         traversalExcludeSet.add(databaseDir);
         traversalExcludeSet.add(sharedPrefsDir);
-        traversalExcludeSet.add(cacheDir);
-        traversalExcludeSet.add(codeCacheDir);
 
         traversalExcludeSet.add(deviceFilesDir);
-        traversalExcludeSet.add(deviceNoBackupDir);
         traversalExcludeSet.add(deviceDatabaseDir);
         traversalExcludeSet.add(deviceSharedPrefsDir);
-        traversalExcludeSet.add(deviceCacheDir);
-        traversalExcludeSet.add(deviceCodeCacheDir);
 
         if (libDir != null) {
             traversalExcludeSet.add(libDir);
         }
 
+        Set<String> extraExcludedDirs = getExtraExcludeDirsIfAny(ceContext);
+        Set<String> extraExcludedDeviceDirs = getExtraExcludeDirsIfAny(deContext);
+        traversalExcludeSet.addAll(extraExcludedDirs);
+        traversalExcludeSet.addAll(extraExcludedDeviceDirs);
+
         // Root dir first.
         applyXmlFiltersAndDoFullBackupForDomain(
                 packageName, FullBackup.ROOT_TREE_TOKEN, manifestIncludeMap,
                 manifestExcludeSet, traversalExcludeSet, data);
         traversalExcludeSet.add(rootDir);
+        // Exclude the extra directories anyway, since we've already covered them if it was needed.
+        traversalExcludeSet.addAll(extraExcludedDirs);
 
         applyXmlFiltersAndDoFullBackupForDomain(
                 packageName, FullBackup.DEVICE_ROOT_TREE_TOKEN, manifestIncludeMap,
                 manifestExcludeSet, traversalExcludeSet, data);
         traversalExcludeSet.add(deviceRootDir);
+        // Exclude the extra directories anyway, since we've already covered them if it was needed.
+        traversalExcludeSet.addAll(extraExcludedDeviceDirs);
 
         // Data dir next.
         traversalExcludeSet.remove(filesDir);
@@ -528,6 +542,41 @@
         }
     }
 
+    private Set<String> getExtraExcludeDirsIfAny(Context context) throws IOException {
+        if (isDeviceToDeviceMigration()) {
+            return Collections.emptySet();
+        }
+
+        // If this is not a migration, also exclude no-backup and cache dirs.
+        Set<String> excludedDirs = new HashSet<>();
+        excludedDirs.add(context.getCacheDir().getCanonicalPath());
+        excludedDirs.add(context.getCodeCacheDir().getCanonicalPath());
+        excludedDirs.add(context.getNoBackupFilesDir().getCanonicalPath());
+        return Collections.unmodifiableSet(excludedDirs);
+    }
+
+    private boolean isDeviceToDeviceMigration() {
+        return mOperationType == OperationType.MIGRATION;
+    }
+
+    /** @hide */
+    @VisibleForTesting
+    public IncludeExcludeRules getIncludeExcludeRules(FullBackup.BackupScheme backupScheme)
+            throws IOException, XmlPullParserException {
+        if (isDeviceToDeviceMigration()) {
+            return IncludeExcludeRules.emptyRules();
+        }
+
+        Map<String, Set<PathWithRequiredFlags>> manifestIncludeMap;
+        ArraySet<PathWithRequiredFlags> manifestExcludeSet;
+
+        manifestIncludeMap =
+                backupScheme.maybeParseAndGetCanonicalIncludePaths();
+        manifestExcludeSet = backupScheme.maybeParseAndGetCanonicalExcludePaths();
+
+        return new IncludeExcludeRules(manifestIncludeMap, manifestExcludeSet);
+    }
+
     /**
      * Notification that the application's current backup operation causes it to exceed
      * the maximum size permitted by the transport.  The ongoing backup operation is
@@ -570,7 +619,7 @@
      */
     private void applyXmlFiltersAndDoFullBackupForDomain(String packageName, String domainToken,
             Map<String, Set<PathWithRequiredFlags>> includeMap,
-            ArraySet<PathWithRequiredFlags> filterSet, ArraySet<String> traversalExcludeSet,
+            Set<PathWithRequiredFlags> filterSet, ArraySet<String> traversalExcludeSet,
             FullBackupDataOutput data) throws IOException {
         if (includeMap == null || includeMap.size() == 0) {
             // Do entire sub-tree for the provided token.
@@ -742,7 +791,7 @@
      * @hide
      */
     protected final void fullBackupFileTree(String packageName, String domain, String startingPath,
-                                            ArraySet<PathWithRequiredFlags> manifestExcludes,
+                                            Set<PathWithRequiredFlags> manifestExcludes,
                                             ArraySet<String> systemExcludes,
             FullBackupDataOutput output) {
         // Pull out the domain and set it aside to use when making the tarball.
@@ -811,7 +860,7 @@
     }
 
     private boolean manifestExcludesContainFilePath(
-        ArraySet<PathWithRequiredFlags> manifestExcludes, String filePath) {
+        Set<PathWithRequiredFlags> manifestExcludes, String filePath) {
         for (PathWithRequiredFlags exclude : manifestExcludes) {
             String excludePath = exclude.getPath();
             if (excludePath != null && excludePath.equals(filePath)) {
@@ -857,6 +906,11 @@
     }
 
     private boolean isFileEligibleForRestore(File destination) throws IOException {
+        if (isDeviceToDeviceMigration()) {
+            // Everything is eligible for device-to-device migration.
+            return true;
+        }
+
         FullBackup.BackupScheme bs = FullBackup.getBackupScheme(this);
         if (!bs.isFullBackupContentEnabled()) {
             if (Log.isLoggable(FullBackup.TAG_XML_PARSER, Log.VERBOSE)) {
@@ -1265,4 +1319,53 @@
             throw new IllegalStateException(mMessage);
         }
     }
+
+    /**  @hide */
+    @VisibleForTesting
+    public static class IncludeExcludeRules {
+        private final Map<String, Set<PathWithRequiredFlags>> mManifestIncludeMap;
+        private final Set<PathWithRequiredFlags> mManifestExcludeSet;
+
+        /** @hide */
+        public IncludeExcludeRules(
+                Map<String, Set<PathWithRequiredFlags>> manifestIncludeMap,
+                Set<PathWithRequiredFlags> manifestExcludeSet) {
+            mManifestIncludeMap = manifestIncludeMap;
+            mManifestExcludeSet = manifestExcludeSet;
+        }
+
+        /**  @hide */
+        @VisibleForTesting
+        public static IncludeExcludeRules emptyRules() {
+            return new IncludeExcludeRules(Collections.emptyMap(), new ArraySet<>());
+        }
+
+        private Map<String, Set<PathWithRequiredFlags>> getIncludeMap() {
+            return mManifestIncludeMap;
+        }
+
+        private Set<PathWithRequiredFlags> getExcludeSet() {
+            return mManifestExcludeSet;
+        }
+
+        /**  @hide */
+        @Override
+        public int hashCode() {
+            return Objects.hash(mManifestIncludeMap, mManifestExcludeSet);
+        }
+
+        /**  @hide */
+        @Override
+        public boolean equals(Object object) {
+            if (this == object) {
+                return true;
+            }
+            if (object == null || getClass() != object.getClass()) {
+                return false;
+            }
+            IncludeExcludeRules that = (IncludeExcludeRules) object;
+            return Objects.equals(mManifestIncludeMap, that.mManifestIncludeMap) &&
+                    Objects.equals(mManifestExcludeSet, that.mManifestExcludeSet);
+        }
+    }
 }
diff --git a/core/java/android/app/backup/BackupManager.java b/core/java/android/app/backup/BackupManager.java
index b1a62bf..9b67587 100644
--- a/core/java/android/app/backup/BackupManager.java
+++ b/core/java/android/app/backup/BackupManager.java
@@ -355,7 +355,36 @@
             try {
                 // All packages, current transport
                 IRestoreSession binder =
-                        sService.beginRestoreSessionForUser(mContext.getUserId(), null, null);
+                        sService.beginRestoreSessionForUser(mContext.getUserId(), null, null,
+                                OperationType.BACKUP);
+                if (binder != null) {
+                    session = new RestoreSession(mContext, binder);
+                }
+            } catch (RemoteException e) {
+                Log.e(TAG, "beginRestoreSession() couldn't connect");
+            }
+        }
+        return session;
+    }
+
+    /**
+     * Begin the process of restoring data from backup.  See the
+     * {@link android.app.backup.RestoreSession} class for documentation on that process.
+     *
+     * @param operationType Type of the operation, see {@link OperationType}
+     *
+     * @hide
+     */
+    @RequiresPermission(android.Manifest.permission.BACKUP)
+    public RestoreSession beginRestoreSession(@OperationType int operationType) {
+        RestoreSession session = null;
+        checkServiceBinder();
+        if (sService != null) {
+            try {
+                // All packages, current transport
+                IRestoreSession binder =
+                        sService.beginRestoreSessionForUser(mContext.getUserId(), null, null,
+                                operationType);
                 if (binder != null) {
                     session = new RestoreSession(mContext, binder);
                 }
diff --git a/core/java/android/app/backup/IBackupManager.aidl b/core/java/android/app/backup/IBackupManager.aidl
index 96b5dd5..e177a74 100644
--- a/core/java/android/app/backup/IBackupManager.aidl
+++ b/core/java/android/app/backup/IBackupManager.aidl
@@ -547,9 +547,11 @@
      *        set can be restored.
      * @param transportID The name of the transport to use for the restore operation.
      *        May be null, in which case the current active transport is used.
+     * @param operationType Type of the operation, see {@link BackupManager#OperationType}
      * @return An interface to the restore session, or null on error.
      */
-    IRestoreSession beginRestoreSessionForUser(int userId, String packageName, String transportID);
+    IRestoreSession beginRestoreSessionForUser(int userId, String packageName, String transportID,
+            int operationType);
 
     /**
      * Notify the backup manager that a BackupAgent has completed the operation
diff --git a/core/java/android/app/people/ConversationChannel.java b/core/java/android/app/people/ConversationChannel.java
new file mode 100644
index 0000000..39c5c85
--- /dev/null
+++ b/core/java/android/app/people/ConversationChannel.java
@@ -0,0 +1,97 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app.people;
+
+import android.app.NotificationChannel;
+import android.content.pm.ShortcutInfo;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * The non-customized notification channel of a conversation. It contains the information to render
+ * the conversation and allows the user to open and customize the conversation setting.
+ *
+ * @hide
+ */
+public final class ConversationChannel implements Parcelable {
+
+    private ShortcutInfo mShortcutInfo;
+    private NotificationChannel mParentNotificationChannel;
+    private long mLastEventTimestamp;
+    private boolean mHasActiveNotifications;
+
+    public static final Creator<ConversationChannel> CREATOR = new Creator<ConversationChannel>() {
+        @Override
+        public ConversationChannel createFromParcel(Parcel in) {
+            return new ConversationChannel(in);
+        }
+
+        @Override
+        public ConversationChannel[] newArray(int size) {
+            return new ConversationChannel[size];
+        }
+    };
+
+    public ConversationChannel(ShortcutInfo shortcutInfo,
+            NotificationChannel parentNotificationChannel, long lastEventTimestamp,
+            boolean hasActiveNotifications) {
+        mShortcutInfo = shortcutInfo;
+        mParentNotificationChannel = parentNotificationChannel;
+        mLastEventTimestamp = lastEventTimestamp;
+        mHasActiveNotifications = hasActiveNotifications;
+    }
+
+    public ConversationChannel(Parcel in) {
+        mShortcutInfo = in.readParcelable(ShortcutInfo.class.getClassLoader());
+        mParentNotificationChannel = in.readParcelable(NotificationChannel.class.getClassLoader());
+        mLastEventTimestamp = in.readLong();
+        mHasActiveNotifications = in.readBoolean();
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeParcelable(mShortcutInfo, flags);
+        dest.writeParcelable(mParentNotificationChannel, flags);
+        dest.writeLong(mLastEventTimestamp);
+        dest.writeBoolean(mHasActiveNotifications);
+    }
+
+    public ShortcutInfo getShortcutInfo() {
+        return mShortcutInfo;
+    }
+
+    public NotificationChannel getParentNotificationChannel() {
+        return mParentNotificationChannel;
+    }
+
+    public long getLastEventTimestamp() {
+        return mLastEventTimestamp;
+    }
+
+    /**
+     * Whether this conversation has any active notifications. If it's true, the shortcut for this
+     * conversation can't be uncached until all its active notifications are dismissed.
+     */
+    public boolean hasActiveNotifications() {
+        return mHasActiveNotifications;
+    }
+}
diff --git a/core/java/android/app/people/IPeopleManager.aidl b/core/java/android/app/people/IPeopleManager.aidl
new file mode 100644
index 0000000..61dac0d
--- /dev/null
+++ b/core/java/android/app/people/IPeopleManager.aidl
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app.people;
+
+import android.content.pm.ParceledListSlice;
+import android.net.Uri;
+import android.os.IBinder;
+
+/**
+ * System private API for talking with the people service.
+ * {@hide}
+ */
+interface IPeopleManager {
+    /**
+     * Returns the recent conversations. The conversations that have customized notification
+     * settings are excluded from the returned list.
+     */
+    ParceledListSlice getRecentConversations();
+
+    /**
+     * Removes the specified conversation from the recent conversations list and uncaches the
+     * shortcut associated with the conversation.
+     */
+    void removeRecentConversation(in String packageName, int userId, in String shortcutId);
+
+    /** Removes all the recent conversations and uncaches their cached shortcuts. */
+    void removeAllRecentConversations();
+}
diff --git a/core/java/android/app/slice/SliceProvider.java b/core/java/android/app/slice/SliceProvider.java
index bd1eea5..cef6ab0 100644
--- a/core/java/android/app/slice/SliceProvider.java
+++ b/core/java/android/app/slice/SliceProvider.java
@@ -155,10 +155,6 @@
     /**
      * @hide
      */
-    public static final String EXTRA_PROVIDER_PKG = "provider_pkg";
-    /**
-     * @hide
-     */
     public static final String EXTRA_RESULT = "result";
 
     private static final boolean DEBUG = false;
@@ -519,7 +515,6 @@
                 com.android.internal.R.string.config_slicePermissionComponent)));
         intent.putExtra(EXTRA_BIND_URI, sliceUri);
         intent.putExtra(EXTRA_PKG, callingPackage);
-        intent.putExtra(EXTRA_PROVIDER_PKG, context.getPackageName());
         // Unique pending intent.
         intent.setData(sliceUri.buildUpon().appendQueryParameter("package", callingPackage)
                 .build());
diff --git a/core/java/android/app/timezonedetector/TimeZoneCapabilities.java b/core/java/android/app/timezonedetector/TimeZoneCapabilities.java
index 236b006..cc0af3f 100644
--- a/core/java/android/app/timezonedetector/TimeZoneCapabilities.java
+++ b/core/java/android/app/timezonedetector/TimeZoneCapabilities.java
@@ -91,11 +91,13 @@
 
     private final @UserIdInt int mUserId;
     private final @CapabilityState int mConfigureAutoDetectionEnabled;
+    private final @CapabilityState int mConfigureGeoDetectionEnabled;
     private final @CapabilityState int mSuggestManualTimeZone;
 
     private TimeZoneCapabilities(@NonNull Builder builder) {
         this.mUserId = builder.mUserId;
         this.mConfigureAutoDetectionEnabled = builder.mConfigureAutoDetectionEnabled;
+        this.mConfigureGeoDetectionEnabled = builder.mConfigureGeoDetectionEnabled;
         this.mSuggestManualTimeZone = builder.mSuggestManualTimeZone;
     }
 
@@ -103,6 +105,7 @@
     private static TimeZoneCapabilities createFromParcel(Parcel in) {
         return new TimeZoneCapabilities.Builder(in.readInt())
                 .setConfigureAutoDetectionEnabled(in.readInt())
+                .setConfigureGeoDetectionEnabled(in.readInt())
                 .setSuggestManualTimeZone(in.readInt())
                 .build();
     }
@@ -111,6 +114,7 @@
     public void writeToParcel(@NonNull Parcel dest, int flags) {
         dest.writeInt(mUserId);
         dest.writeInt(mConfigureAutoDetectionEnabled);
+        dest.writeInt(mConfigureGeoDetectionEnabled);
         dest.writeInt(mSuggestManualTimeZone);
     }
 
@@ -120,8 +124,8 @@
     }
 
     /**
-     * Returns the user's capability state for controlling automatic time zone detection via
-     * {@link TimeZoneDetector#updateConfiguration(TimeZoneConfiguration)} and {@link
+     * Returns the user's capability state for controlling whether automatic time zone detection is
+     * enabled via {@link TimeZoneDetector#updateConfiguration(TimeZoneConfiguration)} and {@link
      * TimeZoneConfiguration#isAutoDetectionEnabled()}.
      */
     @CapabilityState
@@ -130,6 +134,16 @@
     }
 
     /**
+     * Returns the user's capability state for controlling whether geolocation can be used to detect
+     * time zone via {@link TimeZoneDetector#updateConfiguration(TimeZoneConfiguration)} and {@link
+     * TimeZoneConfiguration#isGeoDetectionEnabled()}.
+     */
+    @CapabilityState
+    public int getConfigureGeoDetectionEnabled() {
+        return mConfigureGeoDetectionEnabled;
+    }
+
+    /**
      * Returns the user's capability state for manually setting the time zone on a device via
      * {@link TimeZoneDetector#suggestManualTimeZone(ManualTimeZoneSuggestion)}.
      *
@@ -157,12 +171,16 @@
         TimeZoneCapabilities that = (TimeZoneCapabilities) o;
         return mUserId == that.mUserId
                 && mConfigureAutoDetectionEnabled == that.mConfigureAutoDetectionEnabled
+                && mConfigureGeoDetectionEnabled == that.mConfigureGeoDetectionEnabled
                 && mSuggestManualTimeZone == that.mSuggestManualTimeZone;
     }
 
     @Override
     public int hashCode() {
-        return Objects.hash(mUserId, mConfigureAutoDetectionEnabled, mSuggestManualTimeZone);
+        return Objects.hash(mUserId,
+                mConfigureAutoDetectionEnabled,
+                mConfigureGeoDetectionEnabled,
+                mSuggestManualTimeZone);
     }
 
     @Override
@@ -170,6 +188,7 @@
         return "TimeZoneDetectorCapabilities{"
                 + "mUserId=" + mUserId
                 + ", mConfigureAutomaticDetectionEnabled=" + mConfigureAutoDetectionEnabled
+                + ", mConfigureGeoDetectionEnabled=" + mConfigureGeoDetectionEnabled
                 + ", mSuggestManualTimeZone=" + mSuggestManualTimeZone
                 + '}';
     }
@@ -179,6 +198,7 @@
 
         private final @UserIdInt int mUserId;
         private @CapabilityState int mConfigureAutoDetectionEnabled;
+        private @CapabilityState int mConfigureGeoDetectionEnabled;
         private @CapabilityState int mSuggestManualTimeZone;
 
         /**
@@ -194,6 +214,12 @@
             return this;
         }
 
+        /** Sets the state for the geolocation time zone detection enabled config. */
+        public Builder setConfigureGeoDetectionEnabled(@CapabilityState int value) {
+            this.mConfigureGeoDetectionEnabled = value;
+            return this;
+        }
+
         /** Sets the state for the suggestManualTimeZone action. */
         public Builder setSuggestManualTimeZone(@CapabilityState int value) {
             this.mSuggestManualTimeZone = value;
@@ -204,6 +230,7 @@
         @NonNull
         public TimeZoneCapabilities build() {
             verifyCapabilitySet(mConfigureAutoDetectionEnabled, "configureAutoDetectionEnabled");
+            verifyCapabilitySet(mConfigureGeoDetectionEnabled, "configureGeoDetectionEnabled");
             verifyCapabilitySet(mSuggestManualTimeZone, "suggestManualTimeZone");
             return new TimeZoneCapabilities(this);
         }
diff --git a/core/java/android/app/timezonedetector/TimeZoneConfiguration.java b/core/java/android/app/timezonedetector/TimeZoneConfiguration.java
index 047d349..6f84ee2 100644
--- a/core/java/android/app/timezonedetector/TimeZoneConfiguration.java
+++ b/core/java/android/app/timezonedetector/TimeZoneConfiguration.java
@@ -67,6 +67,10 @@
     @Property
     public static final String PROPERTY_AUTO_DETECTION_ENABLED = "autoDetectionEnabled";
 
+    /** See {@link TimeZoneConfiguration#isGeoDetectionEnabled()} for details. */
+    @Property
+    public static final String PROPERTY_GEO_DETECTION_ENABLED = "geoDetectionEnabled";
+
     private final Bundle mBundle;
 
     private TimeZoneConfiguration(Builder builder) {
@@ -86,7 +90,8 @@
 
     /** Returns {@code true} if all known properties are set. */
     public boolean isComplete() {
-        return hasProperty(PROPERTY_AUTO_DETECTION_ENABLED);
+        return hasProperty(PROPERTY_AUTO_DETECTION_ENABLED)
+                && hasProperty(PROPERTY_GEO_DETECTION_ENABLED);
     }
 
     /** Returns true if the specified property is set. */
@@ -108,6 +113,28 @@
         return mBundle.getBoolean(PROPERTY_AUTO_DETECTION_ENABLED);
     }
 
+    /**
+     * Returns the value of the {@link #PROPERTY_GEO_DETECTION_ENABLED} property. This
+     * controls whether a device can use location to determine time zone. Only used when
+     * {@link #isAutoDetectionEnabled()} is true.
+     *
+     * @throws IllegalStateException if the field has not been set
+     */
+    public boolean isGeoDetectionEnabled() {
+        if (!mBundle.containsKey(PROPERTY_GEO_DETECTION_ENABLED)) {
+            throw new IllegalStateException(PROPERTY_GEO_DETECTION_ENABLED + " is not set");
+        }
+        return mBundle.getBoolean(PROPERTY_GEO_DETECTION_ENABLED);
+    }
+
+    /**
+     * Convenience method to merge this with another. The argument configuration properties have
+     * precedence.
+     */
+    public TimeZoneConfiguration with(TimeZoneConfiguration other) {
+        return new Builder(this).mergeProperties(other).build();
+    }
+
     @Override
     public int describeContents() {
         return 0;
@@ -174,6 +201,12 @@
             return this;
         }
 
+        /** Sets the desired state of the geolocation time zone detection enabled property. */
+        public Builder setGeoDetectionEnabled(boolean enabled) {
+            this.mBundle.putBoolean(PROPERTY_GEO_DETECTION_ENABLED, enabled);
+            return this;
+        }
+
         /** Returns the {@link TimeZoneConfiguration}. */
         @NonNull
         public TimeZoneConfiguration build() {
diff --git a/core/java/android/app/timezonedetector/TimeZoneDetectorImpl.java b/core/java/android/app/timezonedetector/TimeZoneDetectorImpl.java
index 6bd365f..0770aff 100644
--- a/core/java/android/app/timezonedetector/TimeZoneDetectorImpl.java
+++ b/core/java/android/app/timezonedetector/TimeZoneDetectorImpl.java
@@ -117,7 +117,7 @@
     }
 
     private void notifyConfigurationListeners(@NonNull TimeZoneConfiguration configuration) {
-        ArraySet<TimeZoneConfigurationListener> configurationListeners;
+        final ArraySet<TimeZoneConfigurationListener> configurationListeners;
         synchronized (this) {
             configurationListeners = new ArraySet<>(mConfigurationListeners);
         }
diff --git a/core/java/android/app/usage/UsageEvents.java b/core/java/android/app/usage/UsageEvents.java
index 3522b1b..0e6a063 100644
--- a/core/java/android/app/usage/UsageEvents.java
+++ b/core/java/android/app/usage/UsageEvents.java
@@ -199,7 +199,7 @@
         public static final int NOTIFICATION_INTERRUPTION = 12;
 
         /**
-         * A Slice was pinned by the default launcher or the default assistant.
+         * A Slice was pinned by the default assistant.
          * @hide
          */
         @SystemApi
diff --git a/core/java/android/bluetooth/BluetoothAdapter.java b/core/java/android/bluetooth/BluetoothAdapter.java
index d2a774b..c7b20b2 100644
--- a/core/java/android/bluetooth/BluetoothAdapter.java
+++ b/core/java/android/bluetooth/BluetoothAdapter.java
@@ -2674,52 +2674,6 @@
     }
 
     /**
-     * Construct an encrypted, RFCOMM server socket.
-     * Call #accept to retrieve connections to this socket.
-     *
-     * @return An RFCOMM BluetoothServerSocket
-     * @throws IOException On error, for example Bluetooth not available, or insufficient
-     * permissions.
-     * @hide
-     */
-    public BluetoothServerSocket listenUsingEncryptedRfcommOn(int port) throws IOException {
-        BluetoothServerSocket socket =
-                new BluetoothServerSocket(BluetoothSocket.TYPE_RFCOMM, false, true, port);
-        int errno = socket.mSocket.bindListen();
-        if (port == SOCKET_CHANNEL_AUTO_STATIC_NO_SDP) {
-            socket.setChannel(socket.mSocket.getPort());
-        }
-        if (errno < 0) {
-            //TODO(BT): Throw the same exception error code
-            // that the previous code was using.
-            //socket.mSocket.throwErrnoNative(errno);
-            throw new IOException("Error: " + errno);
-        }
-        return socket;
-    }
-
-    /**
-     * Construct a SCO server socket.
-     * Call #accept to retrieve connections to this socket.
-     *
-     * @return A SCO BluetoothServerSocket
-     * @throws IOException On error, for example Bluetooth not available, or insufficient
-     * permissions.
-     * @hide
-     */
-    public static BluetoothServerSocket listenUsingScoOn() throws IOException {
-        BluetoothServerSocket socket =
-                new BluetoothServerSocket(BluetoothSocket.TYPE_SCO, false, false, -1);
-        int errno = socket.mSocket.bindListen();
-        if (errno < 0) {
-            //TODO(BT): Throw the same exception error code
-            // that the previous code was using.
-            //socket.mSocket.throwErrnoNative(errno);
-        }
-        return socket;
-    }
-
-    /**
      * Construct an encrypted, authenticated, L2CAP server socket.
      * Call #accept to retrieve connections to this socket.
      * <p>To auto assign a port without creating a SDP record use
diff --git a/core/java/android/companion/BluetoothDeviceFilter.java b/core/java/android/companion/BluetoothDeviceFilter.java
index 2649fbe..cf9eeca 100644
--- a/core/java/android/companion/BluetoothDeviceFilter.java
+++ b/core/java/android/companion/BluetoothDeviceFilter.java
@@ -142,6 +142,16 @@
     }
 
     @Override
+    public String toString() {
+        return "BluetoothDeviceFilter{"
+                + "mNamePattern=" + mNamePattern
+                + ", mAddress='" + mAddress + '\''
+                + ", mServiceUuids=" + mServiceUuids
+                + ", mServiceUuidMasks=" + mServiceUuidMasks
+                + '}';
+    }
+
+    @Override
     public int describeContents() {
         return 0;
     }
diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java
index 2385712..52b0467 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -21,6 +21,7 @@
 import android.annotation.CheckResult;
 import android.annotation.ColorInt;
 import android.annotation.ColorRes;
+import android.annotation.DisplayContext;
 import android.annotation.DrawableRes;
 import android.annotation.IntDef;
 import android.annotation.NonNull;
@@ -33,6 +34,7 @@
 import android.annotation.SuppressLint;
 import android.annotation.SystemApi;
 import android.annotation.TestApi;
+import android.annotation.UiContext;
 import android.annotation.UserIdInt;
 import android.app.ActivityManager;
 import android.app.IApplicationThread;
@@ -3494,6 +3496,7 @@
             //@hide: TIME_ZONE_DETECTOR_SERVICE,
             PERMISSION_SERVICE,
             LIGHTS_SERVICE,
+            //@hide: PEOPLE_SERVICE,
     })
     @Retention(RetentionPolicy.SOURCE)
     public @interface ServiceName {}
@@ -3750,6 +3753,7 @@
      * @see #getSystemService(String)
      * @see android.view.WindowManager
      */
+    @UiContext
     public static final String WINDOW_SERVICE = "window";
 
     /**
@@ -3760,6 +3764,7 @@
      * @see #getSystemService(String)
      * @see android.view.LayoutInflater
      */
+    @UiContext
     public static final String LAYOUT_INFLATER_SERVICE = "layout_inflater";
 
     /**
@@ -3932,6 +3937,7 @@
      *
      * @see #getSystemService(String)
      */
+    @UiContext
     public static final String WALLPAPER_SERVICE = "wallpaper";
 
     /**
@@ -5184,6 +5190,14 @@
     public static final String SMS_SERVICE = "sms";
 
     /**
+     * Use with {@link #getSystemService(String)} to access people service.
+     *
+     * @see #getSystemService(String)
+     * @hide
+     */
+    public static final String PEOPLE_SERVICE = "people";
+
+    /**
      * Determine whether the given permission is allowed for a particular
      * process and user ID running in the system.
      *
@@ -5789,6 +5803,7 @@
      *
      * @return A {@link Context} for the display.
      */
+    @DisplayContext
     public abstract Context createDisplayContext(@NonNull Display display);
 
     /**
@@ -5853,7 +5868,9 @@
      * the current number of window contexts without adding any view by
      * {@link WindowManager#addView} <b>exceeds five</b>.
      */
-    public @NonNull Context createWindowContext(@WindowType int type, @Nullable Bundle options)  {
+    @UiContext
+    @NonNull
+    public Context createWindowContext(@WindowType int type, @Nullable Bundle options)  {
         throw new RuntimeException("Not implemented. Must override in a subclass.");
     }
 
diff --git a/core/java/android/content/IntentFilter.java b/core/java/android/content/IntentFilter.java
index ee9bd3d..7fe29a9 100644
--- a/core/java/android/content/IntentFilter.java
+++ b/core/java/android/content/IntentFilter.java
@@ -277,6 +277,14 @@
     public static final String SCHEME_HTTPS = "https";
 
     /**
+     * Package scheme
+     *
+     * @see #addDataScheme(String)
+     * @hide
+     */
+    public static final String SCHEME_PACKAGE = "package";
+
+    /**
      * The value to indicate a wildcard for incoming match arguments.
      * @hide
      */
diff --git a/core/java/android/content/LocusId.java b/core/java/android/content/LocusId.java
index 283cea0..98e71f0 100644
--- a/core/java/android/content/LocusId.java
+++ b/core/java/android/content/LocusId.java
@@ -33,7 +33,7 @@
  * by the Android System to correlate state between different subsystems such as content capture,
  * shortcuts, and notifications.
  *
- * <p>For example, if your app provides an activiy representing a chat between 2 users
+ * <p>For example, if your app provides an activity representing a chat between 2 users
  * (say {@code A} and {@code B}, this chat state could be represented by:
  *
  * <pre><code>
diff --git a/core/java/android/content/pm/ActivityInfo.java b/core/java/android/content/pm/ActivityInfo.java
index bd02210..31c77ee 100644
--- a/core/java/android/content/pm/ActivityInfo.java
+++ b/core/java/android/content/pm/ActivityInfo.java
@@ -963,7 +963,7 @@
     /** @hide */
     public static final int LOCK_TASK_LAUNCH_MODE_ALWAYS = 2;
     /** @hide */
-    public static final int LOCK_TASK_LAUNCH_MODE_IF_WHITELISTED = 3;
+    public static final int LOCK_TASK_LAUNCH_MODE_IF_ALLOWLISTED = 3;
 
     /** @hide */
     public static final String lockTaskLaunchModeToString(int lockTaskLaunchMode) {
@@ -974,8 +974,8 @@
                 return "LOCK_TASK_LAUNCH_MODE_NEVER";
             case LOCK_TASK_LAUNCH_MODE_ALWAYS:
                 return "LOCK_TASK_LAUNCH_MODE_ALWAYS";
-            case LOCK_TASK_LAUNCH_MODE_IF_WHITELISTED:
-                return "LOCK_TASK_LAUNCH_MODE_IF_WHITELISTED";
+            case LOCK_TASK_LAUNCH_MODE_IF_ALLOWLISTED:
+                return "LOCK_TASK_LAUNCH_MODE_IF_ALLOWLISTED";
             default:
                 return "unknown=" + lockTaskLaunchMode;
         }
diff --git a/core/java/android/content/pm/ApplicationInfo.java b/core/java/android/content/pm/ApplicationInfo.java
index 043953d..0c6810c 100644
--- a/core/java/android/content/pm/ApplicationInfo.java
+++ b/core/java/android/content/pm/ApplicationInfo.java
@@ -1134,6 +1134,7 @@
      * @hide
      */
     @SystemApi
+    @TestApi
     public int targetSandboxVersion;
 
     /**
diff --git a/core/java/android/content/pm/InstantAppRequest.java b/core/java/android/content/pm/InstantAppRequest.java
index 84f5021..5b5d109f 100644
--- a/core/java/android/content/pm/InstantAppRequest.java
+++ b/core/java/android/content/pm/InstantAppRequest.java
@@ -18,6 +18,7 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.annotation.UserIdInt;
 import android.content.Intent;
 import android.os.Bundle;
 
@@ -40,7 +41,7 @@
     /** Whether or not the requesting package was an instant app */
     public final boolean isRequesterInstantApp;
     /** ID of the user requesting the instant application */
-    public final int userId;
+    public final @UserIdInt int userId;
     /**
      * Optional extra bundle provided by the source application to the installer for additional
      * verification.
@@ -60,7 +61,7 @@
 
     public InstantAppRequest(AuxiliaryResolveInfo responseObj, Intent origIntent,
             String resolvedType, String callingPackage, @Nullable String callingFeatureId,
-            boolean isRequesterInstantApp, int userId, Bundle verificationBundle,
+            boolean isRequesterInstantApp, @UserIdInt int userId, Bundle verificationBundle,
             boolean resolveForStart, @Nullable int[] hostDigestPrefixSecure,
             @NonNull String token) {
         this.responseObj = responseObj;
diff --git a/core/java/android/content/pm/IntentFilterVerificationInfo.java b/core/java/android/content/pm/IntentFilterVerificationInfo.java
index 67bda2c..0a913ac 100644
--- a/core/java/android/content/pm/IntentFilterVerificationInfo.java
+++ b/core/java/android/content/pm/IntentFilterVerificationInfo.java
@@ -56,19 +56,19 @@
 
     private ArraySet<String> mDomains = new ArraySet<>();
     private String mPackageName;
-    private int mMainStatus;
+    private int mStatus;
 
     /** @hide */
     public IntentFilterVerificationInfo() {
         mPackageName = null;
-        mMainStatus = INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_UNDEFINED;
+        mStatus = INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_UNDEFINED;
     }
 
     /** @hide */
     public IntentFilterVerificationInfo(String packageName, ArraySet<String> domains) {
         mPackageName = packageName;
         mDomains = domains;
-        mMainStatus = INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_UNDEFINED;
+        mStatus = INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_UNDEFINED;
     }
 
     /** @hide */
@@ -87,14 +87,14 @@
     }
 
     public int getStatus() {
-        return mMainStatus;
+        return mStatus;
     }
 
     /** @hide */
     public void setStatus(int s) {
         if (s >= INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_UNDEFINED &&
                 s <= INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_NEVER) {
-            mMainStatus = s;
+            mStatus = s;
         } else {
             Log.w(TAG, "Trying to set a non supported status: " + s);
         }
@@ -156,7 +156,7 @@
         if (status == -1) {
             Log.e(TAG, "Unknown status value: " + status);
         }
-        mMainStatus = status;
+        mStatus = status;
 
         int outerDepth = parser.getDepth();
         int type;
@@ -184,7 +184,7 @@
     /** @hide */
     public void writeToXml(XmlSerializer serializer) throws IOException {
         serializer.attribute(null, ATTR_PACKAGE_NAME, mPackageName);
-        serializer.attribute(null, ATTR_STATUS, String.valueOf(mMainStatus));
+        serializer.attribute(null, ATTR_STATUS, String.valueOf(mStatus));
         for (String str : mDomains) {
             serializer.startTag(null, TAG_DOMAIN);
             serializer.attribute(null, ATTR_DOMAIN_NAME, str);
@@ -194,7 +194,7 @@
 
     /** @hide */
     public String getStatusString() {
-        return getStatusStringFromValue(((long)mMainStatus) << 32);
+        return getStatusStringFromValue(((long) mStatus) << 32);
     }
 
     /** @hide */
@@ -233,7 +233,7 @@
 
     private void readFromParcel(Parcel source) {
         mPackageName = source.readString();
-        mMainStatus = source.readInt();
+        mStatus = source.readInt();
         ArrayList<String> list = new ArrayList<>();
         source.readStringList(list);
         mDomains.addAll(list);
@@ -242,7 +242,7 @@
     @Override
     public void writeToParcel(Parcel dest, int flags) {
         dest.writeString(mPackageName);
-        dest.writeInt(mMainStatus);
+        dest.writeInt(mStatus);
         dest.writeStringList(new ArrayList<>(mDomains));
     }
 
diff --git a/core/java/android/content/pm/PackageInstaller.java b/core/java/android/content/pm/PackageInstaller.java
index df9db27..b7c3289 100644
--- a/core/java/android/content/pm/PackageInstaller.java
+++ b/core/java/android/content/pm/PackageInstaller.java
@@ -2075,7 +2075,8 @@
                 STAGED_SESSION_NO_ERROR,
                 STAGED_SESSION_VERIFICATION_FAILED,
                 STAGED_SESSION_ACTIVATION_FAILED,
-                STAGED_SESSION_UNKNOWN})
+                STAGED_SESSION_UNKNOWN,
+                STAGED_SESSION_CONFLICT})
         @Retention(RetentionPolicy.SOURCE)
         public @interface StagedSessionErrorCode{}
         /**
@@ -2101,6 +2102,12 @@
          */
         public static final int STAGED_SESSION_UNKNOWN = 3;
 
+        /**
+         * Constant indicating that the session was in conflict with another staged session and had
+         * to be sacrificed for resolution.
+         */
+        public static final int STAGED_SESSION_CONFLICT = 4;
+
         /** {@hide} */
         @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
         public int sessionId;
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index 42a6107..7b2955d 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -3373,6 +3373,7 @@
      * @hide
      */
     @SystemApi
+    @TestApi
     public static final int FLAG_PERMISSION_GRANTED_BY_DEFAULT =  1 << 5;
 
     /**
@@ -8089,7 +8090,8 @@
     private static final PropertyInvalidatedCache<ApplicationInfoQuery, ApplicationInfo>
             sApplicationInfoCache =
             new PropertyInvalidatedCache<ApplicationInfoQuery, ApplicationInfo>(
-                    16, PermissionManager.CACHE_KEY_PACKAGE_INFO) {
+                    16, PermissionManager.CACHE_KEY_PACKAGE_INFO,
+                    "getApplicationInfo") {
                 @Override
                 protected ApplicationInfo recompute(ApplicationInfoQuery query) {
                     return getApplicationInfoAsUserUncached(
@@ -8190,7 +8192,8 @@
     private static final PropertyInvalidatedCache<PackageInfoQuery, PackageInfo>
             sPackageInfoCache =
             new PropertyInvalidatedCache<PackageInfoQuery, PackageInfo>(
-                    32, PermissionManager.CACHE_KEY_PACKAGE_INFO) {
+                    32, PermissionManager.CACHE_KEY_PACKAGE_INFO,
+                    "getPackageInfo") {
                 @Override
                 protected PackageInfo recompute(PackageInfoQuery query) {
                     return getPackageInfoAsUserUncached(
diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java
index 2006048..0f1f276 100644
--- a/core/java/android/content/pm/PackageParser.java
+++ b/core/java/android/content/pm/PackageParser.java
@@ -88,7 +88,6 @@
 import android.util.SparseArray;
 import android.util.TypedValue;
 import android.util.apk.ApkSignatureVerifier;
-import android.view.Display;
 import android.view.Gravity;
 
 import com.android.internal.R;
@@ -8536,7 +8535,7 @@
                 null,
                 androidAppInfo.resourceDirs,
                 androidAppInfo.sharedLibraryFiles,
-                Display.DEFAULT_DISPLAY,
+                null,
                 null,
                 systemResources.getCompatibilityInfo(),
                 systemResources.getClassLoader(),
diff --git a/core/java/android/content/res/ApkAssets.java b/core/java/android/content/res/ApkAssets.java
index bc41806..b0437ac 100644
--- a/core/java/android/content/res/ApkAssets.java
+++ b/core/java/android/content/res/ApkAssets.java
@@ -101,14 +101,11 @@
     public @interface FormatType {}
 
     @GuardedBy("this")
-    private final long mNativePtr;
+    private long mNativePtr;  // final, except cleared in finalizer.
 
     @Nullable
     @GuardedBy("this")
-    private final StringBlock mStringBlock;
-
-    @GuardedBy("this")
-    private boolean mOpen = true;
+    private final StringBlock mStringBlock;  // null or closed if mNativePtr = 0.
 
     @PropertyFlags
     private final int mFlags;
@@ -380,12 +377,16 @@
     /** @hide */
     @Nullable
     public OverlayableInfo getOverlayableInfo(String overlayableName) throws IOException {
-        return nativeGetOverlayableInfo(mNativePtr, overlayableName);
+        synchronized (this) {
+            return nativeGetOverlayableInfo(mNativePtr, overlayableName);
+        }
     }
 
     /** @hide */
     public boolean definesOverlayable() throws IOException {
-        return nativeDefinesOverlayable(mNativePtr);
+        synchronized (this) {
+            return nativeDefinesOverlayable(mNativePtr);
+        }
     }
 
     /**
@@ -412,12 +413,12 @@
      */
     public void close() {
         synchronized (this) {
-            if (mOpen) {
-                mOpen = false;
+            if (mNativePtr != 0) {
                 if (mStringBlock != null) {
                     mStringBlock.close();
                 }
                 nativeDestroy(mNativePtr);
+                mNativePtr = 0;
             }
         }
     }
diff --git a/core/java/android/content/res/ResourcesKey.java b/core/java/android/content/res/ResourcesKey.java
index 9da0f20..fcb80aa 100644
--- a/core/java/android/content/res/ResourcesKey.java
+++ b/core/java/android/content/res/ResourcesKey.java
@@ -16,6 +16,8 @@
 
 package android.content.res;
 
+import static android.os.Build.VERSION_CODES.O;
+
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.compat.annotation.UnsupportedAppUsage;
@@ -41,8 +43,17 @@
     @Nullable
     public final String[] mLibDirs;
 
-    public final int mDisplayId;
+    /**
+     * The display ID that overrides the global resources display to produce the Resources display.
+     * If set to something other than {@link android.view.Display#INVALID_DISPLAY} this will
+     * override the global resources display for this key.
+     */
+    @UnsupportedAppUsage(maxTargetSdk = O)
+    public int mDisplayId;
 
+    /**
+     * The configuration applied to the global configuration to produce the Resources configuration.
+     */
     @NonNull
     public final Configuration mOverrideConfiguration;
 
@@ -58,7 +69,7 @@
                         @Nullable String[] splitResDirs,
                         @Nullable String[] overlayDirs,
                         @Nullable String[] libDirs,
-                        int displayId,
+                        int overrideDisplayId,
                         @Nullable Configuration overrideConfig,
                         @Nullable CompatibilityInfo compatInfo,
                         @Nullable ResourcesLoader[] loader) {
@@ -67,7 +78,7 @@
         mOverlayDirs = overlayDirs;
         mLibDirs = libDirs;
         mLoaders = (loader != null && loader.length == 0) ? null : loader;
-        mDisplayId = displayId;
+        mDisplayId = overrideDisplayId;
         mOverrideConfiguration = new Configuration(overrideConfig != null
                 ? overrideConfig : Configuration.EMPTY);
         mCompatInfo = compatInfo != null ? compatInfo : CompatibilityInfo.DEFAULT_COMPATIBILITY_INFO;
@@ -77,7 +88,7 @@
         hash = 31 * hash + Arrays.hashCode(mSplitResDirs);
         hash = 31 * hash + Arrays.hashCode(mOverlayDirs);
         hash = 31 * hash + Arrays.hashCode(mLibDirs);
-        hash = 31 * hash + mDisplayId;
+        hash = 31 * hash + Objects.hashCode(mDisplayId);
         hash = 31 * hash + Objects.hashCode(mOverrideConfiguration);
         hash = 31 * hash + Objects.hashCode(mCompatInfo);
         hash = 31 * hash + Arrays.hashCode(mLoaders);
diff --git a/core/java/android/content/res/XmlBlock.java b/core/java/android/content/res/XmlBlock.java
index cb93cbf..fcbe362 100644
--- a/core/java/android/content/res/XmlBlock.java
+++ b/core/java/android/content/res/XmlBlock.java
@@ -23,6 +23,7 @@
 import android.compat.annotation.UnsupportedAppUsage;
 import android.util.TypedValue;
 
+import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.util.XmlUtils;
 
 import dalvik.annotation.optimization.FastNative;
@@ -38,7 +39,8 @@
  * 
  * {@hide}
  */
-final class XmlBlock implements AutoCloseable {
+@VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
+public final class XmlBlock implements AutoCloseable {
     private static final boolean DEBUG=false;
 
     @UnsupportedAppUsage
@@ -88,7 +90,8 @@
         }
     }
 
-    /*package*/ final class Parser implements XmlResourceParser {
+    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
+    public final class Parser implements XmlResourceParser {
         Parser(long parseState, XmlBlock block) {
             mParseState = parseState;
             mBlock = block;
diff --git a/core/java/android/hardware/biometrics/BiometricManager.java b/core/java/android/hardware/biometrics/BiometricManager.java
index a605f5d..f86eb90 100644
--- a/core/java/android/hardware/biometrics/BiometricManager.java
+++ b/core/java/android/hardware/biometrics/BiometricManager.java
@@ -300,26 +300,6 @@
     }
 
     /**
-     * Reset the lockout when user authenticates with strong auth (e.g. PIN, pattern or password)
-     *
-     * @param userId this operation takes effect for.
-     * @param hardwareAuthToken an opaque token returned by password confirmation.
-     * @hide
-     */
-    @RequiresPermission(USE_BIOMETRIC_INTERNAL)
-    public void resetLockout(int userId, byte[] hardwareAuthToken) {
-        if (mService != null) {
-            try {
-                mService.resetLockout(userId, hardwareAuthToken);
-            } catch (RemoteException e) {
-                throw e.rethrowFromSystemServer();
-            }
-        } else {
-            Slog.w(TAG, "resetLockout(): Service not connected");
-        }
-    }
-
-    /**
      * Get a list of AuthenticatorIDs for biometric authenticators which have 1) enrolled templates,
      * and 2) meet the requirements for integrating with Keystore. The AuthenticatorIDs are known
      * in Keystore land as SIDs, and are used during key generation.
diff --git a/core/java/android/hardware/biometrics/IAuthService.aidl b/core/java/android/hardware/biometrics/IAuthService.aidl
index c1d0ad4..dd6aa73 100644
--- a/core/java/android/hardware/biometrics/IAuthService.aidl
+++ b/core/java/android/hardware/biometrics/IAuthService.aidl
@@ -46,9 +46,6 @@
     // Register callback for when keyguard biometric eligibility changes.
     void registerEnabledOnKeyguardCallback(IBiometricEnabledOnKeyguardCallback callback);
 
-    // Reset the lockout when user authenticates with strong auth (e.g. PIN, pattern or password)
-    void resetLockout(int userId, in byte [] hardwareAuthToken);
-
     // Get a list of AuthenticatorIDs for authenticators which have enrolled templates and meet
     // the requirements for integrating with Keystore. The AuthenticatorID are known in Keystore
     // land as SIDs, and are used during key generation.
diff --git a/core/java/android/hardware/biometrics/IBiometricAuthenticator.aidl b/core/java/android/hardware/biometrics/IBiometricAuthenticator.aidl
index 8eb22da..5e6fe68 100644
--- a/core/java/android/hardware/biometrics/IBiometricAuthenticator.aidl
+++ b/core/java/android/hardware/biometrics/IBiometricAuthenticator.aidl
@@ -53,9 +53,6 @@
     // Return the LockoutTracker status for the specified user
     int getLockoutModeForUser(int userId);
 
-    // Reset the lockout when user authenticates with strong auth (e.g. PIN, pattern or password)
-    void resetLockout(int userId, in byte [] hardwareAuthToken);
-
     // Gets the authenticator ID representing the current set of enrolled templates
     long getAuthenticatorId(int callingUserId);
 }
diff --git a/core/java/android/hardware/biometrics/IBiometricService.aidl b/core/java/android/hardware/biometrics/IBiometricService.aidl
index a5b3abb..005ed32 100644
--- a/core/java/android/hardware/biometrics/IBiometricService.aidl
+++ b/core/java/android/hardware/biometrics/IBiometricService.aidl
@@ -56,9 +56,6 @@
     // Client lifecycle is still managed in <Biometric>Service.
     void onReadyForAuthentication(int cookie);
 
-    // Reset the lockout when user authenticates with strong auth (e.g. PIN, pattern or password)
-    void resetLockout(int userId, in byte [] hardwareAuthToken);
-
     // Get a list of AuthenticatorIDs for authenticators which have enrolled templates and meet
     // the requirements for integrating with Keystore. The AuthenticatorID are known in Keystore
     // land as SIDs, and are used during key generation.
diff --git a/core/java/android/hardware/camera2/CameraCaptureSession.java b/core/java/android/hardware/camera2/CameraCaptureSession.java
index f69bbe5..f0a83f0 100644
--- a/core/java/android/hardware/camera2/CameraCaptureSession.java
+++ b/core/java/android/hardware/camera2/CameraCaptureSession.java
@@ -999,7 +999,7 @@
          * <p>If the camera device configuration fails, then {@link #onConfigureFailed} will
          * be invoked instead of this callback.</p>
          *
-         * @param session the session returned by {@link CameraDevice#createCaptureSession}
+         * @param session the successfully configured session instance
          */
         public abstract void onConfigured(@NonNull CameraCaptureSession session);
 
@@ -1014,7 +1014,7 @@
          * to the session prior to this callback will be discarded and will not produce any
          * callbacks on their listeners.</p>
          *
-         * @param session the session returned by {@link CameraDevice#createCaptureSession}
+         * @param session the session instance that failed during configuration
          */
         public abstract void onConfigureFailed(@NonNull CameraCaptureSession session);
 
@@ -1028,7 +1028,7 @@
          * <p>Otherwise, this callback will be invoked any time the session finishes processing
          * all of its active capture requests, and no repeating request or burst is set up.</p>
          *
-         * @param session the session returned by {@link CameraDevice#createCaptureSession}
+         * @param session the session returned by {@link #onConfigured}
          *
          */
         public void onReady(@NonNull CameraCaptureSession session) {
@@ -1045,7 +1045,7 @@
          * <p>If the session runs out of capture requests to process and calls {@link #onReady},
          * then this callback will be invoked again once new requests are submitted for capture.</p>
          *
-         * @param session the session returned by {@link CameraDevice#createCaptureSession}
+         * @param session the session returned by {@link #onConfigured}
          */
         public void onActive(@NonNull CameraCaptureSession session) {
             // default empty implementation
@@ -1075,7 +1075,7 @@
          * {@link #onReady}, which is fired when all requests in both queues have been processed.</p>
          *
          * @param session
-         *            The session returned by {@link CameraDevice#createCaptureSession}
+         *            The session returned by {@link #onConfigured}
          */
         public void onCaptureQueueEmpty(@NonNull CameraCaptureSession session) {
             // default empty implementation
@@ -1093,7 +1093,7 @@
          * However, any in-progress capture requests submitted to the session will be completed
          * as normal.</p>
          *
-         * @param session the session returned by {@link CameraDevice#createCaptureSession}
+         * @param session the session returned by {@link #onConfigured}
          */
         public void onClosed(@NonNull CameraCaptureSession session) {
             // default empty implementation
@@ -1111,7 +1111,7 @@
          * this callback is still invoked after the error is encountered, though some buffers may
          * not have been successfully pre-allocated.</p>
          *
-         * @param session the session returned by {@link CameraDevice#createCaptureSession}
+         * @param session the session returned by {@link #onConfigured}
          * @param surface the Surface that was used with the {@link #prepare} call.
          */
         public void onSurfacePrepared(@NonNull CameraCaptureSession session,
diff --git a/core/java/android/hardware/display/DisplayManager.java b/core/java/android/hardware/display/DisplayManager.java
index c1ba209..392f562 100644
--- a/core/java/android/hardware/display/DisplayManager.java
+++ b/core/java/android/hardware/display/DisplayManager.java
@@ -310,6 +310,7 @@
      * @hide
      */
     // TODO (b/114338689): Remove the flag and use IWindowManager#setShouldShowSystemDecors
+    @TestApi
     public static final int VIRTUAL_DISPLAY_FLAG_SHOULD_SHOW_SYSTEM_DECORATIONS = 1 << 9;
 
     /**
@@ -320,6 +321,7 @@
      * @see #VIRTUAL_DISPLAY_FLAG_SHOULD_SHOW_SYSTEM_DECORATIONS
      * @hide
      */
+    @TestApi
     public static final int VIRTUAL_DISPLAY_FLAG_TRUSTED = 1 << 10;
 
     /** @hide */
diff --git a/core/java/android/hardware/display/DisplayManagerInternal.java b/core/java/android/hardware/display/DisplayManagerInternal.java
index 7ac8d05..c7f8915 100644
--- a/core/java/android/hardware/display/DisplayManagerInternal.java
+++ b/core/java/android/hardware/display/DisplayManagerInternal.java
@@ -260,6 +260,13 @@
             int displayId, long maxFrames, long timestamp);
 
     /**
+     * Temporarily ignore proximity-sensor-based display behavior until there is a change
+     * to the proximity sensor state. This allows the display to turn back on even if something
+     * is obstructing the proximity sensor.
+     */
+    public abstract void ignoreProximitySensorUntilChanged();
+
+    /**
      * Describes the requested power state of the display.
      *
      * This object is intended to describe the general characteristics of the
diff --git a/core/java/android/hardware/face/FaceManager.java b/core/java/android/hardware/face/FaceManager.java
index 885d137..1b114d3 100644
--- a/core/java/android/hardware/face/FaceManager.java
+++ b/core/java/android/hardware/face/FaceManager.java
@@ -72,6 +72,8 @@
     private static final int MSG_SET_FEATURE_COMPLETED = 107;
     private static final int MSG_CHALLENGE_GENERATED = 108;
     private static final int MSG_FACE_DETECTED = 109;
+    private static final int MSG_CHALLENGE_INTERRUPTED = 110;
+    private static final int MSG_CHALLENGE_INTERRUPT_FINISHED = 111;
 
     private final IFaceService mService;
     private final Context mContext;
@@ -141,14 +143,19 @@
         }
 
         @Override
-        public void onChallengeGenerated(long challenge) {
-            if (mGenerateChallengeCallback instanceof InternalGenerateChallengeCallback) {
-                // Perform this on system_server thread, since the application's thread is
-                // blocked waiting for the result
-                mGenerateChallengeCallback.onGenerateChallengeResult(challenge);
-            } else {
-                mHandler.obtainMessage(MSG_CHALLENGE_GENERATED, challenge).sendToTarget();
-            }
+        public void onChallengeGenerated(int sensorId, long challenge) {
+            mHandler.obtainMessage(MSG_CHALLENGE_GENERATED, sensorId, 0, challenge)
+                    .sendToTarget();
+        }
+
+        @Override
+        public void onChallengeInterrupted(int sensorId) {
+            mHandler.obtainMessage(MSG_CHALLENGE_INTERRUPTED, sensorId).sendToTarget();
+        }
+
+        @Override
+        public void onChallengeInterruptFinished(int sensorId) {
+            mHandler.obtainMessage(MSG_CHALLENGE_INTERRUPT_FINISHED, sensorId).sendToTarget();
         }
     };
 
@@ -405,35 +412,6 @@
     }
 
     /**
-     * Same as {@link #generateChallenge(GenerateChallengeCallback)}, except blocks until the
-     * TEE/hardware operation is complete.
-     * @return challenge generated in the TEE/hardware
-     * @hide
-     */
-    @RequiresPermission(MANAGE_BIOMETRIC)
-    public long generateChallengeBlocking() {
-        final AtomicReference<Long> result = new AtomicReference<>();
-        final CountDownLatch latch = new CountDownLatch(1);
-        final GenerateChallengeCallback callback = new InternalGenerateChallengeCallback() {
-            @Override
-            public void onGenerateChallengeResult(long challenge) {
-                result.set(challenge);
-                latch.countDown();
-            }
-        };
-
-        generateChallenge(callback);
-
-        try {
-            latch.await(1, TimeUnit.SECONDS);
-        } catch (InterruptedException e) {
-            Slog.e(TAG, "Interrupted while generatingChallenge", e);
-            e.printStackTrace();
-        }
-        return result.get();
-    }
-
-    /**
      * Generates a unique random challenge in the TEE. A typical use case is to have it wrapped in a
      * HardwareAuthenticationToken, minted by Gatekeeper upon PIN/Pattern/Password verification.
      * The HardwareAuthenticationToken can then be sent to the biometric HAL together with a
@@ -446,11 +424,12 @@
      * @hide
      */
     @RequiresPermission(MANAGE_BIOMETRIC)
-    public void generateChallenge(GenerateChallengeCallback callback) {
+    public void generateChallenge(int sensorId, GenerateChallengeCallback callback) {
         if (mService != null) {
             try {
                 mGenerateChallengeCallback = callback;
-                mService.generateChallenge(mToken, mServiceReceiver, mContext.getOpPackageName());
+                mService.generateChallenge(mToken, sensorId, mServiceReceiver,
+                        mContext.getOpPackageName());
             } catch (RemoteException e) {
                 throw e.rethrowFromSystemServer();
             }
@@ -458,15 +437,66 @@
     }
 
     /**
-     * Invalidates the current auth token.
+     * Same as {@link #generateChallenge(int, GenerateChallengeCallback)}, but assumes the first
+     * enumerated sensor.
+     * @hide
+     */
+    @RequiresPermission(MANAGE_BIOMETRIC)
+    public void generateChallenge(GenerateChallengeCallback callback) {
+        final List<FaceSensorProperties> faceSensorProperties = getSensorProperties();
+        if (faceSensorProperties.isEmpty()) {
+            Slog.e(TAG, "No sensors");
+            return;
+        }
+
+        final int sensorId = faceSensorProperties.get(0).sensorId;
+        generateChallenge(sensorId, callback);
+    }
+
+    /**
+     * Invalidates the current challenge.
      *
      * @hide
      */
     @RequiresPermission(MANAGE_BIOMETRIC)
     public void revokeChallenge() {
+        final List<FaceSensorProperties> faceSensorProperties = getSensorProperties();
+        if (faceSensorProperties.isEmpty()) {
+            Slog.e(TAG, "No sensors during revokeChallenge");
+        }
+        revokeChallenge(faceSensorProperties.get(0).sensorId);
+    }
+
+    /**
+     * Invalidates the current challenge.
+     *
+     * @hide
+     */
+    @RequiresPermission(MANAGE_BIOMETRIC)
+    public void revokeChallenge(int sensorId) {
         if (mService != null) {
             try {
-                mService.revokeChallenge(mToken, mContext.getOpPackageName());
+                mService.revokeChallenge(mToken, sensorId, mContext.getOpPackageName());
+            } catch (RemoteException e) {
+                throw e.rethrowFromSystemServer();
+            }
+        }
+    }
+
+    /**
+     * Reset the lockout when user authenticates with strong auth (e.g. PIN, pattern or password)
+     *
+     * @param sensorId Sensor ID that this operation takes effect for
+     * @param userId User ID that this operation takes effect for.
+     * @param hardwareAuthToken An opaque token returned by password confirmation.
+     * @hide
+     */
+    @RequiresPermission(USE_BIOMETRIC_INTERNAL)
+    public void resetLockout(int sensorId, int userId, @Nullable byte[] hardwareAuthToken) {
+        if (mService != null) {
+            try {
+                mService.resetLockout(mToken, sensorId, userId, hardwareAuthToken,
+                        mContext.getOpPackageName());
             } catch (RemoteException e) {
                 throw e.rethrowFromSystemServer();
             }
@@ -1071,14 +1101,26 @@
     }
 
     /**
+     * Callback structure provided to {@link #generateChallenge(int, GenerateChallengeCallback)}.
      * @hide
      */
-    public abstract static class GenerateChallengeCallback {
-        public abstract void onGenerateChallengeResult(long challenge);
-    }
+    public interface GenerateChallengeCallback {
+        /**
+         * Invoked when a challenge has been generated.
+         */
+        void onGenerateChallengeResult(int sensorId, long challenge);
 
-    private abstract static class InternalGenerateChallengeCallback
-            extends GenerateChallengeCallback {}
+        /**
+         * Invoked if the challenge has not been revoked and a subsequent caller/owner invokes
+         * {@link #generateChallenge(int, GenerateChallengeCallback)}, but
+         */
+        default void onChallengeInterrupted(int sensorId) {}
+
+        /**
+         * Invoked when the interrupting client has finished (e.g. revoked its challenge).
+         */
+        default void onChallengeInterruptFinished(int sensorId) {}
+    }
 
     private class OnEnrollCancelListener implements OnCancelListener {
         @Override
@@ -1151,12 +1193,18 @@
                     args.recycle();
                     break;
                 case MSG_CHALLENGE_GENERATED:
-                    sendChallengeGenerated((long) msg.obj /* challenge */);
+                    sendChallengeGenerated(msg.arg1 /* sensorId */, (long) msg.obj /* challenge */);
                     break;
                 case MSG_FACE_DETECTED:
                     sendFaceDetected(msg.arg1 /* sensorId */, msg.arg2 /* userId */,
                             (boolean) msg.obj /* isStrongBiometric */);
                     break;
+                case MSG_CHALLENGE_INTERRUPTED:
+                    sendChallengeInterrupted((int) msg.obj /* sensorId */);
+                    break;
+                case MSG_CHALLENGE_INTERRUPT_FINISHED:
+                    sendChallengeInterruptFinished((int) msg.obj /* sensorId */);
+                    break;
                 default:
                     Slog.w(TAG, "Unknown message: " + msg.what);
             }
@@ -1178,11 +1226,11 @@
         mGetFeatureCallback.onCompleted(success, feature, value);
     }
 
-    private void sendChallengeGenerated(long challenge) {
+    private void sendChallengeGenerated(int sensorId, long challenge) {
         if (mGenerateChallengeCallback == null) {
             return;
         }
-        mGenerateChallengeCallback.onGenerateChallengeResult(challenge);
+        mGenerateChallengeCallback.onGenerateChallengeResult(sensorId, challenge);
     }
 
     private void sendFaceDetected(int sensorId, int userId, boolean isStrongBiometric) {
@@ -1193,6 +1241,22 @@
         mFaceDetectionCallback.onFaceDetected(sensorId, userId, isStrongBiometric);
     }
 
+    private void sendChallengeInterrupted(int sensorId) {
+        if (mGenerateChallengeCallback == null) {
+            Slog.e(TAG, "sendChallengeInterrupted, callback null");
+            return;
+        }
+        mGenerateChallengeCallback.onChallengeInterrupted(sensorId);
+    }
+
+    private void sendChallengeInterruptFinished(int sensorId) {
+        if (mGenerateChallengeCallback == null) {
+            Slog.e(TAG, "sendChallengeInterruptFinished, callback null");
+            return;
+        }
+        mGenerateChallengeCallback.onChallengeInterruptFinished(sensorId);
+    }
+
     private void sendRemovedResult(Face face, int remaining) {
         if (mRemovalCallback == null) {
             return;
diff --git a/core/java/android/hardware/face/FaceSensorProperties.java b/core/java/android/hardware/face/FaceSensorProperties.java
index e3b2fbb..1015724 100644
--- a/core/java/android/hardware/face/FaceSensorProperties.java
+++ b/core/java/android/hardware/face/FaceSensorProperties.java
@@ -25,20 +25,36 @@
  */
 public class FaceSensorProperties implements Parcelable {
 
+    /**
+     * A statically configured ID representing this sensor. Sensor IDs must be unique across all
+     * biometrics across the device, starting at 0, and in increments of 1.
+     */
     public final int sensorId;
+    /**
+     * True if the sensor is able to perform generic face detection, without running the
+     * matching algorithm, and without affecting the lockout counter.
+     */
     public final boolean supportsFaceDetection;
+    /**
+     * True if the sensor is able to provide self illumination in dark scenarios, without support
+     * from above the HAL.
+     */
+    public final boolean supportsSelfIllumination;
 
     /**
      * Initializes SensorProperties with specified values
      */
-    public FaceSensorProperties(int sensorId, boolean supportsFaceDetection) {
+    public FaceSensorProperties(int sensorId, boolean supportsFaceDetection,
+            boolean supportsSelfIllumination) {
         this.sensorId = sensorId;
         this.supportsFaceDetection = supportsFaceDetection;
+        this.supportsSelfIllumination = supportsSelfIllumination;
     }
 
     protected FaceSensorProperties(Parcel in) {
         sensorId = in.readInt();
         supportsFaceDetection = in.readBoolean();
+        supportsSelfIllumination = in.readBoolean();
     }
 
     public static final Creator<FaceSensorProperties> CREATOR =
@@ -63,5 +79,6 @@
     public void writeToParcel(Parcel dest, int flags) {
         dest.writeInt(sensorId);
         dest.writeBoolean(supportsFaceDetection);
+        dest.writeBoolean(supportsSelfIllumination);
     }
 }
diff --git a/core/java/android/hardware/face/IFaceService.aidl b/core/java/android/hardware/face/IFaceService.aidl
index a9097d4..437feb1 100644
--- a/core/java/android/hardware/face/IFaceService.aidl
+++ b/core/java/android/hardware/face/IFaceService.aidl
@@ -83,10 +83,10 @@
     boolean isHardwareDetected(String opPackageName);
 
     // Get a pre-enrollment authentication token
-    void generateChallenge(IBinder token, IFaceServiceReceiver receiver, String opPackageName);
+    void generateChallenge(IBinder token, int sensorId, IFaceServiceReceiver receiver, String opPackageName);
 
     // Finish an enrollment sequence and invalidate the authentication token
-    void revokeChallenge(IBinder token, String opPackageName);
+    void revokeChallenge(IBinder token, int sensorId, String opPackageName);
 
     // Determine if a user has at least one enrolled face
     boolean hasEnrolledFaces(int userId, String opPackageName);
@@ -98,7 +98,7 @@
     long getAuthenticatorId(int callingUserId);
 
     // Reset the lockout when user authenticates with strong auth (e.g. PIN, pattern or password)
-    void resetLockout(int userId, in byte [] hardwareAuthToken);
+    void resetLockout(IBinder token, int sensorId, int userId, in byte [] hardwareAuthToken, String opPackageName);
 
     // Add a callback which gets notified when the face lockout period expired.
     void addLockoutResetCallback(IBiometricServiceLockoutResetCallback callback, String opPackageName);
diff --git a/core/java/android/hardware/face/IFaceServiceReceiver.aidl b/core/java/android/hardware/face/IFaceServiceReceiver.aidl
index 2600b7de..bd4d3a0 100644
--- a/core/java/android/hardware/face/IFaceServiceReceiver.aidl
+++ b/core/java/android/hardware/face/IFaceServiceReceiver.aidl
@@ -31,5 +31,7 @@
     void onRemoved(in Face face, int remaining);
     void onFeatureSet(boolean success, int feature);
     void onFeatureGet(boolean success, int feature, boolean value);
-    void onChallengeGenerated(long challenge);
+    void onChallengeGenerated(int sensorId, long challenge);
+    void onChallengeInterrupted(int sensorId);
+    void onChallengeInterruptFinished(int sensorId);
 }
diff --git a/core/java/android/hardware/fingerprint/FingerprintManager.java b/core/java/android/hardware/fingerprint/FingerprintManager.java
index e384da7..c12bb39 100644
--- a/core/java/android/hardware/fingerprint/FingerprintManager.java
+++ b/core/java/android/hardware/fingerprint/FingerprintManager.java
@@ -18,6 +18,7 @@
 
 import static android.Manifest.permission.INTERACT_ACROSS_USERS;
 import static android.Manifest.permission.MANAGE_FINGERPRINT;
+import static android.Manifest.permission.RESET_FINGERPRINT_LOCKOUT;
 import static android.Manifest.permission.USE_BIOMETRIC;
 import static android.Manifest.permission.USE_BIOMETRIC_INTERNAL;
 import static android.Manifest.permission.USE_FINGERPRINT;
@@ -377,13 +378,10 @@
     /**
      * @hide
      */
-    public abstract static class GenerateChallengeCallback {
-        public abstract void onChallengeGenerated(long challenge);
+    public interface GenerateChallengeCallback {
+        void onChallengeGenerated(int sensorId, long challenge);
     }
 
-    private abstract static class InternalGenerateChallengeCallback
-            extends GenerateChallengeCallback {}
-
     /**
      * Request authentication of a crypto object. This call warms up the fingerprint hardware
      * and starts scanning for a fingerprint. It terminates when
@@ -581,37 +579,6 @@
     }
 
     /**
-     * Same as {@link #generateChallenge(GenerateChallengeCallback)}, except blocks until the
-     * TEE/hardware operation is complete.
-     * @return challenge generated in the TEE/hardware
-     * @hide
-     */
-    @RequiresPermission(MANAGE_FINGERPRINT)
-    public long generateChallengeBlocking() {
-        final AtomicReference<Long> result = new AtomicReference<>();
-        final CountDownLatch latch = new CountDownLatch(1);
-        final GenerateChallengeCallback callback = new InternalGenerateChallengeCallback() {
-            @Override
-            public void onChallengeGenerated(long challenge) {
-                result.set(challenge);
-                latch.countDown();
-            }
-        };
-
-        generateChallenge(callback);
-
-        try {
-            latch.await(1, TimeUnit.SECONDS);
-        } catch (InterruptedException e) {
-            Slog.e(TAG, "Interrupted while generatingChallenge", e);
-            e.printStackTrace();
-        }
-
-        return result.get();
-    }
-
-
-    /**
      * Generates a unique random challenge in the TEE. A typical use case is to have it wrapped in a
      * HardwareAuthenticationToken, minted by Gatekeeper upon PIN/Pattern/Password verification.
      * The HardwareAuthenticationToken can then be sent to the biometric HAL together with a
@@ -624,16 +591,34 @@
      * @hide
      */
     @RequiresPermission(MANAGE_FINGERPRINT)
-    public void generateChallenge(GenerateChallengeCallback callback) {
+    public void generateChallenge(int sensorId, GenerateChallengeCallback callback) {
         if (mService != null) try {
             mGenerateChallengeCallback = callback;
-            mService.generateChallenge(mToken, mServiceReceiver, mContext.getOpPackageName());
+            mService.generateChallenge(mToken, sensorId, mServiceReceiver,
+                    mContext.getOpPackageName());
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
     }
 
     /**
+     * Same as {@link #generateChallenge(int, GenerateChallengeCallback)}, but assumes the first
+     * enumerated sensor.
+     * @hide
+     */
+    @RequiresPermission(MANAGE_FINGERPRINT)
+    public void generateChallenge(GenerateChallengeCallback callback) {
+        final List<FingerprintSensorProperties> fingerprintSensorProperties = getSensorProperties();
+        if (fingerprintSensorProperties.isEmpty()) {
+            Slog.e(TAG, "No sensors");
+            return;
+        }
+
+        final int sensorId = fingerprintSensorProperties.get(0).sensorId;
+        generateChallenge(sensorId, callback);
+    }
+
+    /**
      * Finishes enrollment and cancels the current auth token.
      * @hide
      */
@@ -647,6 +632,26 @@
     }
 
     /**
+     * Reset the lockout when user authenticates with strong auth (e.g. PIN, pattern or password)
+     *
+     * @param sensorId Sensor ID that this operation takes effect for
+     * @param userId User ID that this operation takes effect for.
+     * @param hardwareAuthToken An opaque token returned by password confirmation.
+     * @hide
+     */
+    @RequiresPermission(RESET_FINGERPRINT_LOCKOUT)
+    public void resetLockout(int sensorId, int userId, @Nullable byte[] hardwareAuthToken) {
+        if (mService != null) {
+            try {
+                mService.resetLockout(mToken, sensorId, userId, hardwareAuthToken,
+                        mContext.getOpPackageName());
+            } catch (RemoteException e) {
+                throw e.rethrowFromSystemServer();
+            }
+        }
+    }
+
+    /**
      * Remove given fingerprint template from fingerprint hardware and/or protected storage.
      * @param fp the fingerprint item to remove
      * @param userId the user who this fingerprint belongs to
@@ -932,7 +937,7 @@
                     sendRemovedResult((Fingerprint) msg.obj, msg.arg1 /* remaining */);
                     break;
                 case MSG_CHALLENGE_GENERATED:
-                    sendChallengeGenerated((long) msg.obj /* challenge */);
+                    sendChallengeGenerated(msg.arg1 /* sensorId */, (long) msg.obj /* challenge */);
                     break;
                 case MSG_FINGERPRINT_DETECTED:
                     sendFingerprintDetected(msg.arg1 /* sensorId */, msg.arg2 /* userId */,
@@ -1020,12 +1025,12 @@
         }
     }
 
-    private void sendChallengeGenerated(long challenge) {
+    private void sendChallengeGenerated(int sensorId, long challenge) {
         if (mGenerateChallengeCallback == null) {
             Slog.e(TAG, "sendChallengeGenerated, callback null");
             return;
         }
-        mGenerateChallengeCallback.onChallengeGenerated(challenge);
+        mGenerateChallengeCallback.onChallengeGenerated(sensorId, challenge);
     }
 
     private void sendFingerprintDetected(int sensorId, int userId, boolean isStrongBiometric) {
@@ -1209,14 +1214,9 @@
         }
 
         @Override // binder call
-        public void onChallengeGenerated(long challenge) {
-            if (mGenerateChallengeCallback instanceof InternalGenerateChallengeCallback) {
-                // Perform this on system_server thread, since the application's thread is
-                // blocked waiting for the result
-                mGenerateChallengeCallback.onChallengeGenerated(challenge);
-            } else {
-                mHandler.obtainMessage(MSG_CHALLENGE_GENERATED, challenge).sendToTarget();
-            }
+        public void onChallengeGenerated(int sensorId, long challenge) {
+            mHandler.obtainMessage(MSG_CHALLENGE_GENERATED, sensorId, 0, challenge)
+                    .sendToTarget();
         }
     };
 
diff --git a/core/java/android/hardware/fingerprint/FingerprintSensorProperties.java b/core/java/android/hardware/fingerprint/FingerprintSensorProperties.java
index a774121..b28551c 100644
--- a/core/java/android/hardware/fingerprint/FingerprintSensorProperties.java
+++ b/core/java/android/hardware/fingerprint/FingerprintSensorProperties.java
@@ -45,18 +45,24 @@
 
     public final int sensorId;
     public final @SensorType int sensorType;
+    // IBiometricsFingerprint@2.1 does not manage timeout below the HAL, so the Gatekeeper HAT
+    // cannot be checked
+    public final boolean resetLockoutRequiresHardwareAuthToken;
 
     /**
      * Initializes SensorProperties with specified values
      */
-    public FingerprintSensorProperties(int sensorId, @SensorType int sensorType) {
+    public FingerprintSensorProperties(int sensorId, @SensorType int sensorType,
+            boolean resetLockoutRequiresHardwareAuthToken) {
         this.sensorId = sensorId;
         this.sensorType = sensorType;
+        this.resetLockoutRequiresHardwareAuthToken = resetLockoutRequiresHardwareAuthToken;
     }
 
     protected FingerprintSensorProperties(Parcel in) {
         sensorId = in.readInt();
         sensorType = in.readInt();
+        resetLockoutRequiresHardwareAuthToken = in.readBoolean();
     }
 
     public static final Creator<FingerprintSensorProperties> CREATOR =
@@ -81,5 +87,6 @@
     public void writeToParcel(Parcel dest, int flags) {
         dest.writeInt(sensorId);
         dest.writeInt(sensorType);
+        dest.writeBoolean(resetLockoutRequiresHardwareAuthToken);
     }
 }
diff --git a/core/java/android/hardware/fingerprint/IFingerprintService.aidl b/core/java/android/hardware/fingerprint/IFingerprintService.aidl
index f6069d8..0fae156 100644
--- a/core/java/android/hardware/fingerprint/IFingerprintService.aidl
+++ b/core/java/android/hardware/fingerprint/IFingerprintService.aidl
@@ -88,7 +88,7 @@
     boolean isHardwareDetected(String opPackageName);
 
     // Get a pre-enrollment authentication token
-    void generateChallenge(IBinder token, IFingerprintServiceReceiver receiver, String opPackageName);
+    void generateChallenge(IBinder token, int sensorId, IFingerprintServiceReceiver receiver, String opPackageName);
 
     // Finish an enrollment sequence and invalidate the authentication token
     void revokeChallenge(IBinder token, String opPackageName);
@@ -103,7 +103,7 @@
     long getAuthenticatorId(int callingUserId);
 
     // Reset the timeout when user authenticates with strong auth (e.g. PIN, pattern or password)
-    void resetLockout(int userId, in byte [] hardwareAuthToken);
+    void resetLockout(IBinder token, int sensorId, int userId, in byte[] hardwareAuthToken, String opPackageNAame);
 
     // Add a callback which gets notified when the fingerprint lockout period expired.
     void addLockoutResetCallback(IBiometricServiceLockoutResetCallback callback, String opPackageName);
diff --git a/core/java/android/hardware/fingerprint/IFingerprintServiceReceiver.aidl b/core/java/android/hardware/fingerprint/IFingerprintServiceReceiver.aidl
index ad8fbc0..095b8e9 100644
--- a/core/java/android/hardware/fingerprint/IFingerprintServiceReceiver.aidl
+++ b/core/java/android/hardware/fingerprint/IFingerprintServiceReceiver.aidl
@@ -29,5 +29,5 @@
     void onAuthenticationFailed();
     void onError(int error, int vendorCode);
     void onRemoved(in Fingerprint fp, int remaining);
-    void onChallengeGenerated(long challenge);
+    void onChallengeGenerated(int sensorId, long challenge);
 }
diff --git a/core/java/android/hardware/fingerprint/IUdfpsOverlayController.aidl b/core/java/android/hardware/fingerprint/IUdfpsOverlayController.aidl
index 32530da..a57726c 100644
--- a/core/java/android/hardware/fingerprint/IUdfpsOverlayController.aidl
+++ b/core/java/android/hardware/fingerprint/IUdfpsOverlayController.aidl
@@ -25,4 +25,7 @@
 
     // Hides the overlay.
     void hideUdfpsOverlay();
+
+    // Shows debug messages on the UDFPS overlay.
+    void setDebugMessage(String message);
 }
diff --git a/core/java/android/hardware/input/IInputManager.aidl b/core/java/android/hardware/input/IInputManager.aidl
index ba0636f..dc6f579 100644
--- a/core/java/android/hardware/input/IInputManager.aidl
+++ b/core/java/android/hardware/input/IInputManager.aidl
@@ -23,6 +23,7 @@
 import android.hardware.input.ITabletModeChangedListener;
 import android.hardware.input.TouchCalibration;
 import android.os.IBinder;
+import android.os.VibrationEffect;
 import android.view.InputDevice;
 import android.view.InputEvent;
 import android.view.InputMonitor;
@@ -83,7 +84,7 @@
     int isMicMuted();
 
     // Input device vibrator control.
-    void vibrate(int deviceId, in long[] pattern, in int[] amplitudes, int repeat, IBinder token);
+    void vibrate(int deviceId, in VibrationEffect effect, IBinder token);
     void cancelVibrate(int deviceId, IBinder token);
 
     void setPointerIconType(int typeId);
diff --git a/core/java/android/hardware/input/InputManager.java b/core/java/android/hardware/input/InputManager.java
index f0faeb0..dd820fa 100644
--- a/core/java/android/hardware/input/InputManager.java
+++ b/core/java/android/hardware/input/InputManager.java
@@ -1297,27 +1297,8 @@
         @Override
         public void vibrate(int uid, String opPkg, VibrationEffect effect,
                 String reason, AudioAttributes attributes) {
-            long[] pattern;
-            int[] amplitudes;
-            int repeat;
-            if (effect instanceof VibrationEffect.OneShot) {
-                VibrationEffect.OneShot oneShot = (VibrationEffect.OneShot) effect;
-                pattern = new long[] { 0, oneShot.getDuration() };
-                amplitudes = new int[] { 0, oneShot.getAmplitude() };
-                repeat = -1;
-            } else if (effect instanceof VibrationEffect.Waveform) {
-                VibrationEffect.Waveform waveform = (VibrationEffect.Waveform) effect;
-                pattern = waveform.getTimings();
-                amplitudes = waveform.getAmplitudes();
-                repeat = waveform.getRepeatIndex();
-            } else {
-                // TODO: Add support for prebaked effects
-                Log.w(TAG, "Pre-baked effects aren't supported on input devices");
-                return;
-            }
-
             try {
-                mIm.vibrate(mDeviceId, pattern, amplitudes, repeat, mToken);
+                mIm.vibrate(mDeviceId, effect, mToken);
             } catch (RemoteException ex) {
                 throw ex.rethrowFromSystemServer();
             }
diff --git a/core/java/android/inputmethodservice/InputMethodService.java b/core/java/android/inputmethodservice/InputMethodService.java
index c5a11abe..7250801 100644
--- a/core/java/android/inputmethodservice/InputMethodService.java
+++ b/core/java/android/inputmethodservice/InputMethodService.java
@@ -32,6 +32,7 @@
 import android.annotation.MainThread;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.annotation.UiContext;
 import android.app.ActivityManager;
 import android.app.Dialog;
 import android.compat.annotation.UnsupportedAppUsage;
@@ -258,6 +259,7 @@
  * @attr ref android.R.styleable#InputMethodService_imeExtractEnterAnimation
  * @attr ref android.R.styleable#InputMethodService_imeExtractExitAnimation
  */
+@UiContext
 public class InputMethodService extends AbstractInputMethodService {
     static final String TAG = "InputMethodService";
     static final boolean DEBUG = false;
diff --git a/core/java/android/net/NetworkTemplate.java b/core/java/android/net/NetworkTemplate.java
index 7234eb1..cd26079 100644
--- a/core/java/android/net/NetworkTemplate.java
+++ b/core/java/android/net/NetworkTemplate.java
@@ -503,6 +503,10 @@
         for (final int ratType : ratTypes) {
             collapsedRatTypes.add(NetworkTemplate.getCollapsedRatType(ratType));
         }
+        // Add NETWORK_TYPE_5G_NSA to the returned list since 5G NSA is a virtual RAT type and
+        // it is not in TelephonyManager#NETWORK_TYPE_* constants.
+        // See {@link NetworkTemplate#NETWORK_TYPE_5G_NSA}.
+        collapsedRatTypes.add(NetworkTemplate.getCollapsedRatType(NETWORK_TYPE_5G_NSA));
         // Ensure that unknown type is returned.
         collapsedRatTypes.add(TelephonyManager.NETWORK_TYPE_UNKNOWN);
         return toIntArray(collapsedRatTypes);
diff --git a/core/java/android/os/BatteryStats.java b/core/java/android/os/BatteryStats.java
index fbe6a50..fecfd3c 100644
--- a/core/java/android/os/BatteryStats.java
+++ b/core/java/android/os/BatteryStats.java
@@ -743,6 +743,12 @@
         @UnsupportedAppUsage
         public abstract ArrayMap<String, ? extends Pkg> getPackageStats();
 
+        /**
+         * Returns the proportion of power consumed by the System Service
+         * calls made by this UID.
+         */
+        public abstract double getProportionalSystemServiceUsage();
+
         public abstract ControllerActivityCounter getWifiControllerActivity();
         public abstract ControllerActivityCounter getBluetoothControllerActivity();
         public abstract ControllerActivityCounter getModemControllerActivity();
@@ -2882,6 +2888,17 @@
     public abstract int getDischargeAmountScreenDozeSinceCharge();
 
     /**
+     * Returns the approximate CPU time (in microseconds) spent by the system server handling
+     * incoming service calls from apps.
+     *
+     * @param cluster the index of the CPU cluster.
+     * @param step the index of the CPU speed. This is not the actual speed of the CPU.
+     * @see com.android.internal.os.PowerProfile#getNumCpuClusters()
+     * @see com.android.internal.os.PowerProfile#getNumSpeedStepsInCpuCluster(int)
+     */
+    public abstract long getSystemServiceTimeAtCpuSpeed(int cluster, int step);
+
+    /**
      * Returns the total, last, or current battery uptime in microseconds.
      *
      * @param curTime the elapsed realtime in microseconds.
@@ -2937,7 +2954,7 @@
      * enough current data to make a decision, or the battery is currently
      * charging.
      *
-     * @param curTime The current elepsed realtime in microseconds.
+     * @param curTime The current elapsed realtime in microseconds.
      */
     @UnsupportedAppUsage
     public abstract long computeBatteryTimeRemaining(long curTime);
diff --git a/core/java/android/os/Build.java b/core/java/android/os/Build.java
index 1050c7f..8e865e7 100755
--- a/core/java/android/os/Build.java
+++ b/core/java/android/os/Build.java
@@ -1012,10 +1012,16 @@
 
         /**
          * Q.
-         * <p>
-         * <em>Why? Why, to give you a taste of your future, a preview of things
-         * to come. Con permiso, Capitan. The hall is rented, the orchestra
-         * engaged. It's now time to see if you can dance.</em>
+         *
+         * <p>Applications targeting this or a later release will get these new changes in behavior.
+         * For more information about this release, see the
+         * <a href="/about/versions/10">Android 10 overview</a>.</p>
+         * <ul>
+         * <li><a href="/about/versions/10/behavior-changes-all">Behavior changes: all apps</a></li>
+         * <li><a href="/about/versions/10/behavior-changes-10">Behavior changes: apps targeting API
+         * 29+</a></li>
+         * </ul>
+         *
          */
         public static final int Q = 29;
 
diff --git a/core/java/android/os/GraphicsEnvironment.java b/core/java/android/os/GraphicsEnvironment.java
index a6b869d..df1f1b2 100644
--- a/core/java/android/os/GraphicsEnvironment.java
+++ b/core/java/android/os/GraphicsEnvironment.java
@@ -64,10 +64,11 @@
     private static final String SYSTEM_DRIVER_NAME = "system";
     private static final String SYSTEM_DRIVER_VERSION_NAME = "";
     private static final long SYSTEM_DRIVER_VERSION_CODE = 0;
-    private static final String PROPERTY_GFX_DRIVER = "ro.gfx.driver.0";
+    private static final String PROPERTY_GFX_DRIVER_PRODUCTION = "ro.gfx.driver.0";
     private static final String PROPERTY_GFX_DRIVER_PRERELEASE = "ro.gfx.driver.1";
     private static final String PROPERTY_GFX_DRIVER_BUILD_TIME = "ro.gfx.driver_build_time";
-    private static final String METADATA_DRIVER_BUILD_TIME = "com.android.gamedriver.build_time";
+    private static final String METADATA_DRIVER_BUILD_TIME =
+            "com.android.graphics.driver.build_time";
     private static final String METADATA_DEVELOPER_DRIVER_ENABLE =
             "com.android.graphics.developerdriver.enable";
     private static final String METADATA_INJECT_LAYERS_ENABLE =
@@ -78,20 +79,20 @@
     private static final String ACTION_ANGLE_FOR_ANDROID_TOAST_MESSAGE =
             "android.app.action.ANGLE_FOR_ANDROID_TOAST_MESSAGE";
     private static final String INTENT_KEY_A4A_TOAST_MESSAGE = "A4A Toast Message";
-    private static final String GAME_DRIVER_ALLOWLIST_ALL = "*";
-    private static final String GAME_DRIVER_SPHAL_LIBRARIES_FILENAME = "sphal_libraries.txt";
+    private static final String UPDATABLE_DRIVER_ALLOWLIST_ALL = "*";
+    private static final String UPDATABLE_DRIVER_SPHAL_LIBRARIES_FILENAME = "sphal_libraries.txt";
     private static final int VULKAN_1_0 = 0x00400000;
     private static final int VULKAN_1_1 = 0x00401000;
 
-    // GAME_DRIVER_ALL_APPS
+    // UPDATABLE_DRIVER_ALL_APPS
     // 0: Default (Invalid values fallback to default as well)
-    // 1: All apps use Game Driver
-    // 2: All apps use Prerelease Driver
+    // 1: All apps use updatable production driver
+    // 2: All apps use updatable prerelease driver
     // 3: All apps use system graphics driver
-    private static final int GAME_DRIVER_GLOBAL_OPT_IN_DEFAULT = 0;
-    private static final int GAME_DRIVER_GLOBAL_OPT_IN_GAME_DRIVER = 1;
-    private static final int GAME_DRIVER_GLOBAL_OPT_IN_PRERELEASE_DRIVER = 2;
-    private static final int GAME_DRIVER_GLOBAL_OPT_IN_OFF = 3;
+    private static final int UPDATABLE_DRIVER_GLOBAL_OPT_IN_DEFAULT = 0;
+    private static final int UPDATABLE_DRIVER_GLOBAL_OPT_IN_PRODUCTION_DRIVER = 1;
+    private static final int UPDATABLE_DRIVER_GLOBAL_OPT_IN_PRERELEASE_DRIVER = 2;
+    private static final int UPDATABLE_DRIVER_GLOBAL_OPT_IN_OFF = 3;
 
     private ClassLoader mClassLoader;
     private String mLibrarySearchPaths;
@@ -722,14 +723,17 @@
      * Return the driver package name to use. Return null for system driver.
      */
     private static String chooseDriverInternal(Bundle coreSettings, ApplicationInfo ai) {
-        final String gameDriver = SystemProperties.get(PROPERTY_GFX_DRIVER);
-        final boolean hasGameDriver = gameDriver != null && !gameDriver.isEmpty();
+        final String productionDriver = SystemProperties.get(PROPERTY_GFX_DRIVER_PRODUCTION);
+        final boolean hasProductionDriver = productionDriver != null && !productionDriver.isEmpty();
 
         final String prereleaseDriver = SystemProperties.get(PROPERTY_GFX_DRIVER_PRERELEASE);
         final boolean hasPrereleaseDriver = prereleaseDriver != null && !prereleaseDriver.isEmpty();
 
-        if (!hasGameDriver && !hasPrereleaseDriver) {
-            if (DEBUG) Log.v(TAG, "Neither Game Driver nor prerelease driver is supported.");
+        if (!hasProductionDriver && !hasPrereleaseDriver) {
+            if (DEBUG) {
+                Log.v(TAG,
+                        "Neither updatable production driver nor prerelease driver is supported.");
+            }
             return null;
         }
 
@@ -745,56 +749,59 @@
                 (ai.metaData != null && ai.metaData.getBoolean(METADATA_DEVELOPER_DRIVER_ENABLE))
                 || isDebuggable();
 
-        // Priority for Game Driver settings global on confliction (Higher priority comes first):
-        // 1. GAME_DRIVER_ALL_APPS
-        // 2. GAME_DRIVER_OPT_OUT_APPS
-        // 3. GAME_DRIVER_PRERELEASE_OPT_IN_APPS
-        // 4. GAME_DRIVER_OPT_IN_APPS
-        // 5. GAME_DRIVER_DENYLIST
-        // 6. GAME_DRIVER_ALLOWLIST
-        switch (coreSettings.getInt(Settings.Global.GAME_DRIVER_ALL_APPS, 0)) {
-            case GAME_DRIVER_GLOBAL_OPT_IN_OFF:
-                if (DEBUG) Log.v(TAG, "Game Driver is turned off on this device.");
+        // Priority of updatable driver settings on confliction (Higher priority comes first):
+        // 1. UPDATABLE_DRIVER_ALL_APPS
+        // 2. UPDATABLE_DRIVER_PRODUCTION_OPT_OUT_APPS
+        // 3. UPDATABLE_DRIVER_PRERELEASE_OPT_IN_APPS
+        // 4. UPDATABLE_DRIVER_PRODUCTION_OPT_IN_APPS
+        // 5. UPDATABLE_DRIVER_PRODUCTION_DENYLIST
+        // 6. UPDATABLE_DRIVER_PRODUCTION_ALLOWLIST
+        switch (coreSettings.getInt(Settings.Global.UPDATABLE_DRIVER_ALL_APPS, 0)) {
+            case UPDATABLE_DRIVER_GLOBAL_OPT_IN_OFF:
+                if (DEBUG) Log.v(TAG, "updatable driver is turned off on this device.");
                 return null;
-            case GAME_DRIVER_GLOBAL_OPT_IN_GAME_DRIVER:
-                if (DEBUG) Log.v(TAG, "All apps opt in to use Game Driver.");
-                return hasGameDriver ? gameDriver : null;
-            case GAME_DRIVER_GLOBAL_OPT_IN_PRERELEASE_DRIVER:
-                if (DEBUG) Log.v(TAG, "All apps opt in to use prerelease driver.");
+            case UPDATABLE_DRIVER_GLOBAL_OPT_IN_PRODUCTION_DRIVER:
+                if (DEBUG) Log.v(TAG, "All apps opt in to use updatable production driver.");
+                return hasProductionDriver ? productionDriver : null;
+            case UPDATABLE_DRIVER_GLOBAL_OPT_IN_PRERELEASE_DRIVER:
+                if (DEBUG) Log.v(TAG, "All apps opt in to use updatable prerelease driver.");
                 return hasPrereleaseDriver && enablePrereleaseDriver ? prereleaseDriver : null;
-            case GAME_DRIVER_GLOBAL_OPT_IN_DEFAULT:
+            case UPDATABLE_DRIVER_GLOBAL_OPT_IN_DEFAULT:
             default:
                 break;
         }
 
         final String appPackageName = ai.packageName;
-        if (getGlobalSettingsString(null, coreSettings, Settings.Global.GAME_DRIVER_OPT_OUT_APPS)
+        if (getGlobalSettingsString(null, coreSettings,
+                                    Settings.Global.UPDATABLE_DRIVER_PRODUCTION_OPT_OUT_APPS)
                         .contains(appPackageName)) {
-            if (DEBUG) Log.v(TAG, "App opts out for Game Driver.");
+            if (DEBUG) Log.v(TAG, "App opts out for updatable production driver.");
             return null;
         }
 
         if (getGlobalSettingsString(
-                    null, coreSettings, Settings.Global.GAME_DRIVER_PRERELEASE_OPT_IN_APPS)
+                    null, coreSettings, Settings.Global.UPDATABLE_DRIVER_PRERELEASE_OPT_IN_APPS)
                         .contains(appPackageName)) {
-            if (DEBUG) Log.v(TAG, "App opts in for prerelease Game Driver.");
+            if (DEBUG) Log.v(TAG, "App opts in for updatable prerelease driver.");
             return hasPrereleaseDriver && enablePrereleaseDriver ? prereleaseDriver : null;
         }
 
-        // Early return here since the rest logic is only for Game Driver.
-        if (!hasGameDriver) {
-            if (DEBUG) Log.v(TAG, "Game Driver is not supported on the device.");
+        // Early return here since the rest logic is only for updatable production Driver.
+        if (!hasProductionDriver) {
+            if (DEBUG) Log.v(TAG, "Updatable production driver is not supported on the device.");
             return null;
         }
 
         final boolean isOptIn =
-                getGlobalSettingsString(null, coreSettings, Settings.Global.GAME_DRIVER_OPT_IN_APPS)
+                getGlobalSettingsString(null, coreSettings,
+                                        Settings.Global.UPDATABLE_DRIVER_PRODUCTION_OPT_IN_APPS)
                         .contains(appPackageName);
         final List<String> allowlist =
-                getGlobalSettingsString(null, coreSettings, Settings.Global.GAME_DRIVER_ALLOWLIST);
-        if (!isOptIn && allowlist.indexOf(GAME_DRIVER_ALLOWLIST_ALL) != 0
+                getGlobalSettingsString(null, coreSettings,
+                                        Settings.Global.UPDATABLE_DRIVER_PRODUCTION_ALLOWLIST);
+        if (!isOptIn && allowlist.indexOf(UPDATABLE_DRIVER_ALLOWLIST_ALL) != 0
                 && !allowlist.contains(appPackageName)) {
-            if (DEBUG) Log.v(TAG, "App is not on the allowlist for Game Driver.");
+            if (DEBUG) Log.v(TAG, "App is not on the allowlist for updatable production driver.");
             return null;
         }
 
@@ -802,13 +809,13 @@
         // terminate early if it's on the denylist and fallback to system driver.
         if (!isOptIn
                 && getGlobalSettingsString(
-                        null, coreSettings, Settings.Global.GAME_DRIVER_DENYLIST)
+                        null, coreSettings, Settings.Global.UPDATABLE_DRIVER_PRODUCTION_DENYLIST)
                            .contains(appPackageName)) {
-            if (DEBUG) Log.v(TAG, "App is on the denylist for Game Driver.");
+            if (DEBUG) Log.v(TAG, "App is on the denylist for updatable production driver.");
             return null;
         }
 
-        return gameDriver;
+        return productionDriver;
     }
 
     /**
@@ -871,9 +878,10 @@
             throw new NullPointerException("apk's meta-data cannot be null");
         }
 
-        final String driverBuildTime = driverAppInfo.metaData.getString(METADATA_DRIVER_BUILD_TIME);
-        if (driverBuildTime == null || driverBuildTime.isEmpty()) {
-            throw new IllegalArgumentException("com.android.gamedriver.build_time is not set");
+        String driverBuildTime = driverAppInfo.metaData.getString(METADATA_DRIVER_BUILD_TIME);
+        if (driverBuildTime == null || driverBuildTime.length() <= 1) {
+            Log.v(TAG, "com.android.graphics.driver.build_time is not set");
+            driverBuildTime = "L0";
         }
         // driver_build_time in the meta-data is in "L<Unix epoch timestamp>" format. e.g. L123456.
         // Long.parseLong will throw if the meta-data "driver_build_time" is not set properly.
@@ -901,7 +909,7 @@
             final Context driverContext =
                     context.createPackageContext(driverPackageName, Context.CONTEXT_RESTRICTED);
             final BufferedReader reader = new BufferedReader(new InputStreamReader(
-                    driverContext.getAssets().open(GAME_DRIVER_SPHAL_LIBRARIES_FILENAME)));
+                    driverContext.getAssets().open(UPDATABLE_DRIVER_SPHAL_LIBRARIES_FILENAME)));
             final ArrayList<String> assetStrings = new ArrayList<>();
             for (String assetString; (assetString = reader.readLine()) != null;) {
                 assetStrings.add(assetString);
@@ -913,7 +921,7 @@
             }
         } catch (IOException e) {
             if (DEBUG) {
-                Log.w(TAG, "Failed to load '" + GAME_DRIVER_SPHAL_LIBRARIES_FILENAME + "'");
+                Log.w(TAG, "Failed to load '" + UPDATABLE_DRIVER_SPHAL_LIBRARIES_FILENAME + "'");
             }
         }
         return "";
diff --git a/core/java/android/os/INetworkManagementService.aidl b/core/java/android/os/INetworkManagementService.aidl
index 0cce192..8f8d451 100644
--- a/core/java/android/os/INetworkManagementService.aidl
+++ b/core/java/android/os/INetworkManagementService.aidl
@@ -287,8 +287,8 @@
     /**
      * Control network activity of a UID over interfaces with a quota limit.
      */
-    void setUidMeteredNetworkBlacklist(int uid, boolean enable);
-    void setUidMeteredNetworkWhitelist(int uid, boolean enable);
+    void setUidMeteredNetworkDenylist(int uid, boolean enable);
+    void setUidMeteredNetworkAllowlist(int uid, boolean enable);
     boolean setDataSaverModeEnabled(boolean enable);
 
     void setUidCleartextNetworkPolicy(int uid, int policy);
diff --git a/core/java/android/os/IPowerManager.aidl b/core/java/android/os/IPowerManager.aidl
index ce6c0ff..a92d91b 100644
--- a/core/java/android/os/IPowerManager.aidl
+++ b/core/java/android/os/IPowerManager.aidl
@@ -18,6 +18,7 @@
 package android.os;
 
 import android.os.BatterySaverPolicyConfig;
+import android.os.ParcelDuration;
 import android.os.PowerSaveState;
 import android.os.WorkSource;
 
@@ -58,6 +59,9 @@
     boolean setAdaptivePowerSavePolicy(in BatterySaverPolicyConfig config);
     boolean setAdaptivePowerSaveEnabled(boolean enabled);
     int getPowerSaveModeTrigger();
+    void setBatteryDischargePrediction(in ParcelDuration timeRemaining, boolean isCustomized);
+    ParcelDuration getBatteryDischargePrediction();
+    boolean isBatteryDischargePredictionPersonalized();
     boolean isDeviceIdleMode();
     boolean isLightDeviceIdleMode();
 
diff --git a/core/java/android/os/OWNERS b/core/java/android/os/OWNERS
index 0ec4fb8..40c291f 100644
--- a/core/java/android/os/OWNERS
+++ b/core/java/android/os/OWNERS
@@ -16,6 +16,6 @@
 per-file PowerManagerInternal.java = michaelwr@google.com, santoscordon@google.com
 
 # Zygote
-per-file ZygoteProcess.java = chriswailes@google.com, ngeoffray@google.com, sehr@google.com, narayan@google.com, maco@google.com
+per-file ZygoteProcess.java = calin@google.com, chriswailes@google.com, maco@google.com, narayan@google.com, ngeoffray@google.com
 
 per-file GraphicsEnvironment.java = chrisforbes@google.com, cnorthrop@google.com, lpy@google.com, timvp@google.com, zzyiwei@google.com
diff --git a/tests/AutoVerify/app1/src/com/android/test/autoverify/MainActivity.java b/core/java/android/os/ParcelDuration.aidl
similarity index 86%
rename from tests/AutoVerify/app1/src/com/android/test/autoverify/MainActivity.java
rename to core/java/android/os/ParcelDuration.aidl
index 09ef472..e1aa109 100644
--- a/tests/AutoVerify/app1/src/com/android/test/autoverify/MainActivity.java
+++ b/core/java/android/os/ParcelDuration.aidl
@@ -13,3 +13,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
+
+package android.os;
+
+parcelable ParcelDuration cpp_header "android/ParcelDuration.h";
\ No newline at end of file
diff --git a/core/java/android/os/ParcelDuration.java b/core/java/android/os/ParcelDuration.java
new file mode 100644
index 0000000..37cde31
--- /dev/null
+++ b/core/java/android/os/ParcelDuration.java
@@ -0,0 +1,103 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.os;
+
+import android.annotation.NonNull;
+
+import java.time.Duration;
+
+/**
+ * Parcelable version of {@link Duration} that can be used in binder calls.
+ *
+ * @hide
+ */
+public final class ParcelDuration implements Parcelable {
+
+    private final long mSeconds;
+    private final int mNanos;
+
+    /**
+     * Construct a Duration object using the given millisecond value.
+     *
+     * @hide
+     */
+    public ParcelDuration(long ms) {
+        this(Duration.ofMillis(ms));
+    }
+
+    /**
+     * Wrap a {@link Duration} instance.
+     *
+     * @param duration The {@link Duration} instance to wrap.
+     */
+    public ParcelDuration(@NonNull Duration duration) {
+        mSeconds = duration.getSeconds();
+        mNanos = duration.getNano();
+    }
+
+    private ParcelDuration(@NonNull Parcel parcel) {
+        mSeconds = parcel.readLong();
+        mNanos = parcel.readInt();
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(@NonNull Parcel parcel, int parcelableFlags) {
+        parcel.writeLong(mSeconds);
+        parcel.writeInt(mNanos);
+    }
+
+    /**
+     * Returns a {@link Duration} instance that's equivalent to this Duration's length.
+     *
+     * @return a {@link Duration} instance of identical length.
+     */
+    @NonNull
+    public Duration getDuration() {
+        return Duration.ofSeconds(mSeconds, mNanos);
+    }
+
+    @Override
+    @NonNull
+    public String toString() {
+        return getDuration().toString();
+    }
+
+    /**
+     * Creator for Duration.
+     */
+    @NonNull
+    public static final Parcelable.Creator<ParcelDuration> CREATOR =
+            new Parcelable.Creator<ParcelDuration>() {
+
+        @Override
+        @NonNull
+        public ParcelDuration createFromParcel(@NonNull Parcel source) {
+            return new ParcelDuration(source);
+        }
+
+        @Override
+        @NonNull
+        public ParcelDuration[] newArray(int size) {
+            return new ParcelDuration[size];
+        }
+    };
+}
diff --git a/core/java/android/os/Parcelable.java b/core/java/android/os/Parcelable.java
index 3d3759e..f14f66b 100644
--- a/core/java/android/os/Parcelable.java
+++ b/core/java/android/os/Parcelable.java
@@ -161,6 +161,7 @@
      * @return true if this parcelable is stable.
      * @hide
      */
+    @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
     default @Stability int getStability() {
         return PARCELABLE_STABILITY_LOCAL;
     }
diff --git a/core/java/android/os/PowerManager.java b/core/java/android/os/PowerManager.java
index 309805f..ed38b3f 100644
--- a/core/java/android/os/PowerManager.java
+++ b/core/java/android/os/PowerManager.java
@@ -41,6 +41,7 @@
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
+import java.time.Duration;
 import java.util.concurrent.Executor;
 import java.util.concurrent.atomic.AtomicLong;
 
@@ -1719,6 +1720,70 @@
     }
 
     /**
+     * Allows an app to tell the system how long it believes the battery will last and whether
+     * this estimate is customized based on historical device usage or on a generic configuration.
+     * These estimates will be displayed on system UI surfaces in place of the system computed
+     * value.
+     *
+     * Calling this requires the {@link android.Manifest.permission#DEVICE_POWER} permission.
+     *
+     * @param timeRemaining  The time remaining as a {@link Duration}.
+     * @param isPersonalized true if personalized based on device usage history, false otherwise.
+     * @throws IllegalStateException if the device is powered or currently charging
+     * @hide
+     */
+    @SystemApi
+    @TestApi
+    @RequiresPermission(android.Manifest.permission.DEVICE_POWER)
+    public void setBatteryDischargePrediction(@NonNull Duration timeRemaining,
+            boolean isPersonalized) {
+        if (timeRemaining == null) {
+            throw new IllegalArgumentException("time remaining must not be null");
+        }
+        try {
+            mService.setBatteryDischargePrediction(new ParcelDuration(timeRemaining),
+                    isPersonalized);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Returns the current battery life remaining estimate.
+     *
+     * @return The estimated battery life remaining as a {@link Duration}. Will be {@code null} if
+     * the device is powered, charging, or an error was encountered.
+     */
+    @Nullable
+    public Duration getBatteryDischargePrediction() {
+        try {
+            final ParcelDuration parcelDuration = mService.getBatteryDischargePrediction();
+            if (parcelDuration == null) {
+                return null;
+            }
+            return parcelDuration.getDuration();
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Returns whether the current battery life remaining estimate is personalized based on device
+     * usage history or not. This value does not take a device's powered or charging state into
+     * account.
+     *
+     * @return A boolean indicating if the current discharge estimate is personalized based on
+     * historical device usage or not.
+     */
+    public boolean isBatteryDischargePredictionPersonalized() {
+        try {
+            return mService.isBatteryDischargePredictionPersonalized();
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
      * Get data about the battery saver mode for a specific service
      * @param serviceType unique key for the service, one of {@link ServiceType}
      * @return Battery saver state data.
@@ -2185,6 +2250,18 @@
     }
 
     /**
+     * Intent that is broadcast when the enhanced battery discharge prediction changes. The new
+     * value can be retrieved via {@link #getBatteryDischargePrediction()}.
+     * This broadcast is only sent to registered receivers.
+     *
+     * @hide
+     */
+    @TestApi
+    @SdkConstant(SdkConstant.SdkConstantType.BROADCAST_INTENT_ACTION)
+    public static final String ACTION_ENHANCED_DISCHARGE_PREDICTION_CHANGED =
+            "android.os.action.ENHANCED_DISCHARGE_PREDICTION_CHANGED";
+
+    /**
      * Intent that is broadcast when the state of {@link #isPowerSaveMode()} changes.
      * This broadcast is only sent to registered receivers.
      */
diff --git a/core/java/android/os/PowerManagerInternal.java b/core/java/android/os/PowerManagerInternal.java
index e30a409..eb18b96 100644
--- a/core/java/android/os/PowerManagerInternal.java
+++ b/core/java/android/os/PowerManagerInternal.java
@@ -17,6 +17,7 @@
 package android.os;
 
 import android.view.Display;
+import android.view.KeyEvent;
 
 import java.util.function.Consumer;
 
@@ -313,4 +314,7 @@
 
     /** Returns information about the last wakeup event. */
     public abstract PowerManager.WakeData getLastWakeup();
+
+    /** Allows power button to intercept a power key button press. */
+    public abstract boolean interceptPowerKeyDown(KeyEvent event);
 }
diff --git a/core/java/android/os/SystemClock.java b/core/java/android/os/SystemClock.java
index fd68c2b..26f3af0 100644
--- a/core/java/android/os/SystemClock.java
+++ b/core/java/android/os/SystemClock.java
@@ -178,6 +178,15 @@
     native public static long uptimeMillis();
 
     /**
+     * Returns nanoseconds since boot, not counting time spent in deep sleep.
+     *
+     * @return nanoseconds of non-sleep uptime since boot.
+     * @hide
+     */
+    @CriticalNative
+    public static native long uptimeNanos();
+
+    /**
      * Return {@link Clock} that starts at system boot, not counting time spent
      * in deep sleep.
      *
diff --git a/core/java/android/os/Trace.java b/core/java/android/os/Trace.java
index 50cc764..58c8efa 100644
--- a/core/java/android/os/Trace.java
+++ b/core/java/android/os/Trace.java
@@ -102,6 +102,8 @@
     /** @hide */
     public static final long TRACE_TAG_RRO = 1L << 26;
     /** @hide */
+    public static final long TRACE_TAG_SYSPROP = 1L << 27;
+    /** @hide */
     public static final long TRACE_TAG_APEX_MANAGER = 1L << 18;
 
     private static final long TRACE_TAG_NOT_READY = 1L << 63;
diff --git a/core/java/android/os/ZygoteProcess.java b/core/java/android/os/ZygoteProcess.java
index 39038f5..ffede09 100644
--- a/core/java/android/os/ZygoteProcess.java
+++ b/core/java/android/os/ZygoteProcess.java
@@ -251,11 +251,11 @@
     private final Object mLock = new Object();
 
     /**
-     * List of exemptions to the API blacklist. These are prefix matches on the runtime format
+     * List of exemptions to the API deny list. These are prefix matches on the runtime format
      * symbol signature. Any matching symbol is treated by the runtime as being on the light grey
      * list.
      */
-    private List<String> mApiBlacklistExemptions = Collections.emptyList();
+    private List<String> mApiDenylistExemptions = Collections.emptyList();
 
     /**
      * Proportion of hidden API accesses that should be logged to the event log; 0 - 0x10000.
@@ -562,7 +562,7 @@
         "--preload-package",
         "--preload-app",
         "--start-child-zygote",
-        "--set-api-blacklist-exemptions",
+        "--set-api-denylist-exemptions",
         "--hidden-api-log-sampling-rate",
         "--hidden-api-statslog-sampling-rate",
         "--invoke-with"
@@ -922,20 +922,20 @@
     }
 
     /**
-     * Push hidden API blacklisting exemptions into the zygote process(es).
+     * Push hidden API deny-listing exemptions into the zygote process(es).
      *
      * <p>The list of exemptions will take affect for all new processes forked from the zygote after
      * this call.
      *
      * @param exemptions List of hidden API exemption prefixes. Any matching members are treated as
-     *        whitelisted/public APIs (i.e. allowed, no logging of usage).
+     *        allowed/public APIs (i.e. allowed, no logging of usage).
      */
-    public boolean setApiBlacklistExemptions(List<String> exemptions) {
+    public boolean setApiDenylistExemptions(List<String> exemptions) {
         synchronized (mLock) {
-            mApiBlacklistExemptions = exemptions;
-            boolean ok = maybeSetApiBlacklistExemptions(primaryZygoteState, true);
+            mApiDenylistExemptions = exemptions;
+            boolean ok = maybeSetApiDenylistExemptions(primaryZygoteState, true);
             if (ok) {
-                ok = maybeSetApiBlacklistExemptions(secondaryZygoteState, true);
+                ok = maybeSetApiDenylistExemptions(secondaryZygoteState, true);
             }
             return ok;
         }
@@ -972,32 +972,32 @@
     }
 
     @GuardedBy("mLock")
-    private boolean maybeSetApiBlacklistExemptions(ZygoteState state, boolean sendIfEmpty) {
+    private boolean maybeSetApiDenylistExemptions(ZygoteState state, boolean sendIfEmpty) {
         if (state == null || state.isClosed()) {
-            Slog.e(LOG_TAG, "Can't set API blacklist exemptions: no zygote connection");
+            Slog.e(LOG_TAG, "Can't set API denylist exemptions: no zygote connection");
             return false;
-        } else if (!sendIfEmpty && mApiBlacklistExemptions.isEmpty()) {
+        } else if (!sendIfEmpty && mApiDenylistExemptions.isEmpty()) {
             return true;
         }
 
         try {
-            state.mZygoteOutputWriter.write(Integer.toString(mApiBlacklistExemptions.size() + 1));
+            state.mZygoteOutputWriter.write(Integer.toString(mApiDenylistExemptions.size() + 1));
             state.mZygoteOutputWriter.newLine();
-            state.mZygoteOutputWriter.write("--set-api-blacklist-exemptions");
+            state.mZygoteOutputWriter.write("--set-api-denylist-exemptions");
             state.mZygoteOutputWriter.newLine();
-            for (int i = 0; i < mApiBlacklistExemptions.size(); ++i) {
-                state.mZygoteOutputWriter.write(mApiBlacklistExemptions.get(i));
+            for (int i = 0; i < mApiDenylistExemptions.size(); ++i) {
+                state.mZygoteOutputWriter.write(mApiDenylistExemptions.get(i));
                 state.mZygoteOutputWriter.newLine();
             }
             state.mZygoteOutputWriter.flush();
             int status = state.mZygoteInputStream.readInt();
             if (status != 0) {
-                Slog.e(LOG_TAG, "Failed to set API blacklist exemptions; status " + status);
+                Slog.e(LOG_TAG, "Failed to set API denylist exemptions; status " + status);
             }
             return true;
         } catch (IOException ioe) {
-            Slog.e(LOG_TAG, "Failed to set API blacklist exemptions", ioe);
-            mApiBlacklistExemptions = Collections.emptyList();
+            Slog.e(LOG_TAG, "Failed to set API denylist exemptions", ioe);
+            mApiDenylistExemptions = Collections.emptyList();
             return false;
         }
     }
@@ -1054,7 +1054,7 @@
             primaryZygoteState =
                     ZygoteState.connect(mZygoteSocketAddress, mUsapPoolSocketAddress);
 
-            maybeSetApiBlacklistExemptions(primaryZygoteState, false);
+            maybeSetApiDenylistExemptions(primaryZygoteState, false);
             maybeSetHiddenApiAccessLogSampleRate(primaryZygoteState);
         }
     }
@@ -1069,7 +1069,7 @@
                     ZygoteState.connect(mZygoteSecondarySocketAddress,
                             mUsapPoolSecondarySocketAddress);
 
-            maybeSetApiBlacklistExemptions(secondaryZygoteState, false);
+            maybeSetApiDenylistExemptions(secondaryZygoteState, false);
             maybeSetHiddenApiAccessLogSampleRate(secondaryZygoteState);
         }
     }
diff --git a/core/java/android/permission/PermissionControllerService.java b/core/java/android/permission/PermissionControllerService.java
index 8ad35e7..e2e6140 100644
--- a/core/java/android/permission/PermissionControllerService.java
+++ b/core/java/android/permission/PermissionControllerService.java
@@ -35,6 +35,8 @@
 import android.annotation.SystemApi;
 import android.app.Service;
 import android.app.admin.DevicePolicyManager.PermissionGrantState;
+import android.compat.annotation.ChangeId;
+import android.compat.annotation.Disabled;
 import android.content.Intent;
 import android.content.pm.PackageInfo;
 import android.content.pm.PackageManager;
@@ -83,6 +85,15 @@
     public static final String SERVICE_INTERFACE = "android.permission.PermissionControllerService";
 
     /**
+     * A ChangeId indicating that this device supports camera and mic indicators. Will be "false"
+     * if present, because the CompatChanges#isChangeEnabled method returns true if the change id
+     * is not present.
+     */
+    @ChangeId
+    @Disabled
+    private static final long CAMERA_MIC_INDICATORS_NOT_PRESENT = 162547999L;
+
+    /**
      * Revoke a set of runtime permissions for various apps.
      *
      * @param requests The permissions to revoke as {@code Map<packageName, List<permission>>}
diff --git a/core/java/android/permission/PermissionManager.java b/core/java/android/permission/PermissionManager.java
index bf3d46f..b9d27e9 100644
--- a/core/java/android/permission/PermissionManager.java
+++ b/core/java/android/permission/PermissionManager.java
@@ -25,6 +25,7 @@
 import android.annotation.SystemApi;
 import android.annotation.SystemService;
 import android.annotation.TestApi;
+import android.annotation.UserIdInt;
 import android.app.ActivityManager;
 import android.app.ActivityThread;
 import android.app.IActivityManager;
@@ -608,7 +609,7 @@
     /** @hide */
     private static final PropertyInvalidatedCache<PermissionQuery, Integer> sPermissionCache =
             new PropertyInvalidatedCache<PermissionQuery, Integer>(
-                    16, CACHE_KEY_PACKAGE_INFO) {
+                    16, CACHE_KEY_PACKAGE_INFO, "checkPermission") {
                 @Override
                 protected Integer recompute(PermissionQuery query) {
                     return checkPermissionUncached(query.permission, query.pid, query.uid);
@@ -637,24 +638,25 @@
     private static final class PackageNamePermissionQuery {
         final String permName;
         final String pkgName;
-        final int uid;
+        final int userId;
 
-        PackageNamePermissionQuery(@Nullable String permName, @Nullable String pkgName, int uid) {
+        PackageNamePermissionQuery(@Nullable String permName, @Nullable String pkgName,
+                @UserIdInt int userId) {
             this.permName = permName;
             this.pkgName = pkgName;
-            this.uid = uid;
+            this.userId = userId;
         }
 
         @Override
         public String toString() {
             return String.format(
-                    "PackageNamePermissionQuery(pkgName=\"%s\", permName=\"%s, uid=%s\")",
-                    pkgName, permName, uid);
+                    "PackageNamePermissionQuery(pkgName=\"%s\", permName=\"%s, userId=%s\")",
+                    pkgName, permName, userId);
         }
 
         @Override
         public int hashCode() {
-            return Objects.hash(permName, pkgName, uid);
+            return Objects.hash(permName, pkgName, userId);
         }
 
         @Override
@@ -670,16 +672,16 @@
             }
             return Objects.equals(permName, other.permName)
                     && Objects.equals(pkgName, other.pkgName)
-                    && uid == other.uid;
+                    && userId == other.userId;
         }
     }
 
     /* @hide */
     private static int checkPackageNamePermissionUncached(
-            String permName, String pkgName, int uid) {
+            String permName, String pkgName, @UserIdInt int userId) {
         try {
             return ActivityThread.getPermissionManager().checkPermission(
-                    permName, pkgName, uid);
+                    permName, pkgName, userId);
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
@@ -689,11 +691,11 @@
     private static PropertyInvalidatedCache<PackageNamePermissionQuery, Integer>
             sPackageNamePermissionCache =
             new PropertyInvalidatedCache<PackageNamePermissionQuery, Integer>(
-                    16, CACHE_KEY_PACKAGE_INFO) {
+                    16, CACHE_KEY_PACKAGE_INFO, "checkPackageNamePermission") {
                 @Override
                 protected Integer recompute(PackageNamePermissionQuery query) {
                     return checkPackageNamePermissionUncached(
-                            query.permName, query.pkgName, query.uid);
+                            query.permName, query.pkgName, query.userId);
                 }
             };
 
@@ -702,9 +704,10 @@
      *
      * @hide
      */
-    public static int checkPackageNamePermission(String permName, String pkgName, int uid) {
+    public static int checkPackageNamePermission(String permName, String pkgName,
+            @UserIdInt int userId) {
         return sPackageNamePermissionCache.query(
-                new PackageNamePermissionQuery(permName, pkgName, uid));
+                new PackageNamePermissionQuery(permName, pkgName, userId));
     }
 
     /**
diff --git a/core/java/android/provider/DeviceConfig.java b/core/java/android/provider/DeviceConfig.java
index b45a1eb..859b703 100644
--- a/core/java/android/provider/DeviceConfig.java
+++ b/core/java/android/provider/DeviceConfig.java
@@ -197,6 +197,13 @@
             "intelligence_content_suggestions";
 
     /**
+     * Namespace for JobScheduler configurations.
+     * @hide
+     */
+    @TestApi
+    public static final String NAMESPACE_JOB_SCHEDULER = "jobscheduler";
+
+    /**
      * Namespace for all media native related features.
      *
      * @hide
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 7ee41b9..c302def 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -65,6 +65,7 @@
 import android.os.DropBoxManager;
 import android.os.IBinder;
 import android.os.LocaleList;
+import android.os.PowerManager;
 import android.os.PowerManager.AutoPowerSaveModeTriggers;
 import android.os.Process;
 import android.os.RemoteCallback;
@@ -7912,6 +7913,13 @@
         public static final String TAPS_APP_TO_EXIT = "taps_app_to_exit";
 
         /**
+         * Internal use, one handed mode tutorial showed times.
+         * @hide
+         */
+        public static final String ONE_HANDED_TUTORIAL_SHOW_COUNT =
+                "one_handed_tutorial_show_count";
+
+        /**
          * The current night mode that has been selected by the user.  Owned
          * and controlled by UiModeManagerService.  Constants are as per
          * UiModeManager.
@@ -8309,6 +8317,13 @@
         public static final String PANIC_GESTURE_ENABLED = "panic_gesture_enabled";
 
         /**
+         * Whether the panic button (emergency sos) sound should be enabled.
+         *
+         * @hide
+         */
+        public static final String PANIC_SOUND_ENABLED = "panic_sound_enabled";
+
+        /**
          * Whether the camera launch gesture to double tap the power button when the screen is off
          * should be disabled.
          *
@@ -8728,16 +8743,6 @@
                 = "bubble_important_conversations";
 
         /**
-         * When enabled, notifications the notification assistant service has modified will show an
-         * indicator. When tapped, this indicator will describe the adjustment made and solicit
-         * feedback. This flag will also add a "automatic" option to the long press menu.
-         *
-         * The value 1 - enable, 0 - disable
-         * @hide
-         */
-        public static final String NOTIFICATION_FEEDBACK_ENABLED = "notification_feedback_enabled";
-
-        /**
          * Whether notifications are dismissed by a right-to-left swipe (instead of a left-to-right
          * swipe).
          *
@@ -8977,6 +8982,14 @@
         public static final String MEDIA_CONTROLS_RESUME = "qs_media_resumption";
 
         /**
+         * Controls which packages are blocked from persisting in media controls when resumption is
+         * enabled. The list of packages is set by the user in the Settings app.
+         * @see Settings.Secure#MEDIA_CONTROLS_RESUME
+         * @hide
+         */
+        public static final String MEDIA_CONTROLS_RESUME_BLOCKED = "qs_media_resumption_blocked";
+
+        /**
          * Controls if window magnification is enabled.
          * @hide
          */
@@ -9025,6 +9038,13 @@
                 "accessibility_magnification_capability";
 
         /**
+         * Whether the Adaptive connectivity option is enabled.
+         *
+         * @hide
+         */
+        public static final String ADAPTIVE_CONNECTIVITY_ENABLED = "adaptive_connectivity_enabled";
+
+        /**
          * Keys we no longer back up under the current schema, but want to continue to
          * process when restoring historical backup datasets.
          *
@@ -11825,36 +11845,6 @@
         public static final String ALARM_MANAGER_CONSTANTS = "alarm_manager_constants";
 
         /**
-         * Job scheduler specific settings.
-         * This is encoded as a key=value list, separated by commas. Ex:
-         *
-         * "min_ready_jobs_count=2,moderate_use_factor=.5"
-         *
-         * The following keys are supported:
-         *
-         * <pre>
-         * min_idle_count                       (int)
-         * min_charging_count                   (int)
-         * min_connectivity_count               (int)
-         * min_content_count                    (int)
-         * min_ready_jobs_count                 (int)
-         * heavy_use_factor                     (float)
-         * moderate_use_factor                  (float)
-         * fg_job_count                         (int)
-         * bg_normal_job_count                  (int)
-         * bg_moderate_job_count                (int)
-         * bg_low_job_count                     (int)
-         * bg_critical_job_count                (int)
-         * </pre>
-         *
-         * <p>
-         * Type: string
-         * @hide
-         * @see com.android.server.job.JobSchedulerService.Constants
-         */
-        public static final String JOB_SCHEDULER_CONSTANTS = "job_scheduler_constants";
-
-        /**
          * Job scheduler QuotaController specific settings.
          * This is encoded as a key=value list, separated by commas. Ex:
          *
@@ -12350,63 +12340,71 @@
                 "show_angle_in_use_dialog_box";
 
         /**
-         * Game Driver global preference for all Apps.
+         * Updatable driver global preference for all Apps.
          * 0 = Default
-         * 1 = All Apps use Game Driver
-         * 2 = All Apps use system graphics driver
+         * 1 = All Apps use updatable production driver
+         * 2 = All apps use updatable prerelease driver
+         * 3 = All Apps use system graphics driver
          * @hide
          */
-        public static final String GAME_DRIVER_ALL_APPS = "game_driver_all_apps";
+        public static final String UPDATABLE_DRIVER_ALL_APPS = "updatable_driver_all_apps";
 
         /**
-         * List of Apps selected to use Game Driver.
+         * List of Apps selected to use updatable production driver.
          * i.e. <pkg1>,<pkg2>,...,<pkgN>
          * @hide
          */
-        public static final String GAME_DRIVER_OPT_IN_APPS = "game_driver_opt_in_apps";
+        public static final String UPDATABLE_DRIVER_PRODUCTION_OPT_IN_APPS =
+                "updatable_driver_production_opt_in_apps";
 
         /**
-         * List of Apps selected to use prerelease Game Driver.
+         * List of Apps selected to use updatable prerelease driver.
          * i.e. <pkg1>,<pkg2>,...,<pkgN>
          * @hide
          */
-        public static final String GAME_DRIVER_PRERELEASE_OPT_IN_APPS =
-                "game_driver_prerelease_opt_in_apps";
+        public static final String UPDATABLE_DRIVER_PRERELEASE_OPT_IN_APPS =
+                "updatable_driver_prerelease_opt_in_apps";
 
         /**
-         * List of Apps selected not to use Game Driver.
+         * List of Apps selected not to use updatable production driver.
          * i.e. <pkg1>,<pkg2>,...,<pkgN>
          * @hide
          */
-        public static final String GAME_DRIVER_OPT_OUT_APPS = "game_driver_opt_out_apps";
+        public static final String UPDATABLE_DRIVER_PRODUCTION_OPT_OUT_APPS =
+                "updatable_driver_production_opt_out_apps";
 
         /**
-         * Apps on the denylist that are forbidden to use Game Driver.
+         * Apps on the denylist that are forbidden to use updatable production driver.
          * @hide
          */
-        public static final String GAME_DRIVER_DENYLIST = "game_driver_denylist";
+        public static final String UPDATABLE_DRIVER_PRODUCTION_DENYLIST =
+                "updatable_driver_production_denylist";
 
         /**
-         * List of denylists, each denylist is a denylist for a specific version of Game Driver.
+         * List of denylists, each denylist is a denylist for a specific version of
+         * updatable production driver.
          * @hide
          */
-        public static final String GAME_DRIVER_DENYLISTS = "game_driver_denylists";
+        public static final String UPDATABLE_DRIVER_PRODUCTION_DENYLISTS =
+                "updatable_driver_production_denylists";
 
         /**
-         * Apps on the allowlist that are allowed to use Game Driver.
+         * Apps on the allowlist that are allowed to use updatable production driver.
          * The string is a list of application package names, seperated by comma.
          * i.e. <apk1>,<apk2>,...,<apkN>
          * @hide
          */
-        public static final String GAME_DRIVER_ALLOWLIST = "game_driver_allowlist";
+        public static final String UPDATABLE_DRIVER_PRODUCTION_ALLOWLIST =
+                "updatable_driver_production_allowlist";
 
         /**
-         * List of libraries in sphal accessible by Game Driver
+         * List of libraries in sphal accessible by updatable driver
          * The string is a list of library names, separated by colon.
          * i.e. <lib1>:<lib2>:...:<libN>
          * @hide
          */
-        public static final String GAME_DRIVER_SPHAL_LIBRARIES = "game_driver_sphal_libraries";
+        public static final String UPDATABLE_DRIVER_SPHAL_LIBRARIES =
+                "updatable_driver_sphal_libraries";
 
         /**
          * Ordered GPU debug layer list for Vulkan
@@ -12518,18 +12516,23 @@
          * millis. See {@link #BATTERY_ESTIMATES_LAST_UPDATE_TIME} for the last time this value
          * was updated.
          *
+         * @deprecated Use {@link PowerManager#getBatteryDischargePrediction()} instead.
          * @hide
          */
+        @Deprecated
         public static final String TIME_REMAINING_ESTIMATE_MILLIS =
                 "time_remaining_estimate_millis";
 
         /**
-         * A boolean indicating whether {@link #TIME_REMAINING_ESTIMATE_MILLIS} is based customized
-         * to the devices usage or using global models. See
+         * A boolean indicating whether {@link #TIME_REMAINING_ESTIMATE_MILLIS} is customized
+         * to the device's usage or using global models. See
          * {@link #BATTERY_ESTIMATES_LAST_UPDATE_TIME} for the last time this value was updated.
          *
+         * @deprecated Use {@link PowerManager#isBatteryDischargePredictionPersonalized()} instead.
+         *
          * @hide
          */
+        @Deprecated
         public static final String TIME_REMAINING_ESTIMATE_BASED_ON_USAGE =
                 "time_remaining_estimate_based_on_usage";
 
@@ -12538,8 +12541,10 @@
          * average based on historical drain rates. See {@link #BATTERY_ESTIMATES_LAST_UPDATE_TIME}
          * for the last time this value was updated.
          *
+         * @deprecated Use {@link PowerManager#getHistoricalDischargeTime()} instead.
          * @hide
          */
+        @Deprecated
         public static final String AVERAGE_TIME_TO_DISCHARGE = "average_time_to_discharge";
 
         /**
@@ -12548,7 +12553,9 @@
          * and {@link #AVERAGE_TIME_TO_DISCHARGE} were last updated.
          *
          * @hide
+         * @deprecated No longer needed due to {@link PowerManager#getBatteryDischargePrediction}.
          */
+        @Deprecated
         public static final String BATTERY_ESTIMATES_LAST_UPDATE_TIME =
                 "battery_estimates_last_update_time";
 
@@ -14047,6 +14054,16 @@
                 "notification_snooze_options";
 
         /**
+         * When enabled, notifications the notification assistant service has modified will show an
+         * indicator. When tapped, this indicator will describe the adjustment made and solicit
+         * feedback. This flag will also add a "automatic" option to the long press menu.
+         *
+         * The value 1 - enable, 0 - disable
+         * @hide
+         */
+        public static final String NOTIFICATION_FEEDBACK_ENABLED = "notification_feedback_enabled";
+
+        /**
          * Settings key for the ratio of notification dismissals to notification views - one of the
          * criteria for showing the notification blocking helper.
          *
diff --git a/core/java/android/service/autofill/AutofillService.java b/core/java/android/service/autofill/AutofillService.java
index 678f43d..04a4ca4 100644
--- a/core/java/android/service/autofill/AutofillService.java
+++ b/core/java/android/service/autofill/AutofillService.java
@@ -137,7 +137,7 @@
  * <p>The service can provide an extra degree of security by requiring the user to authenticate
  * before an app can be autofilled. The authentication is typically required in 2 scenarios:
  * <ul>
- *   <li>To unlock the user data (for example, using a master password or fingerprint
+ *   <li>To unlock the user data (for example, using a main password or fingerprint
  *       authentication) - see
  * {@link FillResponse.Builder#setAuthentication(AutofillId[], android.content.IntentSender, android.widget.RemoteViews)}.
  *   <li>To unlock a specific dataset (for example, by providing a CVC for a credit card) - see
@@ -363,9 +363,9 @@
  * {@code login.some_bank.com} credentials to the {@code my_financial_app}; if the user agrees,
  * then the service returns an unlocked dataset with the {@code some_bank.com} credentials.
  *
- * <p><b>Note:</b> The autofill service could also whitelist well-known browser apps and skip the
- * verifications above, as long as the service can verify the authenticity of the browser app by
- * checking its signing certificate.
+ * <p><b>Note:</b> The autofill service could also add well-known browser apps into an allowlist and
+ * skip the verifications above, as long as the service can verify the authenticity of the browser
+ * app by checking its signing certificate.
  *
  * <a name="MultipleStepsSave"></a>
  * <h3>Saving when data is split in multiple screens</h3>
@@ -507,9 +507,9 @@
  * services and fill data. This mode needs to be explicitly requested for a given package up
  * to a specified max version code allowing clean migration path when the target app begins to
  * support autofill natively. Note that enabling compatibility may degrade performance for the
- * target package and should be used with caution. The platform supports whitelisting which packages
- * can be targeted in compatibility mode to ensure this mode is used only when needed and as long
- * as needed.
+ * target package and should be used with caution. The platform supports creating an allowlist for
+ * including which packages can be targeted in compatibility mode to ensure this mode is used only
+ * when needed and as long as needed.
  *
  * <p>You can request compatibility mode for packages of interest in the meta-data resource
  * associated with your service. Below is a sample service declaration:
diff --git a/core/java/android/service/autofill/InlinePresentation.java b/core/java/android/service/autofill/InlinePresentation.java
index 91416948..6eb2a15 100644
--- a/core/java/android/service/autofill/InlinePresentation.java
+++ b/core/java/android/service/autofill/InlinePresentation.java
@@ -40,6 +40,11 @@
 
     /**
      * Represents the UI content and the action for the inline suggestion.
+     *
+     * <p>The Slice should be constructed using the Content builder provided in the androidx
+     * autofill library e.g. {@code androidx.autofill.inline.v1.InlineSuggestionUi.Content.Builder}
+     * and then converted to a Slice with
+     * {@code androidx.autofill.inline.UiVersions.Content#getSlice()}.</p>
      */
     private final @NonNull Slice mSlice;
 
@@ -90,6 +95,11 @@
      *
      * @param slice
      *   Represents the UI content and the action for the inline suggestion.
+     *
+     *   <p>The Slice should be constructed using the Content builder provided in the androidx
+     *   autofill library e.g. {@code androidx.autofill.inline.v1.InlineSuggestionUi.Content.Builder}
+     *   and then converted to a Slice with
+     *   {@code androidx.autofill.inline.UiVersions.Content#getSlice()}.</p>
      * @param inlinePresentationSpec
      *   Specifies the UI specification for the inline suggestion.
      * @param pinned
@@ -118,6 +128,11 @@
 
     /**
      * Represents the UI content and the action for the inline suggestion.
+     *
+     * <p>The Slice should be constructed using the Content builder provided in the androidx
+     * autofill library e.g. {@code androidx.autofill.inline.v1.InlineSuggestionUi.Content.Builder}
+     * and then converted to a Slice with
+     * {@code androidx.autofill.inline.UiVersions.Content#getSlice()}.</p>
      */
     @DataClass.Generated.Member
     public @NonNull Slice getSlice() {
@@ -244,7 +259,7 @@
     };
 
     @DataClass.Generated(
-            time = 1593131904745L,
+            time = 1596484869201L,
             codegenVersion = "1.0.15",
             sourceFile = "frameworks/base/core/java/android/service/autofill/InlinePresentation.java",
             inputSignatures = "private final @android.annotation.NonNull android.app.slice.Slice mSlice\nprivate final @android.annotation.NonNull android.widget.inline.InlinePresentationSpec mInlinePresentationSpec\nprivate final  boolean mPinned\npublic @android.annotation.NonNull @android.annotation.Size(min=0L) java.lang.String[] getAutofillHints()\nclass InlinePresentation extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genToString=true, genHiddenConstDefs=true, genEqualsHashCode=true)")
diff --git a/core/java/android/telephony/PhoneStateListener.java b/core/java/android/telephony/PhoneStateListener.java
index a6e6d05..b807180 100644
--- a/core/java/android/telephony/PhoneStateListener.java
+++ b/core/java/android/telephony/PhoneStateListener.java
@@ -938,7 +938,6 @@
      *                           {@link SubscriptionManager#getDefaultSubscriptionId})
      *                           and the value as the list of {@link EmergencyNumber};
      *                           null if this information is not available.
-     * @hide
      */
     public void onEmergencyNumberListChanged(
             @NonNull Map<Integer, List<EmergencyNumber>> emergencyNumberList) {
@@ -948,17 +947,50 @@
     /**
      * Callback invoked when an outgoing call is placed to an emergency number.
      *
-     * @param placedEmergencyNumber the emergency number {@link EmergencyNumber} the call is placed
-     *                              to.
+     * This method will be called when an emergency call is placed on any subscription (including
+     * the no-SIM case), regardless of which subscription this listener was registered on.
+     *
+     * This method is deprecated. Both this method and the new
+     * {@link #onOutgoingEmergencyCall(EmergencyNumber, int)} will be called when an outgoing
+     * emergency call is placed.
+     *
+     * @param placedEmergencyNumber The {@link EmergencyNumber} the emergency call was placed to.
+     *
+     * @deprecated Use {@link #onOutgoingEmergencyCall(EmergencyNumber, int)}.
      * @hide
      */
     @SystemApi
     @TestApi
+    @Deprecated
     public void onOutgoingEmergencyCall(@NonNull EmergencyNumber placedEmergencyNumber) {
         // default implementation empty
     }
 
     /**
+     * Callback invoked when an outgoing call is placed to an emergency number.
+     *
+     * This method will be called when an emergency call is placed on any subscription (including
+     * the no-SIM case), regardless of which subscription this listener was registered on.
+     *
+     * Both this method and the deprecated {@link #onOutgoingEmergencyCall(EmergencyNumber)} will be
+     * called when an outgoing emergency call is placed. You should only implement one of these
+     * methods.
+     *
+     * @param placedEmergencyNumber The {@link EmergencyNumber} the emergency call was placed to.
+     * @param subscriptionId The subscription ID used to place the emergency call. If the
+     *                       emergency call was placed without a valid subscription (e.g. when there
+     *                       are no SIM cards in the device), this will be equal to
+     *                       {@link SubscriptionManager#INVALID_SUBSCRIPTION_ID}.
+     *
+     * @hide
+     */
+    @SystemApi
+    @TestApi
+    public void onOutgoingEmergencyCall(@NonNull EmergencyNumber placedEmergencyNumber,
+            int subscriptionId) {
+    }
+
+    /**
      * Callback invoked when an outgoing SMS is placed to an emergency number.
      *
      * @param sentEmergencyNumber the emergency number {@link EmergencyNumber} the SMS is sent to.
@@ -1336,13 +1368,19 @@
                             () -> psl.onEmergencyNumberListChanged(emergencyNumberList)));
         }
 
-        public void onOutgoingEmergencyCall(@NonNull EmergencyNumber placedEmergencyNumber) {
+        public void onOutgoingEmergencyCall(@NonNull EmergencyNumber placedEmergencyNumber,
+                int subscriptionId) {
             PhoneStateListener psl = mPhoneStateListenerWeakRef.get();
             if (psl == null) return;
 
             Binder.withCleanCallingIdentity(
                     () -> mExecutor.execute(
                             () -> psl.onOutgoingEmergencyCall(placedEmergencyNumber)));
+
+            Binder.withCleanCallingIdentity(
+                    () -> mExecutor.execute(
+                            () -> psl.onOutgoingEmergencyCall(placedEmergencyNumber,
+                                    subscriptionId)));
         }
 
         public void onOutgoingEmergencySms(@NonNull EmergencyNumber sentEmergencyNumber) {
diff --git a/core/java/android/telephony/TelephonyRegistryManager.java b/core/java/android/telephony/TelephonyRegistryManager.java
index 0a47032..a720601 100644
--- a/core/java/android/telephony/TelephonyRegistryManager.java
+++ b/core/java/android/telephony/TelephonyRegistryManager.java
@@ -26,10 +26,8 @@
 import android.os.Build;
 import android.os.RemoteException;
 import android.os.ServiceManager;
-import android.telephony.Annotation.ApnType;
 import android.telephony.Annotation.CallState;
 import android.telephony.Annotation.DataActivityType;
-import android.telephony.Annotation.DataFailureCause;
 import android.telephony.Annotation.DisconnectCauses;
 import android.telephony.Annotation.NetworkType;
 import android.telephony.Annotation.PreciseCallStates;
@@ -37,7 +35,6 @@
 import android.telephony.Annotation.RadioPowerState;
 import android.telephony.Annotation.SimActivationState;
 import android.telephony.Annotation.SrvccState;
-import android.telephony.data.ApnSetting;
 import android.telephony.emergency.EmergencyNumber;
 import android.telephony.ims.ImsReasonInfo;
 import android.util.Log;
@@ -402,17 +399,16 @@
      * @param subId for which data connection state changed.
      * @param slotIndex for which data connections state changed. Can be derived from subId except
      * when subId is invalid.
-     * @param apnType the apn type bitmask, defined with {@code ApnSetting#TYPE_*} flags.
      * @param preciseState the PreciseDataConnectionState
      *
-     * @see android.telephony.PreciseDataConnection
+     * @see PreciseDataConnectionState
      * @see TelephonyManager#DATA_DISCONNECTED
      */
     public void notifyDataConnectionForSubscriber(int slotIndex, int subId,
-            @ApnType int apnType, @Nullable PreciseDataConnectionState preciseState) {
+            @NonNull PreciseDataConnectionState preciseState) {
         try {
             sRegistry.notifyDataConnectionForSubscriber(
-                    slotIndex, subId, apnType, preciseState);
+                    slotIndex, subId, preciseState);
         } catch (RemoteException ex) {
             // system process is dead
         }
@@ -612,25 +608,6 @@
     }
 
     /**
-     * Notify precise data connection failed cause on certain subscription.
-     *
-     * @param subId for which data connection failed.
-     * @param slotIndex for which data conenction failed. Can be derived from subId except when
-     * subId is invalid.
-     * @param apnType the apn type bitmask, defined with {@code ApnSetting#TYPE_*} flags.
-     * @param apn the APN {@link ApnSetting#getApnName()} of this data connection.
-     * @param failCause data fail cause.
-     */
-    public void notifyPreciseDataConnectionFailed(int subId, int slotIndex, @ApnType int apnType,
-            @Nullable String apn, @DataFailureCause int failCause) {
-        try {
-            sRegistry.notifyPreciseDataConnectionFailed(slotIndex, subId, apnType, apn, failCause);
-        } catch (RemoteException ex) {
-            // system process is dead
-        }
-    }
-
-    /**
      * Notify single Radio Voice Call Continuity (SRVCC) state change for the currently active call
      * on certain subscription.
      *
diff --git a/core/java/android/view/GestureDetector.java b/core/java/android/view/GestureDetector.java
index 55c527b..8a72218 100644
--- a/core/java/android/view/GestureDetector.java
+++ b/core/java/android/view/GestureDetector.java
@@ -25,6 +25,7 @@
 import static com.android.internal.util.FrameworkStatsLog.TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__SINGLE_TAP;
 import static com.android.internal.util.FrameworkStatsLog.TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__UNKNOWN_CLASSIFICATION;
 
+import android.annotation.UiContext;
 import android.compat.annotation.UnsupportedAppUsage;
 import android.content.Context;
 import android.os.Build;
@@ -393,7 +394,7 @@
      *
      * @throws NullPointerException if {@code listener} is null.
      */
-    public GestureDetector(Context context, OnGestureListener listener) {
+    public GestureDetector(@UiContext Context context, OnGestureListener listener) {
         this(context, listener, null);
     }
 
@@ -412,7 +413,8 @@
      *
      * @throws NullPointerException if {@code listener} is null.
      */
-    public GestureDetector(Context context, OnGestureListener listener, Handler handler) {
+    public GestureDetector(@UiContext Context context, OnGestureListener listener,
+            Handler handler) {
         if (handler != null) {
             mHandler = new GestureHandler(handler);
         } else {
@@ -442,12 +444,12 @@
      *
      * @throws NullPointerException if {@code listener} is null.
      */
-    public GestureDetector(Context context, OnGestureListener listener, Handler handler,
+    public GestureDetector(@UiContext Context context, OnGestureListener listener, Handler handler,
             boolean unused) {
         this(context, listener, handler);
     }
 
-    private void init(Context context) {
+    private void init(@UiContext Context context) {
         if (mListener == null) {
             throw new NullPointerException("OnGestureListener must not be null");
         }
diff --git a/core/java/android/view/ImeInsetsSourceConsumer.java b/core/java/android/view/ImeInsetsSourceConsumer.java
index c1998c6..82f6036 100644
--- a/core/java/android/view/ImeInsetsSourceConsumer.java
+++ b/core/java/android/view/ImeInsetsSourceConsumer.java
@@ -69,7 +69,7 @@
     }
 
     public void onServedEditorChanged(EditorInfo info) {
-        if (isDummyOrEmptyEditor(info)) {
+        if (isFallbackOrEmptyEditor(info)) {
             mShowOnNextImeRender = false;
         }
         mFocusedEditor = info;
@@ -167,15 +167,15 @@
         }
     }
 
-    private boolean isDummyOrEmptyEditor(EditorInfo info) {
-        // TODO(b/123044812): Handle dummy input gracefully in IME Insets API
+    private boolean isFallbackOrEmptyEditor(EditorInfo info) {
+        // TODO(b/123044812): Handle fallback input gracefully in IME Insets API
         return info == null || (info.fieldId <= 0 && info.inputType <= 0);
     }
 
     private boolean isServedEditorRendered() {
         if (mFocusedEditor == null || mPreRenderedEditor == null
-                || isDummyOrEmptyEditor(mFocusedEditor)
-                || isDummyOrEmptyEditor(mPreRenderedEditor)) {
+                || isFallbackOrEmptyEditor(mFocusedEditor)
+                || isFallbackOrEmptyEditor(mPreRenderedEditor)) {
             // No view is focused or ready.
             return false;
         }
diff --git a/core/java/android/view/InputApplicationHandle.java b/core/java/android/view/InputApplicationHandle.java
index 3d05e2a..108345e 100644
--- a/core/java/android/view/InputApplicationHandle.java
+++ b/core/java/android/view/InputApplicationHandle.java
@@ -34,7 +34,7 @@
     public String name;
 
     // Dispatching timeout.
-    public long dispatchingTimeoutNanos;
+    public long dispatchingTimeoutMillis;
 
     public final IBinder token;
 
@@ -46,7 +46,7 @@
 
     public InputApplicationHandle(InputApplicationHandle handle) {
         this.token = handle.token;
-        this.dispatchingTimeoutNanos = handle.dispatchingTimeoutNanos;
+        this.dispatchingTimeoutMillis = handle.dispatchingTimeoutMillis;
         this.name = handle.name;
     }
 
diff --git a/core/java/android/view/InputWindowHandle.java b/core/java/android/view/InputWindowHandle.java
index a7e0305..1ef701f 100644
--- a/core/java/android/view/InputWindowHandle.java
+++ b/core/java/android/view/InputWindowHandle.java
@@ -50,7 +50,7 @@
     public int layoutParamsType;
 
     // Dispatching timeout.
-    public long dispatchingTimeoutNanos;
+    public long dispatchingTimeoutMillis;
 
     // Window frame.
     public int frameLeft;
@@ -70,11 +70,8 @@
     // Window is visible.
     public boolean visible;
 
-    // Window can receive keys.
-    public boolean canReceiveKeys;
-
-    // Window has focus.
-    public boolean hasFocus;
+    // Window can be focused.
+    public boolean focusable;
 
     // Window has wallpaper.  (window is the current wallpaper target)
     public boolean hasWallpaper;
diff --git a/core/java/android/view/InsetsController.java b/core/java/android/view/InsetsController.java
index c383bc7..403ac3a 100644
--- a/core/java/android/view/InsetsController.java
+++ b/core/java/android/view/InsetsController.java
@@ -618,16 +618,20 @@
             return false;
         }
         if (DEBUG) Log.d(TAG, "onStateChanged: " + state);
-        updateState(state);
-
-        boolean localStateChanged = !mState.equals(mLastDispatchedState,
-                true /* excludingCaptionInsets */, true /* excludeInvisibleIme */);
         mLastDispatchedState.set(state, true /* copySources */);
 
+        final InsetsState lastState = new InsetsState(mState, true /* copySources */);
+        updateState(state);
         applyLocalVisibilityOverride();
-        if (localStateChanged) {
-            if (DEBUG) Log.d(TAG, "onStateChanged, notifyInsetsChanged, send state to WM: " + mState);
+
+        if (!mState.equals(lastState, true /* excludingCaptionInsets */,
+                true /* excludeInvisibleIme */)) {
+            if (DEBUG) Log.d(TAG, "onStateChanged, notifyInsetsChanged");
             mHost.notifyInsetsChanged();
+        }
+        if (!mState.equals(mLastDispatchedState, true /* excludingCaptionInsets */,
+                true /* excludeInvisibleIme */)) {
+            if (DEBUG) Log.d(TAG, "onStateChanged, send state to WM: " + mState);
             updateRequestedState();
         }
         return true;
@@ -1134,15 +1138,14 @@
         if (invokeCallback) {
             control.cancel();
         }
+        boolean stateChanged = false;
         for (int i = mRunningAnimations.size() - 1; i >= 0; i--) {
             RunningAnimation runningAnimation = mRunningAnimations.get(i);
             if (runningAnimation.runner == control) {
                 mRunningAnimations.remove(i);
                 ArraySet<Integer> types = toInternalType(control.getTypes());
                 for (int j = types.size() - 1; j >= 0; j--) {
-                    if (getSourceConsumer(types.valueAt(j)).notifyAnimationFinished()) {
-                        mHost.notifyInsetsChanged();
-                    }
+                    stateChanged |= getSourceConsumer(types.valueAt(j)).notifyAnimationFinished();
                 }
                 if (invokeCallback && runningAnimation.startDispatched) {
                     dispatchAnimationEnd(runningAnimation.runner.getAnimation());
@@ -1150,6 +1153,10 @@
                 break;
             }
         }
+        if (stateChanged) {
+            mHost.notifyInsetsChanged();
+            updateRequestedState();
+        }
     }
 
     private void applyLocalVisibilityOverride() {
diff --git a/core/java/android/view/InsetsState.java b/core/java/android/view/InsetsState.java
index 6b0b509..593b37a 100644
--- a/core/java/android/view/InsetsState.java
+++ b/core/java/android/view/InsetsState.java
@@ -60,6 +60,8 @@
  */
 public class InsetsState implements Parcelable {
 
+    public static final InsetsState EMPTY = new InsetsState();
+
     /**
      * Internal representation of inset source types. This is different from the public API in
      * {@link WindowInsets.Type} as one type from the public API might indicate multiple windows
diff --git a/core/java/android/view/LayoutInflater.java b/core/java/android/view/LayoutInflater.java
index 1afe11e..b925b49 100644
--- a/core/java/android/view/LayoutInflater.java
+++ b/core/java/android/view/LayoutInflater.java
@@ -21,6 +21,7 @@
 import android.annotation.Nullable;
 import android.annotation.SystemService;
 import android.annotation.TestApi;
+import android.annotation.UiContext;
 import android.compat.annotation.UnsupportedAppUsage;
 import android.content.Context;
 import android.content.pm.ApplicationInfo;
@@ -94,6 +95,7 @@
      * {@hide}
      */
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
+    @UiContext
     protected final Context mContext;
 
     // these are optional, set by the caller
@@ -277,7 +279,7 @@
     /**
      * Obtains the LayoutInflater from the given context.
      */
-    public static LayoutInflater from(Context context) {
+    public static LayoutInflater from(@UiContext Context context) {
         LayoutInflater LayoutInflater =
                 (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
         if (LayoutInflater == null) {
diff --git a/core/java/android/view/NotificationHeaderView.java b/core/java/android/view/NotificationHeaderView.java
index bf94670..6136a80 100644
--- a/core/java/android/view/NotificationHeaderView.java
+++ b/core/java/android/view/NotificationHeaderView.java
@@ -52,14 +52,12 @@
     private View mHeaderText;
     private View mSecondaryHeaderText;
     private OnClickListener mExpandClickListener;
-    private OnClickListener mAppOpsListener;
     private OnClickListener mFeedbackListener;
     private HeaderTouchListener mTouchListener = new HeaderTouchListener();
     private LinearLayout mTransferChip;
     private NotificationExpandButton mExpandButton;
     private CachingIconView mIcon;
     private View mProfileBadge;
-    private View mAppOps;
     private View mFeedbackIcon;
     private boolean mExpanded;
     private boolean mShowExpandButtonAtEnd;
@@ -117,7 +115,6 @@
         mExpandButton = findViewById(com.android.internal.R.id.expand_button);
         mIcon = findViewById(com.android.internal.R.id.icon);
         mProfileBadge = findViewById(com.android.internal.R.id.profile_badge);
-        mAppOps = findViewById(com.android.internal.R.id.app_ops);
         mFeedbackIcon = findViewById(com.android.internal.R.id.feedback);
     }
 
@@ -146,7 +143,6 @@
             // Icons that should go at the end
             if ((child == mExpandButton && mShowExpandButtonAtEnd)
                     || child == mProfileBadge
-                    || child == mAppOps
                     || child == mFeedbackIcon
                     || child == mTransferChip) {
                 iconWidth += lp.leftMargin + lp.rightMargin + child.getMeasuredWidth();
@@ -212,7 +208,6 @@
             // Icons that should go at the end
             if ((child == mExpandButton && mShowExpandButtonAtEnd)
                     || child == mProfileBadge
-                    || child == mAppOps
                     || child == mFeedbackIcon
                     || child == mTransferChip) {
                 if (end == getMeasuredWidth()) {
@@ -282,7 +277,7 @@
     }
 
     private void updateTouchListener() {
-        if (mExpandClickListener == null && mAppOpsListener == null && mFeedbackListener == null) {
+        if (mExpandClickListener == null && mFeedbackListener == null) {
             setOnTouchListener(null);
             return;
         }
@@ -291,14 +286,6 @@
     }
 
     /**
-     * Sets onclick listener for app ops icons.
-     */
-    public void setAppOpsOnClickListener(OnClickListener l) {
-        mAppOpsListener = l;
-        updateTouchListener();
-    }
-
-    /**
      * Sets onclick listener for feedback icon.
      */
     public void setFeedbackOnClickListener(OnClickListener l) {
@@ -394,7 +381,6 @@
 
         private final ArrayList<Rect> mTouchRects = new ArrayList<>();
         private Rect mExpandButtonRect;
-        private Rect mAppOpsRect;
         private Rect mFeedbackRect;
         private int mTouchSlop;
         private boolean mTrackGesture;
@@ -408,9 +394,7 @@
             mTouchRects.clear();
             addRectAroundView(mIcon);
             mExpandButtonRect = addRectAroundView(mExpandButton);
-            mAppOpsRect = addRectAroundView(mAppOps);
             mFeedbackRect = addRectAroundView(mFeedbackIcon);
-            setTouchDelegate(new TouchDelegate(mAppOpsRect, mAppOps));
             addWidthRect();
             mTouchSlop = ViewConfiguration.get(getContext()).getScaledTouchSlop();
         }
@@ -471,11 +455,7 @@
                     break;
                 case MotionEvent.ACTION_UP:
                     if (mTrackGesture) {
-                        if (mAppOps.isVisibleToUser() && (mAppOpsRect.contains((int) x, (int) y)
-                                || mAppOpsRect.contains((int) mDownX, (int) mDownY))) {
-                            mAppOps.performClick();
-                            return true;
-                        } else if (mFeedbackIcon.isVisibleToUser()
+                        if (mFeedbackIcon.isVisibleToUser()
                                 && (mFeedbackRect.contains((int) x, (int) y))
                                 || mFeedbackRect.contains((int) mDownX, (int) mDownY)) {
                             mFeedbackIcon.performClick();
diff --git a/core/java/android/view/SurfaceControl.java b/core/java/android/view/SurfaceControl.java
index eaa7eaf..6ef086b 100644
--- a/core/java/android/view/SurfaceControl.java
+++ b/core/java/android/view/SurfaceControl.java
@@ -22,8 +22,6 @@
 import static android.graphics.Matrix.MSKEW_Y;
 import static android.graphics.Matrix.MTRANS_X;
 import static android.graphics.Matrix.MTRANS_Y;
-import static android.view.Surface.ROTATION_270;
-import static android.view.Surface.ROTATION_90;
 import static android.view.SurfaceControlProto.HASH_CODE;
 import static android.view.SurfaceControlProto.NAME;
 
@@ -89,13 +87,10 @@
     private static native void nativeWriteToParcel(long nativeObject, Parcel out);
     private static native void nativeRelease(long nativeObject);
     private static native void nativeDisconnect(long nativeObject);
-
-    private static native ScreenshotHardwareBuffer nativeScreenshot(IBinder displayToken,
-            Rect sourceCrop, int width, int height, boolean useIdentityTransform, int rotation,
-            boolean captureSecureLayers);
-    private static native ScreenshotHardwareBuffer nativeCaptureLayers(IBinder displayToken,
-            long layerObject, Rect sourceCrop, float frameScale, long[] excludeLayerObjects,
-            int format);
+    private static native ScreenshotHardwareBuffer nativeCaptureDisplay(
+            DisplayCaptureArgs captureArgs);
+    private static native ScreenshotHardwareBuffer nativeCaptureLayers(
+            LayerCaptureArgs captureArgs);
     private static native long nativeMirrorSurface(long mirrorOfObject);
     private static native long nativeCreateTransaction();
     private static native long nativeGetNativeTransactionFinalizer();
@@ -572,7 +567,8 @@
         * 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 containsSecureLayer Indicates whether this graphic buffer contains captured contents
+        * @param containsSecureLayers Indicates whether this graphic buffer contains captured
+        *                             contents
         *        of secure layers, in which case the screenshot should not be persisted.
         */
         private static ScreenshotHardwareBuffer createFromNative(HardwareBuffer hardwareBuffer,
@@ -592,6 +588,26 @@
         public boolean containsSecureLayers() {
             return mContainsSecureLayers;
         }
+
+        /**
+         * Copy content of ScreenshotHardwareBuffer into a hardware bitmap and return it.
+         * 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)}
+         *
+         * CAVEAT: This can be extremely slow; avoid use unless absolutely necessary; prefer to
+         * directly
+         * use the {@link HardwareBuffer} directly.
+         *
+         * @return Bitmap generated from the {@link HardwareBuffer}
+         */
+        public Bitmap asBitmap() {
+            if (mHardwareBuffer == null) {
+                Log.w(TAG, "Failed to take screenshot. Null screenshot object");
+                return null;
+            }
+            return Bitmap.wrapHardwareBuffer(mHardwareBuffer, mColorSpace);
+        }
     }
 
     /**
@@ -599,7 +615,7 @@
      * are shared between {@link DisplayCaptureArgs} and {@link LayerCaptureArgs}
      * @hide
      */
-    public abstract static class CaptureArgs {
+    private abstract static class CaptureArgs {
         private final int mPixelFormat;
         private final Rect mSourceCrop = new Rect();
         private final float mFrameScale;
@@ -617,7 +633,7 @@
          *
          * @param <T> A builder that extends {@link Builder}
          */
-        public abstract static class Builder<T extends Builder<T>> {
+        abstract static class Builder<T extends Builder<T>> {
             private int mPixelFormat = PixelFormat.RGBA_8888;
             private final Rect mSourceCrop = new Rect();
             private float mFrameScale = 1;
@@ -662,14 +678,14 @@
             /**
              * Each sub class should return itself to allow the builder to chain properly
              */
-            public abstract T getThis();
+            abstract T getThis();
         }
     }
 
     /**
      * The arguments class used to make display capture requests.
      *
-     * @see #nativeScreenshot(IBinder, Rect, int, int, boolean, int, boolean)
+     * @see #nativeCaptureDisplay(DisplayCaptureArgs)
      * @hide
      */
     public static class DisplayCaptureArgs extends CaptureArgs {
@@ -677,7 +693,6 @@
         private final int mWidth;
         private final int mHeight;
         private final boolean mUseIdentityTransform;
-        private final int mRotation;
 
         private DisplayCaptureArgs(Builder builder) {
             super(builder);
@@ -685,7 +700,6 @@
             mWidth = builder.mWidth;
             mHeight = builder.mHeight;
             mUseIdentityTransform = builder.mUseIdentityTransform;
-            mRotation = builder.mRotation;
         }
 
         /**
@@ -696,7 +710,6 @@
             private int mWidth;
             private int mHeight;
             private boolean mUseIdentityTransform;
-            private @Surface.Rotation int mRotation = Surface.ROTATION_0;
 
             /**
              * Construct a new {@link LayerCaptureArgs} with the set parameters. The builder
@@ -738,28 +751,18 @@
             }
 
             /**
-             * Replace whatever transformation (rotation, scaling, translation) the surface
-             * layers are currently using with the identity transformation while taking the
-             * screenshot.
+             * Replace the rotation transform of the display with the identity transformation while
+             * taking the screenshot. This ensures the screenshot is taken in the ROTATION_0
+             * orientation. Set this value to false if the screenshot should be taken in the
+             * current screen orientation.
              */
             public Builder setUseIdentityTransform(boolean useIdentityTransform) {
                 mUseIdentityTransform = useIdentityTransform;
                 return this;
             }
 
-            /**
-             * Apply a custom clockwise rotation to the screenshot, i.e.
-             * Surface.ROTATION_0,90,180,270. SurfaceFlinger will always take screenshots in its
-             * native portrait orientation by default, so this is useful for returning screenshots
-             * that are independent of device orientation.
-             */
-            public Builder setRotation(@Surface.Rotation int rotation) {
-                mRotation = rotation;
-                return this;
-            }
-
             @Override
-            public Builder getThis() {
+            Builder getThis() {
                 return this;
             }
         }
@@ -768,7 +771,7 @@
     /**
      * The arguments class used to make layer capture requests.
      *
-     * @see #nativeCaptureLayers(IBinder, long, Rect, float, long[], int)
+     * @see #nativeCaptureLayers(LayerCaptureArgs)
      * @hide
      */
     public static class LayerCaptureArgs extends CaptureArgs {
@@ -780,9 +783,13 @@
             super(builder);
             mChildrenOnly = builder.mChildrenOnly;
             mNativeLayer = builder.mLayer.mNativeObject;
-            mNativeExcludeLayers = new long[builder.mExcludeLayers.length];
-            for (int i = 0; i < builder.mExcludeLayers.length; i++) {
-                mNativeExcludeLayers[i] = builder.mExcludeLayers[i].mNativeObject;
+            if (builder.mExcludeLayers != null) {
+                mNativeExcludeLayers = new long[builder.mExcludeLayers.length];
+                for (int i = 0; i < builder.mExcludeLayers.length; i++) {
+                    mNativeExcludeLayers[i] = builder.mExcludeLayers[i].mNativeObject;
+                }
+            } else {
+                mNativeExcludeLayers = null;
             }
         }
 
@@ -837,7 +844,7 @@
             }
 
             @Override
-            public Builder getThis() {
+            Builder getThis() {
                 return this;
             }
 
@@ -2219,114 +2226,13 @@
     }
 
     /**
-     * @see SurfaceControl#screenshot(Rect, int, int, boolean, int)}
-     * @hide
-     */
-    @UnsupportedAppUsage
-    public static Bitmap screenshot(Rect sourceCrop, int width, int height, int rotation) {
-        return screenshot(sourceCrop, width, height, false, rotation);
-    }
-
-    /**
-     * Copy the current screen contents into a hardware bitmap and return it.
-     * 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)}
-     *
-     * CAVEAT: Versions of screenshot that return a {@link Bitmap} can be extremely slow; avoid use
-     * unless absolutely necessary; prefer the versions that use a {@link HardwareBuffer} such as
-     * {@link SurfaceControl#screenshotToBuffer(IBinder, Rect, int, int, boolean, int)}.
-     *
-     * @see SurfaceControl#screenshotToBuffer(IBinder, Rect, int, int, boolean, int)}
-     * @hide
-     */
-    @UnsupportedAppUsage
-    public static Bitmap screenshot(Rect sourceCrop, int width, int height,
-            boolean useIdentityTransform, int rotation) {
-        // TODO: should take the display as a parameter
-        final IBinder displayToken = SurfaceControl.getInternalDisplayToken();
-        if (displayToken == null) {
-            Log.w(TAG, "Failed to take screenshot because internal display is disconnected");
-            return null;
-        }
-
-        if (rotation == ROTATION_90 || rotation == ROTATION_270) {
-            rotation = (rotation == ROTATION_90) ? ROTATION_270 : ROTATION_90;
-        }
-
-        SurfaceControl.rotateCropForSF(sourceCrop, rotation);
-        final ScreenshotHardwareBuffer buffer = screenshotToBuffer(displayToken, sourceCrop, width,
-                height, useIdentityTransform, rotation);
-
-        if (buffer == null) {
-            Log.w(TAG, "Failed to take screenshot");
-            return null;
-        }
-        return Bitmap.wrapHardwareBuffer(buffer.getHardwareBuffer(), buffer.getColorSpace());
-    }
-
-    /**
-     * Captures all the surfaces in a display and returns a {@link HardwareBuffer} with the content.
-     *
-     * @param display              The display to take the screenshot of.
-     * @param sourceCrop           The portion of the screen to capture into the Bitmap; caller may
-     *                             pass in 'new Rect()' if no cropping is desired.
-     * @param width                The desired width of the returned bitmap; the raw screen will be
-     *                             scaled down to this size; caller may pass in 0 if no scaling is
-     *                             desired.
-     * @param height               The desired height of the returned bitmap; the raw screen will
-     *                             be scaled down to this size; caller may pass in 0 if no scaling
-     *                             is desired.
-     * @param useIdentityTransform Replace whatever transformation (rotation, scaling, translation)
-     *                             the surface layers are currently using with the identity
-     *                             transformation while taking the screenshot.
-     * @param rotation             Apply a custom clockwise rotation to the screenshot, i.e.
-     *                             Surface.ROTATION_0,90,180,270. SurfaceFlinger will always take
-     *                             screenshots in its native portrait orientation by default, so
-     *                             this is useful for returning screenshots that are independent of
-     *                             device orientation.
-     * @return Returns a HardwareBuffer that contains the captured content.
-     * @hide
-     */
-    public static ScreenshotHardwareBuffer screenshotToBuffer(IBinder display, Rect sourceCrop,
-            int width, int height, boolean useIdentityTransform, int rotation) {
-        if (display == null) {
-            throw new IllegalArgumentException("displayToken must not be null");
-        }
-
-        return nativeScreenshot(display, sourceCrop, width, height, useIdentityTransform, rotation,
-                false /* captureSecureLayers */);
-    }
-
-    /**
-     * Like screenshotToBuffer, but if the caller is AID_SYSTEM, allows
-     * for the capture of secure layers. This is used for the screen rotation
-     * animation where the system server takes screenshots but does
-     * not persist them or allow them to leave the server. However in other
-     * cases in the system server, we mostly want to omit secure layers
-     * like when we take a screenshot on behalf of the assistant.
+     * Captures all the surfaces in a display and returns a {@link ScreenshotHardwareBuffer} with
+     * the content.
      *
      * @hide
      */
-    public static ScreenshotHardwareBuffer screenshotToBufferWithSecureLayersUnsafe(IBinder display,
-            Rect sourceCrop, int width, int height, boolean useIdentityTransform,
-            int rotation) {
-        if (display == null) {
-            throw new IllegalArgumentException("displayToken must not be null");
-        }
-
-        return nativeScreenshot(display, sourceCrop, width, height, useIdentityTransform, rotation,
-                true /* captureSecureLayers */);
-    }
-
-    private static void rotateCropForSF(Rect crop, int rot) {
-        if (rot == Surface.ROTATION_90 || rot == Surface.ROTATION_270) {
-            int tmp = crop.top;
-            crop.top = crop.left;
-            crop.left = tmp;
-            tmp = crop.right;
-            crop.right = crop.bottom;
-            crop.bottom = tmp;
-        }
+    public static ScreenshotHardwareBuffer captureDisplay(DisplayCaptureArgs captureArgs) {
+        return nativeCaptureDisplay(captureArgs);
     }
 
     /**
@@ -2365,24 +2271,37 @@
      */
     public static ScreenshotHardwareBuffer captureLayers(SurfaceControl layer, Rect sourceCrop,
             float frameScale, int format) {
-        final IBinder displayToken = SurfaceControl.getInternalDisplayToken();
-        return nativeCaptureLayers(displayToken, layer.mNativeObject, sourceCrop, frameScale, null,
-                format);
+        LayerCaptureArgs captureArgs = new LayerCaptureArgs.Builder(layer)
+                .setSourceCrop(sourceCrop)
+                .setFrameScale(frameScale)
+                .setPixelFormat(format)
+                .build();
+
+        return nativeCaptureLayers(captureArgs);
     }
 
     /**
-     * Like {@link captureLayers} but with an array of layer handles to exclude.
+     * @hide
+     */
+    public static ScreenshotHardwareBuffer captureLayers(LayerCaptureArgs captureArgs) {
+        return nativeCaptureLayers(captureArgs);
+    }
+
+    /**
+     * Like {@link #captureLayers(SurfaceControl, Rect, float, int)} but with an array of layer
+     * handles to exclude.
      * @hide
      */
     public static ScreenshotHardwareBuffer captureLayersExcluding(SurfaceControl layer,
             Rect sourceCrop, float frameScale, int format, SurfaceControl[] exclude) {
-        final IBinder displayToken = SurfaceControl.getInternalDisplayToken();
-        long[] nativeExcludeObjects = new long[exclude.length];
-        for (int i = 0; i < exclude.length; i++) {
-            nativeExcludeObjects[i] = exclude[i].mNativeObject;
-        }
-        return nativeCaptureLayers(displayToken, layer.mNativeObject, sourceCrop, frameScale,
-                nativeExcludeObjects, PixelFormat.RGBA_8888);
+        LayerCaptureArgs captureArgs = new LayerCaptureArgs.Builder(layer)
+                .setSourceCrop(sourceCrop)
+                .setFrameScale(frameScale)
+                .setPixelFormat(format)
+                .setExcludeLayers(exclude)
+                .build();
+
+        return nativeCaptureLayers(captureArgs);
     }
 
     /**
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index ef4bc91..8917821 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -43,6 +43,7 @@
 import android.annotation.Size;
 import android.annotation.StyleRes;
 import android.annotation.TestApi;
+import android.annotation.UiContext;
 import android.annotation.UiThread;
 import android.compat.annotation.UnsupportedAppUsage;
 import android.content.AutofillOptions;
@@ -4909,6 +4910,7 @@
      */
     @ViewDebug.ExportedProperty(deepExport = true)
     @UnsupportedAppUsage
+    @UiContext
     protected Context mContext;
 
     @UnsupportedAppUsage
@@ -15070,6 +15072,7 @@
      * @return The view's Context.
      */
     @ViewDebug.CapturedViewProperty
+    @UiContext
     public final Context getContext() {
         return mContext;
     }
@@ -23355,7 +23358,7 @@
      *            displaying, else return the result of calling through to the
      *            super class.
      *
-     * @return boolean If true than the Drawable is being displayed in the
+     * @return boolean If true then the Drawable is being displayed in the
      *         view; else false and it is not allowed to animate.
      *
      * @see #unscheduleDrawable(android.graphics.drawable.Drawable)
diff --git a/core/java/android/view/ViewConfiguration.java b/core/java/android/view/ViewConfiguration.java
index ffeeb80..ccf1fb0 100644
--- a/core/java/android/view/ViewConfiguration.java
+++ b/core/java/android/view/ViewConfiguration.java
@@ -20,6 +20,7 @@
 
 import android.annotation.FloatRange;
 import android.annotation.TestApi;
+import android.annotation.UiContext;
 import android.app.Activity;
 import android.app.AppGlobals;
 import android.compat.annotation.UnsupportedAppUsage;
@@ -391,7 +392,7 @@
      * @see #get(android.content.Context)
      * @see android.util.DisplayMetrics
      */
-    private ViewConfiguration(Context context) {
+    private ViewConfiguration(@UiContext Context context) {
         mConstructedWithContext = true;
         final Resources res = context.getResources();
         final DisplayMetrics metrics = res.getDisplayMetrics();
@@ -498,7 +499,8 @@
      *                be {@link Activity} or other {@link Context} created with
      *                {@link Context#createWindowContext(int, Bundle)}.
      */
-    public static ViewConfiguration get(Context context) {
+
+    public static ViewConfiguration get(@UiContext Context context) {
         if (!context.isUiContext() && vmIncorrectContextUseEnabled()) {
             final String errorMessage = "Tried to access UI constants from a non-visual Context:"
                     + context;
diff --git a/core/java/android/view/ViewPropertyAnimator.java b/core/java/android/view/ViewPropertyAnimator.java
index 859e9a4..65cc2f8 100644
--- a/core/java/android/view/ViewPropertyAnimator.java
+++ b/core/java/android/view/ViewPropertyAnimator.java
@@ -19,6 +19,7 @@
 import android.animation.Animator;
 import android.animation.TimeInterpolator;
 import android.animation.ValueAnimator;
+import android.annotation.FloatRange;
 import android.graphics.RenderNode;
 
 import java.util.ArrayList;
@@ -725,7 +726,7 @@
      * @see View#setAlpha(float)
      * @return This object, allowing calls to methods in this class to be chained.
      */
-    public ViewPropertyAnimator alpha(float value) {
+    public ViewPropertyAnimator alpha(@FloatRange(from = 0.0f, to = 1.0f) float value) {
         animateProperty(ALPHA, value);
         return this;
     }
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index 2a2d1e6..5d20381 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -64,6 +64,7 @@
 import android.annotation.AnyThread;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.annotation.UiContext;
 import android.app.ActivityManager;
 import android.app.ActivityThread;
 import android.app.ResourcesManager;
@@ -349,6 +350,7 @@
     @GuardedBy("mWindowCallbacks")
     final ArrayList<WindowCallbacks> mWindowCallbacks = new ArrayList<>();
     @UnsupportedAppUsage
+    @UiContext
     public final Context mContext;
 
     @UnsupportedAppUsage
@@ -719,11 +721,11 @@
                 false /* useSfChoreographer */);
     }
 
-    public ViewRootImpl(Context context, Display display, IWindowSession session) {
+    public ViewRootImpl(@UiContext Context context, Display display, IWindowSession session) {
         this(context, display, session, false /* useSfChoreographer */);
     }
 
-    public ViewRootImpl(Context context, Display display, IWindowSession session,
+    public ViewRootImpl(@UiContext Context context, Display display, IWindowSession session,
             boolean useSfChoreographer) {
         mContext = context;
         mWindowSession = session;
@@ -1812,19 +1814,13 @@
     /**
      * Called after window layout to update the bounds surface. If the surface insets have changed
      * or the surface has resized, update the bounds surface.
-     *
-     * @param shouldReparent Whether it should reparent the bounds layer to the main SurfaceControl.
      */
-    private void updateBoundsLayer(boolean shouldReparent) {
+    private void updateBoundsLayer() {
         if (mBoundsLayer != null) {
             setBoundsLayerCrop();
-            mTransaction.deferTransactionUntil(mBoundsLayer, getRenderSurfaceControl(),
-                    mSurface.getNextFrameNumber());
-
-            if (shouldReparent) {
-                mTransaction.reparent(mBoundsLayer, getRenderSurfaceControl());
-            }
-            mTransaction.apply();
+            mTransaction.deferTransactionUntil(mBoundsLayer,
+                    getRenderSurfaceControl(), mSurface.getNextFrameNumber())
+                    .apply();
         }
     }
 
@@ -2903,16 +2899,7 @@
         }
 
         if (surfaceSizeChanged || surfaceReplaced || surfaceCreated || windowAttributesChanged) {
-            // If the surface has been replaced, there's a chance the bounds layer is not parented
-            // to the new layer. When updating bounds layer, also reparent to the main VRI
-            // SurfaceControl to ensure it's correctly placed in the hierarchy.
-            //
-            // This needs to be done on the client side since WMS won't reparent the children to the
-            // new surface if it thinks the app is closing. WMS gets the signal that the app is
-            // stopping, but on the client side it doesn't get stopped since it's restarted quick
-            // enough. WMS doesn't want to keep around old children since they will leak when the
-            // client creates new children.
-            updateBoundsLayer(surfaceReplaced);
+            updateBoundsLayer();
         }
 
         final boolean didLayout = layoutRequested && (!mStopped || mReportNextDraw);
@@ -8903,6 +8890,9 @@
      * @param targets the search queue for targets
      */
     private void collectRootScrollCaptureTargets(Queue<ScrollCaptureTarget> targets) {
+        if (mRootScrollCaptureCallbacks == null) {
+            return;
+        }
         for (ScrollCaptureCallback cb : mRootScrollCaptureCallbacks) {
             // Add to the list for consideration
             Point offset = new Point(mView.getLeft(), mView.getTop());
diff --git a/core/java/android/view/Window.java b/core/java/android/view/Window.java
index 446e7aa..1dbf37a 100644
--- a/core/java/android/view/Window.java
+++ b/core/java/android/view/Window.java
@@ -27,6 +27,7 @@
 import android.annotation.StyleRes;
 import android.annotation.SystemApi;
 import android.annotation.TestApi;
+import android.annotation.UiContext;
 import android.app.WindowConfiguration;
 import android.compat.annotation.UnsupportedAppUsage;
 import android.content.Context;
@@ -280,6 +281,7 @@
     public static final int DECOR_CAPTION_SHADE_DARK = 2;
 
     @UnsupportedAppUsage
+    @UiContext
     private final Context mContext;
 
     @UnsupportedAppUsage
@@ -722,7 +724,7 @@
     }
 
 
-    public Window(Context context) {
+    public Window(@UiContext Context context) {
         mContext = context;
         mFeatures = mLocalFeatures = getDefaultFeatures(context);
     }
@@ -733,6 +735,7 @@
      *
      * @return Context The Context that was supplied to the constructor.
      */
+    @UiContext
     public final Context getContext() {
         return mContext;
     }
diff --git a/core/java/android/view/WindowManager.java b/core/java/android/view/WindowManager.java
index cf4315f..32ee290 100644
--- a/core/java/android/view/WindowManager.java
+++ b/core/java/android/view/WindowManager.java
@@ -347,6 +347,7 @@
             TRANSIT_FLAG_KEYGUARD_GOING_AWAY_TO_SHADE,
             TRANSIT_FLAG_KEYGUARD_GOING_AWAY_NO_ANIMATION,
             TRANSIT_FLAG_KEYGUARD_GOING_AWAY_WITH_WALLPAPER,
+            TRANSIT_FLAG_KEYGUARD_GOING_AWAY_SUBTLE_ANIMATION
     })
     @Retention(RetentionPolicy.SOURCE)
     @interface TransitionFlags {}
@@ -533,7 +534,8 @@
             ScreenshotSource.SCREENSHOT_KEY_OTHER,
             ScreenshotSource.SCREENSHOT_OVERVIEW,
             ScreenshotSource.SCREENSHOT_ACCESSIBILITY_ACTIONS,
-            ScreenshotSource.SCREENSHOT_OTHER})
+            ScreenshotSource.SCREENSHOT_OTHER,
+            ScreenshotSource.SCREENSHOT_VENDOR_GESTURE})
     @interface ScreenshotSource {
         int SCREENSHOT_GLOBAL_ACTIONS = 0;
         int SCREENSHOT_KEY_CHORD = 1;
@@ -541,6 +543,7 @@
         int SCREENSHOT_OVERVIEW = 3;
         int SCREENSHOT_ACCESSIBILITY_ACTIONS = 4;
         int SCREENSHOT_OTHER = 5;
+        int SCREENSHOT_VENDOR_GESTURE = 6;
     }
 
     /**
diff --git a/core/java/android/view/WindowManagerImpl.java b/core/java/android/view/WindowManagerImpl.java
index b4561c5..27fbfb6 100644
--- a/core/java/android/view/WindowManagerImpl.java
+++ b/core/java/android/view/WindowManagerImpl.java
@@ -25,6 +25,7 @@
 import static android.view.WindowManager.LayoutParams.SOFT_INPUT_ADJUST_NOTHING;
 
 import android.annotation.NonNull;
+import android.annotation.UiContext;
 import android.app.ResourcesManager;
 import android.compat.annotation.UnsupportedAppUsage;
 import android.content.Context;
@@ -69,6 +70,7 @@
 public final class WindowManagerImpl implements WindowManager {
     @UnsupportedAppUsage
     private final WindowManagerGlobal mGlobal = WindowManagerGlobal.getInstance();
+    @UiContext
     @VisibleForTesting
     public final Context mContext;
     private final Window mParentWindow;
diff --git a/core/java/android/view/accessibility/AccessibilityNodeInfo.java b/core/java/android/view/accessibility/AccessibilityNodeInfo.java
index 708e277..2d0f05e 100644
--- a/core/java/android/view/accessibility/AccessibilityNodeInfo.java
+++ b/core/java/android/view/accessibility/AccessibilityNodeInfo.java
@@ -1768,7 +1768,7 @@
      *   <strong>Note:</strong> The primary usage of this API is for UI test automation
      *   and in order to report the fully qualified view id if an {@link AccessibilityNodeInfo}
      *   the client has to set the {@link AccessibilityServiceInfo#FLAG_REPORT_VIEW_IDS}
-     *   flag when configuring their {@link android.accessibilityservice.AccessibilityService}.
+     *   flag when configuring the {@link android.accessibilityservice.AccessibilityService}.
      * </p>
      * <p>
      * <strong>Note:</strong> If this view hierarchy has a {@link SurfaceView} embedding another
@@ -3206,7 +3206,7 @@
      *   <strong>Note:</strong> The primary usage of this API is for UI test automation
      *   and in order to report the source view id of an {@link AccessibilityNodeInfo} the
      *   client has to set the {@link AccessibilityServiceInfo#FLAG_REPORT_VIEW_IDS}
-     *   flag when configuring their {@link android.accessibilityservice.AccessibilityService}.
+     *   flag when configuring the {@link android.accessibilityservice.AccessibilityService}.
      * </p>
 
      * @return The id resource name.
diff --git a/core/java/android/view/accessibility/IWindowMagnificationConnection.aidl b/core/java/android/view/accessibility/IWindowMagnificationConnection.aidl
index e814ec6..eb67191 100644
--- a/core/java/android/view/accessibility/IWindowMagnificationConnection.aidl
+++ b/core/java/android/view/accessibility/IWindowMagnificationConnection.aidl
@@ -29,7 +29,7 @@
 oneway interface IWindowMagnificationConnection {
 
     /**
-     * Enables window magnification on specifed display with specified center and scale.
+     * Enables window magnification on specified display with given center and scale and animation.
      *
      * @param displayId The logical display id.
      * @param scale magnification scale.
@@ -41,7 +41,7 @@
     void enableWindowMagnification(int displayId, float scale, float centerX, float centerY);
 
     /**
-     * Sets the scale of the window magnifier on specifed display.
+     * Sets the scale of the window magnifier on specified display.
      *
      * @param displayId The logical display id.
      * @param scale magnification scale.
@@ -49,14 +49,14 @@
     void setScale(int displayId, float scale);
 
      /**
-     * Disables window magnification on specifed display.
+     * Disables window magnification on specified display with animation.
      *
      * @param displayId The logical display id.
      */
     void disableWindowMagnification(int displayId);
 
     /**
-     * Moves the window magnifier on the specifed display.
+     * Moves the window magnifier on the specified display. It has no effect while animating.
      *
      * @param offsetX the amount in pixels to offset the window magnifier in the X direction, in
      *                current screen pixels.
diff --git a/core/java/android/view/inputmethod/BaseInputConnection.java b/core/java/android/view/inputmethod/BaseInputConnection.java
index d5d631a..eef2726 100644
--- a/core/java/android/view/inputmethod/BaseInputConnection.java
+++ b/core/java/android/view/inputmethod/BaseInputConnection.java
@@ -54,7 +54,7 @@
     /** @hide */
     protected final InputMethodManager mIMM;
     final View mTargetView;
-    final boolean mDummyMode;
+    final boolean mFallbackMode;
 
     private Object[] mDefaultComposingSpans;
 
@@ -64,14 +64,14 @@
     BaseInputConnection(InputMethodManager mgr, boolean fullEditor) {
         mIMM = mgr;
         mTargetView = null;
-        mDummyMode = !fullEditor;
+        mFallbackMode = !fullEditor;
     }
 
     public BaseInputConnection(View targetView, boolean fullEditor) {
         mIMM = (InputMethodManager)targetView.getContext().getSystemService(
                 Context.INPUT_METHOD_SERVICE);
         mTargetView = targetView;
-        mDummyMode = !fullEditor;
+        mFallbackMode = !fullEditor;
     }
 
     public static final void removeComposingSpans(Spannable text) {
@@ -189,7 +189,7 @@
 
     /**
      * Default implementation replaces any existing composing text with
-     * the given text.  In addition, only if dummy mode, a key event is
+     * the given text.  In addition, only if fallback mode, a key event is
      * sent for the new text and the current editable buffer cleared.
      */
     public boolean commitText(CharSequence text, int newCursorPosition) {
@@ -445,7 +445,7 @@
 
     /**
      * The default implementation removes the composing state from the
-     * current editable text.  In addition, only if dummy mode, a key event is
+     * current editable text.  In addition, only if fallback mode, a key event is
      * sent for the new text and the current editable buffer cleared.
      */
     public boolean finishComposingText() {
@@ -454,7 +454,7 @@
         if (content != null) {
             beginBatchEdit();
             removeComposingSpans(content);
-            // Note: sendCurrentText does nothing unless mDummyMode is set
+            // Note: sendCurrentText does nothing unless mFallbackMode is set
             sendCurrentText();
             endBatchEdit();
         }
@@ -464,10 +464,10 @@
     /**
      * The default implementation uses TextUtils.getCapsMode to get the
      * cursor caps mode for the current selection position in the editable
-     * text, unless in dummy mode in which case 0 is always returned.
+     * text, unless in fallback mode in which case 0 is always returned.
      */
     public int getCursorCapsMode(int reqModes) {
-        if (mDummyMode) return 0;
+        if (mFallbackMode) return 0;
 
         final Editable content = getEditable();
         if (content == null) return 0;
@@ -664,7 +664,7 @@
             content.setSpan(COMPOSING, a, b,
                     Spanned.SPAN_EXCLUSIVE_EXCLUSIVE | Spanned.SPAN_COMPOSING);
 
-            // Note: sendCurrentText does nothing unless mDummyMode is set
+            // Note: sendCurrentText does nothing unless mFallbackMode is set
             sendCurrentText();
             endBatchEdit();
         }
@@ -715,7 +715,7 @@
     }
 
     private void sendCurrentText() {
-        if (!mDummyMode) {
+        if (!mFallbackMode) {
             return;
         }
 
diff --git a/core/java/android/view/inputmethod/InputMethodInfo.java b/core/java/android/view/inputmethod/InputMethodInfo.java
index 2864485..7cc347d 100644
--- a/core/java/android/view/inputmethod/InputMethodInfo.java
+++ b/core/java/android/view/inputmethod/InputMethodInfo.java
@@ -294,7 +294,7 @@
      */
     public InputMethodInfo(String packageName, String className,
             CharSequence label, String settingsActivity) {
-        this(buildDummyResolveInfo(packageName, className, label), false /* isAuxIme */,
+        this(buildFakeResolveInfo(packageName, className, label), false /* isAuxIme */,
                 settingsActivity, null /* subtypes */, 0 /* isDefaultResId */,
                 false /* forceDefault */, true /* supportsSwitchingToNextInputMethod */,
                 false /* inlineSuggestionsEnabled */, false /* isVrOnly */);
@@ -344,7 +344,7 @@
         mIsVrOnly = isVrOnly;
     }
 
-    private static ResolveInfo buildDummyResolveInfo(String packageName, String className,
+    private static ResolveInfo buildFakeResolveInfo(String packageName, String className,
             CharSequence label) {
         ResolveInfo ri = new ResolveInfo();
         ServiceInfo si = new ServiceInfo();
diff --git a/core/java/android/view/inputmethod/InputMethodManager.java b/core/java/android/view/inputmethod/InputMethodManager.java
index 793d940..f6671d8 100644
--- a/core/java/android/view/inputmethod/InputMethodManager.java
+++ b/core/java/android/view/inputmethod/InputMethodManager.java
@@ -22,6 +22,7 @@
 import static com.android.internal.inputmethod.StartInputReason.WINDOW_FOCUS_GAIN_REPORT_WITHOUT_CONNECTION;
 import static com.android.internal.inputmethod.StartInputReason.WINDOW_FOCUS_GAIN_REPORT_WITH_CONNECTION;
 
+import android.annotation.DisplayContext;
 import android.annotation.DrawableRes;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -1193,7 +1194,7 @@
      * @hide
      */
     @NonNull
-    public static InputMethodManager forContext(Context context) {
+    public static InputMethodManager forContext(@DisplayContext Context context) {
         final int displayId = context.getDisplayId();
         // For better backward compatibility, we always use Looper.getMainLooper() for the default
         // display case.
diff --git a/core/java/android/view/textclassifier/TextClassificationSession.java b/core/java/android/view/textclassifier/TextClassificationSession.java
index fed3dbf..0008658 100644
--- a/core/java/android/view/textclassifier/TextClassificationSession.java
+++ b/core/java/android/view/textclassifier/TextClassificationSession.java
@@ -20,9 +20,11 @@
 import android.annotation.WorkerThread;
 import android.view.textclassifier.SelectionEvent.InvocationMethod;
 
+import com.android.internal.annotations.GuardedBy;
 import com.android.internal.util.Preconditions;
 
 import java.util.Objects;
+import java.util.function.Supplier;
 
 import sun.misc.Cleaner;
 
@@ -40,6 +42,9 @@
     private final TextClassificationContext mClassificationContext;
     private final Cleaner mCleaner;
 
+    private final Object mLock = new Object();
+
+    @GuardedBy("mLock")
     private boolean mDestroyed;
 
     TextClassificationSession(TextClassificationContext context, TextClassifier delegate) {
@@ -54,8 +59,7 @@
 
     @Override
     public TextSelection suggestSelection(TextSelection.Request request) {
-        checkDestroyed();
-        return mDelegate.suggestSelection(request);
+        return checkDestroyedAndRun(() -> mDelegate.suggestSelection(request));
     }
 
     private void initializeRemoteSession() {
@@ -67,77 +71,97 @@
 
     @Override
     public TextClassification classifyText(TextClassification.Request request) {
-        checkDestroyed();
-        return mDelegate.classifyText(request);
+        return checkDestroyedAndRun(() -> mDelegate.classifyText(request));
     }
 
     @Override
     public TextLinks generateLinks(TextLinks.Request request) {
-        checkDestroyed();
-        return mDelegate.generateLinks(request);
+        return checkDestroyedAndRun(() -> mDelegate.generateLinks(request));
     }
 
     @Override
     public ConversationActions suggestConversationActions(ConversationActions.Request request) {
-        checkDestroyed();
-        return mDelegate.suggestConversationActions(request);
+        return checkDestroyedAndRun(() -> mDelegate.suggestConversationActions(request));
     }
 
     @Override
     public TextLanguage detectLanguage(TextLanguage.Request request) {
-        checkDestroyed();
-        return mDelegate.detectLanguage(request);
+        return checkDestroyedAndRun(() -> mDelegate.detectLanguage(request));
     }
 
     @Override
     public int getMaxGenerateLinksTextLength() {
-        checkDestroyed();
-        return mDelegate.getMaxGenerateLinksTextLength();
+        return checkDestroyedAndRun(mDelegate::getMaxGenerateLinksTextLength);
     }
 
     @Override
     public void onSelectionEvent(SelectionEvent event) {
-        try {
-            if (mEventHelper.sanitizeEvent(event)) {
-                mDelegate.onSelectionEvent(event);
+        checkDestroyedAndRun(() -> {
+            try {
+                if (mEventHelper.sanitizeEvent(event)) {
+                    mDelegate.onSelectionEvent(event);
+                }
+            } catch (Exception e) {
+                // Avoid crashing for event reporting.
+                Log.e(LOG_TAG, "Error reporting text classifier selection event", e);
             }
-        } catch (Exception e) {
-            // Avoid crashing for event reporting.
-            Log.e(LOG_TAG, "Error reporting text classifier selection event", e);
-        }
+            return null;
+        });
     }
 
     @Override
     public void onTextClassifierEvent(TextClassifierEvent event) {
-        try {
-            event.mHiddenTempSessionId = mSessionId;
-            mDelegate.onTextClassifierEvent(event);
-        } catch (Exception e) {
-            // Avoid crashing for event reporting.
-            Log.e(LOG_TAG, "Error reporting text classifier event", e);
-        }
+        checkDestroyedAndRun(() -> {
+            try {
+                event.mHiddenTempSessionId = mSessionId;
+                mDelegate.onTextClassifierEvent(event);
+            } catch (Exception e) {
+                // Avoid crashing for event reporting.
+                Log.e(LOG_TAG, "Error reporting text classifier event", e);
+            }
+            return null;
+        });
     }
 
     @Override
     public void destroy() {
-        mCleaner.clean();
-        mDestroyed = true;
+        synchronized (mLock) {
+            if (!mDestroyed) {
+                mCleaner.clean();
+                mDestroyed = true;
+            }
+        }
     }
 
     @Override
     public boolean isDestroyed() {
-        return mDestroyed;
+        synchronized (mLock) {
+            return mDestroyed;
+        }
     }
 
     /**
-     * @throws IllegalStateException if this TextClassification session has been destroyed.
+     * Check whether the TextClassification Session was destroyed before and after the actual API
+     * invocation, and return response if not.
+     *
+     * @param responseSupplier a Supplier that represents a TextClassifier call
+     * @return the response of the TextClassifier call
+     * @throws IllegalStateException if this TextClassification session was destroyed before the
+     *                               call returned
      * @see #isDestroyed()
      * @see #destroy()
      */
-    private void checkDestroyed() {
-        if (mDestroyed) {
-            throw new IllegalStateException("This TextClassification session has been destroyed");
+    private <T> T checkDestroyedAndRun(Supplier<T> responseSupplier) {
+        if (!isDestroyed()) {
+            T response = responseSupplier.get();
+            synchronized (mLock) {
+                if (!mDestroyed) {
+                    return response;
+                }
+            }
         }
+        throw new IllegalStateException(
+                "This TextClassification session has been destroyed");
     }
 
     /**
diff --git a/core/java/android/webkit/PacProcessor.java b/core/java/android/webkit/PacProcessor.java
index 5ef450f..b04105a 100644
--- a/core/java/android/webkit/PacProcessor.java
+++ b/core/java/android/webkit/PacProcessor.java
@@ -19,7 +19,7 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.SystemApi;
-
+import android.net.Network;
 
 /**
  * Class to evaluate PAC scripts.
@@ -32,6 +32,10 @@
     /**
      * Returns the default PacProcessor instance.
      *
+     * <p> There can only be one default {@link PacProcessor} instance.
+     * This method will create a new instance if one did not already exist, or
+     * if the previous instance was released with {@link #releasePacProcessor}.
+     *
      * @return the default PacProcessor instance.
      */
     @NonNull
@@ -40,6 +44,27 @@
     }
 
     /**
+     * Returns PacProcessor instance associated with the {@link Network}.
+     * The host resolution is done on this {@link Network}.
+     *
+     * <p> There can only be one {@link PacProcessor} instance at a time for each {@link Network}.
+     * This method will create a new instance if one did not already exist, or
+     * if the previous instance was released with {@link #releasePacProcessor}.
+     *
+     * <p> The {@link PacProcessor} instance needs to be released manually with
+     * {@link #releasePacProcessor} when the associated {@link Network} goes away.
+     *
+     * @param network a {@link Network} which this {@link PacProcessor}
+     * will use for host/address resolution.
+     * If {@code null} this method is equivalent to {@link #getInstance}.
+     * @return {@link PacProcessor} instance for the specified network.
+     */
+    @NonNull
+    static PacProcessor getInstanceForNetwork(@Nullable Network network) {
+        return WebViewFactory.getProvider().getPacProcessorForNetwork(network);
+    }
+
+    /**
      * Set PAC script to use.
      *
      * @param script PAC script.
@@ -55,4 +80,26 @@
      */
     @Nullable
     String findProxyForUrl(@NonNull String url);
+
+    /**
+     * Stops support for this {@link PacProcessor} and release its resources.
+     * No methods of this class must be called after calling this method.
+     *
+     * <p> Released instances will not be reused; a subsequent call to
+     * {@link #getInstance} and {@link #getInstanceForNetwork}
+     * for the same network will create a new instance.
+     */
+    default void releasePacProcessor() {
+        throw new UnsupportedOperationException("Not implemented");
+    }
+
+    /**
+     * Returns a {@link Network} associated with this {@link PacProcessor}.
+     *
+     * @return an associated {@link Network} or {@code null} if a network is unspecified.
+     */
+    @Nullable
+    default Network getNetwork() {
+        throw new UnsupportedOperationException("Not implemented");
+    }
 }
diff --git a/core/java/android/webkit/UserPackage.java b/core/java/android/webkit/UserPackage.java
index 556b24c..2e5ee04 100644
--- a/core/java/android/webkit/UserPackage.java
+++ b/core/java/android/webkit/UserPackage.java
@@ -99,7 +99,7 @@
 
     private static List<UserInfo> getAllUsers(Context context) {
         UserManager userManager = (UserManager) context.getSystemService(Context.USER_SERVICE);
-        return userManager.getUsers(false);
+        return userManager.getUsers();
     }
 
 }
diff --git a/core/java/android/webkit/WebViewFactoryProvider.java b/core/java/android/webkit/WebViewFactoryProvider.java
index f7c3ec0..ce999cd 100644
--- a/core/java/android/webkit/WebViewFactoryProvider.java
+++ b/core/java/android/webkit/WebViewFactoryProvider.java
@@ -17,9 +17,11 @@
 package android.webkit;
 
 import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.annotation.SystemApi;
 import android.content.Context;
 import android.content.Intent;
+import android.net.Network;
 import android.net.Uri;
 
 import java.util.List;
@@ -175,7 +177,7 @@
     WebViewDatabase getWebViewDatabase(Context context);
 
     /**
-     * Gets the singleton PacProcessor instance.
+     * Gets the default PacProcessor instance.
      * @return the PacProcessor instance
      */
     @NonNull
@@ -184,6 +186,20 @@
     }
 
     /**
+     * Returns PacProcessor instance associated with the {@link Network}.
+     * The host resolution is done on this {@link Network}.
+     *
+     * @param network a {@link Network} which needs to be associated
+     * with the returned {@link PacProcessor}.
+     * If {@code null} the method returns default {@link PacProcessor}.
+     * @return the {@link PacProcessor} instance associated with {@link Network}.
+     */
+    @NonNull
+    default PacProcessor getPacProcessorForNetwork(@Nullable Network network) {
+        throw new UnsupportedOperationException("Not implemented");
+    }
+
+    /**
      * Gets the classloader used to load internal WebView implementation classes. This interface
      * should only be used by the WebView Support Library.
      */
diff --git a/core/java/android/widget/Editor.java b/core/java/android/widget/Editor.java
index c4eb396..60f8bb7 100644
--- a/core/java/android/widget/Editor.java
+++ b/core/java/android/widget/Editor.java
@@ -154,6 +154,10 @@
     // Specifies whether to use the magnifier when pressing the insertion or selection handles.
     private static final boolean FLAG_USE_MAGNIFIER = true;
 
+    // Specifies how far to make the cursor start float when drag the cursor away from the
+    // beginning or end of the line.
+    private static final int CURSOR_START_FLOAT_DISTANCE_PX = 20;
+
     private static final int DELAY_BEFORE_HANDLE_FADES_OUT = 4000;
     private static final int RECENT_CUT_COPY_DURATION_MS = 15 * 1000; // 15 seconds in millis
 
@@ -289,6 +293,9 @@
     private boolean mRenderCursorRegardlessTiming;
     private Blink mBlink;
 
+    // Whether to let magnifier draw cursor on its surface. This is for floating cursor effect.
+    // And it can only be true when |mNewMagnifierEnabled| is true.
+    private boolean mDrawCursorOnMagnifier;
     boolean mCursorVisible = true;
     boolean mSelectAllOnFocus;
     boolean mTextIsSelectable;
@@ -385,6 +392,7 @@
     private final SuggestionHelper mSuggestionHelper = new SuggestionHelper();
 
     private boolean mFlagCursorDragFromAnywhereEnabled;
+    private float mCursorDragDirectionMinXYRatio;
     private boolean mFlagInsertionHandleGesturesEnabled;
 
     // Specifies whether the new magnifier (with fish-eye effect) is enabled.
@@ -425,6 +433,11 @@
         mFlagCursorDragFromAnywhereEnabled = AppGlobals.getIntCoreSetting(
                 WidgetFlags.KEY_ENABLE_CURSOR_DRAG_FROM_ANYWHERE,
                 WidgetFlags.ENABLE_CURSOR_DRAG_FROM_ANYWHERE_DEFAULT ? 1 : 0) != 0;
+        final int cursorDragMinAngleFromVertical = AppGlobals.getIntCoreSetting(
+                WidgetFlags.KEY_CURSOR_DRAG_MIN_ANGLE_FROM_VERTICAL,
+                WidgetFlags.CURSOR_DRAG_MIN_ANGLE_FROM_VERTICAL_DEFAULT);
+        mCursorDragDirectionMinXYRatio = EditorTouchState.getXYRatio(
+                cursorDragMinAngleFromVertical);
         mFlagInsertionHandleGesturesEnabled = AppGlobals.getIntCoreSetting(
                 WidgetFlags.KEY_ENABLE_INSERTION_HANDLE_GESTURES,
                 WidgetFlags.ENABLE_INSERTION_HANDLE_GESTURES_DEFAULT ? 1 : 0) != 0;
@@ -437,6 +450,8 @@
         if (TextView.DEBUG_CURSOR) {
             logCursor("Editor", "Cursor drag from anywhere is %s.",
                     mFlagCursorDragFromAnywhereEnabled ? "enabled" : "disabled");
+            logCursor("Editor", "Cursor drag min angle from vertical is %d (= %f x/y ratio)",
+                    cursorDragMinAngleFromVertical, mCursorDragDirectionMinXYRatio);
             logCursor("Editor", "Insertion handle gestures is %s.",
                     mFlagInsertionHandleGesturesEnabled ? "enabled" : "disabled");
             logCursor("Editor", "New magnifier is %s.",
@@ -463,6 +478,11 @@
     }
 
     @VisibleForTesting
+    public void setCursorDragMinAngleFromVertical(int degreesFromVertical) {
+        mCursorDragDirectionMinXYRatio = EditorTouchState.getXYRatio(degreesFromVertical);
+    }
+
+    @VisibleForTesting
     public boolean getFlagInsertionHandleGesturesEnabled() {
         return mFlagInsertionHandleGesturesEnabled;
     }
@@ -877,7 +897,7 @@
         }
 
         boolean enabled = windowSupportsHandles && mTextView.getLayout() != null;
-        mInsertionControllerEnabled = enabled && isCursorVisible();
+        mInsertionControllerEnabled = enabled && (mDrawCursorOnMagnifier || isCursorVisible());
         mSelectionControllerEnabled = enabled && mTextView.textCanBeSelected();
 
         if (!mInsertionControllerEnabled) {
@@ -5088,26 +5108,38 @@
             final int[] textViewLocationOnScreen = new int[2];
             mTextView.getLocationOnScreen(textViewLocationOnScreen);
             final float touchXInView = event.getRawX() - textViewLocationOnScreen[0];
-            float leftBound = mTextView.getTotalPaddingLeft() - mTextView.getScrollX();
-            float rightBound = mTextView.getTotalPaddingLeft() - mTextView.getScrollX();
-            if (sameLineSelection && ((trigger == MagnifierHandleTrigger.SELECTION_END) ^ rtl)) {
-                leftBound += getHorizontal(mTextView.getLayout(), otherHandleOffset);
+            float leftBound, rightBound;
+            if (mNewMagnifierEnabled) {
+                leftBound = 0;
+                rightBound = mTextView.getWidth();
+                if (touchXInView < leftBound || touchXInView > rightBound) {
+                    // The touch is too far from the current line / selection, so hide the magnifier.
+                    return false;
+                }
             } else {
-                leftBound += mTextView.getLayout().getLineLeft(lineNumber);
-            }
-            if (sameLineSelection && ((trigger == MagnifierHandleTrigger.SELECTION_START) ^ rtl)) {
-                rightBound += getHorizontal(mTextView.getLayout(), otherHandleOffset);
-            } else {
-                rightBound += mTextView.getLayout().getLineRight(lineNumber);
-            }
-            leftBound *= mTextViewScaleX;
-            rightBound *= mTextViewScaleX;
-            final float contentWidth = Math.round(mMagnifierAnimator.mMagnifier.getWidth()
-                    / mMagnifierAnimator.mMagnifier.getZoom());
-            if (touchXInView < leftBound - contentWidth / 2
-                    || touchXInView > rightBound + contentWidth / 2) {
-                // The touch is too far from the current line / selection, so hide the magnifier.
-                return false;
+                leftBound = mTextView.getTotalPaddingLeft() - mTextView.getScrollX();
+                rightBound = mTextView.getTotalPaddingLeft() - mTextView.getScrollX();
+                if (sameLineSelection && ((trigger == MagnifierHandleTrigger.SELECTION_END)
+                        ^ rtl)) {
+                    leftBound += getHorizontal(mTextView.getLayout(), otherHandleOffset);
+                } else {
+                    leftBound += mTextView.getLayout().getLineLeft(lineNumber);
+                }
+                if (sameLineSelection && ((trigger == MagnifierHandleTrigger.SELECTION_START)
+                        ^ rtl)) {
+                    rightBound += getHorizontal(mTextView.getLayout(), otherHandleOffset);
+                } else {
+                    rightBound += mTextView.getLayout().getLineRight(lineNumber);
+                }
+                leftBound *= mTextViewScaleX;
+                rightBound *= mTextViewScaleX;
+                final float contentWidth = Math.round(mMagnifierAnimator.mMagnifier.getWidth()
+                        / mMagnifierAnimator.mMagnifier.getZoom());
+                if (touchXInView < leftBound - contentWidth / 2
+                        || touchXInView > rightBound + contentWidth / 2) {
+                    // The touch is too far from the current line / selection, so hide the magnifier.
+                    return false;
+                }
             }
 
             final float scaledTouchXInView;
@@ -5165,7 +5197,8 @@
             final Rect magnifierRect = new Rect(magnifierTopLeft.x, magnifierTopLeft.y,
                     magnifierTopLeft.x + mMagnifierAnimator.mMagnifier.getWidth(),
                     magnifierTopLeft.y + mMagnifierAnimator.mMagnifier.getHeight());
-            setVisible(!handleOverlapsMagnifier(HandleView.this, magnifierRect));
+            setVisible(!handleOverlapsMagnifier(HandleView.this, magnifierRect)
+                    && !mDrawCursorOnMagnifier);
             final HandleView otherHandle = getOtherSelectionHandle();
             if (otherHandle != null) {
                 otherHandle.setVisible(!handleOverlapsMagnifier(otherHandle, magnifierRect));
@@ -5195,7 +5228,20 @@
                     lineLeft += mTextView.getTotalPaddingLeft() - mTextView.getScrollX();
                     int lineRight = (int) layout.getLineRight(line);
                     lineRight += mTextView.getTotalPaddingLeft() - mTextView.getScrollX();
-                    mMagnifierAnimator.mMagnifier.setSourceHorizontalBounds(lineLeft, lineRight);
+                    mDrawCursorOnMagnifier =
+                            showPosInView.x < lineLeft - CURSOR_START_FLOAT_DISTANCE_PX
+                            || showPosInView.x > lineRight + CURSOR_START_FLOAT_DISTANCE_PX;
+                    mMagnifierAnimator.mMagnifier.setDrawCursor(
+                            mDrawCursorOnMagnifier, mDrawableForCursor);
+                    boolean cursorVisible = mCursorVisible;
+                    // Updates cursor visibility, so that the real cursor and the float cursor on
+                    // magnifier surface won't appear at the same time.
+                    mCursorVisible = !mDrawCursorOnMagnifier;
+                    if (mCursorVisible && !cursorVisible) {
+                        // When the real cursor is a drawable, hiding/showing it would change its
+                        // bounds. So, call updateCursorPosition() to correct its position.
+                        updateCursorPosition();
+                    }
                     final int lineHeight =
                             layout.getLineBottomWithoutSpacing(line) - layout.getLineTop(line);
                     float zoom = mInitialZoom;
@@ -5217,6 +5263,11 @@
             if (mMagnifierAnimator != null) {
                 mMagnifierAnimator.dismiss();
                 mRenderCursorRegardlessTiming = false;
+                mDrawCursorOnMagnifier = false;
+                if (!mCursorVisible) {
+                    mCursorVisible = true;
+                    mTextView.invalidate();
+                }
                 resumeBlink();
                 setVisible(true);
                 final HandleView otherHandle = getOtherSelectionHandle();
@@ -6127,10 +6178,11 @@
                     if (mIsDraggingCursor) {
                         performCursorDrag(event);
                     } else if (mFlagCursorDragFromAnywhereEnabled
-                                && mTextView.getLayout() != null
-                                && mTextView.isFocused()
-                                && mTouchState.isMovedEnoughForDrag()
-                                && !mTouchState.isDragCloseToVertical()) {
+                            && mTextView.getLayout() != null
+                            && mTextView.isFocused()
+                            && mTouchState.isMovedEnoughForDrag()
+                            && (mTouchState.getInitialDragDirectionXYRatio()
+                            > mCursorDragDirectionMinXYRatio || mTouchState.isOnHandle())) {
                         startCursorDrag(event);
                     }
                     break;
diff --git a/core/java/android/widget/EditorTouchState.java b/core/java/android/widget/EditorTouchState.java
index 9eb63087..7514368 100644
--- a/core/java/android/widget/EditorTouchState.java
+++ b/core/java/android/widget/EditorTouchState.java
@@ -59,7 +59,7 @@
     private boolean mMultiTapInSameArea;
 
     private boolean mMovedEnoughForDrag;
-    private boolean mIsDragCloseToVertical;
+    private float mInitialDragDirectionXYRatio;
 
     public float getLastDownX() {
         return mLastDownX;
@@ -98,8 +98,23 @@
         return mMovedEnoughForDrag;
     }
 
-    public boolean isDragCloseToVertical() {
-        return mIsDragCloseToVertical && !mIsOnHandle;
+    /**
+     * When {@link #isMovedEnoughForDrag()} is {@code true}, this function returns the x/y ratio for
+     * the initial drag direction. Smaller values indicate that the direction is closer to vertical,
+     * while larger values indicate that the direction is closer to horizontal. For example:
+     * <ul>
+     *     <li>if the drag direction is exactly vertical, this returns 0
+     *     <li>if the drag direction is exactly horizontal, this returns {@link Float#MAX_VALUE}
+     *     <li>if the drag direction is 45 deg from vertical, this returns 1
+     *     <li>if the drag direction is 30 deg from vertical, this returns 0.58 (x delta is smaller
+     *     than y delta)
+     *     <li>if the drag direction is 60 deg from vertical, this returns 1.73 (x delta is bigger
+     *     than y delta)
+     * </ul>
+     * This function never returns negative values, regardless of the direction of the drag.
+     */
+    public float getInitialDragDirectionXYRatio() {
+        return mInitialDragDirectionXYRatio;
     }
 
     public void setIsOnHandle(boolean onHandle) {
@@ -155,7 +170,7 @@
             mLastDownY = event.getY();
             mLastDownMillis = event.getEventTime();
             mMovedEnoughForDrag = false;
-            mIsDragCloseToVertical = false;
+            mInitialDragDirectionXYRatio = 0.0f;
         } else if (action == MotionEvent.ACTION_UP) {
             if (TextView.DEBUG_CURSOR) {
                 logCursor("EditorTouchState", "ACTION_UP");
@@ -164,7 +179,7 @@
             mLastUpY = event.getY();
             mLastUpMillis = event.getEventTime();
             mMovedEnoughForDrag = false;
-            mIsDragCloseToVertical = false;
+            mInitialDragDirectionXYRatio = 0.0f;
         } else if (action == MotionEvent.ACTION_MOVE) {
             if (!mMovedEnoughForDrag) {
                 float deltaX = event.getX() - mLastDownX;
@@ -174,9 +189,8 @@
                 int touchSlop = config.getScaledTouchSlop();
                 mMovedEnoughForDrag = distanceSquared > touchSlop * touchSlop;
                 if (mMovedEnoughForDrag) {
-                    // If the direction of the swipe motion is within 45 degrees of vertical, it is
-                    // considered a vertical drag.
-                    mIsDragCloseToVertical = Math.abs(deltaX) <= Math.abs(deltaY);
+                    mInitialDragDirectionXYRatio = (deltaY == 0) ? Float.MAX_VALUE :
+                            Math.abs(deltaX / deltaY);
                 }
             }
         } else if (action == MotionEvent.ACTION_CANCEL) {
@@ -185,7 +199,7 @@
             mMultiTapStatus = MultiTapStatus.NONE;
             mMultiTapInSameArea = false;
             mMovedEnoughForDrag = false;
-            mIsDragCloseToVertical = false;
+            mInitialDragDirectionXYRatio = 0.0f;
         }
     }
 
@@ -201,4 +215,27 @@
         float distanceSquared = (deltaX * deltaX) + (deltaY * deltaY);
         return distanceSquared <= maxDistance * maxDistance;
     }
+
+    /**
+     * Returns the x/y ratio corresponding to the given angle relative to vertical. Smaller angle
+     * values (ie, closer to vertical) will result in a smaller x/y ratio. For example:
+     * <ul>
+     *     <li>if the angle is 45 deg, the ratio is 1
+     *     <li>if the angle is 30 deg, the ratio is 0.58 (x delta is smaller than y delta)
+     *     <li>if the angle is 60 deg, the ratio is 1.73 (x delta is bigger than y delta)
+     * </ul>
+     * If the passed-in value is <= 0, this function returns 0. If the passed-in value is >= 90,
+     * this function returns {@link Float#MAX_VALUE}.
+     *
+     * @see #getInitialDragDirectionXYRatio()
+     */
+    public static float getXYRatio(int angleFromVerticalInDegrees) {
+        if (angleFromVerticalInDegrees <= 0) {
+            return 0.0f;
+        }
+        if (angleFromVerticalInDegrees >= 90) {
+            return Float.MAX_VALUE;
+        }
+        return (float) Math.tan(Math.toRadians(angleFromVerticalInDegrees));
+    }
 }
diff --git a/core/java/android/widget/Magnifier.java b/core/java/android/widget/Magnifier.java
index 89206fda..c72eed4 100644
--- a/core/java/android/widget/Magnifier.java
+++ b/core/java/android/widget/Magnifier.java
@@ -149,9 +149,6 @@
     private int mLeftCutWidth = 0;
     // The width of the cut region on the right edge of the pixel copy source rect.
     private int mRightCutWidth = 0;
-    // The horizontal bounds of the content source in pixels, relative to the view.
-    private int mLeftBound = Integer.MIN_VALUE;
-    private int mRightBound = Integer.MAX_VALUE;
     // The width of the ramp region in pixels on the left & right sides of the fish-eye effect.
     private final int mRamp;
 
@@ -244,18 +241,6 @@
     }
 
     /**
-     * Sets the horizontal bounds of the source when showing the magnifier.
-     * This is used for new style magnifier. e.g. limit the source bounds by the text line bounds.
-     *
-     * @param left the left of the bounds, relative to the view.
-     * @param right the right of the bounds, relative to the view.
-     */
-    void setSourceHorizontalBounds(int left, int right) {
-        mLeftBound = left;
-        mRightBound = right;
-    }
-
-    /**
      * Shows the magnifier on the screen. The method takes the coordinates of the center
      * of the content source going to be magnified and copied to the magnifier. The coordinates
      * are relative to the top left corner of the magnified view. The magnifier will be
@@ -280,6 +265,14 @@
                 sourceCenterY + mDefaultVerticalSourceToMagnifierOffset);
     }
 
+    private Drawable mCursorDrawable;
+    private boolean mDrawCursorEnabled;
+
+    void setDrawCursor(boolean enabled, Drawable cursorDrawable) {
+        mDrawCursorEnabled = enabled;
+        mCursorDrawable = cursorDrawable;
+    }
+
     /**
      * Shows the magnifier on the screen at a position that is independent from its content
      * position. The first two arguments represent the coordinates of the center of the
@@ -309,8 +302,7 @@
             magnifierCenterX = mClampedCenterZoomCoords.x - mViewCoordinatesInSurface[0];
             magnifierCenterY = mClampedCenterZoomCoords.y - mViewCoordinatesInSurface[1];
 
-            // mLeftBound & mRightBound (typically the text line left/right) is for magnified
-            // content. However the PixelCopy requires the pre-magnified bounds.
+            // PixelCopy requires the pre-magnified bounds.
             // The below logic calculates the leftBound & rightBound for the pre-magnified bounds.
             final float rampPre =
                     (mSourceWidth - (mSourceWidth - 2 * mRamp) / mZoom) / 2;
@@ -318,7 +310,7 @@
             // Calculates the pre-zoomed left edge.
             // The leftEdge moves from the left of view towards to sourceCenterX, considering the
             // fisheye-like zooming.
-            final float x0 = sourceCenterX - mSourceWidth / 2;
+            final float x0 = sourceCenterX - mSourceWidth / 2f;
             final float rampX0 = x0 + mRamp;
             float leftEdge = 0;
             if (leftEdge > rampX0) {
@@ -330,12 +322,12 @@
                 // increase per ramp zoom (ramp / rampPre).
                 leftEdge = x0 + rampPre - (rampX0 - leftEdge) * rampPre / mRamp;
             }
-            int leftBound = Math.min(Math.max((int) leftEdge, mLeftBound), mRightBound);
+            int leftBound = Math.min((int) leftEdge, mView.getWidth());
 
             // Calculates the pre-zoomed right edge.
             // The rightEdge moves from the right of view towards to sourceCenterX, considering the
             // fisheye-like zooming.
-            final float x1 = sourceCenterX + mSourceWidth / 2;
+            final float x1 = sourceCenterX + mSourceWidth / 2f;
             final float rampX1 = x1 - mRamp;
             float rightEdge = mView.getWidth();
             if (rightEdge < rampX1) {
@@ -347,7 +339,7 @@
                 // increase per ramp zoom (ramp / rampPre).
                 rightEdge = x1 - rampPre + (rightEdge - rampX1) * rampPre / mRamp;
             }
-            int rightBound = Math.max(leftBound, Math.min((int) rightEdge, mRightBound));
+            int rightBound = Math.max(leftBound, (int) rightEdge);
 
             // Gets the startX for new style, which should be bounded by the horizontal bounds.
             // Also calculates the left/right cut width for pixel copy.
@@ -772,6 +764,23 @@
         }
     }
 
+    private void maybeDrawCursor(Canvas canvas) {
+        if (mDrawCursorEnabled) {
+            if (mCursorDrawable != null) {
+                mCursorDrawable.setBounds(
+                        mSourceWidth / 2, 0,
+                        mSourceWidth / 2 + mCursorDrawable.getIntrinsicWidth(), mSourceHeight);
+                mCursorDrawable.draw(canvas);
+            } else {
+                Paint paint = new Paint();
+                paint.setColor(Color.BLACK);  // The cursor on magnifier is by default in black.
+                canvas.drawRect(
+                        new Rect(mSourceWidth / 2 - 1, 0, mSourceWidth / 2 + 1, mSourceHeight),
+                        paint);
+            }
+        }
+    }
+
     private void performPixelCopy(final int startXInSurface, final int startYInSurface,
             final boolean updateWindowPosition) {
         if (mContentCopySurface.mSurface == null || !mContentCopySurface.mSurface.isValid()) {
@@ -827,8 +836,10 @@
                             final Rect dstRect = new Rect(mLeftCutWidth, 0,
                                     mSourceWidth - mRightCutWidth, bitmap.getHeight());
                             can.drawBitmap(bitmap, null, dstRect, null);
+                            maybeDrawCursor(can);
                             mWindow.updateContent(newBitmap);
                         } else {
+                            maybeDrawCursor(new Canvas(bitmap));
                             mWindow.updateContent(bitmap);
                         }
                     }
diff --git a/core/java/android/widget/WidgetFlags.java b/core/java/android/widget/WidgetFlags.java
index 832dd51..1a49365 100644
--- a/core/java/android/widget/WidgetFlags.java
+++ b/core/java/android/widget/WidgetFlags.java
@@ -41,6 +41,28 @@
     public static final boolean ENABLE_CURSOR_DRAG_FROM_ANYWHERE_DEFAULT = true;
 
     /**
+     * Threshold for the direction of a swipe gesture in order for it to be handled as a cursor drag
+     * rather than a scroll. The direction angle of the swipe gesture must exceed this value in
+     * order to trigger cursor drag; otherwise, the swipe will be assumed to be a scroll gesture.
+     * The value units for this flag is degrees and the valid range is [0,90] inclusive. If a value
+     * < 0 is set, 0 will be used instead; if a value > 90 is set, 90 will be used instead.
+     */
+    public static final String CURSOR_DRAG_MIN_ANGLE_FROM_VERTICAL =
+            "CursorControlFeature__min_angle_from_vertical_to_start_cursor_drag";
+
+    /**
+     * The key used in app core settings for the flag
+     * {@link #CURSOR_DRAG_MIN_ANGLE_FROM_VERTICAL}.
+     */
+    public static final String KEY_CURSOR_DRAG_MIN_ANGLE_FROM_VERTICAL =
+            "widget__min_angle_from_vertical_to_start_cursor_drag";
+
+    /**
+     * Default value for the flag {@link #CURSOR_DRAG_MIN_ANGLE_FROM_VERTICAL}.
+     */
+    public static final int CURSOR_DRAG_MIN_ANGLE_FROM_VERTICAL_DEFAULT = 45;
+
+    /**
      * The flag of finger-to-cursor distance in DP for cursor dragging.
      * The value unit is DP and the range is {0..100}. If the value is out of range, the legacy
      * value, which is based on handle size, will be used.
diff --git a/core/java/android/widget/inline/InlineContentView.java b/core/java/android/widget/inline/InlineContentView.java
index 699a7735..9712311 100644
--- a/core/java/android/widget/inline/InlineContentView.java
+++ b/core/java/android/widget/inline/InlineContentView.java
@@ -59,7 +59,7 @@
  */
 public class InlineContentView extends ViewGroup {
 
-    private static final String TAG = "InlineContentView_test2";
+    private static final String TAG = "InlineContentView";
 
     private static final boolean DEBUG = false;
 
diff --git a/core/java/android/widget/inline/InlinePresentationSpec.java b/core/java/android/widget/inline/InlinePresentationSpec.java
index 5f924c6..e7727fd 100644
--- a/core/java/android/widget/inline/InlinePresentationSpec.java
+++ b/core/java/android/widget/inline/InlinePresentationSpec.java
@@ -42,8 +42,13 @@
     private final Size mMaxSize;
 
     /**
-     * The extras encoding the UI style information. Defaults to {@code Bundle.Empty} in which case
-     * the default system UI style will be used.
+     * The extras encoding the UI style information.
+     *
+     * <p>The style bundles can be created using the relevant Style classes and their builders in
+     * the androidx autofill library e.g. {@code androidx.autofill.inline.UiVersions.StylesBuilder}.
+     * </p>
+     *
+     * <p>The style must be set for the suggestion to render properly.</p>
      *
      * <p>Note: There should be no remote objects in the bundle, all included remote objects will
      * be removed from the bundle before transmission.</p>
@@ -123,8 +128,13 @@
     }
 
     /**
-     * The extras encoding the UI style information. Defaults to {@code Bundle.Empty} in which case
-     * the default system UI style will be used.
+     * The extras encoding the UI style information.
+     *
+     * <p>The style bundles can be created using the relevant Style classes and their builders in
+     * the androidx autofill library e.g. {@code androidx.autofill.inline.UiVersions.StylesBuilder}.
+     * </p>
+     *
+     * <p>The style must be set for the suggestion to render properly.</p>
      *
      * <p>Note: There should be no remote objects in the bundle, all included remote objects will
      * be removed from the bundle before transmission.</p>
@@ -264,8 +274,13 @@
         }
 
         /**
-         * The extras encoding the UI style information. Defaults to {@code Bundle.Empty} in which case
-         * the default system UI style will be used.
+         * The extras encoding the UI style information.
+         *
+         * <p>The style bundles can be created using the relevant Style classes and their builders in
+         * the androidx autofill library e.g. {@code androidx.autofill.inline.UiVersions.StylesBuilder}.
+         * </p>
+         *
+         * <p>The style must be set for the suggestion to render properly.</p>
          *
          * <p>Note: There should be no remote objects in the bundle, all included remote objects will
          * be removed from the bundle before transmission.</p>
@@ -302,7 +317,7 @@
     }
 
     @DataClass.Generated(
-            time = 1588109681295L,
+            time = 1596485189661L,
             codegenVersion = "1.0.15",
             sourceFile = "frameworks/base/core/java/android/widget/inline/InlinePresentationSpec.java",
             inputSignatures = "private final @android.annotation.NonNull android.util.Size mMinSize\nprivate final @android.annotation.NonNull android.util.Size mMaxSize\nprivate final @android.annotation.NonNull android.os.Bundle mStyle\nprivate static @android.annotation.NonNull android.os.Bundle defaultStyle()\nprivate  boolean styleEquals(android.os.Bundle)\npublic  void filterContentTypes()\nclass InlinePresentationSpec extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genEqualsHashCode=true, genToString=true, genBuilder=true)\nclass BaseBuilder extends java.lang.Object implements []")
diff --git a/core/java/android/window/ITaskOrganizerController.aidl b/core/java/android/window/ITaskOrganizerController.aidl
index 1c03b2f..92fa80e 100644
--- a/core/java/android/window/ITaskOrganizerController.aidl
+++ b/core/java/android/window/ITaskOrganizerController.aidl
@@ -25,11 +25,9 @@
 interface ITaskOrganizerController {
 
     /**
-     * Register a TaskOrganizer to manage tasks as they enter the given windowing mode.
-     * If there was already a TaskOrganizer for this windowing mode it will be evicted
-     * and receive taskVanished callbacks in the process.
+     * Register a TaskOrganizer to manage all the tasks with supported windowing modes.
      */
-    void registerTaskOrganizer(ITaskOrganizer organizer, int windowingMode);
+    void registerTaskOrganizer(ITaskOrganizer organizer);
 
     /**
      * Unregisters a previously registered task organizer.
diff --git a/core/java/android/window/TaskOrganizer.java b/core/java/android/window/TaskOrganizer.java
index 502680d..7ec4f99 100644
--- a/core/java/android/window/TaskOrganizer.java
+++ b/core/java/android/window/TaskOrganizer.java
@@ -36,14 +36,12 @@
 public class TaskOrganizer extends WindowOrganizer {
 
     /**
-     * Register a TaskOrganizer to manage tasks as they enter the given windowing mode.
-     * If there was already a TaskOrganizer for this windowing mode it will be evicted
-     * and receive taskVanished callbacks in the process.
+     * Register a TaskOrganizer to manage tasks as they enter a supported windowing mode.
      */
     @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_STACKS)
-    public final void registerOrganizer(int windowingMode) {
+    public final void registerOrganizer() {
         try {
-            getController().registerTaskOrganizer(mInterface, windowingMode);
+            getController().registerTaskOrganizer(mInterface);
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
diff --git a/core/java/android/window/TaskOrganizerTaskEmbedder.java b/core/java/android/window/TaskOrganizerTaskEmbedder.java
index 1b87521..46c72f8 100644
--- a/core/java/android/window/TaskOrganizerTaskEmbedder.java
+++ b/core/java/android/window/TaskOrganizerTaskEmbedder.java
@@ -73,7 +73,7 @@
         // TODO(wm-shell): This currently prevents other organizers from controlling MULT_WINDOW
         // windowing mode tasks. Plan is to migrate this to a wm-shell front-end when that
         // infrastructure is ready.
-        mTaskOrganizer.registerOrganizer(WINDOWING_MODE_MULTI_WINDOW);
+        // mTaskOrganizer.registerOrganizer();
         mTaskOrganizer.setInterceptBackPressedOnTaskRoot(true);
 
         return super.onInitialize();
diff --git a/core/java/android/window/VirtualDisplayTaskEmbedder.java b/core/java/android/window/VirtualDisplayTaskEmbedder.java
index 9ccb4c1..9013da3 100644
--- a/core/java/android/window/VirtualDisplayTaskEmbedder.java
+++ b/core/java/android/window/VirtualDisplayTaskEmbedder.java
@@ -19,6 +19,7 @@
 import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_DESTROY_CONTENT_ON_REMOVAL;
 import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY;
 import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_PUBLIC;
+import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_TRUSTED;
 import static android.view.Display.INVALID_DISPLAY;
 
 import android.app.ActivityManager;
@@ -63,6 +64,7 @@
     private int mDisplayDensityDpi;
     private final boolean mSingleTaskInstance;
     private final boolean mUsePublicVirtualDisplay;
+    private final boolean mUseTrustedDisplay;
     private VirtualDisplay mVirtualDisplay;
     private Insets mForwardedInsets;
     private DisplayMetrics mTmpDisplayMetrics;
@@ -77,10 +79,12 @@
      *                           only applicable if virtual displays are used
      */
     public VirtualDisplayTaskEmbedder(Context context, VirtualDisplayTaskEmbedder.Host host,
-            boolean singleTaskInstance, boolean usePublicVirtualDisplay) {
+            boolean singleTaskInstance, boolean usePublicVirtualDisplay,
+            boolean useTrustedDisplay) {
         super(context, host);
         mSingleTaskInstance = singleTaskInstance;
         mUsePublicVirtualDisplay = usePublicVirtualDisplay;
+        mUseTrustedDisplay = useTrustedDisplay;
     }
 
     /**
@@ -103,6 +107,9 @@
         if (mUsePublicVirtualDisplay) {
             virtualDisplayFlags |= VIRTUAL_DISPLAY_FLAG_PUBLIC;
         }
+        if (mUseTrustedDisplay) {
+            virtualDisplayFlags |= VIRTUAL_DISPLAY_FLAG_TRUSTED;
+        }
 
         mVirtualDisplay = displayManager.createVirtualDisplay(
                 DISPLAY_NAME + "@" + System.identityHashCode(this), mHost.getWidth(),
diff --git a/core/java/com/android/internal/BrightnessSynchronizer.java b/core/java/com/android/internal/BrightnessSynchronizer.java
index 42724be..f08d0ef8 100644
--- a/core/java/com/android/internal/BrightnessSynchronizer.java
+++ b/core/java/com/android/internal/BrightnessSynchronizer.java
@@ -83,63 +83,25 @@
     /**
      * Converts between the int brightness system and the float brightness system.
      */
-    public static float brightnessIntToFloat(Context context, int brightnessInt) {
-        final PowerManager pm = context.getSystemService(PowerManager.class);
-        final float pmMinBrightness = pm.getBrightnessConstraint(
-                PowerManager.BRIGHTNESS_CONSTRAINT_TYPE_MINIMUM);
-        final float pmMaxBrightness = pm.getBrightnessConstraint(
-                PowerManager.BRIGHTNESS_CONSTRAINT_TYPE_MAXIMUM);
-        final int minBrightnessInt = Math.round(brightnessFloatToIntRange(pmMinBrightness,
-                PowerManager.BRIGHTNESS_MIN, PowerManager.BRIGHTNESS_MAX,
-                PowerManager.BRIGHTNESS_OFF + 1, PowerManager.BRIGHTNESS_ON));
-        final int maxBrightnessInt = Math.round(brightnessFloatToIntRange(pmMaxBrightness,
-                PowerManager.BRIGHTNESS_MIN, PowerManager.BRIGHTNESS_MAX,
-                PowerManager.BRIGHTNESS_OFF + 1, PowerManager.BRIGHTNESS_ON));
-
-        return brightnessIntToFloat(brightnessInt, minBrightnessInt, maxBrightnessInt,
-                pmMinBrightness, pmMaxBrightness);
-    }
-
-    /**
-     * Converts between the int brightness system and the float brightness system.
-     */
-    public static float brightnessIntToFloat(int brightnessInt, int minInt, int maxInt,
-            float minFloat, float maxFloat) {
+    public static float brightnessIntToFloat(int brightnessInt) {
         if (brightnessInt == PowerManager.BRIGHTNESS_OFF) {
             return PowerManager.BRIGHTNESS_OFF_FLOAT;
         } else if (brightnessInt == PowerManager.BRIGHTNESS_INVALID) {
             return PowerManager.BRIGHTNESS_INVALID_FLOAT;
         } else {
-            return MathUtils.constrainedMap(minFloat, maxFloat, (float) minInt, (float) maxInt,
-                    brightnessInt);
+            final float minFloat = PowerManager.BRIGHTNESS_MIN;
+            final float maxFloat = PowerManager.BRIGHTNESS_MAX;
+            final float minInt = PowerManager.BRIGHTNESS_OFF + 1;
+            final float maxInt = PowerManager.BRIGHTNESS_ON;
+            return MathUtils.constrainedMap(minFloat, maxFloat, minInt, maxInt, brightnessInt);
         }
     }
 
     /**
      * Converts between the float brightness system and the int brightness system.
      */
-    public static int brightnessFloatToInt(Context context, float brightnessFloat) {
-        return Math.round(brightnessFloatToIntRange(context, brightnessFloat));
-    }
-
-    /**
-     * Converts between the float brightness system and the int brightness system, but returns
-     * the converted value as a float within the int-system's range. This method helps with
-     * conversions from one system to the other without losing the floating-point precision.
-     */
-    public static float brightnessFloatToIntRange(Context context, float brightnessFloat) {
-        final PowerManager pm = context.getSystemService(PowerManager.class);
-        final float minFloat = pm.getBrightnessConstraint(
-                PowerManager.BRIGHTNESS_CONSTRAINT_TYPE_MINIMUM);
-        final float maxFloat = pm.getBrightnessConstraint(
-                PowerManager.BRIGHTNESS_CONSTRAINT_TYPE_MAXIMUM);
-        final float minInt = brightnessFloatToIntRange(minFloat,
-                PowerManager.BRIGHTNESS_MIN, PowerManager.BRIGHTNESS_MAX,
-                PowerManager.BRIGHTNESS_OFF + 1, PowerManager.BRIGHTNESS_ON);
-        final float maxInt = brightnessFloatToIntRange(maxFloat,
-                PowerManager.BRIGHTNESS_MIN, PowerManager.BRIGHTNESS_MAX,
-                PowerManager.BRIGHTNESS_OFF + 1, PowerManager.BRIGHTNESS_ON);
-        return brightnessFloatToIntRange(brightnessFloat, minFloat, maxFloat, minInt, maxInt);
+    public static int brightnessFloatToInt(float brightnessFloat) {
+        return Math.round(brightnessFloatToIntRange(brightnessFloat));
     }
 
     /**
@@ -148,20 +110,24 @@
      * Value returned as a float privimite (to preserve precision), but is a value within the
      * int-system range.
      */
-    private static float brightnessFloatToIntRange(float brightnessFloat, float minFloat,
-            float maxFloat, float minInt, float maxInt) {
+    public static float brightnessFloatToIntRange(float brightnessFloat) {
         if (floatEquals(brightnessFloat, PowerManager.BRIGHTNESS_OFF_FLOAT)) {
             return PowerManager.BRIGHTNESS_OFF;
         } else if (Float.isNaN(brightnessFloat)) {
             return PowerManager.BRIGHTNESS_INVALID;
         } else {
+            final float minFloat = PowerManager.BRIGHTNESS_MIN;
+            final float maxFloat = PowerManager.BRIGHTNESS_MAX;
+            final float minInt = PowerManager.BRIGHTNESS_OFF + 1;
+            final float maxInt = PowerManager.BRIGHTNESS_ON;
             return MathUtils.constrainedMap(minInt, maxInt, minFloat, maxFloat, brightnessFloat);
         }
     }
 
     private static float getScreenBrightnessFloat(Context context) {
         return Settings.System.getFloatForUser(context.getContentResolver(),
-                Settings.System.SCREEN_BRIGHTNESS_FLOAT, Float.NaN, UserHandle.USER_CURRENT);
+                Settings.System.SCREEN_BRIGHTNESS_FLOAT, PowerManager.BRIGHTNESS_INVALID_FLOAT,
+                UserHandle.USER_CURRENT);
     }
 
     private static int getScreenBrightnessInt(Context context) {
@@ -185,10 +151,10 @@
         if (topOfQueue != null && topOfQueue.equals(value)) {
             mWriteHistory.poll();
         } else {
-            if (brightnessFloatToInt(mContext, mPreferredSettingValue) == value) {
+            if (brightnessFloatToInt(mPreferredSettingValue) == value) {
                 return;
             }
-            float newBrightnessFloat = brightnessIntToFloat(mContext, value);
+            float newBrightnessFloat = brightnessIntToFloat(value);
             mWriteHistory.offer(newBrightnessFloat);
             mPreferredSettingValue = newBrightnessFloat;
             Settings.System.putFloatForUser(mContext.getContentResolver(),
@@ -207,7 +173,7 @@
      * @param value Brightness setting as float to store in int setting.
      */
     private void updateBrightnessIntFromFloat(float value) {
-        int newBrightnessInt = brightnessFloatToInt(mContext, value);
+        int newBrightnessInt = brightnessFloatToInt(value);
         Object topOfQueue = mWriteHistory.peek();
         if (topOfQueue != null && topOfQueue.equals(value)) {
             mWriteHistory.poll();
diff --git a/core/java/com/android/internal/app/ChooserActivity.java b/core/java/com/android/internal/app/ChooserActivity.java
index 3a89dcd..fd90b56 100644
--- a/core/java/com/android/internal/app/ChooserActivity.java
+++ b/core/java/com/android/internal/app/ChooserActivity.java
@@ -206,6 +206,7 @@
     public static final int SELECTION_TYPE_APP = 2;
     public static final int SELECTION_TYPE_STANDARD = 3;
     public static final int SELECTION_TYPE_COPY = 4;
+    public static final int SELECTION_TYPE_NEARBY = 5;
 
     private static final int SCROLL_STATUS_IDLE = 0;
     private static final int SCROLL_STATUS_SCROLLING_VERTICAL = 1;
@@ -784,8 +785,8 @@
                 FrameworkStatsLog.SHARESHEET_STARTED,
                 getReferrerPackageName(),
                 target.getType(),
-                initialIntents == null ? 0 : initialIntents.length,
                 mCallerChooserTargets == null ? 0 : mCallerChooserTargets.length,
+                initialIntents == null ? 0 : initialIntents.length,
                 isWorkProfile(),
                 findPreferredContentPreview(getTargetIntent(), getContentResolver()),
                 target.getAction()
@@ -1135,7 +1136,8 @@
         return displayContentPreview(previewType, targetIntent, getLayoutInflater(), parent);
     }
 
-    private ComponentName getNearbySharingComponent() {
+    @VisibleForTesting
+    protected ComponentName getNearbySharingComponent() {
         String nearbyComponent = Settings.Secure.getString(
                 getContentResolver(),
                 Settings.Secure.NEARBY_SHARING_COMPONENT);
@@ -1148,7 +1150,8 @@
         return ComponentName.unflattenFromString(nearbyComponent);
     }
 
-    private TargetInfo getNearbySharingTarget(Intent originalIntent) {
+    @VisibleForTesting
+    protected TargetInfo getNearbySharingTarget(Intent originalIntent) {
         final ComponentName cn = getNearbySharingComponent();
         if (cn == null) return null;
 
@@ -1216,14 +1219,21 @@
         final TargetInfo ti = getNearbySharingTarget(originalIntent);
         if (ti == null) return null;
 
-        return createActionButton(
+        final Button b = createActionButton(
                 ti.getDisplayIcon(this),
                 ti.getDisplayLabel(),
                 (View unused) -> {
+                    // Log share completion via nearby
+                    getChooserActivityLogger().logShareTargetSelected(
+                            SELECTION_TYPE_NEARBY,
+                            "",
+                            -1);
                     safelyStartActivity(ti);
                     finish();
                 }
         );
+        b.setId(R.id.chooser_nearby_button);
+        return b;
     }
 
     private void addActionButton(ViewGroup parent, Button b) {
@@ -2616,7 +2626,9 @@
         }
     }
 
-    static final class EmptyTargetInfo extends NotSelectableTargetInfo {
+    protected static final class EmptyTargetInfo extends NotSelectableTargetInfo {
+        public EmptyTargetInfo() {}
+
         public Drawable getDisplayIcon(Context context) {
             return null;
         }
@@ -3134,7 +3146,9 @@
         // ends up disabled. That's because at some point the old tab's vertical scrolling is
         // disabled and the new tab's is enabled. For context, see b/159997845
         setVerticalScrollEnabled(true);
-        mResolverDrawerLayout.scrollNestedScrollableChildBackToTop();
+        if (mResolverDrawerLayout != null) {
+            mResolverDrawerLayout.scrollNestedScrollableChildBackToTop();
+        }
     }
 
     @Override
diff --git a/core/java/com/android/internal/app/ChooserActivityLogger.java b/core/java/com/android/internal/app/ChooserActivityLogger.java
index c26bac4..426859e 100644
--- a/core/java/com/android/internal/app/ChooserActivityLogger.java
+++ b/core/java/com/android/internal/app/ChooserActivityLogger.java
@@ -116,7 +116,9 @@
         @UiEvent(doc = "User selected a standard target.")
         SHARESHEET_STANDARD_TARGET_SELECTED(234),
         @UiEvent(doc = "User selected the copy target.")
-        SHARESHEET_COPY_TARGET_SELECTED(235);
+        SHARESHEET_COPY_TARGET_SELECTED(235),
+        @UiEvent(doc = "User selected the nearby target.")
+        SHARESHEET_NEARBY_TARGET_SELECTED(626);
 
         private final int mId;
         SharesheetTargetSelectedEvent(int id) {
@@ -136,6 +138,8 @@
                     return SHARESHEET_STANDARD_TARGET_SELECTED;
                 case ChooserActivity.SELECTION_TYPE_COPY:
                     return SHARESHEET_COPY_TARGET_SELECTED;
+                case ChooserActivity.SELECTION_TYPE_NEARBY:
+                    return SHARESHEET_NEARBY_TARGET_SELECTED;
                 default:
                     return INVALID;
             }
diff --git a/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java b/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java
index ea3d2de..eb59f0f 100644
--- a/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java
+++ b/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java
@@ -123,10 +123,15 @@
     // Flag related to Privacy Indicators
 
     /**
-     * Whether the Permissions Hub is showing.
+     * Whether to show the complete ongoing app ops chip.
      */
     public static final String PROPERTY_PERMISSIONS_HUB_ENABLED = "permissions_hub_2_enabled";
 
+    /**
+     * Whether to show app ops chip for just microphone + camera.
+     */
+    public static final String PROPERTY_MIC_CAMERA_ENABLED = "camera_mic_icons_enabled";
+
     // Flags related to Assistant
 
     /**
diff --git a/core/java/com/android/internal/os/BatterySipper.java b/core/java/com/android/internal/os/BatterySipper.java
index b3ea118..2620ba0 100644
--- a/core/java/com/android/internal/os/BatterySipper.java
+++ b/core/java/com/android/internal/os/BatterySipper.java
@@ -129,6 +129,7 @@
     public double videoPowerMah;
     public double wakeLockPowerMah;
     public double wifiPowerMah;
+    public double systemServiceCpuPowerMah;
 
     //                           ****************
     // This list must be kept current with atoms.proto (frameworks/base/cmds/statsd/src/atoms.proto)
@@ -242,6 +243,7 @@
         videoPowerMah += other.videoPowerMah;
         proportionalSmearMah += other.proportionalSmearMah;
         totalSmearedPowerMah += other.totalSmearedPowerMah;
+        systemServiceCpuPowerMah += other.systemServiceCpuPowerMah;
     }
 
     /**
@@ -253,7 +255,8 @@
     public double sumPower() {
         totalPowerMah = usagePowerMah + wifiPowerMah + gpsPowerMah + cpuPowerMah +
                 sensorPowerMah + mobileRadioPowerMah + wakeLockPowerMah + cameraPowerMah +
-                flashlightPowerMah + bluetoothPowerMah + audioPowerMah + videoPowerMah;
+                flashlightPowerMah + bluetoothPowerMah + audioPowerMah + videoPowerMah
+                + systemServiceCpuPowerMah;
         totalSmearedPowerMah = totalPowerMah + screenPowerMah + proportionalSmearMah;
 
         return totalPowerMah;
diff --git a/core/java/com/android/internal/os/BatteryStatsHelper.java b/core/java/com/android/internal/os/BatteryStatsHelper.java
index b131ab8..3dfa3c3 100644
--- a/core/java/com/android/internal/os/BatteryStatsHelper.java
+++ b/core/java/com/android/internal/os/BatteryStatsHelper.java
@@ -133,6 +133,7 @@
     private double mMaxDrainedPower;
 
     PowerCalculator mCpuPowerCalculator;
+    SystemServicePowerCalculator mSystemServicePowerCalculator;
     PowerCalculator mWakelockPowerCalculator;
     MobileRadioPowerCalculator mMobileRadioPowerCalculator;
     PowerCalculator mWifiPowerCalculator;
@@ -396,6 +397,11 @@
         }
         mCpuPowerCalculator.reset();
 
+        if (mSystemServicePowerCalculator == null) {
+            mSystemServicePowerCalculator = new SystemServicePowerCalculator(mPowerProfile, mStats);
+        }
+        mSystemServicePowerCalculator.reset();
+
         if (mMemoryPowerCalculator == null) {
             mMemoryPowerCalculator = new MemoryPowerCalculator(mPowerProfile);
         }
@@ -588,6 +594,8 @@
             mFlashlightPowerCalculator.calculateApp(app, u, mRawRealtimeUs, mRawUptimeUs,
                     mStatsType);
             mMediaPowerCalculator.calculateApp(app, u, mRawRealtimeUs, mRawUptimeUs, mStatsType);
+            mSystemServicePowerCalculator.calculateApp(app, u, mRawRealtimeUs, mRawUptimeUs,
+                    mStatsType);
 
             final double totalPower = app.sumPower();
             if (DEBUG && totalPower != 0) {
diff --git a/core/java/com/android/internal/os/BatteryStatsImpl.java b/core/java/com/android/internal/os/BatteryStatsImpl.java
index 58ba16b..8498151 100644
--- a/core/java/com/android/internal/os/BatteryStatsImpl.java
+++ b/core/java/com/android/internal/os/BatteryStatsImpl.java
@@ -144,6 +144,7 @@
     private static final boolean DEBUG = false;
     public static final boolean DEBUG_ENERGY = false;
     private static final boolean DEBUG_ENERGY_CPU = DEBUG_ENERGY;
+    private static final boolean DEBUG_BINDER_STATS = true;
     private static final boolean DEBUG_MEMORY = false;
     private static final boolean DEBUG_HISTORY = false;
     private static final boolean USE_OLD_HISTORY = false;   // for debugging.
@@ -154,7 +155,7 @@
     private static final int MAGIC = 0xBA757475; // 'BATSTATS'
 
     // Current on-disk Parcel version
-    static final int VERSION = 186 + (USE_OLD_HISTORY ? 1000 : 0);
+    static final int VERSION = 187 + (USE_OLD_HISTORY ? 1000 : 0);
 
     // The maximum number of names wakelocks we will keep track of
     // per uid; once the limit is reached, we batch the remaining wakelocks
@@ -218,10 +219,13 @@
             new KernelCpuUidClusterTimeReader(true);
     @VisibleForTesting
     protected KernelSingleUidTimeReader mKernelSingleUidTimeReader;
+    @VisibleForTesting
+    protected SystemServerCpuThreadReader mSystemServerCpuThreadReader;
 
     private final KernelMemoryBandwidthStats mKernelMemoryBandwidthStats
             = new KernelMemoryBandwidthStats();
     private final LongSparseArray<SamplingTimer> mKernelMemoryStats = new LongSparseArray<>();
+
     public LongSparseArray<SamplingTimer> getKernelMemoryStats() {
         return mKernelMemoryStats;
     }
@@ -267,6 +271,7 @@
 
     /** Container for Rail Energy Data stats. */
     private final RailStats mTmpRailStats = new RailStats();
+
     /**
      * Use a queue to delay removing UIDs from {@link KernelCpuUidUserSysTimeReader},
      * {@link KernelCpuUidActiveTimeReader}, {@link KernelCpuUidClusterTimeReader},
@@ -1007,6 +1012,16 @@
 
     private long[] mCpuFreqs;
 
+    /**
+     * Times spent by the system server threads grouped by cluster and CPU speed.
+     */
+    private LongSamplingCounter[][] mSystemServerThreadCpuTimesUs;
+
+    /**
+     * Times spent by the system server threads handling incoming binder requests.
+     */
+    private LongSamplingCounter[][] mBinderThreadCpuTimesUs;
+
     @VisibleForTesting
     protected PowerProfile mPowerProfile;
 
@@ -6131,10 +6146,77 @@
      * the power consumption to the calling app.
      */
     public void noteBinderCallStats(int workSourceUid, long incrementalCallCount,
-            Collection<BinderCallsStats.CallStat> callStats) {
+            Collection<BinderCallsStats.CallStat> callStats, int[] binderThreadNativeTids) {
         synchronized (this) {
             getUidStatsLocked(workSourceUid).noteBinderCallStatsLocked(incrementalCallCount,
                     callStats);
+            mSystemServerCpuThreadReader.setBinderThreadNativeTids(binderThreadNativeTids);
+        }
+    }
+
+    /**
+     * Estimates the proportion of system server CPU activity handling incoming binder calls
+     * that can be attributed to each app
+     */
+    @VisibleForTesting
+    public void updateSystemServiceCallStats() {
+        // Start off by computing the average duration of recorded binder calls,
+        // regardless of which binder or transaction. We will use this as a fallback
+        // for calls that were not sampled at all.
+        int totalRecordedCallCount = 0;
+        long totalRecordedCallTimeMicros = 0;
+        for (int i = 0; i < mUidStats.size(); i++) {
+            Uid uid = mUidStats.valueAt(i);
+            ArraySet<BinderCallStats> binderCallStats = uid.mBinderCallStats;
+            for (int j = binderCallStats.size() - 1; j >= 0; j--) {
+                BinderCallStats stats = binderCallStats.valueAt(j);
+                totalRecordedCallCount += stats.recordedCallCount;
+                totalRecordedCallTimeMicros += stats.recordedCpuTimeMicros;
+            }
+        }
+
+        long totalSystemServiceTimeMicros = 0;
+
+        // For every UID, use recorded durations of sampled binder calls to estimate
+        // the total time the system server spent handling requests from this UID.
+        for (int i = 0; i < mUidStats.size(); i++) {
+            Uid uid = mUidStats.valueAt(i);
+
+            long totalTimeForUid = 0;
+            int totalCallCountForUid = 0;
+            ArraySet<BinderCallStats> binderCallStats = uid.mBinderCallStats;
+            for (int j = binderCallStats.size() - 1; j >= 0; j--) {
+                BinderCallStats stats = binderCallStats.valueAt(j);
+                totalCallCountForUid += stats.callCount;
+                if (stats.recordedCallCount > 0) {
+                    totalTimeForUid +=
+                            stats.callCount * stats.recordedCpuTimeMicros / stats.recordedCallCount;
+                } else if (totalRecordedCallCount > 0) {
+                    totalTimeForUid +=
+                            stats.callCount * totalRecordedCallTimeMicros / totalRecordedCallCount;
+                }
+            }
+
+            if (totalCallCountForUid < uid.mBinderCallCount && totalRecordedCallCount > 0) {
+                // Estimate remaining calls, which were not tracked because of binder call
+                // stats sampling
+                totalTimeForUid +=
+                        (uid.mBinderCallCount - totalCallCountForUid) * totalRecordedCallTimeMicros
+                                / totalRecordedCallCount;
+            }
+
+            uid.mSystemServiceTimeUs = totalTimeForUid;
+            totalSystemServiceTimeMicros += totalTimeForUid;
+        }
+
+        for (int i = 0; i < mUidStats.size(); i++) {
+            Uid uid = mUidStats.valueAt(i);
+            if (totalSystemServiceTimeMicros > 0) {
+                uid.mProportionalSystemServiceUsage =
+                        (double) uid.mSystemServiceTimeUs / totalSystemServiceTimeMicros;
+            } else {
+                uid.mProportionalSystemServiceUsage = 0;
+            }
         }
     }
 
@@ -6583,7 +6665,7 @@
     /**
      * Accumulates stats for a specific binder transaction.
      */
-    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
+    @VisibleForTesting
     protected static class BinderCallStats {
         static final Comparator<BinderCallStats> COMPARATOR =
                 Comparator.comparing(BinderCallStats::getClassName)
@@ -6822,6 +6904,16 @@
          */
         private final ArraySet<BinderCallStats> mBinderCallStats = new ArraySet<>();
 
+        /**
+         * Estimated total time spent by the system server handling requests from this uid.
+         */
+        private long mSystemServiceTimeUs;
+
+        /**
+         * Estimated proportion of system server binder call CPU cost for this uid.
+         */
+        private double mProportionalSystemServiceUsage;
+
         public Uid(BatteryStatsImpl bsi, int uid) {
             mBsi = bsi;
             mUid = uid;
@@ -6899,7 +6991,6 @@
             return nullIfAllZeros(mCpuClusterTimesMs, STATS_SINCE_CHARGED);
         }
 
-
         @Override
         public long[] getCpuFreqTimes(int which, int procState) {
             if (which < 0 || which >= NUM_PROCESS_STATE) {
@@ -6934,10 +7025,16 @@
             return mBinderCallCount;
         }
 
+        @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
         public ArraySet<BinderCallStats> getBinderCallStats() {
             return mBinderCallStats;
         }
 
+        @Override
+        public  double getProportionalSystemServiceUsage() {
+            return mProportionalSystemServiceUsage;
+        }
+
         public void addIsolatedUid(int isolatedUid) {
             if (mChildUids == null) {
                 mChildUids = new IntArray();
@@ -8029,9 +8126,12 @@
             mBinderCallCount = 0;
             mBinderCallStats.clear();
 
+            mProportionalSystemServiceUsage = 0;
+
             mLastStepUserTime = mLastStepSystemTime = 0;
             mCurStepUserTime = mCurStepSystemTime = 0;
 
+
             return !active;
         }
 
@@ -8373,28 +8473,7 @@
             mUserCpuTime.writeToParcel(out);
             mSystemCpuTime.writeToParcel(out);
 
-            if (mCpuClusterSpeedTimesUs != null) {
-                out.writeInt(1);
-                out.writeInt(mCpuClusterSpeedTimesUs.length);
-                for (LongSamplingCounter[] cpuSpeeds : mCpuClusterSpeedTimesUs) {
-                    if (cpuSpeeds != null) {
-                        out.writeInt(1);
-                        out.writeInt(cpuSpeeds.length);
-                        for (LongSamplingCounter c : cpuSpeeds) {
-                            if (c != null) {
-                                out.writeInt(1);
-                                c.writeToParcel(out);
-                            } else {
-                                out.writeInt(0);
-                            }
-                        }
-                    } else {
-                        out.writeInt(0);
-                    }
-                }
-            } else {
-                out.writeInt(0);
-            }
+            mBsi.writeCpuSpeedCountersToParcel(out, mCpuClusterSpeedTimesUs);
 
             LongSamplingCounterArray.writeToParcel(out, mCpuFreqTimeMs);
             LongSamplingCounterArray.writeToParcel(out, mScreenOffCpuFreqTimeMs);
@@ -8432,6 +8511,7 @@
             } else {
                 out.writeInt(0);
             }
+            out.writeDouble(mProportionalSystemServiceUsage);
         }
 
         void readJobCompletionsFromParcelLocked(Parcel in) {
@@ -8692,36 +8772,7 @@
             mUserCpuTime = new LongSamplingCounter(mBsi.mOnBatteryTimeBase, in);
             mSystemCpuTime = new LongSamplingCounter(mBsi.mOnBatteryTimeBase, in);
 
-            if (in.readInt() != 0) {
-                int numCpuClusters = in.readInt();
-                if (mBsi.mPowerProfile != null && mBsi.mPowerProfile.getNumCpuClusters() != numCpuClusters) {
-                    throw new ParcelFormatException("Incompatible number of cpu clusters");
-                }
-
-                mCpuClusterSpeedTimesUs = new LongSamplingCounter[numCpuClusters][];
-                for (int cluster = 0; cluster < numCpuClusters; cluster++) {
-                    if (in.readInt() != 0) {
-                        int numSpeeds = in.readInt();
-                        if (mBsi.mPowerProfile != null &&
-                                mBsi.mPowerProfile.getNumSpeedStepsInCpuCluster(cluster) != numSpeeds) {
-                            throw new ParcelFormatException("Incompatible number of cpu speeds");
-                        }
-
-                        final LongSamplingCounter[] cpuSpeeds = new LongSamplingCounter[numSpeeds];
-                        mCpuClusterSpeedTimesUs[cluster] = cpuSpeeds;
-                        for (int speed = 0; speed < numSpeeds; speed++) {
-                            if (in.readInt() != 0) {
-                                cpuSpeeds[speed] = new LongSamplingCounter(
-                                        mBsi.mOnBatteryTimeBase, in);
-                            }
-                        }
-                    } else {
-                        mCpuClusterSpeedTimesUs[cluster] = null;
-                    }
-                }
-            } else {
-                mCpuClusterSpeedTimesUs = null;
-            }
+            mCpuClusterSpeedTimesUs = mBsi.readCpuSpeedCountersFromParcel(in);
 
             mCpuFreqTimeMs = LongSamplingCounterArray.readFromParcel(in, mBsi.mOnBatteryTimeBase);
             mScreenOffCpuFreqTimeMs = LongSamplingCounterArray.readFromParcel(
@@ -8762,6 +8813,8 @@
             } else {
                 mWifiRadioApWakeupCount = null;
             }
+
+            mProportionalSystemServiceUsage = in.readDouble();
         }
 
         public void noteJobsDeferredLocked(int numDeferred, long sinceLast) {
@@ -9904,7 +9957,6 @@
             UserInfoProvider userInfoProvider) {
         init(clocks);
 
-
         if (systemDir == null) {
             mStatsFile = null;
             mBatteryStatsHistory = new BatteryStatsHistory(this, mHistoryBuffer);
@@ -10046,6 +10098,8 @@
             firstCpuOfCluster += mPowerProfile.getNumCoresInCpuCluster(i);
         }
 
+        mSystemServerCpuThreadReader = SystemServerCpuThreadReader.create();
+
         if (mEstimatedBatteryCapacity == -1) {
             // Initialize the estimated battery capacity to a known preset one.
             mEstimatedBatteryCapacity = (int) mPowerProfile.getBatteryCapacity();
@@ -10726,6 +10780,9 @@
 
         mTmpRailStats.reset();
 
+        resetIfNotNull(mSystemServerThreadCpuTimesUs, false);
+        resetIfNotNull(mBinderThreadCpuTimesUs, false);
+
         mLastHistoryStepDetails = null;
         mLastStepCpuUserTime = mLastStepCpuSystemTime = 0;
         mCurStepCpuUserTime = mCurStepCpuSystemTime = 0;
@@ -10853,7 +10910,7 @@
         return null;
     }
 
-   /**
+    /**
      * Distribute WiFi energy info and network traffic to apps.
      * @param info The energy information from the WiFi controller.
      */
@@ -11772,6 +11829,7 @@
             for (int cluster = mKernelCpuSpeedReaders.length - 1; cluster >= 0; --cluster) {
                 mKernelCpuSpeedReaders[cluster].readDelta();
             }
+            mSystemServerCpuThreadReader.readDelta();
             return;
         }
 
@@ -11791,6 +11849,87 @@
             readKernelUidCpuClusterTimesLocked(onBattery);
             mNumAllUidCpuTimeReads += 2;
         }
+
+        updateSystemServerThreadStats();
+    }
+
+    /**
+     * Estimates the proportion of the System Server CPU activity (per cluster per speed)
+     * spent on handling incoming binder calls.
+     */
+    @VisibleForTesting
+    public void updateSystemServerThreadStats() {
+        // There are some simplifying assumptions made in this algorithm
+        // 1) We assume that if a thread handles incoming binder calls, all of its activity
+        //    is spent doing that.  Most incoming calls are handled by threads allocated
+        //    by the native layer in the binder thread pool, so this assumption is reasonable.
+        // 2) We use the aggregate CPU time spent in different threads as a proxy for the CPU
+        //    cost. In reality, in multi-core CPUs, the CPU cost may not be linearly
+        //    affected by additional threads.
+
+        SystemServerCpuThreadReader.SystemServiceCpuThreadTimes systemServiceCpuThreadTimes =
+                    mSystemServerCpuThreadReader.readDelta();
+
+        int index = 0;
+        int numCpuClusters = mPowerProfile.getNumCpuClusters();
+        if (mSystemServerThreadCpuTimesUs == null) {
+            mSystemServerThreadCpuTimesUs = new LongSamplingCounter[numCpuClusters][];
+            mBinderThreadCpuTimesUs = new LongSamplingCounter[numCpuClusters][];
+        }
+        for (int cluster = 0; cluster < numCpuClusters; cluster++) {
+            int numSpeeds = mPowerProfile.getNumSpeedStepsInCpuCluster(cluster);
+            if (mSystemServerThreadCpuTimesUs[cluster] == null) {
+                mSystemServerThreadCpuTimesUs[cluster] = new LongSamplingCounter[numSpeeds];
+                mBinderThreadCpuTimesUs[cluster] = new LongSamplingCounter[numSpeeds];
+                for (int speed = 0; speed < numSpeeds; speed++) {
+                    mSystemServerThreadCpuTimesUs[cluster][speed] =
+                            new LongSamplingCounter(mOnBatteryTimeBase);
+                    mBinderThreadCpuTimesUs[cluster][speed] =
+                            new LongSamplingCounter(mOnBatteryTimeBase);
+                }
+            }
+            for (int speed = 0; speed < numSpeeds; speed++) {
+                mSystemServerThreadCpuTimesUs[cluster][speed].addCountLocked(
+                        systemServiceCpuThreadTimes.threadCpuTimesUs[index]);
+                mBinderThreadCpuTimesUs[cluster][speed].addCountLocked(
+                        systemServiceCpuThreadTimes.binderThreadCpuTimesUs[index]);
+                index++;
+            }
+        }
+        if (DEBUG_BINDER_STATS) {
+            Slog.d(TAG, "System server threads per CPU cluster (binder threads/total threads/%)");
+            long binderThreadTime = 0;
+            long totalThreadTime = 0;
+            int cpuIndex = 0;
+            for (int cluster = 0; cluster < numCpuClusters; cluster++) {
+                StringBuilder sb = new StringBuilder();
+                sb.append("cpu").append(cpuIndex).append(": [");
+                int numSpeeds = mPowerProfile.getNumSpeedStepsInCpuCluster(cluster);
+                for (int speed = 0; speed < numSpeeds; speed++) {
+                    if (speed != 0) {
+                        sb.append(", ");
+                    }
+                    long totalCount = mSystemServerThreadCpuTimesUs[cluster][speed].getCountLocked(
+                            0) / 1000;
+                    long binderCount = mBinderThreadCpuTimesUs[cluster][speed].getCountLocked(0)
+                            / 1000;
+                    sb.append(String.format("%d/%d(%.1f%%)",
+                            binderCount,
+                            totalCount,
+                            totalCount != 0 ? (double) binderCount * 100 / totalCount : 0));
+
+                    totalThreadTime += totalCount;
+                    binderThreadTime += binderCount;
+                    index++;
+                }
+                cpuIndex += mPowerProfile.getNumCoresInCpuCluster(cluster);
+                Slog.d(TAG, sb.toString());
+            }
+            Slog.d(TAG, "Total system server thread time (ms): " + totalThreadTime);
+            Slog.d(TAG, String.format("Total Binder thread time (ms): %d (%.1f%%)",
+                    binderThreadTime,
+                    binderThreadTime != 0 ? (double) binderThreadTime * 100 / totalThreadTime : 0));
+        }
     }
 
     /**
@@ -12998,6 +13137,75 @@
         }
     }
 
+
+    @Override
+    public long getSystemServiceTimeAtCpuSpeed(int cluster, int step) {
+        // Estimates the time spent by the system server handling incoming binder requests.
+        //
+        // The data that we can get from the kernel is this:
+        //   - CPU duration for a (thread - cluster - CPU speed) combination
+        //   - CPU duration for a (UID - cluster - CPU speed) combination
+        //
+        // The configuration we have in the Power Profile is this:
+        //   - Average CPU power for a (cluster - CPU speed) combination.
+        //
+        // The model used by BatteryStats can be illustrated with this example:
+        //
+        // - Let's say the system server has 10 threads.
+        // - These 10 threads spent 1000 ms of CPU time in aggregate
+        // - Of the 10 threads 4 were execute exclusively incoming binder calls.
+        // - These 4 "binder" threads consumed 600 ms of CPU time in aggregate
+        // - The real time spent by the system server UID doing all of this is, say, 200 ms.
+        //
+        // We will assume that power consumption is proportional to the time spent by the CPU
+        // across all threads.  This is a crude assumption, but we don't have more detailed data.
+        // Thus,
+        //   binderRealTime = realTime * aggregateBinderThreadTime / aggregateAllThreadTime
+        //
+        // In our example,
+        //   binderRealTime = 200 * 600 / 1000 = 120ms
+        //
+        // We can then multiply this estimated time by the average power to obtain an estimate
+        // of the total power consumed by incoming binder calls for the given cluster/speed
+        // combination.
+
+        if (mSystemServerThreadCpuTimesUs == null) {
+            return 0;
+        }
+
+        if (cluster < 0 || cluster >= mSystemServerThreadCpuTimesUs.length) {
+            return 0;
+        }
+
+        final LongSamplingCounter[] threadTimesForCluster = mSystemServerThreadCpuTimesUs[cluster];
+
+        if (step < 0 || step >= threadTimesForCluster.length) {
+            return 0;
+        }
+
+        Uid systemUid = mUidStats.get(Process.SYSTEM_UID);
+        if (systemUid == null) {
+            return 0;
+        }
+
+        final long uidTimeAtCpuSpeed = systemUid.getTimeAtCpuSpeed(cluster, step,
+                BatteryStats.STATS_SINCE_CHARGED);
+        if (uidTimeAtCpuSpeed == 0) {
+            return 0;
+        }
+
+        final long uidThreadTime =
+                threadTimesForCluster[step].getCountLocked(BatteryStats.STATS_SINCE_CHARGED);
+
+        if (uidThreadTime == 0) {
+            return 0;
+        }
+
+        final long binderThreadTime = mBinderThreadCpuTimesUs[cluster][step].getCountLocked(
+                BatteryStats.STATS_SINCE_CHARGED);
+        return uidTimeAtCpuSpeed * binderThreadTime / uidThreadTime;
+    }
+
     /**
      * Retrieve the statistics object for a particular uid, creating if needed.
      */
@@ -13327,45 +13535,7 @@
             pw.print(uid.getUserCpuTimeUs(STATS_SINCE_CHARGED) / 1000); pw.print(" ");
             pw.println(uid.getSystemCpuTimeUs(STATS_SINCE_CHARGED) / 1000);
         }
-        pw.println("Per UID system service calls:");
-        BinderTransactionNameResolver nameResolver = new BinderTransactionNameResolver();
-        for (int i = 0; i < size; i++) {
-            int u = mUidStats.keyAt(i);
-            Uid uid = mUidStats.get(u);
-            long binderCallCount = uid.getBinderCallCount();
-            if (binderCallCount != 0) {
-                pw.print(" ");
-                pw.print(u);
-                pw.print(" system service calls: ");
-                pw.print(binderCallCount);
-                ArraySet<BinderCallStats> binderCallStats = uid.getBinderCallStats();
-                if (!binderCallStats.isEmpty()) {
-                    pw.println(", including");
-                    BinderCallStats[] bcss = new BinderCallStats[binderCallStats.size()];
-                    binderCallStats.toArray(bcss);
-                    for (BinderCallStats bcs : bcss) {
-                        bcs.ensureMethodName(nameResolver);
-                    }
-                    Arrays.sort(bcss, BinderCallStats.COMPARATOR);
-                    for (BinderCallStats callStats : bcss) {
-                        pw.print("    ");
-                        pw.print(callStats.getClassName());
-                        pw.print('#');
-                        pw.print(callStats.getMethodName());
-                        pw.print(" calls: ");
-                        pw.print(callStats.callCount);
-                        if (callStats.recordedCallCount != 0) {
-                            pw.print(" time: ");
-                            pw.print(callStats.callCount * callStats.recordedCpuTimeMicros
-                                    / callStats.recordedCallCount / 1000);
-                        }
-                        pw.println();
-                    }
-                } else {
-                    pw.println();
-                }
-            }
-        }
+
         pw.println("Per UID CPU active time in ms:");
         for (int i = 0; i < size; i++) {
             int u = mUidStats.keyAt(i);
@@ -13390,6 +13560,30 @@
                 pw.print("  "); pw.print(u); pw.print(": "); pw.println(Arrays.toString(times));
             }
         }
+
+        updateSystemServiceCallStats();
+        if (mSystemServerThreadCpuTimesUs != null) {
+            pw.println("Per UID System server binder time in ms:");
+            for (int i = 0; i < size; i++) {
+                int u = mUidStats.keyAt(i);
+                Uid uid = mUidStats.get(u);
+                double proportionalSystemServiceUsage = uid.getProportionalSystemServiceUsage();
+
+                long time = 0;
+                for (int cluster = 0; cluster < mSystemServerThreadCpuTimesUs.length; cluster++) {
+                    int numSpeeds = mSystemServerThreadCpuTimesUs[cluster].length;
+                    for (int speed = 0; speed < numSpeeds; speed++) {
+                        time += getSystemServiceTimeAtCpuSpeed(cluster, speed)
+                                * proportionalSystemServiceUsage;
+                    }
+                }
+
+                pw.print("  ");
+                pw.print(u);
+                pw.print(": ");
+                pw.println(time);
+            }
+        }
     }
 
     final ReentrantLock mWriteLock = new ReentrantLock();
@@ -14908,6 +15102,9 @@
             u.readFromParcelLocked(mOnBatteryTimeBase, mOnBatteryScreenOffTimeBase, in);
             mUidStats.append(uid, u);
         }
+
+        mSystemServerThreadCpuTimesUs = readCpuSpeedCountersFromParcel(in);
+        mBinderThreadCpuTimesUs = readCpuSpeedCountersFromParcel(in);
     }
 
     public void writeToParcel(Parcel out, int flags) {
@@ -14923,6 +15120,8 @@
         // Need to update with current kernel wake lock counts.
         pullPendingStateUpdatesLocked();
 
+        updateSystemServiceCallStats();
+
         // Pull the clock time.  This may update the time and make a new history entry
         // if we had originally pulled a time before the RTC was set.
         getStartClockTime();
@@ -15105,6 +15304,73 @@
         } else {
             out.writeInt(0);
         }
+        writeCpuSpeedCountersToParcel(out, mSystemServerThreadCpuTimesUs);
+        writeCpuSpeedCountersToParcel(out, mBinderThreadCpuTimesUs);
+    }
+
+    private void writeCpuSpeedCountersToParcel(Parcel out, LongSamplingCounter[][] counters) {
+        if (counters == null) {
+            out.writeInt(0);
+            return;
+        }
+
+        out.writeInt(1);
+        out.writeInt(counters.length);
+        for (int i = 0; i < counters.length; i++) {
+            LongSamplingCounter[] counterArray = counters[i];
+            if (counterArray == null) {
+                out.writeInt(0);
+                continue;
+            }
+
+            out.writeInt(1);
+            out.writeInt(counterArray.length);
+            for (int j = 0; j < counterArray.length; j++) {
+                LongSamplingCounter c = counterArray[j];
+                if (c != null) {
+                    out.writeInt(1);
+                    c.writeToParcel(out);
+                } else {
+                    out.writeInt(0);
+                }
+            }
+        }
+    }
+
+    private LongSamplingCounter[][] readCpuSpeedCountersFromParcel(Parcel in) {
+        LongSamplingCounter[][] counters;
+        if (in.readInt() != 0) {
+            int numCpuClusters = in.readInt();
+            if (mPowerProfile != null
+                    && mPowerProfile.getNumCpuClusters() != numCpuClusters) {
+                throw new ParcelFormatException("Incompatible number of cpu clusters");
+            }
+
+            counters = new LongSamplingCounter[numCpuClusters][];
+            for (int cluster = 0; cluster < numCpuClusters; cluster++) {
+                if (in.readInt() != 0) {
+                    int numSpeeds = in.readInt();
+                    if (mPowerProfile != null
+                            && mPowerProfile.getNumSpeedStepsInCpuCluster(cluster) != numSpeeds) {
+                        throw new ParcelFormatException("Incompatible number of cpu speeds");
+                    }
+
+                    final LongSamplingCounter[] cpuSpeeds = new LongSamplingCounter[numSpeeds];
+                    counters[cluster] = cpuSpeeds;
+                    for (int speed = 0; speed < numSpeeds; speed++) {
+                        if (in.readInt() != 0) {
+                            cpuSpeeds[speed] = new LongSamplingCounter(mOnBatteryTimeBase, in);
+                        }
+                    }
+                } else {
+                    counters[cluster] = null;
+                }
+            }
+        } else {
+            counters = null;
+        }
+
+        return counters;
     }
 
     @UnsupportedAppUsage
diff --git a/core/java/com/android/internal/os/BinderCallsStats.java b/core/java/com/android/internal/os/BinderCallsStats.java
index e09ef49..f5bef0b 100644
--- a/core/java/com/android/internal/os/BinderCallsStats.java
+++ b/core/java/com/android/internal/os/BinderCallsStats.java
@@ -74,6 +74,10 @@
 
     // Whether to collect all the data: cpu + exceptions + reply/request sizes.
     private boolean mDetailedTracking = DETAILED_TRACKING_DEFAULT;
+    // If set to true, indicates that all transactions for specific UIDs are being
+    // recorded, ignoring sampling. The UidEntry.recordAllTransactions flag is also set
+    // for the UIDs being tracked.
+    private boolean mRecordingAllTransactionsForUid;
     // Sampling period to control how often to track CPU usage. 1 means all calls, 100 means ~1 out
     // of 100 requests.
     private int mPeriodicSamplingInterval = PERIODIC_SAMPLING_INTERVAL_DEFAULT;
@@ -115,7 +119,8 @@
                     if (uidEntry != null) {
                         ArrayMap<CallStatKey, CallStat> callStats = uidEntry.mCallStats;
                         mCallStatsObserver.noteCallStats(uidEntry.workSourceUid,
-                                uidEntry.incrementalCallCount, callStats.values());
+                                uidEntry.incrementalCallCount, callStats.values(),
+                                mNativeTids.toArray());
                         uidEntry.incrementalCallCount = 0;
                         for (int j = callStats.size() - 1; j >= 0; j--) {
                             callStats.valueAt(j).incrementalCallCount = 0;
@@ -177,7 +182,8 @@
     @Override
     @Nullable
     public CallSession callStarted(Binder binder, int code, int workSourceUid) {
-        if (mDeviceState == null || mDeviceState.isCharging()) {
+        if (!mRecordingAllTransactionsForUid
+                && (mDeviceState == null || mDeviceState.isCharging())) {
             return null;
         }
 
@@ -189,7 +195,9 @@
         s.exceptionThrown = false;
         s.cpuTimeStarted = -1;
         s.timeStarted = -1;
-        if (shouldRecordDetailedData()) {
+        s.recordedCall = shouldRecordDetailedData();
+
+        if (mRecordingAllTransactionsForUid || s.recordedCall) {
             s.cpuTimeStarted = getThreadTimeMicro();
             s.timeStarted = getElapsedRealtimeMicro();
         }
@@ -217,8 +225,17 @@
 
     private void processCallEnded(CallSession s,
             int parcelRequestSize, int parcelReplySize, int workSourceUid) {
-        // Non-negative time signals we need to record data for this call.
-        final boolean recordCall = s.cpuTimeStarted >= 0;
+        UidEntry uidEntry = null;
+        final boolean recordCall;
+        if (s.recordedCall) {
+            recordCall = true;
+        } else if (mRecordingAllTransactionsForUid) {
+            uidEntry = getUidEntry(workSourceUid);
+            recordCall = uidEntry.recordAllTransactions;
+        } else {
+            recordCall = false;
+        }
+
         final long duration;
         final long latencyDuration;
         if (recordCall) {
@@ -237,14 +254,17 @@
 
         synchronized (mLock) {
             // This was already checked in #callStart but check again while synchronized.
-            if (mDeviceState == null || mDeviceState.isCharging()) {
+            if (!mRecordingAllTransactionsForUid
+                    && (mDeviceState == null || mDeviceState.isCharging())) {
                 return;
             }
 
-            final UidEntry uidEntry = getUidEntry(workSourceUid);
+            if (uidEntry == null) {
+                uidEntry = getUidEntry(workSourceUid);
+            }
+
             uidEntry.callCount++;
             uidEntry.incrementalCallCount++;
-
             if (recordCall) {
                 uidEntry.cpuTimeMicros += duration;
                 uidEntry.recordedCallCount++;
@@ -356,28 +376,67 @@
             for (int entryIdx = 0; entryIdx < uidEntriesSize; entryIdx++) {
                 final UidEntry entry = mUidEntries.valueAt(entryIdx);
                 for (CallStat stat : entry.getCallStatsList()) {
-                    ExportedCallStat exported = new ExportedCallStat();
-                    exported.workSourceUid = entry.workSourceUid;
-                    exported.callingUid = stat.callingUid;
-                    exported.className = stat.binderClass.getName();
-                    exported.binderClass = stat.binderClass;
-                    exported.transactionCode = stat.transactionCode;
-                    exported.screenInteractive = stat.screenInteractive;
-                    exported.cpuTimeMicros = stat.cpuTimeMicros;
-                    exported.maxCpuTimeMicros = stat.maxCpuTimeMicros;
-                    exported.latencyMicros = stat.latencyMicros;
-                    exported.maxLatencyMicros = stat.maxLatencyMicros;
-                    exported.recordedCallCount = stat.recordedCallCount;
-                    exported.callCount = stat.callCount;
-                    exported.maxRequestSizeBytes = stat.maxRequestSizeBytes;
-                    exported.maxReplySizeBytes = stat.maxReplySizeBytes;
-                    exported.exceptionCount = stat.exceptionCount;
-                    resultCallStats.add(exported);
+                    resultCallStats.add(getExportedCallStat(entry.workSourceUid, stat));
                 }
             }
         }
 
         // Resolve codes outside of the lock since it can be slow.
+        resolveBinderMethodNames(resultCallStats);
+
+        // Debug entries added to help validate the data.
+        if (mAddDebugEntries && mBatteryStopwatch != null) {
+            resultCallStats.add(createDebugEntry("start_time_millis", mStartElapsedTime));
+            resultCallStats.add(createDebugEntry("end_time_millis", SystemClock.elapsedRealtime()));
+            resultCallStats.add(
+                    createDebugEntry("battery_time_millis", mBatteryStopwatch.getMillis()));
+            resultCallStats.add(createDebugEntry("sampling_interval", mPeriodicSamplingInterval));
+        }
+
+        return resultCallStats;
+    }
+
+    /**
+     * This method is expensive to call.
+     */
+    public ArrayList<ExportedCallStat> getExportedCallStats(int workSourceUid) {
+        ArrayList<ExportedCallStat> resultCallStats = new ArrayList<>();
+        synchronized (mLock) {
+            final UidEntry entry = getUidEntry(workSourceUid);
+            for (CallStat stat : entry.getCallStatsList()) {
+                resultCallStats.add(getExportedCallStat(workSourceUid, stat));
+            }
+        }
+
+        // Resolve codes outside of the lock since it can be slow.
+        resolveBinderMethodNames(resultCallStats);
+
+        return resultCallStats;
+    }
+
+    private ExportedCallStat getExportedCallStat(int workSourceUid, CallStat stat) {
+        ExportedCallStat exported = new ExportedCallStat();
+        exported.workSourceUid = workSourceUid;
+        exported.callingUid = stat.callingUid;
+        exported.className = stat.binderClass.getName();
+        exported.binderClass = stat.binderClass;
+        exported.transactionCode = stat.transactionCode;
+        exported.screenInteractive = stat.screenInteractive;
+        exported.cpuTimeMicros = stat.cpuTimeMicros;
+        exported.maxCpuTimeMicros = stat.maxCpuTimeMicros;
+        exported.latencyMicros = stat.latencyMicros;
+        exported.maxLatencyMicros = stat.maxLatencyMicros;
+        exported.recordedCallCount = stat.recordedCallCount;
+        exported.callCount = stat.callCount;
+        exported.maxRequestSizeBytes = stat.maxRequestSizeBytes;
+        exported.maxReplySizeBytes = stat.maxReplySizeBytes;
+        exported.exceptionCount = stat.exceptionCount;
+        return exported;
+    }
+
+    private void resolveBinderMethodNames(
+            ArrayList<ExportedCallStat> resultCallStats) {
+        // Resolve codes outside of the lock since it can be slow.
         ExportedCallStat previous = null;
         String previousMethodName = null;
         resultCallStats.sort(BinderCallsStats::compareByBinderClassAndCode);
@@ -397,17 +456,6 @@
             exported.methodName = methodName;
             previous = exported;
         }
-
-        // Debug entries added to help validate the data.
-        if (mAddDebugEntries && mBatteryStopwatch != null) {
-            resultCallStats.add(createDebugEntry("start_time_millis", mStartElapsedTime));
-            resultCallStats.add(createDebugEntry("end_time_millis", SystemClock.elapsedRealtime()));
-            resultCallStats.add(
-                    createDebugEntry("battery_time_millis", mBatteryStopwatch.getMillis()));
-            resultCallStats.add(createDebugEntry("sampling_interval", mPeriodicSamplingInterval));
-        }
-
-        return resultCallStats;
     }
 
     private ExportedCallStat createDebugEntry(String variableName, long value) {
@@ -431,33 +479,24 @@
     }
 
     /** Writes the collected statistics to the supplied {@link PrintWriter}.*/
-    public void dump(PrintWriter pw, AppIdToPackageMap packageMap, boolean verbose) {
+    public void dump(PrintWriter pw, AppIdToPackageMap packageMap, int workSourceUid,
+            boolean verbose) {
         synchronized (mLock) {
-            dumpLocked(pw, packageMap, verbose);
+            dumpLocked(pw, packageMap, workSourceUid, verbose);
         }
     }
 
-    private void dumpLocked(PrintWriter pw, AppIdToPackageMap packageMap, boolean verbose) {
-        long totalCallsCount = 0;
-        long totalRecordedCallsCount = 0;
-        long totalCpuTime = 0;
+    private void dumpLocked(PrintWriter pw, AppIdToPackageMap packageMap, int workSourceUid,
+            boolean verbose) {
+        if (workSourceUid != Process.INVALID_UID) {
+            verbose = true;
+        }
         pw.print("Start time: ");
         pw.println(DateFormat.format("yyyy-MM-dd HH:mm:ss", mStartCurrentTime));
         pw.print("On battery time (ms): ");
         pw.println(mBatteryStopwatch != null ? mBatteryStopwatch.getMillis() : 0);
         pw.println("Sampling interval period: " + mPeriodicSamplingInterval);
-        final List<UidEntry> entries = new ArrayList<>();
 
-        final int uidEntriesSize = mUidEntries.size();
-        for (int i = 0; i < uidEntriesSize; i++) {
-            UidEntry e = mUidEntries.valueAt(i);
-            entries.add(e);
-            totalCpuTime += e.cpuTimeMicros;
-            totalRecordedCallsCount += e.recordedCallCount;
-            totalCallsCount += e.callCount;
-        }
-
-        entries.sort(Comparator.<UidEntry>comparingDouble(value -> value.cpuTimeMicros).reversed());
         final String datasetSizeDesc = verbose ? "" : "(top 90% by cpu time) ";
         final StringBuilder sb = new StringBuilder();
         pw.println("Per-UID raw data " + datasetSizeDesc
@@ -466,10 +505,15 @@
                 + "latency_time_micros, max_latency_time_micros, exception_count, "
                 + "max_request_size_bytes, max_reply_size_bytes, recorded_call_count, "
                 + "call_count):");
-        final List<ExportedCallStat> exportedCallStats = getExportedCallStats();
+        final List<ExportedCallStat> exportedCallStats;
+        if (workSourceUid != Process.INVALID_UID) {
+            exportedCallStats = getExportedCallStats(workSourceUid);
+        } else {
+            exportedCallStats = getExportedCallStats();
+        }
         exportedCallStats.sort(BinderCallsStats::compareByCpuDesc);
         for (ExportedCallStat e : exportedCallStats) {
-            if (e.methodName.startsWith(DEBUG_ENTRY_PREFIX)) {
+            if (e.methodName != null && e.methodName.startsWith(DEBUG_ENTRY_PREFIX)) {
                 // Do not dump debug entries.
                 continue;
             }
@@ -493,6 +537,30 @@
             pw.println(sb);
         }
         pw.println();
+        final List<UidEntry> entries = new ArrayList<>();
+        long totalCallsCount = 0;
+        long totalRecordedCallsCount = 0;
+        long totalCpuTime = 0;
+
+        if (workSourceUid != Process.INVALID_UID) {
+            UidEntry e = getUidEntry(workSourceUid);
+            entries.add(e);
+            totalCpuTime += e.cpuTimeMicros;
+            totalRecordedCallsCount += e.recordedCallCount;
+            totalCallsCount += e.callCount;
+        } else {
+            final int uidEntriesSize = mUidEntries.size();
+            for (int i = 0; i < uidEntriesSize; i++) {
+                UidEntry e = mUidEntries.valueAt(i);
+                entries.add(e);
+                totalCpuTime += e.cpuTimeMicros;
+                totalRecordedCallsCount += e.recordedCallCount;
+                totalCallsCount += e.callCount;
+            }
+            entries.sort(
+                    Comparator.<UidEntry>comparingDouble(value -> value.cpuTimeMicros).reversed());
+        }
+
         pw.println("Per-UID Summary " + datasetSizeDesc
                 + "(cpu_time, % of total cpu_time, recorded_call_count, call_count, package/uid):");
         final List<UidEntry> summaryEntries = verbose ? entries
@@ -504,10 +572,13 @@
                     entry.recordedCallCount, entry.callCount, uidStr));
         }
         pw.println();
-        pw.println(String.format("  Summary: total_cpu_time=%d, "
-                        + "calls_count=%d, avg_call_cpu_time=%.0f",
-                totalCpuTime, totalCallsCount, (double) totalCpuTime / totalRecordedCallsCount));
-        pw.println();
+        if (workSourceUid == Process.INVALID_UID) {
+            pw.println(String.format("  Summary: total_cpu_time=%d, "
+                            + "calls_count=%d, avg_call_cpu_time=%.0f",
+                    totalCpuTime, totalCallsCount,
+                    (double) totalCpuTime / totalRecordedCallsCount));
+            pw.println();
+        }
 
         pw.println("Exceptions thrown (exception_count, class_name):");
         final List<Pair<String, Integer>> exceptionEntries = new ArrayList<>();
@@ -589,6 +660,22 @@
         }
     }
 
+    /**
+     * Marks the specified work source UID for total binder call tracking: detailed information
+     * will be recorded for all calls from this source ID.
+     *
+     * This is expensive and can cause memory pressure, therefore this mode should only be used
+     * for debugging.
+     */
+    public void recordAllCallsForWorkSourceUid(int workSourceUid) {
+        setDetailedTracking(true);
+
+        Slog.i(TAG, "Recording all Binder calls for UID: "  + workSourceUid);
+        UidEntry uidEntry = getUidEntry(workSourceUid);
+        uidEntry.recordAllTransactions = true;
+        mRecordingAllTransactionsForUid = true;
+    }
+
     public void setAddDebugEntries(boolean addDebugEntries) {
         mAddDebugEntries = addDebugEntries;
     }
@@ -636,6 +723,7 @@
             if (mBatteryStopwatch != null) {
                 mBatteryStopwatch.reset();
             }
+            mRecordingAllTransactionsForUid = false;
         }
     }
 
@@ -766,6 +854,8 @@
         public long cpuTimeMicros;
         // Call count that gets reset after delivery to BatteryStats
         public long incrementalCallCount;
+        // Indicates that all transactions for the UID must be tracked
+        public boolean recordAllTransactions;
 
         UidEntry(int uid) {
             this.workSourceUid = uid;
diff --git a/core/java/com/android/internal/os/BinderInternal.java b/core/java/com/android/internal/os/BinderInternal.java
index feb5aab..2645b8e 100644
--- a/core/java/com/android/internal/os/BinderInternal.java
+++ b/core/java/com/android/internal/os/BinderInternal.java
@@ -85,9 +85,10 @@
         long timeStarted;
         // Should be set to one when an exception is thrown.
         boolean exceptionThrown;
+        // Detailed information should be recorded for this call when it ends.
+        public boolean recordedCall;
     }
 
-
     /**
      * Responsible for resolving a work source.
      */
@@ -142,7 +143,8 @@
          * Notes incoming binder call stats associated with this work source UID.
          */
         void noteCallStats(int workSourceUid, long incrementalCallCount,
-                Collection<BinderCallsStats.CallStat> callStats);
+                Collection<BinderCallsStats.CallStat> callStats,
+                int[] binderThreadNativeTids);
     }
 
     /**
diff --git a/core/java/com/android/internal/os/ChildZygoteInit.java b/core/java/com/android/internal/os/ChildZygoteInit.java
index 1f816c1..749ff84 100644
--- a/core/java/com/android/internal/os/ChildZygoteInit.java
+++ b/core/java/com/android/internal/os/ChildZygoteInit.java
@@ -116,7 +116,7 @@
         try {
             server.registerServerSocketAtAbstractName(socketName);
 
-            // Add the abstract socket to the FD whitelist so that the native zygote code
+            // Add the abstract socket to the FD allow list so that the native zygote code
             // can properly detach it after forking.
             Zygote.nativeAllowFileAcrossFork("ABSTRACT/" + socketName);
 
diff --git a/core/java/com/android/internal/os/KernelCpuThreadReader.java b/core/java/com/android/internal/os/KernelCpuThreadReader.java
index 3407670..2ba372a 100644
--- a/core/java/com/android/internal/os/KernelCpuThreadReader.java
+++ b/core/java/com/android/internal/os/KernelCpuThreadReader.java
@@ -225,19 +225,22 @@
 
     /** Set the number of frequency buckets to use */
     void setNumBuckets(int numBuckets) {
-        if (numBuckets < 1) {
-            Slog.w(TAG, "Number of buckets must be at least 1, but was " + numBuckets);
-            return;
-        }
         // If `numBuckets` hasn't changed since the last set, do nothing
         if (mFrequenciesKhz != null && mFrequenciesKhz.length == numBuckets) {
             return;
         }
-        mFrequencyBucketCreator =
-                new FrequencyBucketCreator(mProcTimeInStateReader.getFrequenciesKhz(), numBuckets);
-        mFrequenciesKhz =
-                mFrequencyBucketCreator.bucketFrequencies(
-                        mProcTimeInStateReader.getFrequenciesKhz());
+
+        final long[] frequenciesKhz = mProcTimeInStateReader.getFrequenciesKhz();
+        if (numBuckets != 0) {
+            mFrequencyBucketCreator = new FrequencyBucketCreator(frequenciesKhz, numBuckets);
+            mFrequenciesKhz = mFrequencyBucketCreator.bucketFrequencies(frequenciesKhz);
+        } else {
+            mFrequencyBucketCreator = null;
+            mFrequenciesKhz = new int[frequenciesKhz.length];
+            for (int i = 0; i < frequenciesKhz.length; i++) {
+                mFrequenciesKhz[i] = (int) frequenciesKhz[i];
+            }
+        }
     }
 
     /** Set the UID predicate for {@link #getProcessCpuUsage} */
@@ -320,8 +323,15 @@
         if (cpuUsagesLong == null) {
             return null;
         }
-        int[] cpuUsages = mFrequencyBucketCreator.bucketValues(cpuUsagesLong);
-
+        final int[] cpuUsages;
+        if (mFrequencyBucketCreator != null) {
+            cpuUsages = mFrequencyBucketCreator.bucketValues(cpuUsagesLong);
+        } else {
+            cpuUsages = new int[cpuUsagesLong.length];
+            for (int i = 0; i < cpuUsagesLong.length; i++) {
+                cpuUsages[i] = (int) cpuUsagesLong[i];
+            }
+        }
         return new ThreadCpuUsage(threadId, threadName, cpuUsages);
     }
 
diff --git a/core/java/com/android/internal/os/OWNERS b/core/java/com/android/internal/os/OWNERS
index 9283105..afc9432 100644
--- a/core/java/com/android/internal/os/OWNERS
+++ b/core/java/com/android/internal/os/OWNERS
@@ -1 +1 @@
-per-file ZygoteArguments.java,ZygoteConnection.java,ZygoteInit.java,Zygote.java,ZygoteServer.java = chriswailes@google.com, ngeoffray@google.com, sehr@google.com, narayan@google.com, maco@google.com
+per-file ZygoteArguments.java,ZygoteConnection.java,ZygoteInit.java,Zygote.java,ZygoteServer.java = calin@google.com, chriswailes@google.com, maco@google.com, narayan@google.com, ngeoffray@google.com
diff --git a/core/java/com/android/internal/os/SystemServerCpuThreadReader.java b/core/java/com/android/internal/os/SystemServerCpuThreadReader.java
new file mode 100644
index 0000000..1cdd42c
--- /dev/null
+++ b/core/java/com/android/internal/os/SystemServerCpuThreadReader.java
@@ -0,0 +1,151 @@
+/*
+ * 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.os;
+
+import android.os.Process;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.io.IOException;
+import java.nio.file.Path;
+import java.util.ArrayList;
+import java.util.Arrays;
+
+/**
+ * Reads /proc/UID/task/TID/time_in_state files to obtain statistics on CPU usage
+ * by various threads of the System Server.
+ */
+public class SystemServerCpuThreadReader {
+    private KernelCpuThreadReader mKernelCpuThreadReader;
+    private int[] mBinderThreadNativeTids;
+
+    private int[] mThreadCpuTimesUs;
+    private int[] mBinderThreadCpuTimesUs;
+    private long[] mLastThreadCpuTimesUs;
+    private long[] mLastBinderThreadCpuTimesUs;
+
+    /**
+     * Times (in microseconds) spent by the system server UID.
+     */
+    public static class SystemServiceCpuThreadTimes {
+        // All threads
+        public long[] threadCpuTimesUs;
+        // Just the threads handling incoming binder calls
+        public long[] binderThreadCpuTimesUs;
+    }
+
+    private SystemServiceCpuThreadTimes mDeltaCpuThreadTimes = new SystemServiceCpuThreadTimes();
+
+    /**
+     * Creates a configured instance of SystemServerCpuThreadReader.
+     */
+    public static SystemServerCpuThreadReader create() {
+        return new SystemServerCpuThreadReader(
+                KernelCpuThreadReader.create(0, uid -> uid == Process.myUid()));
+    }
+
+    @VisibleForTesting
+    public SystemServerCpuThreadReader(Path procPath, int systemServerUid) throws IOException {
+        this(new KernelCpuThreadReader(0, uid -> uid == systemServerUid, null, null,
+                new KernelCpuThreadReader.Injector() {
+                    @Override
+                    public int getUidForPid(int pid) {
+                        return systemServerUid;
+                    }
+                }));
+    }
+
+    @VisibleForTesting
+    public SystemServerCpuThreadReader(KernelCpuThreadReader kernelCpuThreadReader) {
+        mKernelCpuThreadReader = kernelCpuThreadReader;
+    }
+
+    public void setBinderThreadNativeTids(int[] nativeTids) {
+        mBinderThreadNativeTids = nativeTids;
+    }
+
+    /**
+     * Returns delta of CPU times, per thread, since the previous call to this method.
+     */
+    public SystemServiceCpuThreadTimes readDelta() {
+        if (mBinderThreadCpuTimesUs == null) {
+            int numCpuFrequencies = mKernelCpuThreadReader.getCpuFrequenciesKhz().length;
+            mThreadCpuTimesUs = new int[numCpuFrequencies];
+            mBinderThreadCpuTimesUs = new int[numCpuFrequencies];
+
+            mLastThreadCpuTimesUs = new long[numCpuFrequencies];
+            mLastBinderThreadCpuTimesUs = new long[numCpuFrequencies];
+
+            mDeltaCpuThreadTimes.threadCpuTimesUs = new long[numCpuFrequencies];
+            mDeltaCpuThreadTimes.binderThreadCpuTimesUs = new long[numCpuFrequencies];
+        }
+
+        Arrays.fill(mThreadCpuTimesUs, 0);
+        Arrays.fill(mBinderThreadCpuTimesUs, 0);
+
+        ArrayList<KernelCpuThreadReader.ProcessCpuUsage> processCpuUsage =
+                mKernelCpuThreadReader.getProcessCpuUsage();
+        int processCpuUsageSize = processCpuUsage.size();
+        for (int i = 0; i < processCpuUsageSize; i++) {
+            KernelCpuThreadReader.ProcessCpuUsage pcu = processCpuUsage.get(i);
+            ArrayList<KernelCpuThreadReader.ThreadCpuUsage> threadCpuUsages = pcu.threadCpuUsages;
+            if (threadCpuUsages != null) {
+                int threadCpuUsagesSize = threadCpuUsages.size();
+                for (int j = 0; j < threadCpuUsagesSize; j++) {
+                    KernelCpuThreadReader.ThreadCpuUsage tcu = threadCpuUsages.get(j);
+                    boolean isBinderThread = isBinderThread(tcu.threadId);
+
+                    final int len = Math.min(tcu.usageTimesMillis.length, mThreadCpuTimesUs.length);
+                    for (int k = 0; k < len; k++) {
+                        int usageTimeUs = tcu.usageTimesMillis[k] * 1000;
+                        mThreadCpuTimesUs[k] += usageTimeUs;
+                        if (isBinderThread) {
+                            mBinderThreadCpuTimesUs[k] += usageTimeUs;
+                        }
+                    }
+                }
+            }
+        }
+
+        for (int i = 0; i < mThreadCpuTimesUs.length; i++) {
+            if (mThreadCpuTimesUs[i] < mLastThreadCpuTimesUs[i]) {
+                mDeltaCpuThreadTimes.threadCpuTimesUs[i] = mThreadCpuTimesUs[i];
+                mDeltaCpuThreadTimes.binderThreadCpuTimesUs[i] = mBinderThreadCpuTimesUs[i];
+            } else {
+                mDeltaCpuThreadTimes.threadCpuTimesUs[i] =
+                        mThreadCpuTimesUs[i] - mLastThreadCpuTimesUs[i];
+                mDeltaCpuThreadTimes.binderThreadCpuTimesUs[i] =
+                        mBinderThreadCpuTimesUs[i] - mLastBinderThreadCpuTimesUs[i];
+            }
+            mLastThreadCpuTimesUs[i] = mThreadCpuTimesUs[i];
+            mLastBinderThreadCpuTimesUs[i] = mBinderThreadCpuTimesUs[i];
+        }
+
+        return mDeltaCpuThreadTimes;
+    }
+
+    private boolean isBinderThread(int threadId) {
+        if (mBinderThreadNativeTids != null) {
+            for (int i = 0; i < mBinderThreadNativeTids.length; i++) {
+                if (threadId == mBinderThreadNativeTids[i]) {
+                    return true;
+                }
+            }
+        }
+        return false;
+    }
+}
diff --git a/core/java/com/android/internal/os/SystemServicePowerCalculator.java b/core/java/com/android/internal/os/SystemServicePowerCalculator.java
new file mode 100644
index 0000000..481b901
--- /dev/null
+++ b/core/java/com/android/internal/os/SystemServicePowerCalculator.java
@@ -0,0 +1,91 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.os;
+
+import android.os.BatteryStats;
+import android.util.Log;
+
+import java.util.Arrays;
+
+/**
+ * Estimates the amount of power consumed by the System Server handling requests from
+ * a given app.
+ */
+public class SystemServicePowerCalculator extends PowerCalculator {
+    private static final boolean DEBUG = false;
+    private static final String TAG = "SystemServicePowerCalc";
+
+    private static final long MICROSEC_IN_HR = (long) 60 * 60 * 1000 * 1000;
+
+    private final PowerProfile mPowerProfile;
+    private final BatteryStats mBatteryStats;
+    // Tracks system server CPU [cluster][speed] power in milliAmp-microseconds
+    private double[][] mSystemServicePowerMaUs;
+
+    public SystemServicePowerCalculator(PowerProfile powerProfile, BatteryStats batteryStats) {
+        mPowerProfile = powerProfile;
+        mBatteryStats = batteryStats;
+    }
+
+    @Override
+    public void calculateApp(BatterySipper app, BatteryStats.Uid u, long rawRealtimeUs,
+            long rawUptimeUs, int statsType) {
+        final double proportionalUsage = u.getProportionalSystemServiceUsage();
+        if (proportionalUsage > 0) {
+            if (mSystemServicePowerMaUs == null) {
+                updateSystemServicePower();
+            }
+
+            double cpuPowerMaUs = 0;
+            int numCpuClusters = mPowerProfile.getNumCpuClusters();
+            for (int cluster = 0; cluster < numCpuClusters; cluster++) {
+                final int numSpeeds = mPowerProfile.getNumSpeedStepsInCpuCluster(cluster);
+                for (int speed = 0; speed < numSpeeds; speed++) {
+                    cpuPowerMaUs += mSystemServicePowerMaUs[cluster][speed] * proportionalUsage;
+                }
+            }
+
+            app.systemServiceCpuPowerMah = cpuPowerMaUs / MICROSEC_IN_HR;
+        }
+    }
+
+    private void updateSystemServicePower() {
+        final int numCpuClusters = mPowerProfile.getNumCpuClusters();
+        mSystemServicePowerMaUs = new double[numCpuClusters][];
+        for (int cluster = 0; cluster < numCpuClusters; cluster++) {
+            final int numSpeeds = mPowerProfile.getNumSpeedStepsInCpuCluster(cluster);
+            mSystemServicePowerMaUs[cluster] = new double[numSpeeds];
+            for (int speed = 0; speed < numSpeeds; speed++) {
+                mSystemServicePowerMaUs[cluster][speed] =
+                        mBatteryStats.getSystemServiceTimeAtCpuSpeed(cluster, speed)
+                                * mPowerProfile.getAveragePowerForCpuCore(cluster, speed);
+            }
+        }
+        if (DEBUG) {
+            Log.d(TAG, "System service power per CPU cluster and frequency");
+            for (int cluster = 0; cluster < numCpuClusters; cluster++) {
+                Log.d(TAG, "Cluster[" + cluster  + "]: "
+                        + Arrays.toString(mSystemServicePowerMaUs[cluster]));
+            }
+        }
+    }
+
+    @Override
+    public void reset() {
+        mSystemServicePowerMaUs = null;
+    }
+}
diff --git a/core/java/com/android/internal/os/TEST_MAPPING b/core/java/com/android/internal/os/TEST_MAPPING
index f44b9fb..9698f19 100644
--- a/core/java/com/android/internal/os/TEST_MAPPING
+++ b/core/java/com/android/internal/os/TEST_MAPPING
@@ -26,5 +26,20 @@
         "KernelSingleUidTimeReader\\.java"
       ]
     }
+  ],
+  "postsubmit": [
+    {
+      "name": "FrameworksCoreTests",
+      "options": [
+        {
+          "include-filter": "com.android.internal.os.BstatsCpuTimesValidationTest"
+        }
+      ],
+      "file_patterns": [
+        "BatteryStatsImpl\\.java",
+        "KernelCpuUidFreqTimeReader\\.java",
+        "KernelSingleUidTimeReader\\.java"
+      ]
+    }
   ]
 }
diff --git a/core/java/com/android/internal/os/Zygote.java b/core/java/com/android/internal/os/Zygote.java
index 2989a5e..1ca9250 100644
--- a/core/java/com/android/internal/os/Zygote.java
+++ b/core/java/com/android/internal/os/Zygote.java
@@ -816,9 +816,9 @@
             throw new IllegalArgumentException(USAP_ERROR_PREFIX + "--preload-app");
         } else if (args.mStartChildZygote) {
             throw new IllegalArgumentException(USAP_ERROR_PREFIX + "--start-child-zygote");
-        } else if (args.mApiBlacklistExemptions != null) {
+        } else if (args.mApiDenylistExemptions != null) {
             throw new IllegalArgumentException(
-                USAP_ERROR_PREFIX + "--set-api-blacklist-exemptions");
+                    USAP_ERROR_PREFIX + "--set-api-denylist-exemptions");
         } else if (args.mHiddenApiAccessLogSampleRate != -1) {
             throw new IllegalArgumentException(
                     USAP_ERROR_PREFIX + "--hidden-api-log-sampling-rate=");
diff --git a/core/java/com/android/internal/os/ZygoteArguments.java b/core/java/com/android/internal/os/ZygoteArguments.java
index 94c1f71..22082d0 100644
--- a/core/java/com/android/internal/os/ZygoteArguments.java
+++ b/core/java/com/android/internal/os/ZygoteArguments.java
@@ -192,10 +192,10 @@
     boolean mBootCompleted;
 
     /**
-     * Exemptions from API blacklisting. These are sent to the pre-forked zygote at boot time, or
-     * when they change, via --set-api-blacklist-exemptions.
+     * Exemptions from API deny-listing. These are sent to the pre-forked zygote at boot time, or
+     * when they change, via --set-api-denylist-exemptions.
      */
-    String[] mApiBlacklistExemptions;
+    String[] mApiDenylistExemptions;
 
     /**
      * Sampling rate for logging hidden API accesses to the event log. This is sent to the
@@ -416,10 +416,10 @@
                 expectRuntimeArgs = false;
             } else if (arg.equals("--start-child-zygote")) {
                 mStartChildZygote = true;
-            } else if (arg.equals("--set-api-blacklist-exemptions")) {
+            } else if (arg.equals("--set-api-denylist-exemptions")) {
                 // consume all remaining args; this is a stand-alone command, never included
                 // with the regular fork command.
-                mApiBlacklistExemptions = Arrays.copyOfRange(args, curArg + 1, args.length);
+                mApiDenylistExemptions = Arrays.copyOfRange(args, curArg + 1, args.length);
                 curArg = args.length;
                 expectRuntimeArgs = false;
             } else if (arg.startsWith("--hidden-api-log-sampling-rate=")) {
diff --git a/core/java/com/android/internal/os/ZygoteConnection.java b/core/java/com/android/internal/os/ZygoteConnection.java
index ec1b05a..5a576ebb 100644
--- a/core/java/com/android/internal/os/ZygoteConnection.java
+++ b/core/java/com/android/internal/os/ZygoteConnection.java
@@ -185,8 +185,8 @@
             return null;
         }
 
-        if (parsedArgs.mApiBlacklistExemptions != null) {
-            return handleApiBlacklistExemptions(zygoteServer, parsedArgs.mApiBlacklistExemptions);
+        if (parsedArgs.mApiDenylistExemptions != null) {
+            return handleApiDenylistExemptions(zygoteServer, parsedArgs.mApiDenylistExemptions);
         }
 
         if (parsedArgs.mHiddenApiAccessLogSampleRate != -1
@@ -367,11 +367,11 @@
     }
 
     /**
-     * Makes the necessary changes to implement a new API blacklist exemption policy, and then
+     * Makes the necessary changes to implement a new API deny list exemption policy, and then
      * responds to the system server, letting it know that the task has been completed.
      *
      * This necessitates a change to the internal state of the Zygote.  As such, if the USAP
-     * pool is enabled all existing USAPs have an incorrect API blacklist exemption list.  To
+     * pool is enabled all existing USAPs have an incorrect API deny list exemption list.  To
      * properly handle this request the pool must be emptied and refilled.  This process can return
      * a Runnable object that must be returned to ZygoteServer.runSelectLoop to be invoked.
      *
@@ -380,9 +380,9 @@
      * @return A Runnable object representing a new app in any USAPs spawned from here; the
      *         zygote process will always receive a null value from this function.
      */
-    private Runnable handleApiBlacklistExemptions(ZygoteServer zygoteServer, String[] exemptions) {
+    private Runnable handleApiDenylistExemptions(ZygoteServer zygoteServer, String[] exemptions) {
         return stateChangeWithUsapPoolReset(zygoteServer,
-                () -> ZygoteInit.setApiBlacklistExemptions(exemptions));
+                () -> ZygoteInit.setApiDenylistExemptions(exemptions));
     }
 
     private Runnable handleUsapPoolStatusChange(ZygoteServer zygoteServer, boolean newStatus) {
diff --git a/core/java/com/android/internal/os/ZygoteInit.java b/core/java/com/android/internal/os/ZygoteInit.java
index 5fd9333..32e7fdc 100644
--- a/core/java/com/android/internal/os/ZygoteInit.java
+++ b/core/java/com/android/internal/os/ZygoteInit.java
@@ -583,7 +583,10 @@
         VMRuntime.registerAppInfo(profilePath, codePaths);
     }
 
-    public static void setApiBlacklistExemptions(String[] exemptions) {
+    /**
+     * Sets the list of classes/methods for the hidden API
+     */
+    public static void setApiDenylistExemptions(String[] exemptions) {
         VMRuntime.getRuntime().setHiddenApiExemptions(exemptions);
     }
 
diff --git a/core/java/com/android/internal/os/ZygoteServer.java b/core/java/com/android/internal/os/ZygoteServer.java
index a0b50df..bb17337 100644
--- a/core/java/com/android/internal/os/ZygoteServer.java
+++ b/core/java/com/android/internal/os/ZygoteServer.java
@@ -451,7 +451,7 @@
              * For reasons of correctness the USAP pool pipe and event FDs
              * must be processed before the session and server sockets.  This
              * is to ensure that the USAP pool accounting information is
-             * accurate when handling other requests like API blacklist
+             * accurate when handling other requests like API deny list
              * exemptions.
              */
 
diff --git a/core/java/com/android/internal/os/logging/MetricsLoggerWrapper.java b/core/java/com/android/internal/os/logging/MetricsLoggerWrapper.java
index ba60fa5..b42ea7d 100644
--- a/core/java/com/android/internal/os/logging/MetricsLoggerWrapper.java
+++ b/core/java/com/android/internal/os/logging/MetricsLoggerWrapper.java
@@ -16,14 +16,8 @@
 
 package com.android.internal.os.logging;
 
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.pm.PackageManager.NameNotFoundException;
-import android.util.Pair;
 import android.view.WindowManager.LayoutParams;
 
-import com.android.internal.logging.MetricsLogger;
-import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
 import com.android.internal.util.FrameworkStatsLog;
 
 /**
@@ -32,81 +26,6 @@
  */
 public class MetricsLoggerWrapper {
 
-    private static final int METRIC_VALUE_DISMISSED_BY_TAP = 0;
-    private static final int METRIC_VALUE_DISMISSED_BY_DRAG = 1;
-
-    public static void logPictureInPictureDismissByTap(Context context,
-            Pair<ComponentName, Integer> topActivityInfo) {
-        MetricsLogger.action(context, MetricsEvent.ACTION_PICTURE_IN_PICTURE_DISMISSED,
-                METRIC_VALUE_DISMISSED_BY_TAP);
-        FrameworkStatsLog.write(FrameworkStatsLog.PICTURE_IN_PICTURE_STATE_CHANGED,
-                getUid(context, topActivityInfo.first, topActivityInfo.second),
-                topActivityInfo.first.flattenToString(),
-                FrameworkStatsLog.PICTURE_IN_PICTURE_STATE_CHANGED__STATE__DISMISSED);
-    }
-
-    public static void logPictureInPictureDismissByDrag(Context context,
-            Pair<ComponentName, Integer> topActivityInfo) {
-        MetricsLogger.action(context,
-                MetricsEvent.ACTION_PICTURE_IN_PICTURE_DISMISSED,
-                METRIC_VALUE_DISMISSED_BY_DRAG);
-        FrameworkStatsLog.write(FrameworkStatsLog.PICTURE_IN_PICTURE_STATE_CHANGED,
-                getUid(context, topActivityInfo.first, topActivityInfo.second),
-                topActivityInfo.first.flattenToString(),
-                FrameworkStatsLog.PICTURE_IN_PICTURE_STATE_CHANGED__STATE__DISMISSED);
-    }
-
-    public static void logPictureInPictureMinimize(Context context, boolean isMinimized,
-            Pair<ComponentName, Integer> topActivityInfo) {
-        MetricsLogger.action(context, MetricsEvent.ACTION_PICTURE_IN_PICTURE_MINIMIZED,
-                isMinimized);
-        FrameworkStatsLog.write(FrameworkStatsLog.PICTURE_IN_PICTURE_STATE_CHANGED,
-                getUid(context, topActivityInfo.first, topActivityInfo.second),
-                topActivityInfo.first.flattenToString(),
-                FrameworkStatsLog.PICTURE_IN_PICTURE_STATE_CHANGED__STATE__MINIMIZED);
-    }
-
-    /**
-     * Get uid from component name and user Id
-     * @return uid. -1 if not found.
-     */
-    private static int getUid(Context context, ComponentName componentName, int userId) {
-        int uid = -1;
-        if (componentName == null) {
-            return uid;
-        }
-        try {
-            uid = context.getPackageManager().getApplicationInfoAsUser(
-                    componentName.getPackageName(), 0, userId).uid;
-        } catch (NameNotFoundException e) {
-        }
-        return uid;
-    }
-
-    public static void logPictureInPictureMenuVisible(Context context, boolean menuStateFull) {
-        MetricsLogger.visibility(context, MetricsEvent.ACTION_PICTURE_IN_PICTURE_MENU,
-                menuStateFull);
-    }
-
-    public static void logPictureInPictureEnter(Context context,
-            int uid, String shortComponentName, boolean supportsEnterPipOnTaskSwitch) {
-        MetricsLogger.action(context, MetricsEvent.ACTION_PICTURE_IN_PICTURE_ENTERED,
-                supportsEnterPipOnTaskSwitch);
-        FrameworkStatsLog.write(FrameworkStatsLog.PICTURE_IN_PICTURE_STATE_CHANGED, uid,
-                shortComponentName,
-                FrameworkStatsLog.PICTURE_IN_PICTURE_STATE_CHANGED__STATE__ENTERED);
-    }
-
-    public static void logPictureInPictureFullScreen(Context context, int uid,
-            String shortComponentName) {
-        MetricsLogger.action(context,
-                MetricsEvent.ACTION_PICTURE_IN_PICTURE_EXPANDED_TO_FULLSCREEN);
-        FrameworkStatsLog.write(FrameworkStatsLog.PICTURE_IN_PICTURE_STATE_CHANGED,
-                uid,
-                shortComponentName,
-                FrameworkStatsLog.PICTURE_IN_PICTURE_STATE_CHANGED__STATE__EXPANDED_TO_FULL_SCREEN);
-    }
-
     public static void logAppOverlayEnter(int uid, String packageName, boolean changed, int type, boolean usingAlertWindow) {
         if (changed) {
             if (type != LayoutParams.TYPE_APPLICATION_OVERLAY) {
diff --git a/core/java/com/android/internal/policy/PhoneWindow.java b/core/java/com/android/internal/policy/PhoneWindow.java
index 0a3fe09..2f8c457 100644
--- a/core/java/com/android/internal/policy/PhoneWindow.java
+++ b/core/java/com/android/internal/policy/PhoneWindow.java
@@ -35,6 +35,7 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.annotation.UiContext;
 import android.app.ActivityManager;
 import android.app.KeyguardManager;
 import android.app.SearchManager;
@@ -342,17 +343,17 @@
     static final RotationWatcher sRotationWatcher = new RotationWatcher();
 
     @UnsupportedAppUsage
-    public PhoneWindow(Context context) {
+    public PhoneWindow(@UiContext Context context) {
         super(context);
         mLayoutInflater = LayoutInflater.from(context);
         mRenderShadowsInCompositor = Settings.Global.getInt(context.getContentResolver(),
-                DEVELOPMENT_RENDER_SHADOWS_IN_COMPOSITOR, 0) != 0;
+                DEVELOPMENT_RENDER_SHADOWS_IN_COMPOSITOR, 1) != 0;
     }
 
     /**
      * Constructor for main window of an activity.
      */
-    public PhoneWindow(Context context, Window preservedWindow,
+    public PhoneWindow(@UiContext Context context, Window preservedWindow,
             ActivityConfigCallback activityConfigCallback) {
         this(context);
         // Only main activity windows use decor context, all the other windows depend on whatever
diff --git a/services/core/java/com/android/server/wm/ProtoLogGroup.java b/core/java/com/android/internal/protolog/ProtoLogGroup.java
similarity index 88%
rename from services/core/java/com/android/server/wm/ProtoLogGroup.java
rename to core/java/com/android/internal/protolog/ProtoLogGroup.java
index 51725ce..73d148c 100644
--- a/services/core/java/com/android/server/wm/ProtoLogGroup.java
+++ b/core/java/com/android/internal/protolog/ProtoLogGroup.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2019 The Android Open Source Project
+ * 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.
@@ -14,11 +14,9 @@
  * limitations under the License.
  */
 
-package com.android.server.wm;
+package com.android.internal.protolog;
 
-import com.android.internal.annotations.VisibleForTesting;
-import com.android.server.protolog.common.IProtoLogGroup;
-import com.android.server.protolog.common.ProtoLog;
+import com.android.internal.protolog.common.IProtoLogGroup;
 
 /**
  * Defines logging groups for ProtoLog.
@@ -118,16 +116,6 @@
         this.mLogToLogcat = logToLogcat;
     }
 
-    /**
-     * Test function for automated integration tests. Can be also called manually from adb shell.
-     */
-    @VisibleForTesting
-    public static void testProtoLog() {
-        ProtoLog.e(ProtoLogGroup.TEST_GROUP,
-                "Test completed successfully: %b %d %o %x %e %g %f %% %s.",
-                true, 1, 2, 3, 0.4, 0.5, 0.6, "ok");
-    }
-
     private static class Consts {
         private static final String TAG_WM = "WindowManager";
 
diff --git a/services/core/java/com/android/server/protolog/ProtoLogImpl.java b/core/java/com/android/internal/protolog/ProtoLogImpl.java
similarity index 92%
rename from services/core/java/com/android/server/protolog/ProtoLogImpl.java
rename to core/java/com/android/internal/protolog/ProtoLogImpl.java
index c9d42c8..6874f10 100644
--- a/services/core/java/com/android/server/protolog/ProtoLogImpl.java
+++ b/core/java/com/android/internal/protolog/ProtoLogImpl.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2019 The Android Open Source Project
+ * 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.
@@ -14,20 +14,20 @@
  * limitations under the License.
  */
 
-package com.android.server.protolog;
+package com.android.internal.protolog;
 
-import static com.android.server.protolog.ProtoLogFileProto.LOG;
-import static com.android.server.protolog.ProtoLogFileProto.MAGIC_NUMBER;
-import static com.android.server.protolog.ProtoLogFileProto.MAGIC_NUMBER_H;
-import static com.android.server.protolog.ProtoLogFileProto.MAGIC_NUMBER_L;
-import static com.android.server.protolog.ProtoLogFileProto.REAL_TIME_TO_ELAPSED_TIME_OFFSET_MILLIS;
-import static com.android.server.protolog.ProtoLogFileProto.VERSION;
-import static com.android.server.protolog.ProtoLogMessage.BOOLEAN_PARAMS;
-import static com.android.server.protolog.ProtoLogMessage.DOUBLE_PARAMS;
-import static com.android.server.protolog.ProtoLogMessage.ELAPSED_REALTIME_NANOS;
-import static com.android.server.protolog.ProtoLogMessage.MESSAGE_HASH;
-import static com.android.server.protolog.ProtoLogMessage.SINT64_PARAMS;
-import static com.android.server.protolog.ProtoLogMessage.STR_PARAMS;
+import static com.android.internal.protolog.ProtoLogFileProto.LOG;
+import static com.android.internal.protolog.ProtoLogFileProto.MAGIC_NUMBER;
+import static com.android.internal.protolog.ProtoLogFileProto.MAGIC_NUMBER_H;
+import static com.android.internal.protolog.ProtoLogFileProto.MAGIC_NUMBER_L;
+import static com.android.internal.protolog.ProtoLogFileProto.REAL_TIME_TO_ELAPSED_TIME_OFFSET_MILLIS;
+import static com.android.internal.protolog.ProtoLogFileProto.VERSION;
+import static com.android.internal.protolog.ProtoLogMessage.BOOLEAN_PARAMS;
+import static com.android.internal.protolog.ProtoLogMessage.DOUBLE_PARAMS;
+import static com.android.internal.protolog.ProtoLogMessage.ELAPSED_REALTIME_NANOS;
+import static com.android.internal.protolog.ProtoLogMessage.MESSAGE_HASH;
+import static com.android.internal.protolog.ProtoLogMessage.SINT64_PARAMS;
+import static com.android.internal.protolog.ProtoLogMessage.STR_PARAMS;
 
 import android.annotation.Nullable;
 import android.os.ShellCommand;
@@ -36,10 +36,9 @@
 import android.util.proto.ProtoOutputStream;
 
 import com.android.internal.annotations.VisibleForTesting;
-import com.android.server.protolog.common.IProtoLogGroup;
-import com.android.server.protolog.common.LogDataType;
+import com.android.internal.protolog.common.IProtoLogGroup;
+import com.android.internal.protolog.common.LogDataType;
 import com.android.internal.util.TraceBuffer;
-import com.android.server.wm.ProtoLogGroup;
 
 import java.io.File;
 import java.io.IOException;
@@ -62,7 +61,7 @@
      * Must be invoked after every action that could change the result of {@link #isEnabled}, eg.
      * starting / stopping proto log, or enabling / disabling log groups.
      */
-    static Runnable sCacheUpdater = () -> { };
+    public static Runnable sCacheUpdater = () -> { };
 
     private static void addLogGroupEnum(IProtoLogGroup[] config) {
         for (IProtoLogGroup group : config) {
@@ -289,9 +288,7 @@
         }
     }
 
-
-    @VisibleForTesting
-    ProtoLogImpl(File file, int bufferCapacity, ProtoLogViewerConfigReader viewerConfig) {
+    public ProtoLogImpl(File file, int bufferCapacity, ProtoLogViewerConfigReader viewerConfig) {
         mLogFile = file;
         mBuffer = new TraceBuffer(bufferCapacity);
         mViewerConfig = viewerConfig;
diff --git a/services/core/java/com/android/server/protolog/ProtoLogViewerConfigReader.java b/core/java/com/android/internal/protolog/ProtoLogViewerConfigReader.java
similarity index 87%
rename from services/core/java/com/android/server/protolog/ProtoLogViewerConfigReader.java
rename to core/java/com/android/internal/protolog/ProtoLogViewerConfigReader.java
index 4944217..e381d30 100644
--- a/services/core/java/com/android/server/protolog/ProtoLogViewerConfigReader.java
+++ b/core/java/com/android/internal/protolog/ProtoLogViewerConfigReader.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2019 The Android Open Source Project
+ * 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.
@@ -14,9 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.server.protolog;
-
-import static com.android.server.protolog.ProtoLogImpl.logAndPrintln;
+package com.android.internal.protolog;
 
 import org.json.JSONException;
 import org.json.JSONObject;
@@ -80,16 +78,17 @@
                     // Not a messageHash - skip it
                 }
             }
-            logAndPrintln(pw, "Loaded " + mLogMessageMap.size() + " log definitions from "
-                    + viewerConfigFilename);
+            ProtoLogImpl.logAndPrintln(pw, "Loaded " + mLogMessageMap.size()
+                    + " log definitions from " + viewerConfigFilename);
         } catch (FileNotFoundException e) {
-            logAndPrintln(pw, "Unable to load log definitions: File "
+            ProtoLogImpl.logAndPrintln(pw, "Unable to load log definitions: File "
                     + viewerConfigFilename + " not found." + e);
         } catch (IOException e) {
-            logAndPrintln(pw, "Unable to load log definitions: IOException while reading "
+            ProtoLogImpl.logAndPrintln(pw,
+                    "Unable to load log definitions: IOException while reading "
                     + viewerConfigFilename + ". " + e);
         } catch (JSONException e) {
-            logAndPrintln(pw,
+            ProtoLogImpl.logAndPrintln(pw,
                     "Unable to load log definitions: JSON parsing exception while reading "
                             + viewerConfigFilename + ". " + e);
         }
diff --git a/services/core/java/com/android/server/protolog/common/BitmaskConversionException.java b/core/java/com/android/internal/protolog/common/BitmaskConversionException.java
similarity index 88%
rename from services/core/java/com/android/server/protolog/common/BitmaskConversionException.java
rename to core/java/com/android/internal/protolog/common/BitmaskConversionException.java
index 7bb27b2..68b9d69 100644
--- a/services/core/java/com/android/server/protolog/common/BitmaskConversionException.java
+++ b/core/java/com/android/internal/protolog/common/BitmaskConversionException.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2019 The Android Open Source Project
+ * 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.
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.server.protolog.common;
+package com.android.internal.protolog.common;
 
 /**
  * Error while converting a bitmask representing a list of LogDataTypes.
diff --git a/services/core/java/com/android/server/protolog/common/IProtoLogGroup.java b/core/java/com/android/internal/protolog/common/IProtoLogGroup.java
similarity index 93%
rename from services/core/java/com/android/server/protolog/common/IProtoLogGroup.java
rename to core/java/com/android/internal/protolog/common/IProtoLogGroup.java
index 2c65341..e3db468 100644
--- a/services/core/java/com/android/server/protolog/common/IProtoLogGroup.java
+++ b/core/java/com/android/internal/protolog/common/IProtoLogGroup.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2019 The Android Open Source Project
+ * 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.
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.server.protolog.common;
+package com.android.internal.protolog.common;
 
 /**
  * Defines a log group configuration object for ProtoLog. Should be implemented as en enum.
diff --git a/services/core/java/com/android/server/protolog/common/InvalidFormatStringException.java b/core/java/com/android/internal/protolog/common/InvalidFormatStringException.java
similarity index 89%
rename from services/core/java/com/android/server/protolog/common/InvalidFormatStringException.java
rename to core/java/com/android/internal/protolog/common/InvalidFormatStringException.java
index 947bf98..97d3dfb 100644
--- a/services/core/java/com/android/server/protolog/common/InvalidFormatStringException.java
+++ b/core/java/com/android/internal/protolog/common/InvalidFormatStringException.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2019 The Android Open Source Project
+ * 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.
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.server.protolog.common;
+package com.android.internal.protolog.common;
 
 /**
  * Unsupported/invalid message format string error.
diff --git a/services/core/java/com/android/server/protolog/common/LogDataType.java b/core/java/com/android/internal/protolog/common/LogDataType.java
similarity index 96%
rename from services/core/java/com/android/server/protolog/common/LogDataType.java
rename to core/java/com/android/internal/protolog/common/LogDataType.java
index e73b41a..651932a 100644
--- a/services/core/java/com/android/server/protolog/common/LogDataType.java
+++ b/core/java/com/android/internal/protolog/common/LogDataType.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2019 The Android Open Source Project
+ * 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.
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.server.protolog.common;
+package com.android.internal.protolog.common;
 
 import java.util.ArrayList;
 import java.util.List;
diff --git a/services/core/java/com/android/server/protolog/common/ProtoLog.java b/core/java/com/android/internal/protolog/common/ProtoLog.java
similarity index 97%
rename from services/core/java/com/android/server/protolog/common/ProtoLog.java
rename to core/java/com/android/internal/protolog/common/ProtoLog.java
index b631bcb..ab58d35 100644
--- a/services/core/java/com/android/server/protolog/common/ProtoLog.java
+++ b/core/java/com/android/internal/protolog/common/ProtoLog.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2019 The Android Open Source Project
+ * 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.
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.server.protolog.common;
+package com.android.internal.protolog.common;
 
 /**
  * ProtoLog API - exposes static logging methods. Usage of this API is similar
diff --git a/core/java/com/android/internal/telephony/IPhoneStateListener.aidl b/core/java/com/android/internal/telephony/IPhoneStateListener.aidl
index b2c5a99..d41d307 100644
--- a/core/java/com/android/internal/telephony/IPhoneStateListener.aidl
+++ b/core/java/com/android/internal/telephony/IPhoneStateListener.aidl
@@ -61,7 +61,7 @@
     void onRadioPowerStateChanged(in int state);
     void onCallAttributesChanged(in CallAttributes callAttributes);
     void onEmergencyNumberListChanged(in Map emergencyNumberList);
-    void onOutgoingEmergencyCall(in EmergencyNumber placedEmergencyNumber);
+    void onOutgoingEmergencyCall(in EmergencyNumber placedEmergencyNumber, int subscriptionId);
     void onOutgoingEmergencySms(in EmergencyNumber sentEmergencyNumber);
     void onCallDisconnectCauseChanged(in int disconnectCause, in int preciseDisconnectCause);
     void onImsCallDisconnectCauseChanged(in ImsReasonInfo imsReasonInfo);
diff --git a/core/java/com/android/internal/telephony/ITelephonyRegistry.aidl b/core/java/com/android/internal/telephony/ITelephonyRegistry.aidl
index 5a04992..ea09fc8 100644
--- a/core/java/com/android/internal/telephony/ITelephonyRegistry.aidl
+++ b/core/java/com/android/internal/telephony/ITelephonyRegistry.aidl
@@ -65,9 +65,7 @@
     void notifyDataActivity(int state);
     void notifyDataActivityForSubscriber(in int subId, int state);
     void notifyDataConnectionForSubscriber(
-            int phoneId, int subId, int apnType, in PreciseDataConnectionState preciseState);
-    @UnsupportedAppUsage
-    void notifyDataConnectionFailed(String apnType);
+            int phoneId, int subId, in PreciseDataConnectionState preciseState);
     // Uses CellIdentity which is Parcelable here; will convert to CellLocation in client.
     void notifyCellLocation(in CellIdentity cellLocation);
     void notifyCellLocationForSubscriber(in int subId, in CellIdentity cellLocation);
@@ -77,8 +75,6 @@
             int foregroundCallState, int backgroundCallState);
     void notifyDisconnectCause(int phoneId, int subId, int disconnectCause,
             int preciseDisconnectCause);
-    void notifyPreciseDataConnectionFailed(int phoneId, int subId, int apnType, String apn,
-            int failCause);
     void notifyCellInfoForSubscriber(in int subId, in List<CellInfo> cellInfo);
     void notifySrvccStateChanged(in int subId, in int lteState);
     void notifySimActivationStateChangedForPhoneId(in int phoneId, in int subId,
diff --git a/core/java/com/android/internal/util/Preconditions.java b/core/java/com/android/internal/util/Preconditions.java
index 330c15c..937b942 100644
--- a/core/java/com/android/internal/util/Preconditions.java
+++ b/core/java/com/android/internal/util/Preconditions.java
@@ -187,6 +187,46 @@
     }
 
     /**
+     * Ensures the truth of an expression involving whether the calling identity is authorized to
+     * call the calling method.
+     *
+     * @param expression a boolean expression
+     * @throws SecurityException if {@code expression} is false
+     */
+    public static void checkCallAuthorization(final boolean expression) {
+        if (!expression) {
+            throw new SecurityException("Calling identity is not authorized");
+        }
+    }
+
+    /**
+     * Ensures the truth of an expression involving whether the calling identity is authorized to
+     * call the calling method.
+     *
+     * @param expression a boolean expression
+     * @param message the message of the security exception to be thrown
+     * @throws SecurityException if {@code expression} is false
+     */
+    public static void checkSecurity(final boolean expression, final String message) {
+        if (!expression) {
+            throw new SecurityException(message);
+        }
+    }
+
+    /**
+     * Ensures the truth of an expression involving whether the calling user is authorized to
+     * call the calling method.
+     *
+     * @param expression a boolean expression
+     * @throws SecurityException if {@code expression} is false
+     */
+    public static void checkCallingUser(final boolean expression) {
+        if (!expression) {
+            throw new SecurityException("Calling user is not authorized");
+        }
+    }
+
+    /**
      * Check the requested flags, throwing if any requested flags are outside
      * the allowed set.
      *
diff --git a/core/java/com/android/internal/util/ScreenshotHelper.java b/core/java/com/android/internal/util/ScreenshotHelper.java
index 9bf0513..a23fc4b 100644
--- a/core/java/com/android/internal/util/ScreenshotHelper.java
+++ b/core/java/com/android/internal/util/ScreenshotHelper.java
@@ -291,7 +291,7 @@
             };
 
             Message msg = Message.obtain(null, screenshotType, screenshotRequest);
-            final ServiceConnection myConn = mScreenshotConnection;
+
             Handler h = new Handler(handler.getLooper()) {
                 @Override
                 public void handleMessage(Message msg) {
@@ -304,8 +304,8 @@
                             break;
                         case SCREENSHOT_MSG_PROCESS_COMPLETE:
                             synchronized (mScreenshotLock) {
-                                if (myConn != null && mScreenshotConnection == myConn) {
-                                    mContext.unbindService(myConn);
+                                if (mScreenshotConnection != null) {
+                                    mContext.unbindService(mScreenshotConnection);
                                     mScreenshotConnection = null;
                                     mScreenshotService = null;
                                 }
@@ -368,6 +368,7 @@
                 }
             } else {
                 Messenger messenger = new Messenger(mScreenshotService);
+
                 try {
                     messenger.send(msg);
                 } catch (RemoteException e) {
diff --git a/core/java/com/android/internal/util/StatLogger.java b/core/java/com/android/internal/util/StatLogger.java
index 29568d5..2d65090 100644
--- a/core/java/com/android/internal/util/StatLogger.java
+++ b/core/java/com/android/internal/util/StatLogger.java
@@ -84,7 +84,7 @@
      * give it back to the {@link #logDurationStat(int, long)}} after the event.
      */
     public long getTime() {
-        return SystemClock.elapsedRealtimeNanos() / 1000;
+        return SystemClock.uptimeNanos() / 1000;
     }
 
     /**
diff --git a/core/java/com/android/internal/widget/ConversationLayout.java b/core/java/com/android/internal/widget/ConversationLayout.java
index 3332143..289a36f 100644
--- a/core/java/com/android/internal/widget/ConversationLayout.java
+++ b/core/java/com/android/internal/widget/ConversationLayout.java
@@ -168,8 +168,6 @@
     private int mFacePileProtectionWidthExpanded;
     private boolean mImportantConversation;
     private TextView mUnreadBadge;
-    private ViewGroup mAppOps;
-    private Rect mAppOpsTouchRect = new Rect();
     private View mFeedbackIcon;
     private float mMinTouchSize;
     private Icon mConversationIcon;
@@ -214,7 +212,6 @@
         mConversationIconView = findViewById(R.id.conversation_icon);
         mConversationIconContainer = findViewById(R.id.conversation_icon_container);
         mIcon = findViewById(R.id.icon);
-        mAppOps = findViewById(com.android.internal.R.id.app_ops);
         mFeedbackIcon = findViewById(com.android.internal.R.id.feedback);
         mMinTouchSize = 48 * getResources().getDisplayMetrics().density;
         mImportanceRingView = findViewById(R.id.conversation_icon_badge_ring);
@@ -1174,43 +1171,6 @@
             });
         }
         mTouchDelegate.clear();
-        if (mAppOps.getWidth() > 0) {
-
-            // Let's increase the touch size of the app ops view if it's here
-            mAppOpsTouchRect.set(
-                    mAppOps.getLeft(),
-                    mAppOps.getTop(),
-                    mAppOps.getRight(),
-                    mAppOps.getBottom());
-            for (int i = 0; i < mAppOps.getChildCount(); i++) {
-                View child = mAppOps.getChildAt(i);
-                if (child.getVisibility() == GONE) {
-                    continue;
-                }
-                // Make sure each child has at least a minTouchSize touch target around it
-                float childTouchLeft = child.getLeft() + child.getWidth() / 2.0f
-                        - mMinTouchSize / 2.0f;
-                float childTouchRight = childTouchLeft + mMinTouchSize;
-                mAppOpsTouchRect.left = (int) Math.min(mAppOpsTouchRect.left,
-                        mAppOps.getLeft() + childTouchLeft);
-                mAppOpsTouchRect.right = (int) Math.max(mAppOpsTouchRect.right,
-                        mAppOps.getLeft() + childTouchRight);
-            }
-
-            // Increase the height
-            int heightIncrease = 0;
-            if (mAppOpsTouchRect.height() < mMinTouchSize) {
-                heightIncrease = (int) Math.ceil((mMinTouchSize - mAppOpsTouchRect.height())
-                        / 2.0f);
-            }
-            mAppOpsTouchRect.inset(0, -heightIncrease);
-
-            getRelativeTouchRect(mAppOpsTouchRect, mAppOps);
-
-            // Extend the size of the app opps to be at least 48dp
-            mTouchDelegate.add(new TouchDelegate(mAppOpsTouchRect, mAppOps));
-
-        }
         if (mFeedbackIcon.getVisibility() == VISIBLE) {
             updateFeedbackIconMargins();
             float width = Math.max(mMinTouchSize, mFeedbackIcon.getWidth());
@@ -1240,13 +1200,7 @@
 
     private void updateFeedbackIconMargins() {
         MarginLayoutParams lp = (MarginLayoutParams) mFeedbackIcon.getLayoutParams();
-        if (mAppOps.getWidth() == 0) {
-            lp.setMarginStart(mNotificationHeaderSeparatingMargin);
-        } else {
-            float width = Math.max(mMinTouchSize, mFeedbackIcon.getWidth());
-            int horizontalMargin = (int) ((width - mFeedbackIcon.getWidth()) / 2);
-            lp.setMarginStart(horizontalMargin);
-        }
+        lp.setMarginStart(mNotificationHeaderSeparatingMargin);
         mFeedbackIcon.setLayoutParams(lp);
     }
 
diff --git a/core/java/com/android/internal/widget/ILockSettings.aidl b/core/java/com/android/internal/widget/ILockSettings.aidl
index e35fda1..654b461 100644
--- a/core/java/com/android/internal/widget/ILockSettings.aidl
+++ b/core/java/com/android/internal/widget/ILockSettings.aidl
@@ -47,8 +47,10 @@
     void resetKeyStore(int userId);
     VerifyCredentialResponse checkCredential(in LockscreenCredential credential, int userId,
             in ICheckCredentialProgressCallback progressCallback);
-    VerifyCredentialResponse verifyCredential(in LockscreenCredential credential, long challenge, int userId);
-    VerifyCredentialResponse verifyTiedProfileChallenge(in LockscreenCredential credential, long challenge, int userId);
+    VerifyCredentialResponse verifyCredential(in LockscreenCredential credential, int userId, int flags);
+    VerifyCredentialResponse verifyTiedProfileChallenge(in LockscreenCredential credential, int userId, int flags);
+    VerifyCredentialResponse verifyGatekeeperPasswordHandle(long gatekeeperPasswordHandle, long challenge, int userId);
+    void removeGatekeeperPasswordHandle(long gatekeeperPasswordHandle);
     boolean checkVoldPassword(int userId);
     int getCredentialType(int userId);
     byte[] getHashFactor(in LockscreenCredential currentCredential, int userId);
diff --git a/core/java/com/android/internal/widget/LockPatternChecker.java b/core/java/com/android/internal/widget/LockPatternChecker.java
index 85a45fd..5adbc58 100644
--- a/core/java/com/android/internal/widget/LockPatternChecker.java
+++ b/core/java/com/android/internal/widget/LockPatternChecker.java
@@ -1,5 +1,6 @@
 package com.android.internal.widget;
 
+import android.annotation.NonNull;
 import android.os.AsyncTask;
 
 import com.android.internal.widget.LockPatternUtils.RequestThrottledException;
@@ -41,11 +42,11 @@
         /**
          * Invoked when a security verification is finished.
          *
-         * @param attestation The attestation that the challenge was verified, or null.
+         * @param response The response, optionally containing Gatekeeper HAT or Gatekeeper Password
          * @param throttleTimeoutMs The amount of time in ms to wait before reattempting
-         * the call. Only non-0 if attestation is null.
+         * the call. Only non-0 if the response is {@link VerifyCredentialResponse#RESPONSE_RETRY}.
          */
-        void onVerified(byte[] attestation, int throttleTimeoutMs);
+        void onVerified(@NonNull VerifyCredentialResponse response, int throttleTimeoutMs);
     }
 
     /**
@@ -53,33 +54,27 @@
      *
      * @param utils The LockPatternUtils instance to use.
      * @param credential The credential to check.
-     * @param challenge The challenge to verify against the credential.
      * @param userId The user to check against the credential.
+     * @param flags See {@link LockPatternUtils.VerifyFlag}
      * @param callback The callback to be invoked with the verification result.
      */
     public static AsyncTask<?, ?, ?> verifyCredential(final LockPatternUtils utils,
             final LockscreenCredential credential,
-            final long challenge,
             final int userId,
+            final @LockPatternUtils.VerifyFlag int flags,
             final OnVerifyCallback callback) {
         // Create a copy of the credential since checking credential is asynchrounous.
         final LockscreenCredential credentialCopy = credential.duplicate();
-        AsyncTask<Void, Void, byte[]> task = new AsyncTask<Void, Void, byte[]>() {
-            private int mThrottleTimeout;
-
+        AsyncTask<Void, Void, VerifyCredentialResponse> task =
+                new AsyncTask<Void, Void, VerifyCredentialResponse>() {
             @Override
-            protected byte[] doInBackground(Void... args) {
-                try {
-                    return utils.verifyCredential(credentialCopy, challenge, userId);
-                } catch (RequestThrottledException ex) {
-                    mThrottleTimeout = ex.getTimeoutMs();
-                    return null;
-                }
+            protected VerifyCredentialResponse doInBackground(Void... args) {
+                return utils.verifyCredential(credentialCopy, userId, flags);
             }
 
             @Override
-            protected void onPostExecute(byte[] result) {
-                callback.onVerified(result, mThrottleTimeout);
+            protected void onPostExecute(@NonNull VerifyCredentialResponse result) {
+                callback.onVerified(result, result.getTimeout());
                 credentialCopy.zeroize();
             }
 
@@ -141,33 +136,27 @@
      *
      * @param utils The LockPatternUtils instance to use.
      * @param credential The credential to check.
-     * @param challenge The challenge to verify against the credential.
      * @param userId The user to check against the credential.
+     * @param flags See {@link LockPatternUtils.VerifyFlag}
      * @param callback The callback to be invoked with the verification result.
      */
     public static AsyncTask<?, ?, ?> verifyTiedProfileChallenge(final LockPatternUtils utils,
             final LockscreenCredential credential,
-            final long challenge,
             final int userId,
+            final @LockPatternUtils.VerifyFlag int flags,
             final OnVerifyCallback callback) {
-        // Create a copy of the credential since checking credential is asynchrounous.
+        // Create a copy of the credential since checking credential is asynchronous.
         final LockscreenCredential credentialCopy = credential.duplicate();
-        AsyncTask<Void, Void, byte[]> task = new AsyncTask<Void, Void, byte[]>() {
-            private int mThrottleTimeout;
-
+        AsyncTask<Void, Void, VerifyCredentialResponse> task =
+                new AsyncTask<Void, Void, VerifyCredentialResponse>() {
             @Override
-            protected byte[] doInBackground(Void... args) {
-                try {
-                    return utils.verifyTiedProfileChallenge(credentialCopy, challenge, userId);
-                } catch (RequestThrottledException ex) {
-                    mThrottleTimeout = ex.getTimeoutMs();
-                    return null;
-                }
+            protected VerifyCredentialResponse doInBackground(Void... args) {
+                return utils.verifyTiedProfileChallenge(credentialCopy, userId, flags);
             }
 
             @Override
-            protected void onPostExecute(byte[] result) {
-                callback.onVerified(result, mThrottleTimeout);
+            protected void onPostExecute(@NonNull VerifyCredentialResponse response) {
+                callback.onVerified(response, response.getTimeout());
                 credentialCopy.zeroize();
             }
 
diff --git a/core/java/com/android/internal/widget/LockPatternUtils.java b/core/java/com/android/internal/widget/LockPatternUtils.java
index 93690cd..960c11f 100644
--- a/core/java/com/android/internal/widget/LockPatternUtils.java
+++ b/core/java/com/android/internal/widget/LockPatternUtils.java
@@ -130,6 +130,19 @@
     public @interface CredentialType {}
 
     /**
+     * Flag provided to {@link #verifyCredential(LockscreenCredential, int, int)} . If set, the
+     * method will return a handle to the Gatekeeper Password in the
+     * {@link VerifyCredentialResponse}.
+     */
+    public static final int VERIFY_FLAG_REQUEST_GK_PW_HANDLE = 1 << 0;
+
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(flag = true, value = {
+            VERIFY_FLAG_REQUEST_GK_PW_HANDLE
+    })
+    public @interface VerifyFlag {}
+
+    /**
      * Special user id for triggering the FRP verification flow.
      */
     public static final int USER_FRP = UserHandle.USER_NULL + 1;
@@ -374,29 +387,54 @@
      * If credential matches, return an opaque attestation that the challenge was verified.
      *
      * @param credential The credential to check.
-     * @param challenge The challenge to verify against the credential
      * @param userId The user whose credential is being verified
-     * @return the attestation that the challenge was verified, or null
-     * @throws RequestThrottledException if credential verification is being throttled due to
-     *         to many incorrect attempts.
+     * @param flags See {@link VerifyFlag}
      * @throws IllegalStateException if called on the main thread.
      */
-    public byte[] verifyCredential(@NonNull LockscreenCredential credential, long challenge,
-            int userId) throws RequestThrottledException {
+    @NonNull
+    public VerifyCredentialResponse verifyCredential(@NonNull LockscreenCredential credential,
+            int userId, @VerifyFlag int flags) {
         throwIfCalledOnMainThread();
         try {
-            VerifyCredentialResponse response = getLockSettings().verifyCredential(
-                    credential, challenge, userId);
-            if (response.getResponseCode() == VerifyCredentialResponse.RESPONSE_OK) {
-                return response.getPayload();
-            } else if (response.getResponseCode() == VerifyCredentialResponse.RESPONSE_RETRY) {
-                throw new RequestThrottledException(response.getTimeout());
+            final VerifyCredentialResponse response = getLockSettings().verifyCredential(
+                    credential, userId, flags);
+            if (response == null) {
+                return VerifyCredentialResponse.ERROR;
             } else {
-                return null;
+                return response;
             }
         } catch (RemoteException re) {
             Log.e(TAG, "failed to verify credential", re);
-            return null;
+            return VerifyCredentialResponse.ERROR;
+        }
+    }
+
+    /**
+     * With the Gatekeeper Password Handle returned via {@link #verifyCredential(
+     * LockscreenCredential, int, int)}, request Gatekeeper to create a HardwareAuthToken wrapping
+     * the given challenge.
+     */
+    @NonNull
+    public VerifyCredentialResponse verifyGatekeeperPasswordHandle(long gatekeeperPasswordHandle,
+            long challenge, int userId) {
+        try {
+            final VerifyCredentialResponse response = getLockSettings()
+                    .verifyGatekeeperPasswordHandle(gatekeeperPasswordHandle, challenge, userId);
+            if (response == null) {
+                return VerifyCredentialResponse.ERROR;
+            }
+            return response;
+        } catch (RemoteException e) {
+            Log.e(TAG, "failed to verify gatekeeper password", e);
+            return VerifyCredentialResponse.ERROR;
+        }
+    }
+
+    public void removeGatekeeperPasswordHandle(long gatekeeperPasswordHandle) {
+        try {
+            getLockSettings().removeGatekeeperPasswordHandle(gatekeeperPasswordHandle);
+        } catch (RemoteException e) {
+            Log.e(TAG, "failed to remove gatekeeper password handle", e);
         }
     }
 
@@ -418,8 +456,9 @@
         try {
             VerifyCredentialResponse response = getLockSettings().checkCredential(
                     credential, userId, wrapCallback(progressCallback));
-
-            if (response.getResponseCode() == VerifyCredentialResponse.RESPONSE_OK) {
+            if (response == null) {
+                return false;
+            } else if (response.getResponseCode() == VerifyCredentialResponse.RESPONSE_OK) {
                 return true;
             } else if (response.getResponseCode() == VerifyCredentialResponse.RESPONSE_RETRY) {
                 throw new RequestThrottledException(response.getTimeout());
@@ -439,30 +478,26 @@
      * verified.
      *
      * @param credential The parent user's credential to check.
-     * @param challenge The challenge to verify against the credential
      * @return the attestation that the challenge was verified, or null
      * @param userId The managed profile user id
-     * @throws RequestThrottledException if credential verification is being throttled due to
-     *         to many incorrect attempts.
+     * @param flags See {@link VerifyFlag}
      * @throws IllegalStateException if called on the main thread.
      */
-    public byte[] verifyTiedProfileChallenge(@NonNull LockscreenCredential credential,
-            long challenge, int userId) throws RequestThrottledException {
+    @NonNull
+    public VerifyCredentialResponse verifyTiedProfileChallenge(
+            @NonNull LockscreenCredential credential, int userId, @VerifyFlag int flags) {
         throwIfCalledOnMainThread();
         try {
-            VerifyCredentialResponse response =
-                    getLockSettings().verifyTiedProfileChallenge(credential, challenge, userId);
-
-            if (response.getResponseCode() == VerifyCredentialResponse.RESPONSE_OK) {
-                return response.getPayload();
-            } else if (response.getResponseCode() == VerifyCredentialResponse.RESPONSE_RETRY) {
-                throw new RequestThrottledException(response.getTimeout());
+            final VerifyCredentialResponse response = getLockSettings()
+                    .verifyTiedProfileChallenge(credential, userId, flags);
+            if (response == null) {
+                return VerifyCredentialResponse.ERROR;
             } else {
-                return null;
+                return response;
             }
         } catch (RemoteException re) {
             Log.e(TAG, "failed to verify tied profile credential", re);
-            return null;
+            return VerifyCredentialResponse.ERROR;
         }
     }
 
diff --git a/core/java/com/android/internal/widget/LockscreenCredential.java b/core/java/com/android/internal/widget/LockscreenCredential.java
index 55f30fb..a488449 100644
--- a/core/java/com/android/internal/widget/LockscreenCredential.java
+++ b/core/java/com/android/internal/widget/LockscreenCredential.java
@@ -48,7 +48,7 @@
  *     // Process the credential in some way
  * }
  * </pre>
- * With this construct, we can garantee that there will be no copies of the password left in
+ * With this construct, we can guarantee that there will be no copies of the password left in
  * memory when the credential goes out of scope. This should help mitigate certain class of
  * attacks where the attcker gains read-only access to full device memory (cold boot attack,
  * unsecured software/hardware memory dumping interfaces such as JTAG).
diff --git a/core/java/com/android/internal/widget/VerifyCredentialResponse.java b/core/java/com/android/internal/widget/VerifyCredentialResponse.java
index 7d1c706..ab14634 100644
--- a/core/java/com/android/internal/widget/VerifyCredentialResponse.java
+++ b/core/java/com/android/internal/widget/VerifyCredentialResponse.java
@@ -16,11 +16,16 @@
 
 package com.android.internal.widget;
 
+import android.annotation.IntDef;
+import android.annotation.Nullable;
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.service.gatekeeper.GateKeeperResponse;
 import android.util.Slog;
 
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
 /**
  * Response object for a ILockSettings credential verification request.
  * @hide
@@ -30,78 +35,114 @@
     public static final int RESPONSE_ERROR = -1;
     public static final int RESPONSE_OK = 0;
     public static final int RESPONSE_RETRY = 1;
+    @IntDef({RESPONSE_ERROR,
+            RESPONSE_OK,
+            RESPONSE_RETRY})
+    @Retention(RetentionPolicy.SOURCE)
+    @interface ResponseCode {}
 
-    public static final VerifyCredentialResponse OK = new VerifyCredentialResponse();
-    public static final VerifyCredentialResponse ERROR
-            = new VerifyCredentialResponse(RESPONSE_ERROR, 0, null);
+    public static final VerifyCredentialResponse OK = new VerifyCredentialResponse.Builder()
+            .build();
+    public static final VerifyCredentialResponse ERROR = fromError();
     private static final String TAG = "VerifyCredentialResponse";
 
-    private int mResponseCode;
-    private byte[] mPayload;
-    private int mTimeout;
+    private final @ResponseCode int mResponseCode;
+    private final int mTimeout;
+    @Nullable private final byte[] mGatekeeperHAT;
+    private final long mGatekeeperPasswordHandle;
 
     public static final Parcelable.Creator<VerifyCredentialResponse> CREATOR
             = new Parcelable.Creator<VerifyCredentialResponse>() {
         @Override
         public VerifyCredentialResponse createFromParcel(Parcel source) {
-            int responseCode = source.readInt();
-            VerifyCredentialResponse response = new VerifyCredentialResponse(responseCode, 0, null);
-            if (responseCode == RESPONSE_RETRY) {
-                response.setTimeout(source.readInt());
-            } else if (responseCode == RESPONSE_OK) {
-                int size = source.readInt();
-                if (size > 0) {
-                    byte[] payload = new byte[size];
-                    source.readByteArray(payload);
-                    response.setPayload(payload);
-                }
-            }
-            return response;
+            final @ResponseCode int responseCode = source.readInt();
+            final int timeout = source.readInt();
+            final byte[] gatekeeperHAT = source.createByteArray();
+            long gatekeeperPasswordHandle = source.readLong();
+
+            return new VerifyCredentialResponse(responseCode, timeout, gatekeeperHAT,
+                    gatekeeperPasswordHandle);
         }
 
         @Override
         public VerifyCredentialResponse[] newArray(int size) {
             return new VerifyCredentialResponse[size];
         }
-
     };
 
-    public VerifyCredentialResponse() {
-        mResponseCode = RESPONSE_OK;
-        mPayload = null;
+    public static class Builder {
+        @Nullable private byte[] mGatekeeperHAT;
+        private long mGatekeeperPasswordHandle;
+
+        /**
+         * @param gatekeeperHAT Gatekeeper HardwareAuthToken, minted upon successful authentication.
+         */
+        public Builder setGatekeeperHAT(byte[] gatekeeperHAT) {
+            mGatekeeperHAT = gatekeeperHAT;
+            return this;
+        }
+
+        public Builder setGatekeeperPasswordHandle(long gatekeeperPasswordHandle) {
+            mGatekeeperPasswordHandle = gatekeeperPasswordHandle;
+            return this;
+        }
+
+        /**
+         * Builds a VerifyCredentialResponse with {@link #RESPONSE_OK} and any other parameters
+         * that were preveiously set.
+         * @return
+         */
+        public VerifyCredentialResponse build() {
+            return new VerifyCredentialResponse(RESPONSE_OK,
+                    0 /* timeout */,
+                    mGatekeeperHAT,
+                    mGatekeeperPasswordHandle);
+        }
     }
 
-
-    public VerifyCredentialResponse(byte[] payload) {
-        mPayload = payload;
-        mResponseCode = RESPONSE_OK;
+    /**
+     * Since timeouts are always an error, provide a way to create the VerifyCredentialResponse
+     * object directly. None of the other fields (Gatekeeper HAT, Gatekeeper Password, etc)
+     * are valid in this case. Similarly, the response code will always be
+     * {@link #RESPONSE_RETRY}.
+     */
+    public static VerifyCredentialResponse fromTimeout(int timeout) {
+        return new VerifyCredentialResponse(RESPONSE_RETRY,
+                timeout,
+                null /* gatekeeperHAT */,
+                0L /* gatekeeperPasswordHandle */);
     }
 
-    public VerifyCredentialResponse(int timeout) {
-        mTimeout = timeout;
-        mResponseCode = RESPONSE_RETRY;
-        mPayload = null;
+    /**
+     * Since error (incorrect password) should never result in any of the other fields from
+     * being populated, provide a default method to return a VerifyCredentialResponse.
+     */
+    public static VerifyCredentialResponse fromError() {
+        return new VerifyCredentialResponse(RESPONSE_ERROR,
+                0 /* timeout */,
+                null /* gatekeeperHAT */,
+                0L /* gatekeeperPasswordHandle */);
     }
 
-    private VerifyCredentialResponse(int responseCode, int timeout, byte[] payload) {
+    private VerifyCredentialResponse(@ResponseCode int responseCode, int timeout,
+            @Nullable byte[] gatekeeperHAT, long gatekeeperPasswordHandle) {
         mResponseCode = responseCode;
         mTimeout = timeout;
-        mPayload = payload;
+        mGatekeeperHAT = gatekeeperHAT;
+        mGatekeeperPasswordHandle = gatekeeperPasswordHandle;
+    }
+
+    public VerifyCredentialResponse stripPayload() {
+        return new VerifyCredentialResponse(mResponseCode, mTimeout,
+                null /* gatekeeperHAT */, 0L /* gatekeeperPasswordHandle */);
     }
 
     @Override
     public void writeToParcel(Parcel dest, int flags) {
         dest.writeInt(mResponseCode);
-        if (mResponseCode == RESPONSE_RETRY) {
-            dest.writeInt(mTimeout);
-        } else if (mResponseCode == RESPONSE_OK) {
-            if (mPayload != null) {
-                dest.writeInt(mPayload.length);
-                dest.writeByteArray(mPayload);
-            } else {
-                dest.writeInt(0);
-            }
-        }
+        dest.writeInt(mTimeout);
+        dest.writeByteArray(mGatekeeperHAT);
+        dest.writeLong(mGatekeeperPasswordHandle);
     }
 
     @Override
@@ -109,48 +150,54 @@
         return 0;
     }
 
-    public byte[] getPayload() {
-        return mPayload;
+    @Nullable
+    public byte[] getGatekeeperHAT() {
+        return mGatekeeperHAT;
+    }
+
+    public long getGatekeeperPasswordHandle() {
+        return mGatekeeperPasswordHandle;
+    }
+
+    public boolean containsGatekeeperPasswordHandle() {
+        return mGatekeeperPasswordHandle != 0L;
     }
 
     public int getTimeout() {
         return mTimeout;
     }
 
-    public int getResponseCode() {
+    public @ResponseCode int getResponseCode() {
         return mResponseCode;
     }
 
-    private void setTimeout(int timeout) {
-        mTimeout = timeout;
+    public boolean isMatched() {
+        return mResponseCode == RESPONSE_OK;
     }
 
-    private void setPayload(byte[] payload) {
-        mPayload = payload;
-    }
-
-    public VerifyCredentialResponse stripPayload() {
-        return new VerifyCredentialResponse(mResponseCode, mTimeout, new byte[0]);
+    @Override
+    public String toString() {
+        return "Response: " + mResponseCode
+                + ", GK HAT: " + (mGatekeeperHAT != null)
+                + ", GK PW: " + (mGatekeeperPasswordHandle != 0L);
     }
 
     public static VerifyCredentialResponse fromGateKeeperResponse(
             GateKeeperResponse gateKeeperResponse) {
-        VerifyCredentialResponse response;
         int responseCode = gateKeeperResponse.getResponseCode();
         if (responseCode == GateKeeperResponse.RESPONSE_RETRY) {
-            response = new VerifyCredentialResponse(gateKeeperResponse.getTimeout());
+            return fromTimeout(gateKeeperResponse.getTimeout());
         } else if (responseCode == GateKeeperResponse.RESPONSE_OK) {
             byte[] token = gateKeeperResponse.getPayload();
             if (token == null) {
                 // something's wrong if there's no payload with a challenge
                 Slog.e(TAG, "verifyChallenge response had no associated payload");
-                response = VerifyCredentialResponse.ERROR;
+                return fromError();
             } else {
-                response = new VerifyCredentialResponse(token);
+                return new VerifyCredentialResponse.Builder().setGatekeeperHAT(token).build();
             }
         } else {
-            response = VerifyCredentialResponse.ERROR;
+            return fromError();
         }
-        return response;
     }
 }
diff --git a/core/java/com/android/server/SystemConfig.java b/core/java/com/android/server/SystemConfig.java
index c6a1153..6b754ca 100644
--- a/core/java/com/android/server/SystemConfig.java
+++ b/core/java/com/android/server/SystemConfig.java
@@ -19,6 +19,7 @@
 import static com.android.internal.util.ArrayUtils.appendInt;
 
 import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.app.ActivityManager;
 import android.content.ComponentName;
 import android.content.pm.FeatureInfo;
@@ -245,6 +246,14 @@
      */
     private Map<String, Map<String, String>> mNamedActors = null;
 
+    // Package name of the package pre-installed on a read-only
+    // partition that is used to verify if an overlay package fulfills
+    // the 'config_signature' policy by comparing their signatures:
+    // if the overlay package is signed with the same certificate as
+    // the package declared in 'config-signature' tag, then the
+    // overlay package fulfills the 'config_signature' policy.
+    private String mOverlayConfigSignaturePackage;
+
     public static SystemConfig getInstance() {
         if (!isSystemProcess()) {
             Slog.wtf(TAG, "SystemConfig is being accessed by a process other than "
@@ -432,6 +441,12 @@
         return mNamedActors != null ? mNamedActors : Collections.emptyMap();
     }
 
+    @Nullable
+    public String getOverlayConfigSignaturePackage() {
+        return TextUtils.isEmpty(mOverlayConfigSignaturePackage)
+                ? null : mOverlayConfigSignaturePackage;
+    }
+
     /**
      * Only use for testing. Do NOT use in production code.
      * @param readPermissions false to create an empty SystemConfig; true to read the permissions.
@@ -1137,6 +1152,27 @@
                         }
                         XmlUtils.skipCurrentTag(parser);
                     } break;
+                    case "overlay-config-signature": {
+                        if (allowAll) {
+                            String pkgName = parser.getAttributeValue(null, "package");
+                            if (pkgName == null) {
+                                Slog.w(TAG, "<" + name + "> without package in " + permFile
+                                        + " at " + parser.getPositionDescription());
+                            } else {
+                                if (TextUtils.isEmpty(mOverlayConfigSignaturePackage)) {
+                                    mOverlayConfigSignaturePackage = pkgName.intern();
+                                } else {
+                                    throw new IllegalStateException("Reference signature package "
+                                                  + "defined as both "
+                                                  + mOverlayConfigSignaturePackage
+                                                  + " and " + pkgName);
+                                }
+                            }
+                        } else {
+                            logNotAllowedInPartition(name, permFile, parser);
+                        }
+                        XmlUtils.skipCurrentTag(parser);
+                    } break;
                     case "rollback-whitelisted-app": {
                         String pkgname = parser.getAttributeValue(null, "package");
                         if (pkgname == null) {
diff --git a/core/jni/OWNERS b/core/jni/OWNERS
index d7d8621..7d80993 100644
--- a/core/jni/OWNERS
+++ b/core/jni/OWNERS
@@ -17,4 +17,4 @@
 per-file android_view_PointerIcon.* = michaelwr@google.com, svv@google.com
 
 # Zygote
-per-file com_android_internal_os_Zygote.*,fd_utils.* = chriswailes@google.com, ngeoffray@google.com, sehr@google.com, narayan@google.com, maco@google.com
+per-file com_android_internal_os_Zygote.*,fd_utils.* = calin@google.com, chriswailes@google.com, maco@google.com, narayan@google.com, ngeoffray@google.com
diff --git a/core/jni/android_hardware_input_InputApplicationHandle.cpp b/core/jni/android_hardware_input_InputApplicationHandle.cpp
index ff73c74..7756a62 100644
--- a/core/jni/android_hardware_input_InputApplicationHandle.cpp
+++ b/core/jni/android_hardware_input_InputApplicationHandle.cpp
@@ -30,7 +30,7 @@
 static struct {
     jfieldID ptr;
     jfieldID name;
-    jfieldID dispatchingTimeoutNanos;
+    jfieldID dispatchingTimeoutMillis;
     jfieldID token;
 } gInputApplicationHandleClassInfo;
 
@@ -61,8 +61,8 @@
 
     mInfo.name = getStringField(env, obj, gInputApplicationHandleClassInfo.name, "<null>");
 
-    mInfo.dispatchingTimeoutNanos =
-            env->GetLongField(obj, gInputApplicationHandleClassInfo.dispatchingTimeoutNanos);
+    mInfo.dispatchingTimeoutMillis =
+            env->GetLongField(obj, gInputApplicationHandleClassInfo.dispatchingTimeoutMillis);
 
     jobject tokenObj = env->GetObjectField(obj,
             gInputApplicationHandleClassInfo.token);
@@ -144,9 +144,8 @@
     GET_FIELD_ID(gInputApplicationHandleClassInfo.name, clazz,
             "name", "Ljava/lang/String;");
 
-    GET_FIELD_ID(gInputApplicationHandleClassInfo.dispatchingTimeoutNanos,
-            clazz,
-            "dispatchingTimeoutNanos", "J");
+    GET_FIELD_ID(gInputApplicationHandleClassInfo.dispatchingTimeoutMillis, clazz,
+                 "dispatchingTimeoutMillis", "J");
 
     GET_FIELD_ID(gInputApplicationHandleClassInfo.token, clazz,
             "token", "Landroid/os/IBinder;");
diff --git a/core/jni/android_hardware_input_InputWindowHandle.cpp b/core/jni/android_hardware_input_InputWindowHandle.cpp
index 796c5c4..a063820 100644
--- a/core/jni/android_hardware_input_InputWindowHandle.cpp
+++ b/core/jni/android_hardware_input_InputWindowHandle.cpp
@@ -47,7 +47,7 @@
     jfieldID name;
     jfieldID layoutParamsFlags;
     jfieldID layoutParamsType;
-    jfieldID dispatchingTimeoutNanos;
+    jfieldID dispatchingTimeoutMillis;
     jfieldID frameLeft;
     jfieldID frameTop;
     jfieldID frameRight;
@@ -56,8 +56,7 @@
     jfieldID scaleFactor;
     jfieldID touchableRegion;
     jfieldID visible;
-    jfieldID canReceiveKeys;
-    jfieldID hasFocus;
+    jfieldID focusable;
     jfieldID hasWallpaper;
     jfieldID paused;
     jfieldID trustedOverlay;
@@ -118,8 +117,8 @@
             env->GetIntField(obj, gInputWindowHandleClassInfo.layoutParamsFlags));
     mInfo.type = static_cast<InputWindowInfo::Type>(
             env->GetIntField(obj, gInputWindowHandleClassInfo.layoutParamsType));
-    mInfo.dispatchingTimeout = decltype(mInfo.dispatchingTimeout)(
-            env->GetLongField(obj, gInputWindowHandleClassInfo.dispatchingTimeoutNanos));
+    mInfo.dispatchingTimeout = std::chrono::milliseconds(
+            env->GetLongField(obj, gInputWindowHandleClassInfo.dispatchingTimeoutMillis));
     mInfo.frameLeft = env->GetIntField(obj,
             gInputWindowHandleClassInfo.frameLeft);
     mInfo.frameTop = env->GetIntField(obj,
@@ -145,10 +144,7 @@
 
     mInfo.visible = env->GetBooleanField(obj,
             gInputWindowHandleClassInfo.visible);
-    mInfo.canReceiveKeys = env->GetBooleanField(obj,
-            gInputWindowHandleClassInfo.canReceiveKeys);
-    mInfo.hasFocus = env->GetBooleanField(obj,
-            gInputWindowHandleClassInfo.hasFocus);
+    mInfo.focusable = env->GetBooleanField(obj, gInputWindowHandleClassInfo.focusable);
     mInfo.hasWallpaper = env->GetBooleanField(obj,
             gInputWindowHandleClassInfo.hasWallpaper);
     mInfo.paused = env->GetBooleanField(obj,
@@ -293,8 +289,8 @@
     GET_FIELD_ID(gInputWindowHandleClassInfo.layoutParamsType, clazz,
             "layoutParamsType", "I");
 
-    GET_FIELD_ID(gInputWindowHandleClassInfo.dispatchingTimeoutNanos, clazz,
-            "dispatchingTimeoutNanos", "J");
+    GET_FIELD_ID(gInputWindowHandleClassInfo.dispatchingTimeoutMillis, clazz,
+                 "dispatchingTimeoutMillis", "J");
 
     GET_FIELD_ID(gInputWindowHandleClassInfo.frameLeft, clazz,
             "frameLeft", "I");
@@ -320,11 +316,7 @@
     GET_FIELD_ID(gInputWindowHandleClassInfo.visible, clazz,
             "visible", "Z");
 
-    GET_FIELD_ID(gInputWindowHandleClassInfo.canReceiveKeys, clazz,
-            "canReceiveKeys", "Z");
-
-    GET_FIELD_ID(gInputWindowHandleClassInfo.hasFocus, clazz,
-            "hasFocus", "Z");
+    GET_FIELD_ID(gInputWindowHandleClassInfo.focusable, clazz, "focusable", "Z");
 
     GET_FIELD_ID(gInputWindowHandleClassInfo.hasWallpaper, clazz,
             "hasWallpaper", "Z");
diff --git a/core/jni/android_media_AudioDeviceAttributes.cpp b/core/jni/android_media_AudioDeviceAttributes.cpp
index e79c95e..2a16dce 100644
--- a/core/jni/android_media_AudioDeviceAttributes.cpp
+++ b/core/jni/android_media_AudioDeviceAttributes.cpp
@@ -31,7 +31,7 @@
                                  const AudioDeviceTypeAddr *devTypeAddr) {
     jint jStatus = (jint)AUDIO_JAVA_SUCCESS;
     jint jNativeType = (jint)devTypeAddr->mType;
-    ScopedLocalRef<jstring> jAddress(env, env->NewStringUTF(devTypeAddr->mAddress.data()));
+    ScopedLocalRef<jstring> jAddress(env, env->NewStringUTF(devTypeAddr->getAddress()));
 
     *jAudioDeviceAttributes = env->NewObject(gAudioDeviceAttributesClass, gAudioDeviceAttributesCstor,
             jNativeType, jAddress.get());
diff --git a/core/jni/android_media_AudioSystem.cpp b/core/jni/android_media_AudioSystem.cpp
index 22bb210..3f39478 100644
--- a/core/jni/android_media_AudioSystem.cpp
+++ b/core/jni/android_media_AudioSystem.cpp
@@ -172,6 +172,8 @@
     jmethodID postRecordConfigEventFromNative;
 } gAudioPolicyEventHandlerMethods;
 
+static struct { jmethodID add; } gListMethods;
+
 //
 // JNI Initialization for OpenSLES routing
 //
@@ -310,7 +312,7 @@
 
 static jint getVectorOfAudioDeviceTypeAddr(JNIEnv *env, jintArray deviceTypes,
                                            jobjectArray deviceAddresses,
-                                           Vector<AudioDeviceTypeAddr> &audioDeviceTypeAddrVector) {
+                                           AudioDeviceTypeAddrVector &audioDeviceTypeAddrVector) {
     if (deviceTypes == nullptr || deviceAddresses == nullptr) {
         return (jint)AUDIO_JAVA_BAD_VALUE;
     }
@@ -337,7 +339,7 @@
         }
         const char *address = env->GetStringUTFChars((jstring)addrJobj, NULL);
         AudioDeviceTypeAddr dev = AudioDeviceTypeAddr(typesPtr[i], address);
-        audioDeviceTypeAddrVector.add(dev);
+        audioDeviceTypeAddrVector.push_back(dev);
         env->ReleaseStringUTFChars((jstring)addrJobj, address);
     }
     env->ReleaseIntArrayElements(deviceTypes, typesPtr, 0);
@@ -974,7 +976,7 @@
         if (jHandle == NULL) {
             return (jint)AUDIO_JAVA_ERROR;
         }
-        // create dummy port and port config objects with just the correct handle
+        // create placeholder port and port config objects with just the correct handle
         // and configuration data. The actual AudioPortConfig objects will be
         // constructed by java code with correct class type (device, mix etc...)
         // and reference to AudioPort instance in this client
@@ -2062,7 +2064,7 @@
 
 static jint android_media_AudioSystem_setUidDeviceAffinities(JNIEnv *env, jobject clazz,
         jint uid, jintArray deviceTypes, jobjectArray deviceAddresses) {
-    Vector<AudioDeviceTypeAddr> deviceVector;
+    AudioDeviceTypeAddrVector deviceVector;
     jint results = getVectorOfAudioDeviceTypeAddr(env, deviceTypes, deviceAddresses, deviceVector);
     if (results != NO_ERROR) {
         return results;
@@ -2080,7 +2082,7 @@
 static jint android_media_AudioSystem_setUserIdDeviceAffinities(JNIEnv *env, jobject clazz,
                                                                 jint userId, jintArray deviceTypes,
                                                                 jobjectArray deviceAddresses) {
-    Vector<AudioDeviceTypeAddr> deviceVector;
+    AudioDeviceTypeAddrVector deviceVector;
     jint results = getVectorOfAudioDeviceTypeAddr(env, deviceTypes, deviceAddresses, deviceVector);
     if (results != NO_ERROR) {
         return results;
@@ -2361,46 +2363,48 @@
     return AudioSystem::isCallScreenModeSupported();
 }
 
-static jint
-android_media_AudioSystem_setPreferredDeviceForStrategy(JNIEnv *env, jobject thiz,
-        jint strategy, jint deviceType, jstring deviceAddress) {
-
-    const char *c_address = env->GetStringUTFChars(deviceAddress, NULL);
+static jint android_media_AudioSystem_setDevicesRoleForStrategy(JNIEnv *env, jobject thiz,
+                                                                jint strategy, jint role,
+                                                                jintArray jDeviceTypes,
+                                                                jobjectArray jDeviceAddresses) {
+    AudioDeviceTypeAddrVector nDevices;
+    jint results = getVectorOfAudioDeviceTypeAddr(env, jDeviceTypes, jDeviceAddresses, nDevices);
+    if (results != NO_ERROR) {
+        return results;
+    }
     int status = check_AudioSystem_Command(
-            AudioSystem::setPreferredDeviceForStrategy((product_strategy_t) strategy,
-                    AudioDeviceTypeAddr(deviceType, c_address)));
-    env->ReleaseStringUTFChars(deviceAddress, c_address);
+            AudioSystem::setDevicesRoleForStrategy((product_strategy_t)strategy,
+                                                   (device_role_t)role, nDevices));
     return (jint) status;
 }
 
-static jint
-android_media_AudioSystem_removePreferredDeviceForStrategy(JNIEnv *env, jobject thiz, jint strategy)
-{
-    return (jint) check_AudioSystem_Command(
-            AudioSystem::removePreferredDeviceForStrategy((product_strategy_t) strategy));
+static jint android_media_AudioSystem_removeDevicesRoleForStrategy(JNIEnv *env, jobject thiz,
+                                                                   jint strategy, jint role) {
+    return (jint)check_AudioSystem_Command(
+            AudioSystem::removeDevicesRoleForStrategy((product_strategy_t)strategy,
+                                                      (device_role_t)role));
 }
 
-static jint
-android_media_AudioSystem_getPreferredDeviceForStrategy(JNIEnv *env, jobject thiz,
-        jint strategy, jobjectArray jDeviceArray)
-{
-    if (jDeviceArray == nullptr || env->GetArrayLength(jDeviceArray) != 1) {
-        ALOGE("%s invalid array to store AudioDeviceAttributes", __FUNCTION__);
-        return (jint)AUDIO_JAVA_BAD_VALUE;
-    }
-
-    AudioDeviceTypeAddr elDevice;
+static jint android_media_AudioSystem_getDevicesForRoleAndStrategy(JNIEnv *env, jobject thiz,
+                                                                   jint strategy, jint role,
+                                                                   jobject jDevices) {
+    AudioDeviceTypeAddrVector nDevices;
     status_t status = check_AudioSystem_Command(
-            AudioSystem::getPreferredDeviceForStrategy((product_strategy_t) strategy, elDevice));
+            AudioSystem::getDevicesForRoleAndStrategy((product_strategy_t)strategy,
+                                                      (device_role_t)role, nDevices));
     if (status != NO_ERROR) {
         return (jint) status;
     }
-    jobject jAudioDeviceAttributes = NULL;
-    jint jStatus = createAudioDeviceAttributesFromNative(env, &jAudioDeviceAttributes, &elDevice);
-    if (jStatus == AUDIO_JAVA_SUCCESS) {
-        env->SetObjectArrayElement(jDeviceArray, 0, jAudioDeviceAttributes);
+    for (const auto &device : nDevices) {
+        jobject jAudioDeviceAttributes = NULL;
+        jint jStatus = createAudioDeviceAttributesFromNative(env, &jAudioDeviceAttributes, &device);
+        if (jStatus != AUDIO_JAVA_SUCCESS) {
+            return jStatus;
+        }
+        env->CallBooleanMethod(jDevices, gListMethods.add, jAudioDeviceAttributes);
+        env->DeleteLocalRef(jAudioDeviceAttributes);
     }
-    return jStatus;
+    return AUDIO_JAVA_SUCCESS;
 }
 
 static jint
@@ -2548,12 +2552,12 @@
          {"setAudioHalPids", "([I)I", (void *)android_media_AudioSystem_setAudioHalPids},
          {"isCallScreeningModeSupported", "()Z",
           (void *)android_media_AudioSystem_isCallScreeningModeSupported},
-         {"setPreferredDeviceForStrategy", "(IILjava/lang/String;)I",
-          (void *)android_media_AudioSystem_setPreferredDeviceForStrategy},
-         {"removePreferredDeviceForStrategy", "(I)I",
-          (void *)android_media_AudioSystem_removePreferredDeviceForStrategy},
-         {"getPreferredDeviceForStrategy", "(I[Landroid/media/AudioDeviceAttributes;)I",
-          (void *)android_media_AudioSystem_getPreferredDeviceForStrategy},
+         {"setDevicesRoleForStrategy", "(II[I[Ljava/lang/String;)I",
+          (void *)android_media_AudioSystem_setDevicesRoleForStrategy},
+         {"removeDevicesRoleForStrategy", "(II)I",
+          (void *)android_media_AudioSystem_removeDevicesRoleForStrategy},
+         {"getDevicesForRoleAndStrategy", "(IILjava/util/List;)I",
+          (void *)android_media_AudioSystem_getDevicesForRoleAndStrategy},
          {"getDevicesForAttributes",
           "(Landroid/media/AudioAttributes;[Landroid/media/AudioDeviceAttributes;)I",
           (void *)android_media_AudioSystem_getDevicesForAttributes},
@@ -2755,6 +2759,9 @@
     gMidAudioRecordRoutingProxy_release =
             android::GetMethodIDOrDie(env, gClsAudioRecordRoutingProxy, "native_release", "()V");
 
+    jclass listClass = FindClassOrDie(env, "java/util/List");
+    gListMethods.add = GetMethodIDOrDie(env, listClass, "add", "(Ljava/lang/Object;)Z");
+
     AudioSystem::addErrorCallback(android_media_AudioSystem_error_callback);
 
     RegisterMethodsOrDie(env, kClassPathName, gMethods, NELEM(gMethods));
diff --git a/core/jni/android_net_NetUtils.cpp b/core/jni/android_net_NetUtils.cpp
index e56809f..8d4c4e5 100644
--- a/core/jni/android_net_NetUtils.cpp
+++ b/core/jni/android_net_NetUtils.cpp
@@ -93,13 +93,13 @@
 
 static void android_net_utils_detachBPFFilter(JNIEnv *env, jobject clazz, jobject javaFd)
 {
-    int dummy = 0;
+    int optval_ignored = 0;
     int fd = jniGetFDFromFileDescriptor(env, javaFd);
-    if (setsockopt(fd, SOL_SOCKET, SO_DETACH_FILTER, &dummy, sizeof(dummy)) != 0) {
+    if (setsockopt(fd, SOL_SOCKET, SO_DETACH_FILTER, &optval_ignored, sizeof(optval_ignored)) !=
+        0) {
         jniThrowExceptionFmt(env, "java/net/SocketException",
                 "setsockopt(SO_DETACH_FILTER): %s", strerror(errno));
     }
-
 }
 
 static jboolean android_net_utils_bindProcessToNetwork(JNIEnv *env, jobject thiz, jint netId)
diff --git a/core/jni/android_os_Debug.cpp b/core/jni/android_os_Debug.cpp
index d6e8531..ef0eeec 100644
--- a/core/jni/android_os_Debug.cpp
+++ b/core/jni/android_os_Debug.cpp
@@ -920,7 +920,7 @@
 {
     jclass clazz = env->FindClass("android/os/Debug$MemoryInfo");
 
-    // Sanity check the number of other statistics expected in Java matches here.
+    // Check the number of other statistics expected in Java matches here.
     jfieldID numOtherStats_field = env->GetStaticFieldID(clazz, "NUM_OTHER_STATS", "I");
     jint numOtherStats = env->GetStaticIntField(clazz, numOtherStats_field);
     jfieldID numDvkStats_field = env->GetStaticFieldID(clazz, "NUM_DVK_STATS", "I");
diff --git a/core/jni/android_os_HwRemoteBinder.cpp b/core/jni/android_os_HwRemoteBinder.cpp
index f8f841c..3af55fe 100644
--- a/core/jni/android_os_HwRemoteBinder.cpp
+++ b/core/jni/android_os_HwRemoteBinder.cpp
@@ -269,22 +269,9 @@
     return obj;
 }
 
-JHwRemoteBinder::JHwRemoteBinder(
-        JNIEnv *env, jobject thiz, const sp<hardware::IBinder> &binder)
-    : mBinder(binder) {
-    mDeathRecipientList = new HwBinderDeathRecipientList();
-    jclass clazz = env->GetObjectClass(thiz);
-    CHECK(clazz != NULL);
-
-    mObject = env->NewWeakGlobalRef(thiz);
-}
-
-JHwRemoteBinder::~JHwRemoteBinder() {
-    JNIEnv *env = AndroidRuntime::getJNIEnv();
-
-    env->DeleteWeakGlobalRef(mObject);
-    mObject = NULL;
-}
+JHwRemoteBinder::JHwRemoteBinder(JNIEnv* env, jobject /* thiz */,
+                                 const sp<hardware::IBinder>& binder)
+      : mBinder(binder), mDeathRecipientList(new HwBinderDeathRecipientList()) {}
 
 sp<hardware::IBinder> JHwRemoteBinder::getBinder() const {
     return mBinder;
diff --git a/core/jni/android_os_HwRemoteBinder.h b/core/jni/android_os_HwRemoteBinder.h
index 4b5a4c8..7eb81f3 100644
--- a/core/jni/android_os_HwRemoteBinder.h
+++ b/core/jni/android_os_HwRemoteBinder.h
@@ -36,9 +36,13 @@
     std::vector<sp<HwBinderDeathRecipient>> mList;
     Mutex mLock;
 
+protected:
+    ~HwBinderDeathRecipientList() override;
+
 public:
-    HwBinderDeathRecipientList();
-    ~HwBinderDeathRecipientList();
+    explicit HwBinderDeathRecipientList();
+
+    DISALLOW_COPY_AND_ASSIGN(HwBinderDeathRecipientList);
 
     void add(const sp<HwBinderDeathRecipient>& recipient);
     void remove(const sp<HwBinderDeathRecipient>& recipient);
@@ -66,12 +70,7 @@
     void setBinder(const sp<hardware::IBinder> &binder);
     sp<HwBinderDeathRecipientList> getDeathRecipientList() const;
 
-protected:
-    virtual ~JHwRemoteBinder();
-
 private:
-    jobject mObject;
-
     sp<hardware::IBinder> mBinder;
     sp<HwBinderDeathRecipientList> mDeathRecipientList;
     DISALLOW_COPY_AND_ASSIGN(JHwRemoteBinder);
diff --git a/core/jni/android_os_Parcel.cpp b/core/jni/android_os_Parcel.cpp
index 2000ecb..7cfe3bc 100644
--- a/core/jni/android_os_Parcel.cpp
+++ b/core/jni/android_os_Parcel.cpp
@@ -330,7 +330,7 @@
     if (parcel != NULL) {
         int32_t len = parcel->readInt32();
 
-        // sanity check the stored length against the true data size
+        // Validate the stored length against the true data size
         if (len >= 0 && len <= (int32_t)parcel->dataAvail()) {
             ret = env->NewByteArray(len);
 
diff --git a/core/jni/android_os_SystemClock.cpp b/core/jni/android_os_SystemClock.cpp
index b1f6000..2fba445 100644
--- a/core/jni/android_os_SystemClock.cpp
+++ b/core/jni/android_os_SystemClock.cpp
@@ -37,10 +37,12 @@
 static_assert(std::is_same<int64_t, jlong>::value, "jlong isn't an int64_t");
 static_assert(std::is_same<decltype(uptimeMillis()), int64_t>::value,
         "uptimeMillis signature change, expected int64_t return value");
+static_assert(std::is_same<decltype(uptimeNanos()), int64_t>::value,
+        "uptimeNanos signature change, expected int64_t return value");
 static_assert(std::is_same<decltype(elapsedRealtime()), int64_t>::value,
-        "uptimeMillis signature change, expected int64_t return value");
+        "elapsedRealtime signature change, expected int64_t return value");
 static_assert(std::is_same<decltype(elapsedRealtimeNano()), int64_t>::value,
-        "uptimeMillis signature change, expected int64_t return value");
+        "elapsedRealtimeNano signature change, expected int64_t return value");
 
 /*
  * native public static long currentThreadTimeMillis();
@@ -76,6 +78,7 @@
     // All of these are @CriticalNative, so we can defer directly to SystemClock.h for
     // some of these
     { "uptimeMillis", "()J", (void*) uptimeMillis },
+    { "uptimeNanos", "()J", (void*) uptimeNanos },
     { "elapsedRealtime", "()J", (void*) elapsedRealtime },
     { "elapsedRealtimeNanos", "()J", (void*) elapsedRealtimeNano },
 
diff --git a/core/jni/android_util_Binder.cpp b/core/jni/android_util_Binder.cpp
index 885b0a3..e118038 100644
--- a/core/jni/android_util_Binder.cpp
+++ b/core/jni/android_util_Binder.cpp
@@ -962,7 +962,7 @@
 
 static void android_os_Binder_restoreCallingIdentity(JNIEnv* env, jobject clazz, jlong token)
 {
-    // XXX temporary sanity check to debug crashes.
+    // XXX temporary validation check to debug crashes.
     int uid = (int)(token>>32);
     if (uid > 0 && uid < 999) {
         // In Android currently there are no uids in this range.
diff --git a/core/jni/android_view_DisplayEventReceiver.cpp b/core/jni/android_view_DisplayEventReceiver.cpp
index 7daefd3..50a557bb 100644
--- a/core/jni/android_view_DisplayEventReceiver.cpp
+++ b/core/jni/android_view_DisplayEventReceiver.cpp
@@ -63,6 +63,7 @@
     void dispatchHotplug(nsecs_t timestamp, PhysicalDisplayId displayId, bool connected) override;
     void dispatchConfigChanged(nsecs_t timestamp, PhysicalDisplayId displayId,
                                int32_t configId, nsecs_t vsyncPeriod) override;
+    void dispatchNullEvent(nsecs_t timestamp, PhysicalDisplayId displayId) override {}
 };
 
 
@@ -95,8 +96,8 @@
     ScopedLocalRef<jobject> receiverObj(env, jniGetReferent(env, mReceiverWeakGlobal));
     if (receiverObj.get()) {
         ALOGV("receiver %p ~ Invoking vsync handler.", this);
-        env->CallVoidMethod(receiverObj.get(),
-                gDisplayEventReceiverClassInfo.dispatchVsync, timestamp, displayId, count);
+        env->CallVoidMethod(receiverObj.get(), gDisplayEventReceiverClassInfo.dispatchVsync,
+                            timestamp, displayId.value, count);
         ALOGV("receiver %p ~ Returned from vsync handler.", this);
     }
 
@@ -110,8 +111,8 @@
     ScopedLocalRef<jobject> receiverObj(env, jniGetReferent(env, mReceiverWeakGlobal));
     if (receiverObj.get()) {
         ALOGV("receiver %p ~ Invoking hotplug handler.", this);
-        env->CallVoidMethod(receiverObj.get(),
-                gDisplayEventReceiverClassInfo.dispatchHotplug, timestamp, displayId, connected);
+        env->CallVoidMethod(receiverObj.get(), gDisplayEventReceiverClassInfo.dispatchHotplug,
+                            timestamp, displayId.value, connected);
         ALOGV("receiver %p ~ Returned from hotplug handler.", this);
     }
 
@@ -126,9 +127,8 @@
                                       jniGetReferent(env, mReceiverWeakGlobal));
   if (receiverObj.get()) {
     ALOGV("receiver %p ~ Invoking config changed handler.", this);
-    env->CallVoidMethod(receiverObj.get(),
-                        gDisplayEventReceiverClassInfo.dispatchConfigChanged,
-                        timestamp, displayId, configId);
+    env->CallVoidMethod(receiverObj.get(), gDisplayEventReceiverClassInfo.dispatchConfigChanged,
+                        timestamp, displayId.value, configId);
     ALOGV("receiver %p ~ Returned from config changed handler.", this);
   }
 
diff --git a/core/jni/android_view_InputQueue.cpp b/core/jni/android_view_InputQueue.cpp
index 24c3ff8..70a9be7 100644
--- a/core/jni/android_view_InputQueue.cpp
+++ b/core/jni/android_view_InputQueue.cpp
@@ -176,8 +176,8 @@
     Mutex::Autolock _l(mLock);
     mPendingEvents.push(event);
     if (mPendingEvents.size() == 1) {
-        char dummy = 0;
-        int res = TEMP_FAILURE_RETRY(write(mDispatchWriteFd, &dummy, sizeof(dummy)));
+        char payload = '\0';
+        int res = TEMP_FAILURE_RETRY(write(mDispatchWriteFd, &payload, sizeof(payload)));
         if (res < 0 && errno != EAGAIN) {
             ALOGW("Failed writing to dispatch fd: %s", strerror(errno));
         }
diff --git a/core/jni/android_view_SurfaceControl.cpp b/core/jni/android_view_SurfaceControl.cpp
index a965ab3..85b4fe1 100644
--- a/core/jni/android_view_SurfaceControl.cpp
+++ b/core/jni/android_view_SurfaceControl.cpp
@@ -104,6 +104,26 @@
     jfieldID top;
 } gRectClassInfo;
 
+static struct {
+    jfieldID pixelFormat;
+    jfieldID sourceCrop;
+    jfieldID frameScale;
+    jfieldID captureSecureLayers;
+} gCaptureArgsClassInfo;
+
+static struct {
+    jfieldID displayToken;
+    jfieldID width;
+    jfieldID height;
+    jfieldID useIdentityTransform;
+} gDisplayCaptureArgsClassInfo;
+
+static struct {
+    jfieldID layer;
+    jfieldID excludeLayers;
+    jfieldID childrenOnly;
+} gLayerCaptureArgsClassInfo;
+
 // Implements SkMallocPixelRef::ReleaseProc, to delete the screenshot on unref.
 void DeleteScreenshot(void* addr, void* context) {
     delete ((ScreenshotClient*) context);
@@ -276,55 +296,77 @@
     return Rect(left, top, right, bottom);
 }
 
-static jobject nativeScreenshot(JNIEnv* env, jclass clazz,
-        jobject displayTokenObj, jobject sourceCropObj, jint width, jint height,
-        bool useIdentityTransform, int rotation, bool captureSecureLayers) {
-    sp<IBinder> displayToken = ibinderForJavaObject(env, displayTokenObj);
-    if (displayToken == NULL) {
+static void getCaptureArgs(JNIEnv* env, jobject captureArgsObject, CaptureArgs& captureArgs) {
+    captureArgs.pixelFormat = static_cast<ui::PixelFormat>(
+            env->GetIntField(captureArgsObject, gCaptureArgsClassInfo.pixelFormat));
+    captureArgs.sourceCrop =
+            rectFromObj(env,
+                        env->GetObjectField(captureArgsObject, gCaptureArgsClassInfo.sourceCrop));
+    captureArgs.frameScale =
+            env->GetFloatField(captureArgsObject, gCaptureArgsClassInfo.frameScale);
+    captureArgs.captureSecureLayers =
+            env->GetBooleanField(captureArgsObject, gCaptureArgsClassInfo.captureSecureLayers);
+}
+
+static DisplayCaptureArgs displayCaptureArgsFromObject(JNIEnv* env,
+                                                       jobject displayCaptureArgsObject) {
+    DisplayCaptureArgs captureArgs;
+    getCaptureArgs(env, displayCaptureArgsObject, captureArgs);
+
+    captureArgs.displayToken =
+            ibinderForJavaObject(env,
+                                 env->GetObjectField(displayCaptureArgsObject,
+                                                     gDisplayCaptureArgsClassInfo.displayToken));
+    captureArgs.width =
+            env->GetIntField(displayCaptureArgsObject, gDisplayCaptureArgsClassInfo.width);
+    captureArgs.height =
+            env->GetIntField(displayCaptureArgsObject, gDisplayCaptureArgsClassInfo.height);
+    captureArgs.useIdentityTransform =
+            env->GetBooleanField(displayCaptureArgsObject,
+                                 gDisplayCaptureArgsClassInfo.useIdentityTransform);
+    return captureArgs;
+}
+
+static jobject nativeCaptureDisplay(JNIEnv* env, jclass clazz, jobject displayCaptureArgsObject) {
+    const DisplayCaptureArgs captureArgs =
+            displayCaptureArgsFromObject(env, displayCaptureArgsObject);
+
+    if (captureArgs.displayToken == NULL) {
         return NULL;
     }
-    const ui::ColorMode colorMode = SurfaceComposerClient::getActiveColorMode(displayToken);
-    const ui::Dataspace dataspace = pickDataspaceFromColorMode(colorMode);
 
-    Rect sourceCrop = rectFromObj(env, sourceCropObj);
-    sp<GraphicBuffer> buffer;
-    bool capturedSecureLayers = false;
-    status_t res = ScreenshotClient::capture(displayToken, dataspace,
-            ui::PixelFormat::RGBA_8888,
-            sourceCrop, width, height,
-            useIdentityTransform, ui::toRotation(rotation),
-            captureSecureLayers, &buffer, capturedSecureLayers);
+    ScreenCaptureResults captureResults;
+    status_t res = ScreenshotClient::captureDisplay(captureArgs, captureResults);
     if (res != NO_ERROR) {
         return NULL;
     }
 
-    jobject jhardwareBuffer =
-            android_hardware_HardwareBuffer_createFromAHardwareBuffer(env,
-                                                                      buffer->toAHardwareBuffer());
-    const jint namedColorSpace = fromDataspaceToNamedColorSpaceValue(dataspace);
+    jobject jhardwareBuffer = android_hardware_HardwareBuffer_createFromAHardwareBuffer(
+            env, captureResults.buffer->toAHardwareBuffer());
+    const jint namedColorSpace =
+            fromDataspaceToNamedColorSpaceValue(captureResults.capturedDataspace);
     return env->CallStaticObjectMethod(gScreenshotHardwareBufferClassInfo.clazz,
                                        gScreenshotHardwareBufferClassInfo.builder, jhardwareBuffer,
-                                       namedColorSpace, capturedSecureLayers);
+                                       namedColorSpace, captureResults.capturedSecureLayers);
 }
 
-static jobject nativeCaptureLayers(JNIEnv* env, jclass clazz, jobject displayTokenObj,
-        jlong layerObject, jobject sourceCropObj, jfloat frameScale,
-        jlongArray excludeObjectArray, jint format) {
-
-    auto layer = reinterpret_cast<SurfaceControl *>(layerObject);
-    if (layer == NULL) {
-        return NULL;
+static jobject nativeCaptureLayers(JNIEnv* env, jclass clazz, jobject layerCaptureArgsObject) {
+    LayerCaptureArgs captureArgs;
+    getCaptureArgs(env, layerCaptureArgsObject, captureArgs);
+    SurfaceControl* layer = reinterpret_cast<SurfaceControl*>(
+            env->GetLongField(layerCaptureArgsObject, gLayerCaptureArgsClassInfo.layer));
+    if (layer == nullptr) {
+        return nullptr;
     }
 
-    Rect sourceCrop;
-    if (sourceCropObj != NULL) {
-        sourceCrop = rectFromObj(env, sourceCropObj);
-    }
-
-    std::unordered_set<sp<IBinder>,ISurfaceComposer::SpHash<IBinder>> excludeHandles;
+    captureArgs.layerHandle = layer->getHandle();
+    captureArgs.childrenOnly =
+            env->GetBooleanField(layerCaptureArgsObject, gLayerCaptureArgsClassInfo.childrenOnly);
+    jlongArray excludeObjectArray = reinterpret_cast<jlongArray>(
+            env->GetObjectField(layerCaptureArgsObject, gLayerCaptureArgsClassInfo.excludeLayers));
     if (excludeObjectArray != NULL) {
         const jsize len = env->GetArrayLength(excludeObjectArray);
-        excludeHandles.reserve(len);
+        captureArgs.excludeHandles.reserve(len);
 
         const jlong* objects = env->GetLongArrayElements(excludeObjectArray, nullptr);
         for (jsize i = 0; i < len; i++) {
@@ -333,33 +375,24 @@
                 jniThrowNullPointerException(env, "Exclude layer is null");
                 return NULL;
             }
-            excludeHandles.emplace(excludeObject->getHandle());
+            captureArgs.excludeHandles.emplace(excludeObject->getHandle());
         }
         env->ReleaseLongArrayElements(excludeObjectArray, const_cast<jlong*>(objects), JNI_ABORT);
     }
 
-    sp<GraphicBuffer> buffer;
-    ui::Dataspace dataspace = ui::Dataspace::V0_SRGB;
-    sp<IBinder> displayToken = ibinderForJavaObject(env, displayTokenObj);
-    if (displayToken != nullptr) {
-        const ui::ColorMode colorMode = SurfaceComposerClient::getActiveColorMode(displayToken);
-        dataspace = pickDataspaceFromColorMode(colorMode);
-    }
-    status_t res = ScreenshotClient::captureChildLayers(layer->getHandle(), dataspace,
-                                                        static_cast<ui::PixelFormat>(format),
-                                                        sourceCrop, excludeHandles, frameScale,
-                                                        &buffer);
+    ScreenCaptureResults captureResults;
+    status_t res = ScreenshotClient::captureLayers(captureArgs, captureResults);
     if (res != NO_ERROR) {
         return NULL;
     }
 
-    jobject jhardwareBuffer =
-            android_hardware_HardwareBuffer_createFromAHardwareBuffer(env,
-                                                                      buffer->toAHardwareBuffer());
-    const jint namedColorSpace = fromDataspaceToNamedColorSpaceValue(dataspace);
+    jobject jhardwareBuffer = android_hardware_HardwareBuffer_createFromAHardwareBuffer(
+            env, captureResults.buffer->toAHardwareBuffer());
+    const jint namedColorSpace =
+            fromDataspaceToNamedColorSpaceValue(captureResults.capturedDataspace);
     return env->CallStaticObjectMethod(gScreenshotHardwareBufferClassInfo.clazz,
                                        gScreenshotHardwareBufferClassInfo.builder, jhardwareBuffer,
-                                       namedColorSpace, false /* capturedSecureLayers */);
+                                       namedColorSpace, captureResults.capturedSecureLayers);
 }
 
 static void nativeApplyTransaction(JNIEnv* env, jclass clazz, jlong transactionObj, jboolean sync) {
@@ -667,7 +700,7 @@
 
     jlong* values = env->GetLongArrayElements(array, 0);
     for (size_t i = 0; i < displayIds.size(); ++i) {
-        values[i] = static_cast<jlong>(displayIds[i]);
+        values[i] = static_cast<jlong>(displayIds[i].value);
     }
 
     env->ReleaseLongArrayElements(array, values, 0);
@@ -675,7 +708,8 @@
 }
 
 static jobject nativeGetPhysicalDisplayToken(JNIEnv* env, jclass clazz, jlong physicalDisplayId) {
-    sp<IBinder> token = SurfaceComposerClient::getPhysicalDisplayToken(physicalDisplayId);
+    sp<IBinder> token =
+            SurfaceComposerClient::getPhysicalDisplayToken(PhysicalDisplayId(physicalDisplayId));
     return javaObjectForIBinder(env, token);
 }
 
@@ -1614,13 +1648,12 @@
             (void*)nativeSeverChildren } ,
     {"nativeSetOverrideScalingMode", "(JJI)V",
             (void*)nativeSetOverrideScalingMode },
-    {"nativeScreenshot",
-            "(Landroid/os/IBinder;Landroid/graphics/Rect;IIZIZ)"
+    {"nativeCaptureDisplay",
+            "(Landroid/view/SurfaceControl$DisplayCaptureArgs;)"
             "Landroid/view/SurfaceControl$ScreenshotHardwareBuffer;",
-            (void*)nativeScreenshot },
+            (void*)nativeCaptureDisplay },
     {"nativeCaptureLayers",
-            "(Landroid/os/IBinder;JLandroid/graphics/Rect;"
-            "F[JI)"
+            "(Landroid/view/SurfaceControl$LayerCaptureArgs;)"
             "Landroid/view/SurfaceControl$ScreenshotHardwareBuffer;",
             (void*)nativeCaptureLayers },
     {"nativeSetInputWindowInfo", "(JJLandroid/view/InputWindowHandle;)V",
@@ -1795,6 +1828,34 @@
     gDesiredDisplayConfigSpecsClassInfo.appRequestRefreshRateMax =
             GetFieldIDOrDie(env, desiredDisplayConfigSpecsClazz, "appRequestRefreshRateMax", "F");
 
+    jclass captureArgsClazz = FindClassOrDie(env, "android/view/SurfaceControl$CaptureArgs");
+    gCaptureArgsClassInfo.pixelFormat = GetFieldIDOrDie(env, captureArgsClazz, "mPixelFormat", "I");
+    gCaptureArgsClassInfo.sourceCrop =
+            GetFieldIDOrDie(env, captureArgsClazz, "mSourceCrop", "Landroid/graphics/Rect;");
+    gCaptureArgsClassInfo.frameScale = GetFieldIDOrDie(env, captureArgsClazz, "mFrameScale", "F");
+    gCaptureArgsClassInfo.captureSecureLayers =
+            GetFieldIDOrDie(env, captureArgsClazz, "mCaptureSecureLayers", "Z");
+
+    jclass displayCaptureArgsClazz =
+            FindClassOrDie(env, "android/view/SurfaceControl$DisplayCaptureArgs");
+    gDisplayCaptureArgsClassInfo.displayToken =
+            GetFieldIDOrDie(env, displayCaptureArgsClazz, "mDisplayToken", "Landroid/os/IBinder;");
+    gDisplayCaptureArgsClassInfo.width =
+            GetFieldIDOrDie(env, displayCaptureArgsClazz, "mWidth", "I");
+    gDisplayCaptureArgsClassInfo.height =
+            GetFieldIDOrDie(env, displayCaptureArgsClazz, "mHeight", "I");
+    gDisplayCaptureArgsClassInfo.useIdentityTransform =
+            GetFieldIDOrDie(env, displayCaptureArgsClazz, "mUseIdentityTransform", "Z");
+
+    jclass layerCaptureArgsClazz =
+            FindClassOrDie(env, "android/view/SurfaceControl$LayerCaptureArgs");
+    gLayerCaptureArgsClassInfo.layer =
+            GetFieldIDOrDie(env, layerCaptureArgsClazz, "mNativeLayer", "J");
+    gLayerCaptureArgsClassInfo.excludeLayers =
+            GetFieldIDOrDie(env, layerCaptureArgsClazz, "mNativeExcludeLayers", "[J");
+    gLayerCaptureArgsClassInfo.childrenOnly =
+            GetFieldIDOrDie(env, layerCaptureArgsClazz, "mChildrenOnly", "Z");
+
     return err;
 }
 
diff --git a/core/jni/com_android_internal_os_Zygote.cpp b/core/jni/com_android_internal_os_Zygote.cpp
index c73441c..f96ed36 100644
--- a/core/jni/com_android_internal_os_Zygote.cpp
+++ b/core/jni/com_android_internal_os_Zygote.cpp
@@ -1790,8 +1790,7 @@
 #ifdef ANDROID_EXPERIMENTAL_MTE
       SetTagCheckingLevel(PR_MTE_TCF_SYNC);
 #endif
-      // TODO(pcc): Use SYNC here once the allocator supports it.
-      heap_tagging_level = M_HEAP_TAGGING_LEVEL_ASYNC;
+      heap_tagging_level = M_HEAP_TAGGING_LEVEL_SYNC;
       break;
     default:
 #ifdef ANDROID_EXPERIMENTAL_MTE
diff --git a/core/jni/core_jni_helpers.h b/core/jni/core_jni_helpers.h
index eeda275..d629e0d 100644
--- a/core/jni/core_jni_helpers.h
+++ b/core/jni/core_jni_helpers.h
@@ -104,6 +104,31 @@
     return std::string(defaultValue);
 }
 
+static inline JNIEnv* GetJNIEnvironment(JavaVM* vm, jint version = JNI_VERSION_1_4) {
+    JNIEnv* env;
+    if (vm->GetEnv(reinterpret_cast<void**>(&env), version) != JNI_OK) {
+        return nullptr;
+    }
+    return env;
+}
+
+static inline JNIEnv* GetOrAttachJNIEnvironment(JavaVM* jvm, jint version = JNI_VERSION_1_4) {
+    JNIEnv* env = GetJNIEnvironment(jvm, version);
+    if (!env) {
+        int result = jvm->AttachCurrentThread(&env, nullptr);
+        LOG_ALWAYS_FATAL_IF(result != JNI_OK, "JVM thread attach failed.");
+        struct VmDetacher {
+            VmDetacher(JavaVM* vm) : mVm(vm) {}
+            ~VmDetacher() { mVm->DetachCurrentThread(); }
+
+        private:
+            JavaVM* const mVm;
+        };
+        static thread_local VmDetacher detacher(jvm);
+    }
+    return env;
+}
+
 }  // namespace android
 
 #endif  // CORE_JNI_HELPERS
diff --git a/core/proto/android/app/settings_enums.proto b/core/proto/android/app/settings_enums.proto
index 5b22e31..6eb8904 100644
--- a/core/proto/android/app/settings_enums.proto
+++ b/core/proto/android/app/settings_enums.proto
@@ -744,6 +744,41 @@
     // CATEGORY: SETTINGS
     // OS: R
     ACTION_CONFIRM_SIM_DELETION_OFF = 1739;
+
+    // ACTION: Settings > System > Gestures > Double tap > Toggle on Double tap
+    // CATEGORY: SETTINGS
+    // OS: S
+    ACTION_COLUMBUS_ENABLED = 1740;
+
+    // ACTION: Settings > System > Gestures > Double tap > Toggle off Double tap
+    // CATEGORY: SETTINGS
+    // OS: S
+    ACTION_COLUMBUS_DISABLED = 1741;
+
+    // ACTION: Settings > System > Gestures > Double tap > Invoke Assistant
+    // CATEGORY: SETTINGS
+    // OS: S
+    ACTION_COLUMBUS_ACTION_ASSISTANT = 1742;
+
+    // ACTION: Settings > System > Gestures > Double tap > Take screenshot
+    // CATEGORY: SETTINGS
+    // OS: S
+    ACTION_COLUMBUS_ACTION_SCREENSHOT = 1743;
+
+    // ACTION: Settings > System > Gestures > Double tap > Play and pause
+    // CATEGORY: SETTINGS
+    // OS: S
+    ACTION_COLUMBUS_ACTION_PLAY_PAUSE = 1744;
+
+    // ACTION: Settings > System > Gestures > Double tap > Open app overview
+    // CATEGORY: SETTINGS
+    // OS: S
+    ACTION_COLUMBUS_ACTION_OVERVIEW = 1745;
+
+    // ACTION: Settings > System > Gestures > Double tap > Open notification shade
+    // CATEGORY: SETTINGS
+    // OS: S
+    ACTION_COLUMBUS_ACTION_NOTIFICATION_SHADE = 1746;
 }
 
 /**
@@ -2310,10 +2345,10 @@
     // OS: Q
     ZEN_CUSTOM_SETTINGS_DIALOG = 1612;
 
-    // OPEN: Settings > Developer Options > Game Driver Preferences
+    // OPEN: Settings > Developer Options > Graphics Driver Preferences
     // CATEGORY: SETTINGS
     // OS: Q
-    SETTINGS_GAME_DRIVER_DASHBOARD = 1613;
+    SETTINGS_GRAPHICS_DRIVER_DASHBOARD = 1613;
 
     // OPEN: Settings > Accessibility > Vibration > Ring vibration
     // CATEGORY: SETTINGS
@@ -2698,4 +2733,9 @@
     // CATEGORY: SETTINGS
     // OS: S
     EMERGENCY_SOS_GESTURE_SETTINGS = 1847;
+
+    // OPEN: Settings > System > Gestures > Double tap
+    // CATEGORY: SETTINGS
+    // OS: S
+    SETTINGS_COLUMBUS = 1848;
 }
diff --git a/core/proto/android/app/tvsettings_enums.proto b/core/proto/android/app/tvsettings_enums.proto
index 31c5dd6..4a3c594 100644
--- a/core/proto/android/app/tvsettings_enums.proto
+++ b/core/proto/android/app/tvsettings_enums.proto
@@ -168,7 +168,11 @@
     // Google Assistant > Personal results (toggle)
     ACCOUNT_SLICE_REG_ACCOUNT_ASSISTANT_PERSONAL_RESULTS = 0x12134000;
 
-    // Reserving [0x12140000, 0x12190000] for possible future settings
+    // TvSettings > Account & Sign In (Slice) > [A regular account] >
+    // Apps only mode (toggle)
+    ACCOUNT_SLICE_REG_ACCOUNT_APPS_ONLY_MODE = 0x12140000;
+
+    // Reserving [0x12150000, 0x12190000] for possible future settings
 
     // TvSettings > Account & Sign In (Slice) > [A regular account] > Remove
     ACCOUNT_SLICE_REG_ACCOUNT_REMOVE = 0x121A0000;
diff --git a/core/proto/android/server/protolog.proto b/core/proto/android/internal/protolog.proto
similarity index 98%
rename from core/proto/android/server/protolog.proto
rename to core/proto/android/internal/protolog.proto
index 34dc55b..fee7a87 100644
--- a/core/proto/android/server/protolog.proto
+++ b/core/proto/android/internal/protolog.proto
@@ -16,7 +16,7 @@
 
 syntax = "proto2";
 
-package com.android.server.protolog;
+package com.android.internal.protolog;
 
 option java_multiple_files = true;
 
diff --git a/core/proto/android/providers/settings/global.proto b/core/proto/android/providers/settings/global.proto
index e4a142b..9fccdaf 100644
--- a/core/proto/android/providers/settings/global.proto
+++ b/core/proto/android/providers/settings/global.proto
@@ -433,35 +433,36 @@
         // Ordered GPU debug layer list for GLES
         // i.e. <layer1>:<layer2>:...:<layerN>
         optional SettingProto debug_layers_gles = 7;
-        // Game Driver - global preference for all Apps
+        // Updatable Driver - global preference for all Apps
         // 0 = Default
-        // 1 = All Apps use Game Driver
-        // 2 = All Apps use system graphics driver
-        optional SettingProto game_driver_all_apps = 8;
-        // Game Driver - List of Apps selected to use Game Driver
+        // 1 = All Apps use updatable production driver
+        // 2 = All apps use updatable prerelease driver
+        // 3 = All Apps use system graphics driver
+        optional SettingProto updatable_driver_all_apps = 8;
+        // Updatable Driver - List of Apps selected to use updatable production driver
         // i.e. <pkg1>,<pkg2>,...,<pkgN>
-        optional SettingProto game_driver_opt_in_apps = 9;
-        // Game Driver - List of Apps selected not to use Game Driver
+        optional SettingProto updatable_driver_production_opt_in_apps = 9;
+        // Updatable Driver - List of Apps selected not to use updatable production driver
         // i.e. <pkg1>,<pkg2>,...,<pkgN>
-        optional SettingProto game_driver_opt_out_apps = 10;
-        // Game Driver - List of Apps that are forbidden to use Game Driver
-        optional SettingProto game_driver_denylist = 11;
-        // Game Driver - List of Apps that are allowed to use Game Driver
-        optional SettingProto game_driver_allowlist = 12;
+        optional SettingProto updatable_driver_production_opt_out_apps = 10;
+        // Updatable Driver - List of Apps that are forbidden to use updatable production driver
+        optional SettingProto updatable_driver_production_denylist = 11;
+        // Updatable Driver - List of Apps that are allowed to use updatable production driver
+        optional SettingProto updatable_driver_production_allowlist = 12;
         // ANGLE - List of Apps that can check ANGLE rules
         optional SettingProto angle_allowlist = 13;
-        // Game Driver - List of denylists, each denylist is a denylist for
-        // a specific Game Driver version
-        optional SettingProto game_driver_denylists = 14;
+        // Updatable Driver - List of denylists, each denylist is a denylist for
+        // a specific updatable production driver version
+        optional SettingProto updatable_driver_production_denylists = 14;
         // ANGLE - Show a dialog box when ANGLE is selected for the currently running PKG
         optional SettingProto show_angle_in_use_dialog = 15;
-        // Game Driver - List of libraries in sphal accessible by Game Driver
-        optional SettingProto game_driver_sphal_libraries = 16;
+        // Updatable Driver - List of libraries in sphal accessible by updatable driver
+        optional SettingProto updatable_driver_sphal_libraries = 16;
         // ANGLE - External package containing ANGLE libraries
         optional SettingProto angle_debug_package = 17;
-        // Game Driver - List of Apps selected to use prerelease Game Driver
+        // Updatable Driver - List of Apps selected to use updatable prerelease driver
         // i.e. <pkg1>,<pkg2>,...,<pkgN>
-        optional SettingProto game_driver_prerelease_opt_in_apps = 18;
+        optional SettingProto updatable_driver_prerelease_opt_in_apps = 18;
     }
     optional Gpu gpu = 59;
 
@@ -506,7 +507,7 @@
     }
     optional IntentFirewall intent_firewall = 65;
 
-    optional SettingProto job_scheduler_constants = 66 [ (android.privacy).dest = DEST_AUTOMATIC ];
+    reserved 66; // job_scheduler_constants
     optional SettingProto job_scheduler_quota_controller_constants = 149 [ (android.privacy).dest = DEST_AUTOMATIC ];
     reserved 150; // job_scheduler_time_controller_constants
 
diff --git a/core/proto/android/providers/settings/secure.proto b/core/proto/android/providers/settings/secure.proto
index ca4dc18..3d12d07 100644
--- a/core/proto/android/providers/settings/secure.proto
+++ b/core/proto/android/providers/settings/secure.proto
@@ -181,6 +181,7 @@
     optional SettingProto cmas_additional_broadcast_pkg = 14 [ (android.privacy).dest = DEST_AUTOMATIC ];
     repeated SettingProto completed_categories = 15;
     optional SettingProto connectivity_release_pending_intent_delay_ms = 16 [ (android.privacy).dest = DEST_AUTOMATIC ];
+    optional SettingProto adaptive_connectivity_enabled = 84 [ (android.privacy).dest = DEST_AUTOMATIC ];
 
     message Controls {
         option (android.msg_privacy).dest = DEST_EXPLICIT;
@@ -211,6 +212,7 @@
 
     message EmergencyResponse {
         optional SettingProto panic_gesture_enabled = 1  [ (android.privacy).dest = DEST_AUTOMATIC ];
+        optional SettingProto panic_sound_enabled = 2  [ (android.privacy).dest = DEST_AUTOMATIC ];
     }
 
     optional EmergencyResponse emergency_response = 83;
@@ -613,5 +615,5 @@
 
     // Please insert fields in alphabetical order and group them into messages
     // if possible (to avoid reaching the method limit).
-    // Next tag = 84;
+    // Next tag = 85;
 }
diff --git a/core/proto/android/server/powermanagerservice.proto b/core/proto/android/server/powermanagerservice.proto
index 0455d58..a2f2c46 100644
--- a/core/proto/android/server/powermanagerservice.proto
+++ b/core/proto/android/server/powermanagerservice.proto
@@ -174,6 +174,16 @@
     optional BatterySaverStateMachineProto battery_saver_state_machine = 50;
     // Attentive timeout in ms. The timeout is disabled if it is set to -1.
     optional sint32 attentive_timeout_ms = 51;
+    // The time (in the elapsed realtime timebase) at which the battery level will reach 0%. This
+    // is provided as an enhanced estimate and only valid if
+    // last_enhanced_discharge_time_updated_elapsed is greater than 0.
+    optional int64 enhanced_discharge_time_elapsed = 52;
+    // Timestamp (in the elapsed realtime timebase) of last update to enhanced battery estimate
+    // data.
+    optional int64 last_enhanced_discharge_time_updated_elapsed = 53;
+    // Whether or not the current enhanced discharge prediction is personalized based on device
+    // usage or not.
+    optional bool is_enhanced_discharge_prediction_personalized = 54;
 }
 
 // A com.android.server.power.PowerManagerService.SuspendBlockerImpl object.
diff --git a/core/res/Android.bp b/core/res/Android.bp
index b365de4..f94a2b0 100644
--- a/core/res/Android.bp
+++ b/core/res/Android.bp
@@ -46,6 +46,13 @@
     },
 }
 
+java_genrule {
+    name: "framework-res-package-jar",
+    srcs: [":framework-res{.export-package.apk}"],
+    out: ["framework-res-package.jar"],
+    cmd: "cp $(in) $(out)",
+}
+
 // This logic can be removed once robolectric's transition to binary resources is complete
 filegroup {
     name: "robolectric_framework_raw_res_files",
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index fe290f3..57c1fcf 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -102,6 +102,7 @@
     <protected-broadcast android:name="android.os.action.POWER_SAVE_WHITELIST_CHANGED" />
     <protected-broadcast android:name="android.os.action.POWER_SAVE_TEMP_WHITELIST_CHANGED" />
     <protected-broadcast android:name="android.os.action.POWER_SAVE_MODE_CHANGED_INTERNAL" />
+    <protected-broadcast android:name="android.os.action.ENHANCED_DISCHARGE_PREDICTION_CHANGED" />
 
     <!-- @deprecated This is rarely used and will be phased out soon. -->
     <protected-broadcast android:name="android.os.action.SCREEN_BRIGHTNESS_BOOST_CHANGED" />
@@ -558,6 +559,7 @@
     <protected-broadcast android:name="android.os.action.ACTION_EFFECTS_SUPPRESSOR_CHANGED" />
     <protected-broadcast android:name="android.app.action.NOTIFICATION_CHANNEL_BLOCK_STATE_CHANGED" />
     <protected-broadcast android:name="android.app.action.NOTIFICATION_CHANNEL_GROUP_BLOCK_STATE_CHANGED" />
+    <protected-broadcast android:name="android.app.action.NOTIFICATION_LISTENER_ENABLED_CHANGED" />
     <protected-broadcast android:name="android.app.action.APP_BLOCK_STATE_CHANGED" />
 
     <protected-broadcast android:name="android.permission.GET_APP_GRANTED_URI_PERMISSIONS" />
@@ -1342,7 +1344,7 @@
         android:priority="800" />
 
     <!-- Allows an application to access data from sensors that the user uses to
-         measure what is happening inside his/her body, such as heart rate.
+         measure what is happening inside their body, such as heart rate.
          <p>Protection level: dangerous -->
     <permission android:name="android.permission.BODY_SENSORS"
         android:permissionGroup="android.permission-group.UNDEFINED"
@@ -5039,6 +5041,10 @@
     <permission android:name="android.permission.RESET_APP_ERRORS"
         android:protectionLevel="signature" />
 
+    <!-- @hide Allows an application to create/destroy input consumer. -->
+    <permission android:name="android.permission.INPUT_CONSUMER"
+                android:protectionLevel="signature" />
+
     <!-- Attribution for Geofencing service. -->
     <attribution android:tag="GeofencingService" android:label="@string/geofencing_service"/>
     <!-- Attribution for Country Detector. -->
diff --git a/core/res/res/layout/notification_template_header.xml b/core/res/res/layout/notification_template_header.xml
index d22a19f..9a1b592 100644
--- a/core/res/res/layout/notification_template_header.xml
+++ b/core/res/res/layout/notification_template_header.xml
@@ -160,43 +160,6 @@
         android:visibility="gone"
         android:contentDescription="@string/notification_work_profile_content_description"
         />
-    <LinearLayout
-        android:id="@+id/app_ops"
-        android:layout_height="match_parent"
-        android:layout_width="wrap_content"
-        android:layout_marginStart="6dp"
-        android:background="?android:selectableItemBackgroundBorderless"
-        android:orientation="horizontal">
-        <ImageView
-            android:id="@+id/camera"
-            android:layout_width="?attr/notificationHeaderIconSize"
-            android:layout_height="?attr/notificationHeaderIconSize"
-            android:src="@drawable/ic_camera"
-            android:visibility="gone"
-            android:focusable="false"
-            android:contentDescription="@string/notification_appops_camera_active"
-            />
-        <ImageView
-            android:id="@+id/mic"
-            android:layout_width="?attr/notificationHeaderIconSize"
-            android:layout_height="?attr/notificationHeaderIconSize"
-            android:src="@drawable/ic_mic"
-            android:layout_marginStart="4dp"
-            android:visibility="gone"
-            android:focusable="false"
-            android:contentDescription="@string/notification_appops_microphone_active"
-            />
-        <ImageView
-            android:id="@+id/overlay"
-            android:layout_width="?attr/notificationHeaderIconSize"
-            android:layout_height="?attr/notificationHeaderIconSize"
-            android:src="@drawable/ic_alert_window_layer"
-            android:layout_marginStart="4dp"
-            android:visibility="gone"
-            android:focusable="false"
-            android:contentDescription="@string/notification_appops_overlay_active"
-            />
-    </LinearLayout>
     <include
         layout="@layout/notification_material_media_transfer_action"
         android:id="@+id/media_seamless"
diff --git a/core/res/res/values-ar/strings.xml b/core/res/res/values-ar/strings.xml
index 26dac61..b6f6627 100644
--- a/core/res/res/values-ar/strings.xml
+++ b/core/res/res/values-ar/strings.xml
@@ -515,9 +515,9 @@
     <string name="permdesc_changeWifiMulticastState" product="tv" msgid="1336952358450652595">"‏للسماح للتطبيق بتلقّي الحِزم التي يتم إرسالها إلى جميع الأجهزة على شبكة Wi-Fi باستخدام عناوين بث متعدد، وليس باستخدام جهاز Android TV فقط. ويؤدي ذلك إلى استخدام قدر أكبر من الطاقة يفوق ما يتم استهلاكه في وضع البث غير المتعدد."</string>
     <string name="permdesc_changeWifiMulticastState" product="default" msgid="8296627590220222740">"‏للسماح للتطبيق بتلقي الحزم التي يتم إرسالها إلى جميع الأجهزة على شبكة Wi-Fi باستخدام عناوين بث متعدد، وليس باستخدام هاتفك فقط. ويؤدي ذلك إلى استخدام قدر أكبر من الطاقة يفوق وضع البث غير المتعدد."</string>
     <string name="permlab_bluetoothAdmin" msgid="6490373569441946064">"الدخول إلى إعدادات بلوتوث"</string>
-    <string name="permdesc_bluetoothAdmin" product="tablet" msgid="5370837055438574863">"للسماح للتطبيق بتهيئة لوحة البلوتوث المحلي، واكتشاف أجهزة التحكم عن بعد والاقتران بها."</string>
+    <string name="permdesc_bluetoothAdmin" product="tablet" msgid="5370837055438574863">"للسماح للتطبيق بإعداد لوحة البلوتوث المحلي، واكتشاف أجهزة التحكم عن بعد والاقتران بها."</string>
     <string name="permdesc_bluetoothAdmin" product="tv" msgid="1623992984547014588">"‏للسماح للتطبيق بضبط البلوتوث على جهاز Android TV واكتشاف الأجهزة البعيدة والاقتران بها."</string>
-    <string name="permdesc_bluetoothAdmin" product="default" msgid="7381341743021234863">"للسماح للتطبيق بتهيئة هاتف البلوتوث المحلي، واكتشاف أجهزة التحكم عن بعد والاقتران بها."</string>
+    <string name="permdesc_bluetoothAdmin" product="default" msgid="7381341743021234863">"للسماح للتطبيق بإعداد هاتف البلوتوث المحلي، واكتشاف أجهزة التحكم عن بعد والاقتران بها."</string>
     <string name="permlab_accessWimaxState" msgid="7029563339012437434">"‏الاتصال بـشبكة WiMAX وقطع الاتصال بها"</string>
     <string name="permdesc_accessWimaxState" msgid="5372734776802067708">"‏للسماح للتطبيق بتحديد ما إذا تم تفعيل WiMAX وتحديد معلومات حول أي شبكات WiMAX متصلة."</string>
     <string name="permlab_changeWimaxState" msgid="6223305780806267462">"‏تغيير حالة WiMAX"</string>
@@ -1412,7 +1412,7 @@
     <string name="select_input_method" msgid="3971267998568587025">"اختيار أسلوب الإدخال"</string>
     <string name="show_ime" msgid="6406112007347443383">"استمرار عرضها على الشاشة أثناء نشاط لوحة المفاتيح الفعلية"</string>
     <string name="hardware" msgid="1800597768237606953">"إظهار لوحة المفاتيح الافتراضية"</string>
-    <string name="select_keyboard_layout_notification_title" msgid="4427643867639774118">"تهيئة لوحة المفاتيح الفعلية"</string>
+    <string name="select_keyboard_layout_notification_title" msgid="4427643867639774118">"إعداد لوحة المفاتيح الفعلية"</string>
     <string name="select_keyboard_layout_notification_message" msgid="8835158247369158154">"انقر لاختيار لغة وتنسيق"</string>
     <string name="fast_scroll_alphabet" msgid="8854435958703888376">" أ ب ت ث ج ح خ د ذ ر ز س ش ص ض ط ظ ع غ ف ق ك ل م ن ه و ي"</string>
     <string name="fast_scroll_numeric_alphabet" msgid="2529539945421557329">" 0123456789 أ ب ت ث ج ح خ د ذ ر ز س ش ص ض ط ظ ع غ ف ق ك ل م ن ه و ي"</string>
diff --git a/core/res/res/values-as/strings.xml b/core/res/res/values-as/strings.xml
index d0ed635..bad330e 100644
--- a/core/res/res/values-as/strings.xml
+++ b/core/res/res/values-as/strings.xml
@@ -983,9 +983,9 @@
     <string name="menu_space_shortcut_label" msgid="5949311515646872071">"স্পেচ"</string>
     <string name="menu_enter_shortcut_label" msgid="6709499510082897320">"লিখক"</string>
     <string name="menu_delete_shortcut_label" msgid="4365787714477739080">"মচক"</string>
-    <string name="search_go" msgid="2141477624421347086">"অনুসন্ধান কৰক"</string>
+    <string name="search_go" msgid="2141477624421347086">"Search"</string>
     <string name="search_hint" msgid="455364685740251925">"অনুসন্ধান কৰক…"</string>
-    <string name="searchview_description_search" msgid="1045552007537359343">"অনুসন্ধান কৰক"</string>
+    <string name="searchview_description_search" msgid="1045552007537359343">"Search"</string>
     <string name="searchview_description_query" msgid="7430242366971716338">"প্ৰশ্নৰ সন্ধান কৰক"</string>
     <string name="searchview_description_clear" msgid="1989371719192982900">"প্ৰশ্ন মচক"</string>
     <string name="searchview_description_submit" msgid="6771060386117334686">"প্ৰশ্ন দাখিল কৰক"</string>
@@ -1401,7 +1401,7 @@
     <string name="tutorial_double_tap_to_zoom_message_short" msgid="1842872462124648678">"জুম নিয়ন্ত্ৰণ কৰিবলৈ দুবাৰ টিপক"</string>
     <string name="gadget_host_error_inflating" msgid="2449961590495198720">"ৱিজেট যোগ কৰিব পৰা নগ\'ল।"</string>
     <string name="ime_action_go" msgid="5536744546326495436">"যাওক"</string>
-    <string name="ime_action_search" msgid="4501435960587287668">"অনুসন্ধান কৰক"</string>
+    <string name="ime_action_search" msgid="4501435960587287668">"Search"</string>
     <string name="ime_action_send" msgid="8456843745664334138">"পঠিয়াওক"</string>
     <string name="ime_action_next" msgid="4169702997635728543">"পৰৱৰ্তী"</string>
     <string name="ime_action_done" msgid="6299921014822891569">"সম্পন্ন হ’ল"</string>
@@ -1881,7 +1881,7 @@
     <string name="language_picker_section_suggested" msgid="6556199184638990447">"প্ৰস্তাৱিত"</string>
     <string name="language_picker_section_all" msgid="1985809075777564284">"সকলো ভাষা"</string>
     <string name="region_picker_section_all" msgid="756441309928774155">"সকলো অঞ্চল"</string>
-    <string name="locale_search_menu" msgid="6258090710176422934">"অনুসন্ধান কৰক"</string>
+    <string name="locale_search_menu" msgid="6258090710176422934">"Search"</string>
     <string name="app_suspended_title" msgid="888873445010322650">"এপটো নাই"</string>
     <string name="app_suspended_default_message" msgid="6451215678552004172">"এই মুহূৰ্তত <xliff:g id="APP_NAME_0">%1$s</xliff:g> উপলব্ধ নহয়। ইয়াক <xliff:g id="APP_NAME_1">%2$s</xliff:g>এ পৰিচালনা কৰে।"</string>
     <string name="app_suspended_more_details" msgid="211260942831587014">"অধিক জানক"</string>
diff --git a/core/res/res/values-be/strings.xml b/core/res/res/values-be/strings.xml
index cff1218..56736c0 100644
--- a/core/res/res/values-be/strings.xml
+++ b/core/res/res/values-be/strings.xml
@@ -976,7 +976,7 @@
     <string name="save_password_remember" msgid="6490888932657708341">"Запомніць"</string>
     <string name="save_password_never" msgid="6776808375903410659">"Ніколі"</string>
     <string name="open_permission_deny" msgid="5136793905306987251">"У вас няма дазволу на адкрыццё гэтай старонкі."</string>
-    <string name="text_copied" msgid="2531420577879738860">"Тэкст скапіяваны ў буфер абмену."</string>
+    <string name="text_copied" msgid="2531420577879738860">"Тэкст скапіраваны ў буфер абмену."</string>
     <string name="copied" msgid="4675902854553014676">"Скапіравана"</string>
     <string name="more_item_label" msgid="7419249600215749115">"Больш"</string>
     <string name="prepend_shortcut_label" msgid="1743716737502867951">"Меню+"</string>
diff --git a/core/res/res/values-bn/strings.xml b/core/res/res/values-bn/strings.xml
index ffc9ecf..22589cd 100644
--- a/core/res/res/values-bn/strings.xml
+++ b/core/res/res/values-bn/strings.xml
@@ -983,9 +983,9 @@
     <string name="menu_space_shortcut_label" msgid="5949311515646872071">"স্পেস"</string>
     <string name="menu_enter_shortcut_label" msgid="6709499510082897320">"enter"</string>
     <string name="menu_delete_shortcut_label" msgid="4365787714477739080">"মুছুন"</string>
-    <string name="search_go" msgid="2141477624421347086">"খুঁজুন"</string>
+    <string name="search_go" msgid="2141477624421347086">"সার্চ"</string>
     <string name="search_hint" msgid="455364685740251925">"সার্চ করুন..."</string>
-    <string name="searchview_description_search" msgid="1045552007537359343">"খুঁজুন"</string>
+    <string name="searchview_description_search" msgid="1045552007537359343">"সার্চ"</string>
     <string name="searchview_description_query" msgid="7430242366971716338">"সার্চ ক্যোয়ারী"</string>
     <string name="searchview_description_clear" msgid="1989371719192982900">"ক্যোয়ারী সাফ করুন"</string>
     <string name="searchview_description_submit" msgid="6771060386117334686">"ক্যোয়ারী জমা দিন"</string>
@@ -1401,7 +1401,7 @@
     <string name="tutorial_double_tap_to_zoom_message_short" msgid="1842872462124648678">"জুম নিয়ন্ত্রণের জন্য দুবার ট্যাপ করুন"</string>
     <string name="gadget_host_error_inflating" msgid="2449961590495198720">"উইজেট যোগ করা যায়নি৷"</string>
     <string name="ime_action_go" msgid="5536744546326495436">"যান"</string>
-    <string name="ime_action_search" msgid="4501435960587287668">"খুঁজুন"</string>
+    <string name="ime_action_search" msgid="4501435960587287668">"সার্চ"</string>
     <string name="ime_action_send" msgid="8456843745664334138">"পাঠান"</string>
     <string name="ime_action_next" msgid="4169702997635728543">"পরবর্তী"</string>
     <string name="ime_action_done" msgid="6299921014822891569">"সম্পন্ন হয়েছে"</string>
@@ -1881,7 +1881,7 @@
     <string name="language_picker_section_suggested" msgid="6556199184638990447">"প্রস্তাবিত"</string>
     <string name="language_picker_section_all" msgid="1985809075777564284">"সকল ভাষা"</string>
     <string name="region_picker_section_all" msgid="756441309928774155">"সমস্ত অঞ্চল"</string>
-    <string name="locale_search_menu" msgid="6258090710176422934">"খুঁজুন"</string>
+    <string name="locale_search_menu" msgid="6258090710176422934">"সার্চ"</string>
     <string name="app_suspended_title" msgid="888873445010322650">"অ্যাপটি উপলভ্য নয়"</string>
     <string name="app_suspended_default_message" msgid="6451215678552004172">"<xliff:g id="APP_NAME_0">%1$s</xliff:g> এখন উপলভ্য নয়। এই অ্যাপটিকে <xliff:g id="APP_NAME_1">%2$s</xliff:g> অ্যাপ ম্যানেজ করে।"</string>
     <string name="app_suspended_more_details" msgid="211260942831587014">"আরও জানুন"</string>
diff --git a/core/res/res/values-ca/strings.xml b/core/res/res/values-ca/strings.xml
index 2e4169e..4f55bf5 100644
--- a/core/res/res/values-ca/strings.xml
+++ b/core/res/res/values-ca/strings.xml
@@ -470,10 +470,10 @@
     <string name="permdesc_wakeLock" product="tablet" msgid="2441742939101526277">"Permet que l\'aplicació impedeixi que la tauleta entri en repòs."</string>
     <string name="permdesc_wakeLock" product="tv" msgid="2329298966735118796">"Permet que l\'aplicació impedeixi que el dispositiu Android TV entri en repòs."</string>
     <string name="permdesc_wakeLock" product="default" msgid="3689523792074007163">"Permet que l\'aplicació impedeixi que el telèfon entri en repòs."</string>
-    <string name="permlab_transmitIr" msgid="8077196086358004010">"transmissió d\'infraroigs"</string>
-    <string name="permdesc_transmitIr" product="tablet" msgid="5884738958581810253">"Permet que l\'aplicació utilitzi el transmissor d\'infraroigs de la tauleta."</string>
-    <string name="permdesc_transmitIr" product="tv" msgid="3278506969529173281">"Permet que l\'aplicació faci servir el transmissor d\'infraroigs del dispositiu Android TV."</string>
-    <string name="permdesc_transmitIr" product="default" msgid="8484193849295581808">"Permet que l\'aplicació utilitzi el transmissor d\'infraroigs del telèfon."</string>
+    <string name="permlab_transmitIr" msgid="8077196086358004010">"transmissió d\'infrarojos"</string>
+    <string name="permdesc_transmitIr" product="tablet" msgid="5884738958581810253">"Permet que l\'aplicació utilitzi el transmissor d\'infrarojos de la tauleta."</string>
+    <string name="permdesc_transmitIr" product="tv" msgid="3278506969529173281">"Permet que l\'aplicació faci servir el transmissor d\'infrarojos del dispositiu Android TV."</string>
+    <string name="permdesc_transmitIr" product="default" msgid="8484193849295581808">"Permet que l\'aplicació utilitzi el transmissor d\'infrarojos del telèfon."</string>
     <string name="permlab_setWallpaper" msgid="6959514622698794511">"establir fons de pantalla"</string>
     <string name="permdesc_setWallpaper" msgid="2973996714129021397">"Permet que l\'aplicació estableixi el fons de pantalla del sistema."</string>
     <string name="permlab_setWallpaperHints" msgid="1153485176642032714">"ajustament de la mida del fons de pantalla"</string>
diff --git a/core/res/res/values-es-rUS/strings.xml b/core/res/res/values-es-rUS/strings.xml
index 27ff3f0..84f3243 100644
--- a/core/res/res/values-es-rUS/strings.xml
+++ b/core/res/res/values-es-rUS/strings.xml
@@ -1937,7 +1937,7 @@
       <item quantity="one">Una sugerencia de Autocompletar</item>
     </plurals>
     <string name="autofill_save_title" msgid="7719802414283739775">"¿Quieres guardar en "<b>"<xliff:g id="LABEL">%1$s</xliff:g>"</b>"?"</string>
-    <string name="autofill_save_title_with_type" msgid="3002460014579799605">"¿Quieres guardar <xliff:g id="TYPE">%1$s</xliff:g> en "<b>"<xliff:g id="LABEL">%2$s</xliff:g>"</b>"?"</string>
+    <string name="autofill_save_title_with_type" msgid="3002460014579799605">"¿Quieres guardar la <xliff:g id="TYPE">%1$s</xliff:g> en "<b>"<xliff:g id="LABEL">%2$s</xliff:g>"</b>"?"</string>
     <string name="autofill_save_title_with_2types" msgid="3783270967447869241">"¿Quieres guardar <xliff:g id="TYPE_0">%1$s</xliff:g> y <xliff:g id="TYPE_1">%2$s</xliff:g> en "<b>"<xliff:g id="LABEL">%3$s</xliff:g>"</b>"?"</string>
     <string name="autofill_save_title_with_3types" msgid="6598228952100102578">"¿Quieres guardar <xliff:g id="TYPE_0">%1$s</xliff:g>, <xliff:g id="TYPE_1">%2$s</xliff:g> y <xliff:g id="TYPE_2">%3$s</xliff:g> en "<b>"<xliff:g id="LABEL">%4$s</xliff:g>"</b>"?"</string>
     <string name="autofill_update_title" msgid="3630695947047069136">"¿Quieres actualizar en "<b>"<xliff:g id="LABEL">%1$s</xliff:g>"</b>"?"</string>
diff --git a/core/res/res/values-gu/strings.xml b/core/res/res/values-gu/strings.xml
index ca35a68..5ffc420 100644
--- a/core/res/res/values-gu/strings.xml
+++ b/core/res/res/values-gu/strings.xml
@@ -985,7 +985,7 @@
     <string name="menu_delete_shortcut_label" msgid="4365787714477739080">"ડિલીટ કરો"</string>
     <string name="search_go" msgid="2141477624421347086">"શોધો"</string>
     <string name="search_hint" msgid="455364685740251925">"શોધો…"</string>
-    <string name="searchview_description_search" msgid="1045552007537359343">"શોધ"</string>
+    <string name="searchview_description_search" msgid="1045552007537359343">"શોધો"</string>
     <string name="searchview_description_query" msgid="7430242366971716338">"શોધ ક્વેરી"</string>
     <string name="searchview_description_clear" msgid="1989371719192982900">"ક્વેરી સાફ કરો"</string>
     <string name="searchview_description_submit" msgid="6771060386117334686">"ક્વેરી સબમિટ કરો"</string>
diff --git a/core/res/res/values-hy/strings.xml b/core/res/res/values-hy/strings.xml
index b115025..264a8fc 100644
--- a/core/res/res/values-hy/strings.xml
+++ b/core/res/res/values-hy/strings.xml
@@ -1894,7 +1894,7 @@
     <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>
-    <string name="new_sms_notification_content" msgid="3197949934153460639">"Դիտելու համար բացել SMS հավելվածը"</string>
+    <string name="new_sms_notification_content" msgid="3197949934153460639">"Դիտելու համար բացել SMS-ների փոխանակման հավելվածը"</string>
     <string name="profile_encrypted_title" msgid="9001208667521266472">"Որոշ գործառույթներ կարող են չաշխատել"</string>
     <string name="profile_encrypted_detail" msgid="5279730442756849055">"Աշխատանքային պրոֆիլը կողպված է"</string>
     <string name="profile_encrypted_message" msgid="1128512616293157802">"Հպեք՝ այն ապակողպելու համար"</string>
diff --git a/core/res/res/values-in/strings.xml b/core/res/res/values-in/strings.xml
index 688996d..f9e3e2f 100644
--- a/core/res/res/values-in/strings.xml
+++ b/core/res/res/values-in/strings.xml
@@ -866,12 +866,12 @@
     <string name="lockscreen_failed_attempts_almost_glogin" product="tablet" msgid="3069635524964070596">"Anda telah <xliff:g id="NUMBER_0">%1$d</xliff:g> kali salah menggambar pola pembuka kunci. Setelah <xliff:g id="NUMBER_1">%2$d</xliff:g> lagi upaya gagal, Anda akan diminta membuka kunci tablet menggunakan proses masuk Google.\n\nCoba lagi dalam <xliff:g id="NUMBER_2">%3$d</xliff:g> detik."</string>
     <string name="lockscreen_failed_attempts_almost_glogin" product="tv" msgid="6399092175942158529">"Sudah <xliff:g id="NUMBER_0">%1$d</xliff:g> kali Anda salah menggambar pola pembuka kunci. Setelah gagal <xliff:g id="NUMBER_1">%2$d</xliff:g> kali lagi, Anda akan diminta membuka kunci perangkat Android TV menggunakan login Google.\n\n Coba lagi dalam <xliff:g id="NUMBER_2">%3$d</xliff:g> detik."</string>
     <string name="lockscreen_failed_attempts_almost_glogin" product="default" msgid="5691623136957148335">"Anda telah <xliff:g id="NUMBER_0">%1$d</xliff:g> kali salah menggambar pola pembuka kunci. Setelah <xliff:g id="NUMBER_1">%2$d</xliff:g> lagi upaya gagal, Anda akan diminta membuka kunci ponsel menggunakan proses masuk Google.\n\nCoba lagi dalam <xliff:g id="NUMBER_2">%3$d</xliff:g> detik."</string>
-    <string name="lockscreen_failed_attempts_almost_at_wipe" product="tablet" msgid="7914445759242151426">"Anda telah gagal mencoba membuka gembok tablet sebanyak <xliff:g id="NUMBER_0">%1$d</xliff:g> kali. Setelah <xliff:g id="NUMBER_1">%2$d</xliff:g> upaya gagal lagi, tablet akan disetel ulang ke setelan default pabrik dan semua data pengguna hilang."</string>
+    <string name="lockscreen_failed_attempts_almost_at_wipe" product="tablet" msgid="7914445759242151426">"Anda telah gagal mencoba membuka gembok tablet sebanyak <xliff:g id="NUMBER_0">%1$d</xliff:g> kali. Setelah <xliff:g id="NUMBER_1">%2$d</xliff:g> upaya gagal lagi, tablet akan direset ke setelan default pabrik dan semua data pengguna hilang."</string>
     <string name="lockscreen_failed_attempts_almost_at_wipe" product="tv" msgid="4275591249631864248">"Anda telah <xliff:g id="NUMBER_0">%1$d</xliff:g> kali gagal membuka kunci perangkat Android TV. Setelah gagal <xliff:g id="NUMBER_1">%2$d</xliff:g> kali lagi, perangkat Android TV akan direset ke default pabrik dan semua data pengguna akan hilang."</string>
-    <string name="lockscreen_failed_attempts_almost_at_wipe" product="default" msgid="1166532464798446579">"Anda telah gagal mencoba membuka gembok ponsel sebanyak <xliff:g id="NUMBER_0">%1$d</xliff:g> kali. Setelah <xliff:g id="NUMBER_1">%2$d</xliff:g> upaya gagal lagi, ponsel akan disetel ulang ke setelan default pabrik dan semua data pengguna hilang."</string>
-    <string name="lockscreen_failed_attempts_now_wiping" product="tablet" msgid="8682445539263683414">"Anda telah gagal mencoba membuka gembok tablet sebanyak <xliff:g id="NUMBER">%d</xliff:g> kali. Kini tablet akan disetel ulang ke setelan default pabrik."</string>
+    <string name="lockscreen_failed_attempts_almost_at_wipe" product="default" msgid="1166532464798446579">"Anda telah gagal mencoba membuka gembok ponsel sebanyak <xliff:g id="NUMBER_0">%1$d</xliff:g> kali. Setelah <xliff:g id="NUMBER_1">%2$d</xliff:g> upaya gagal lagi, ponsel akan direset ke setelan default pabrik dan semua data pengguna hilang."</string>
+    <string name="lockscreen_failed_attempts_now_wiping" product="tablet" msgid="8682445539263683414">"Anda telah gagal mencoba membuka gembok tablet sebanyak <xliff:g id="NUMBER">%d</xliff:g> kali. Kini tablet akan direset ke setelan default pabrik."</string>
     <string name="lockscreen_failed_attempts_now_wiping" product="tv" msgid="2205435033340091883">"Anda telah <xliff:g id="NUMBER">%d</xliff:g> kali gagal membuka kunci perangkat Android TV. Perangkat Android TV sekarang akan direset ke default pabrik."</string>
-    <string name="lockscreen_failed_attempts_now_wiping" product="default" msgid="2203704707679895487">"Anda telah gagal mencoba membuka gembok ponsel sebanyak <xliff:g id="NUMBER">%d</xliff:g> kali. Kini ponsel akan disetel ulang ke setelan default pabrik."</string>
+    <string name="lockscreen_failed_attempts_now_wiping" product="default" msgid="2203704707679895487">"Anda telah gagal mencoba membuka gembok ponsel sebanyak <xliff:g id="NUMBER">%d</xliff:g> kali. Kini ponsel akan direset ke setelan default pabrik."</string>
     <string name="lockscreen_too_many_failed_attempts_countdown" msgid="6807200118164539589">"Coba lagi dalam <xliff:g id="NUMBER">%d</xliff:g> detik."</string>
     <string name="lockscreen_forgot_pattern_button_text" msgid="8362442730606839031">"Lupa pola?"</string>
     <string name="lockscreen_glogin_forgot_pattern" msgid="9218940117797602518">"Pembuka kunci akun"</string>
@@ -945,7 +945,7 @@
     <string name="autofill_postal_code" msgid="7034789388968295591">"Kode pos"</string>
     <string name="autofill_state" msgid="3341725337190434069">"Negara Bagian"</string>
     <string name="autofill_zip_code" msgid="1315503730274962450">"Kode pos"</string>
-    <string name="autofill_county" msgid="7781382735643492173">"Wilayah"</string>
+    <string name="autofill_county" msgid="7781382735643492173">"County"</string>
     <string name="autofill_island" msgid="5367139008536593734">"Pulau"</string>
     <string name="autofill_district" msgid="6428712062213557327">"Distrik"</string>
     <string name="autofill_department" msgid="9047276226873531529">"Departemen"</string>
@@ -1438,7 +1438,7 @@
     <string name="vpn_lockdown_config" msgid="8331697329868252169">"Ubah setelan jaringan atau VPN"</string>
     <string name="upload_file" msgid="8651942222301634271">"Pilih file"</string>
     <string name="no_file_chosen" msgid="4146295695162318057">"Tidak ada file yang dipilih"</string>
-    <string name="reset" msgid="3865826612628171429">"Setel ulang"</string>
+    <string name="reset" msgid="3865826612628171429">"Reset"</string>
     <string name="submit" msgid="862795280643405865">"Kirim"</string>
     <string name="car_mode_disable_notification_title" msgid="8450693275833142896">"Aplikasi mengemudi sedang berjalan"</string>
     <string name="car_mode_disable_notification_message" msgid="8954550232288567515">"Ketuk untuk keluar dari aplikasi mengemudi."</string>
@@ -1608,12 +1608,12 @@
     <string name="kg_too_many_failed_pin_attempts_dialog_message" msgid="23741434207544038">"Anda telah <xliff:g id="NUMBER_0">%1$d</xliff:g> kali salah mengetik PIN. \n\nCoba lagi dalam <xliff:g id="NUMBER_1">%2$d</xliff:g> detik."</string>
     <string name="kg_too_many_failed_password_attempts_dialog_message" msgid="3328686432962224215">"Anda telah <xliff:g id="NUMBER_0">%1$d</xliff:g> kali salah mengetik sandi. \n\nCoba lagi dalam <xliff:g id="NUMBER_1">%2$d</xliff:g> detik."</string>
     <string name="kg_too_many_failed_pattern_attempts_dialog_message" msgid="7357404233979139075">"Anda telah <xliff:g id="NUMBER_0">%1$d</xliff:g> kali salah menggambar pola pembuka kunci. \n\nCoba lagi dalam <xliff:g id="NUMBER_1">%2$d</xliff:g> detik."</string>
-    <string name="kg_failed_attempts_almost_at_wipe" product="tablet" msgid="3479940221343361587">"Anda telah <xliff:g id="NUMBER_0">%1$d</xliff:g> kali gagal saat berusaha membuka kunci tablet. Setelah <xliff:g id="NUMBER_1">%2$d</xliff:g> lagi upaya gagal, tablet akan disetel ulang ke setelan default pabrik dan semua data pengguna akan hilang."</string>
+    <string name="kg_failed_attempts_almost_at_wipe" product="tablet" msgid="3479940221343361587">"Anda telah <xliff:g id="NUMBER_0">%1$d</xliff:g> kali gagal saat berusaha membuka kunci tablet. Setelah <xliff:g id="NUMBER_1">%2$d</xliff:g> lagi upaya gagal, tablet akan direset ke setelan default pabrik dan semua data pengguna akan hilang."</string>
     <string name="kg_failed_attempts_almost_at_wipe" product="tv" msgid="9064457748587850217">"Anda telah <xliff:g id="NUMBER_0">%1$d</xliff:g> kali gagal membuka kunci perangkat Android TV. Setelah gagal <xliff:g id="NUMBER_1">%2$d</xliff:g> kali lagi, perangkat Android TV akan direset ke default pabrik dan semua data pengguna akan hilang."</string>
-    <string name="kg_failed_attempts_almost_at_wipe" product="default" msgid="5955398963754432548">"Anda telah <xliff:g id="NUMBER_0">%1$d</xliff:g> kali gagal saat berusaha membuka kunci ponsel. Setelah <xliff:g id="NUMBER_1">%2$d</xliff:g> lagi upaya gagal, ponsel akan disetel ulang ke setelan default pabrik dan semua data pengguna akan hilang."</string>
-    <string name="kg_failed_attempts_now_wiping" product="tablet" msgid="2299099385175083308">"Anda telah <xliff:g id="NUMBER">%d</xliff:g> kali gagal saat berusaha membuka kunci tablet. Kini tablet akan disetel ulang ke setelan default pabrik."</string>
+    <string name="kg_failed_attempts_almost_at_wipe" product="default" msgid="5955398963754432548">"Anda telah <xliff:g id="NUMBER_0">%1$d</xliff:g> kali gagal saat berusaha membuka kunci ponsel. Setelah <xliff:g id="NUMBER_1">%2$d</xliff:g> lagi upaya gagal, ponsel akan direset ke setelan default pabrik dan semua data pengguna akan hilang."</string>
+    <string name="kg_failed_attempts_now_wiping" product="tablet" msgid="2299099385175083308">"Anda telah <xliff:g id="NUMBER">%d</xliff:g> kali gagal saat berusaha membuka kunci tablet. Kini tablet akan direset ke setelan default pabrik."</string>
     <string name="kg_failed_attempts_now_wiping" product="tv" msgid="5045460916106267585">"Anda telah <xliff:g id="NUMBER">%d</xliff:g> kali gagal membuka kunci perangkat Android TV. Perangkat Android TV sekarang akan direset ke default pabrik."</string>
-    <string name="kg_failed_attempts_now_wiping" product="default" msgid="5043730590446071189">"Anda telah <xliff:g id="NUMBER">%d</xliff:g> kali gagal saat berusaha untuk membuka kunci ponsel. Kini ponsel akan disetel ulang ke setelan default pabrik."</string>
+    <string name="kg_failed_attempts_now_wiping" product="default" msgid="5043730590446071189">"Anda telah <xliff:g id="NUMBER">%d</xliff:g> kali gagal saat berusaha untuk membuka kunci ponsel. Kini ponsel akan direset ke setelan default pabrik."</string>
     <string name="kg_failed_attempts_almost_at_login" product="tablet" msgid="7086799295109717623">"Anda telah <xliff:g id="NUMBER_0">%1$d</xliff:g> kali salah menggambar pola pembuka kunci. Setelah <xliff:g id="NUMBER_1">%2$d</xliff:g> lagi upaya gagal, Anda akan diminta membuka kunci tablet menggunakan akun email.\n\nCoba lagi dalam <xliff:g id="NUMBER_2">%3$d</xliff:g> detik."</string>
     <string name="kg_failed_attempts_almost_at_login" product="tv" msgid="4670840383567106114">"Anda telah <xliff:g id="NUMBER_0">%1$d</xliff:g> kali salah menggambar pola pembuka kunci. Setelah gagal <xliff:g id="NUMBER_1">%2$d</xliff:g> kali lagi, Anda akan diminta membuka kunci perangkat Android TV menggunakan akun email.\n\n Coba lagi dalam <xliff:g id="NUMBER_2">%3$d</xliff:g> detik."</string>
     <string name="kg_failed_attempts_almost_at_login" product="default" msgid="5270861875006378092">"Anda telah <xliff:g id="NUMBER_0">%1$d</xliff:g> kali salah menggambar pola pembuka kunci. Setelah <xliff:g id="NUMBER_1">%2$d</xliff:g> lagi upaya gagal, Anda akan diminta membuka kunci ponsel menggunakan akun email.\n\nCoba lagi dalam <xliff:g id="NUMBER_2">%3$d</xliff:g> detik."</string>
@@ -1907,7 +1907,7 @@
     <string name="app_info" msgid="6113278084877079851">"Info aplikasi"</string>
     <string name="negative_duration" msgid="1938335096972945232">"−<xliff:g id="TIME">%1$s</xliff:g>"</string>
     <string name="demo_starting_message" msgid="6577581216125805905">"Memulai demo..."</string>
-    <string name="demo_restarting_message" msgid="1160053183701746766">"Menyetel ulang perangkat..."</string>
+    <string name="demo_restarting_message" msgid="1160053183701746766">"Mereset perangkat..."</string>
     <string name="suspended_widget_accessibility" msgid="6331451091851326101">"<xliff:g id="LABEL">%1$s</xliff:g> dinonaktifkan"</string>
     <string name="conference_call" msgid="5731633152336490471">"Konferensi Telepon"</string>
     <string name="tooltip_popup_title" msgid="7863719020269945722">"Keterangan alat"</string>
diff --git a/core/res/res/values-it/strings.xml b/core/res/res/values-it/strings.xml
index 400c44a..14bfa03 100644
--- a/core/res/res/values-it/strings.xml
+++ b/core/res/res/values-it/strings.xml
@@ -1937,7 +1937,7 @@
       <item quantity="one">Un suggerimento di Compilazione automatica</item>
     </plurals>
     <string name="autofill_save_title" msgid="7719802414283739775">"Vuoi salvare su "<b>"<xliff:g id="LABEL">%1$s</xliff:g>"</b>"?"</string>
-    <string name="autofill_save_title_with_type" msgid="3002460014579799605">"Vuoi salvare <xliff:g id="TYPE">%1$s</xliff:g> su "<b>"<xliff:g id="LABEL">%2$s</xliff:g>"</b>"?"</string>
+    <string name="autofill_save_title_with_type" msgid="3002460014579799605">"Vuoi salvare la <xliff:g id="TYPE">%1$s</xliff:g> su "<b>"<xliff:g id="LABEL">%2$s</xliff:g>"</b>"?"</string>
     <string name="autofill_save_title_with_2types" msgid="3783270967447869241">"Vuoi salvare <xliff:g id="TYPE_0">%1$s</xliff:g> e <xliff:g id="TYPE_1">%2$s</xliff:g> su "<b>"<xliff:g id="LABEL">%3$s</xliff:g>"</b>"?"</string>
     <string name="autofill_save_title_with_3types" msgid="6598228952100102578">"Vuoi salvare <xliff:g id="TYPE_0">%1$s</xliff:g>, <xliff:g id="TYPE_1">%2$s</xliff:g> e <xliff:g id="TYPE_2">%3$s</xliff:g> su "<b>"<xliff:g id="LABEL">%4$s</xliff:g>"</b>"?"</string>
     <string name="autofill_update_title" msgid="3630695947047069136">"Vuoi aggiornare su "<b>"<xliff:g id="LABEL">%1$s</xliff:g>"</b>"?"</string>
diff --git a/core/res/res/values-iw/strings.xml b/core/res/res/values-iw/strings.xml
index 2c42745..2b6d226 100644
--- a/core/res/res/values-iw/strings.xml
+++ b/core/res/res/values-iw/strings.xml
@@ -1202,7 +1202,7 @@
     <string name="aerr_application_repeated" msgid="7804378743218496566">"האפליקציה <xliff:g id="APPLICATION">%1$s</xliff:g> נעצרת שוב ושוב"</string>
     <string name="aerr_process_repeated" msgid="1153152413537954974">"האפליקציה <xliff:g id="PROCESS">%1$s</xliff:g> נעצרת שוב ושוב"</string>
     <string name="aerr_restart" msgid="2789618625210505419">"פתח שוב את האפליקציה"</string>
-    <string name="aerr_report" msgid="3095644466849299308">"משוב"</string>
+    <string name="aerr_report" msgid="3095644466849299308">"שליחת משוב"</string>
     <string name="aerr_close" msgid="3398336821267021852">"סגירה"</string>
     <string name="aerr_mute" msgid="2304972923480211376">"השתק עד הפעלה מחדש של המכשיר"</string>
     <string name="aerr_wait" msgid="3198677780474548217">"המתן"</string>
diff --git a/core/res/res/values-kn/strings.xml b/core/res/res/values-kn/strings.xml
index cbb9ca2..c7786ae 100644
--- a/core/res/res/values-kn/strings.xml
+++ b/core/res/res/values-kn/strings.xml
@@ -983,9 +983,9 @@
     <string name="menu_space_shortcut_label" msgid="5949311515646872071">"space"</string>
     <string name="menu_enter_shortcut_label" msgid="6709499510082897320">"enter"</string>
     <string name="menu_delete_shortcut_label" msgid="4365787714477739080">"ಅಳಿಸಿ"</string>
-    <string name="search_go" msgid="2141477624421347086">"ಹುಡುಕಿ"</string>
+    <string name="search_go" msgid="2141477624421347086">"Search"</string>
     <string name="search_hint" msgid="455364685740251925">"ಹುಡುಕಿ…"</string>
-    <string name="searchview_description_search" msgid="1045552007537359343">"ಹುಡುಕಿ"</string>
+    <string name="searchview_description_search" msgid="1045552007537359343">"Search"</string>
     <string name="searchview_description_query" msgid="7430242366971716338">"ಪ್ರಶ್ನೆಯನ್ನು ಹುಡುಕಿ"</string>
     <string name="searchview_description_clear" msgid="1989371719192982900">"ಪ್ರಶ್ನೆಯನ್ನು ತೆರವುಗೊಳಿಸು"</string>
     <string name="searchview_description_submit" msgid="6771060386117334686">"ಪ್ರಶ್ನೆಯನ್ನು ಸಲ್ಲಿಸು"</string>
@@ -1401,7 +1401,7 @@
     <string name="tutorial_double_tap_to_zoom_message_short" msgid="1842872462124648678">"ಝೂಮ್‌ ನಿಯಂತ್ರಿಸಲು ಎರಡು ಬಾರಿ ಟ್ಯಾಪ್ ಮಾಡಿ"</string>
     <string name="gadget_host_error_inflating" msgid="2449961590495198720">"ವಿಜೆಟ್ ಸೇರಿಸಲು ಸಾಧ್ಯವಾಗುತ್ತಿಲ್ಲ."</string>
     <string name="ime_action_go" msgid="5536744546326495436">"ಹೋಗು"</string>
-    <string name="ime_action_search" msgid="4501435960587287668">"ಹುಡುಕಿ"</string>
+    <string name="ime_action_search" msgid="4501435960587287668">"Search"</string>
     <string name="ime_action_send" msgid="8456843745664334138">"ಕಳುಹಿಸು"</string>
     <string name="ime_action_next" msgid="4169702997635728543">"ಮುಂದೆ"</string>
     <string name="ime_action_done" msgid="6299921014822891569">"ಮುಗಿದಿದೆ"</string>
@@ -1881,7 +1881,7 @@
     <string name="language_picker_section_suggested" msgid="6556199184638990447">"ಸೂಚಿತ ಭಾಷೆ"</string>
     <string name="language_picker_section_all" msgid="1985809075777564284">"ಎಲ್ಲಾ ಭಾಷೆಗಳು"</string>
     <string name="region_picker_section_all" msgid="756441309928774155">"ಎಲ್ಲಾ ಪ್ರದೇಶಗಳು"</string>
-    <string name="locale_search_menu" msgid="6258090710176422934">"ಹುಡುಕಿ"</string>
+    <string name="locale_search_menu" msgid="6258090710176422934">"Search"</string>
     <string name="app_suspended_title" msgid="888873445010322650">"ಅಪ್ಲಿಕೇಶನ್ ಲಭ್ಯವಿಲ್ಲ"</string>
     <string name="app_suspended_default_message" msgid="6451215678552004172">"<xliff:g id="APP_NAME_0">%1$s</xliff:g> ಅಪ್ಲಿಕೇಶನ್‌ ಸದ್ಯಕ್ಕೆ ಲಭ್ಯವಿಲ್ಲ. ಇದನ್ನು <xliff:g id="APP_NAME_1">%2$s</xliff:g> ನಲ್ಲಿ ನಿರ್ವಹಿಸಲಾಗುತ್ತಿದೆ."</string>
     <string name="app_suspended_more_details" msgid="211260942831587014">"ಇನ್ನಷ್ಟು ತಿಳಿಯಿರಿ"</string>
diff --git a/core/res/res/values-mk/strings.xml b/core/res/res/values-mk/strings.xml
index 8871293..df7781e 100644
--- a/core/res/res/values-mk/strings.xml
+++ b/core/res/res/values-mk/strings.xml
@@ -1773,7 +1773,7 @@
     <string name="restr_pin_try_later" msgid="5897719962541636727">"Обиди се повторно подоцна"</string>
     <string name="immersive_cling_title" msgid="2307034298721541791">"Се прикажува на цел екран"</string>
     <string name="immersive_cling_description" msgid="7092737175345204832">"За да излезете, повлечете одозгора надолу."</string>
-    <string name="immersive_cling_positive" msgid="7047498036346489883">"Разбрав"</string>
+    <string name="immersive_cling_positive" msgid="7047498036346489883">"Сфатив"</string>
     <string name="done_label" msgid="7283767013231718521">"Готово"</string>
     <string name="hour_picker_description" msgid="5153757582093524635">"Приказ на часови во кружно движење"</string>
     <string name="minute_picker_description" msgid="9029797023621927294">"Приказ на минути во кружно движење"</string>
diff --git a/core/res/res/values-ml/strings.xml b/core/res/res/values-ml/strings.xml
index af75969..12c0cb4 100644
--- a/core/res/res/values-ml/strings.xml
+++ b/core/res/res/values-ml/strings.xml
@@ -983,9 +983,9 @@
     <string name="menu_space_shortcut_label" msgid="5949311515646872071">"space"</string>
     <string name="menu_enter_shortcut_label" msgid="6709499510082897320">"enter"</string>
     <string name="menu_delete_shortcut_label" msgid="4365787714477739080">"delete"</string>
-    <string name="search_go" msgid="2141477624421347086">"തിരയൽ"</string>
+    <string name="search_go" msgid="2141477624421347086">"Search"</string>
     <string name="search_hint" msgid="455364685740251925">"തിരയുക…"</string>
-    <string name="searchview_description_search" msgid="1045552007537359343">"തിരയൽ"</string>
+    <string name="searchview_description_search" msgid="1045552007537359343">"Search"</string>
     <string name="searchview_description_query" msgid="7430242366971716338">"തിരയൽ അന്വേഷണം"</string>
     <string name="searchview_description_clear" msgid="1989371719192982900">"അന്വേഷണം മായ്‌ക്കുക"</string>
     <string name="searchview_description_submit" msgid="6771060386117334686">"ചോദ്യം സമർപ്പിക്കുക"</string>
@@ -1401,7 +1401,7 @@
     <string name="tutorial_double_tap_to_zoom_message_short" msgid="1842872462124648678">"സൂം നിയന്ത്രണം ലഭിക്കാൻ രണ്ടുതവണ ടാപ്പുചെയ്യുക"</string>
     <string name="gadget_host_error_inflating" msgid="2449961590495198720">"വിജറ്റ് ചേർക്കാനായില്ല."</string>
     <string name="ime_action_go" msgid="5536744546326495436">"പോവുക"</string>
-    <string name="ime_action_search" msgid="4501435960587287668">"തിരയൽ"</string>
+    <string name="ime_action_search" msgid="4501435960587287668">"Search"</string>
     <string name="ime_action_send" msgid="8456843745664334138">"അയയ്‌ക്കുക"</string>
     <string name="ime_action_next" msgid="4169702997635728543">"അടുത്തത്"</string>
     <string name="ime_action_done" msgid="6299921014822891569">"പൂർത്തിയായി"</string>
@@ -1881,7 +1881,7 @@
     <string name="language_picker_section_suggested" msgid="6556199184638990447">"നിര്‍‌ദ്ദേശിച്ചത്"</string>
     <string name="language_picker_section_all" msgid="1985809075777564284">"എല്ലാ ഭാഷകളും"</string>
     <string name="region_picker_section_all" msgid="756441309928774155">"എല്ലാ പ്രദേശങ്ങളും"</string>
-    <string name="locale_search_menu" msgid="6258090710176422934">"തിരയുക"</string>
+    <string name="locale_search_menu" msgid="6258090710176422934">"Search"</string>
     <string name="app_suspended_title" msgid="888873445010322650">"ആപ്പ് ലഭ്യമല്ല"</string>
     <string name="app_suspended_default_message" msgid="6451215678552004172">"<xliff:g id="APP_NAME_0">%1$s</xliff:g> ഇപ്പോൾ ലഭ്യമല്ല. <xliff:g id="APP_NAME_1">%2$s</xliff:g> ആണ് ഇത് മാനേജ് ചെയ്യുന്നത്."</string>
     <string name="app_suspended_more_details" msgid="211260942831587014">"കൂടുതലറിയുക"</string>
diff --git a/core/res/res/values-mr/strings.xml b/core/res/res/values-mr/strings.xml
index ec240c1..da6f2b7 100644
--- a/core/res/res/values-mr/strings.xml
+++ b/core/res/res/values-mr/strings.xml
@@ -983,9 +983,9 @@
     <string name="menu_space_shortcut_label" msgid="5949311515646872071">"स्पेस"</string>
     <string name="menu_enter_shortcut_label" msgid="6709499510082897320">"एंटर"</string>
     <string name="menu_delete_shortcut_label" msgid="4365787714477739080">"हटवा"</string>
-    <string name="search_go" msgid="2141477624421347086">"शोध"</string>
+    <string name="search_go" msgid="2141477624421347086">"Search"</string>
     <string name="search_hint" msgid="455364685740251925">"शोधा…"</string>
-    <string name="searchview_description_search" msgid="1045552007537359343">"शोध"</string>
+    <string name="searchview_description_search" msgid="1045552007537359343">"Search"</string>
     <string name="searchview_description_query" msgid="7430242366971716338">"शोध क्वेरी"</string>
     <string name="searchview_description_clear" msgid="1989371719192982900">"क्‍वेरी साफ करा"</string>
     <string name="searchview_description_submit" msgid="6771060386117334686">"क्वेरी सबमिट करा"</string>
@@ -1401,7 +1401,7 @@
     <string name="tutorial_double_tap_to_zoom_message_short" msgid="1842872462124648678">"झूम नियंत्रणासाठी दोनदा टॅप करा"</string>
     <string name="gadget_host_error_inflating" msgid="2449961590495198720">"विजेट जोडू शकलो नाही."</string>
     <string name="ime_action_go" msgid="5536744546326495436">"जा"</string>
-    <string name="ime_action_search" msgid="4501435960587287668">"शोधा"</string>
+    <string name="ime_action_search" msgid="4501435960587287668">"Search"</string>
     <string name="ime_action_send" msgid="8456843745664334138">"पाठवा"</string>
     <string name="ime_action_next" msgid="4169702997635728543">"पुढे"</string>
     <string name="ime_action_done" msgid="6299921014822891569">"पूर्ण झाले"</string>
@@ -1881,7 +1881,7 @@
     <string name="language_picker_section_suggested" msgid="6556199184638990447">"सुचवलेल्या भाषा"</string>
     <string name="language_picker_section_all" msgid="1985809075777564284">"सर्व भाषा"</string>
     <string name="region_picker_section_all" msgid="756441309928774155">"सर्व प्रदेश"</string>
-    <string name="locale_search_menu" msgid="6258090710176422934">"शोध"</string>
+    <string name="locale_search_menu" msgid="6258090710176422934">"Search"</string>
     <string name="app_suspended_title" msgid="888873445010322650">"अ‍ॅप उपलब्ध नाही"</string>
     <string name="app_suspended_default_message" msgid="6451215678552004172">"<xliff:g id="APP_NAME_0">%1$s</xliff:g> आत्ता उपलब्ध नाही. हे <xliff:g id="APP_NAME_1">%2$s</xliff:g> कडून व्यवस्थापित केले जाते."</string>
     <string name="app_suspended_more_details" msgid="211260942831587014">"अधिक जाणून घ्या"</string>
diff --git a/core/res/res/values-ne/strings.xml b/core/res/res/values-ne/strings.xml
index fd7b1b2..bb79c21 100644
--- a/core/res/res/values-ne/strings.xml
+++ b/core/res/res/values-ne/strings.xml
@@ -1633,7 +1633,7 @@
     <string name="accessibility_shortcut_menu_item_status_off" msgid="5531598275559472393">"निष्क्रिय"</string>
     <string name="accessibility_enable_service_title" msgid="3931558336268541484">"<xliff:g id="SERVICE">%1$s</xliff:g> लाई तपाईंको यन्त्र पूर्ण रूपमा नियन्त्रण गर्न दिने हो?"</string>
     <string name="accessibility_enable_service_encryption_warning" msgid="8603532708618236909">"तपाईंले <xliff:g id="SERVICE">%1$s</xliff:g> सक्रिय गर्नुभयो भने तपाईंको यन्त्रले डेटा इन्क्रिप्ट गर्ने सुविधाको स्तरोन्नति गर्न तपाईंको स्क्रिन लक सुविधाको प्रयोग गर्ने छैन।"</string>
-    <string name="accessibility_service_warning_description" msgid="291674995220940133">"तपाईंलाई पहुँच राख्न आवश्यक पर्ने कुरामा सहयोग गर्ने अनुप्रयोगहरूमाथि पूर्ण नियन्त्रण गर्नु उपयुक्त हुन्छ तर अधिकांश अनुप्रयोगहरूका हकमा यस्तो नियन्त्रण उपयुक्त हुँदैन।"</string>
+    <string name="accessibility_service_warning_description" msgid="291674995220940133">"तपाईंलाई पहुँच राख्न आवश्यक पर्ने कुरामा सहयोग गर्ने एपमाथि पूर्ण नियन्त्रण गर्नु उपयुक्त हुन्छ तर अधिकांश अनुप्रयोगहरूका हकमा यस्तो नियन्त्रण उपयुक्त हुँदैन।"</string>
     <string name="accessibility_service_screen_control_title" msgid="190017412626919776">"स्क्रिन हेर्नुहोस् र नियन्त्रण गर्नुहोस्"</string>
     <string name="accessibility_service_screen_control_description" msgid="6946315917771791525">"यसले स्क्रिनमा देखिने सबै सामग्री पढ्न सक्छ र अन्य एपहरूमा उक्त सामग्री देखाउन सक्छ।"</string>
     <string name="accessibility_service_action_perform_title" msgid="779670378951658160">"कारबाहीहरू हेर्नुहोस् र तिनमा कार्य गर्नुहोस्"</string>
diff --git a/core/res/res/values-or/strings.xml b/core/res/res/values-or/strings.xml
index a1fd3f0..1cb01ea 100644
--- a/core/res/res/values-or/strings.xml
+++ b/core/res/res/values-or/strings.xml
@@ -983,9 +983,9 @@
     <string name="menu_space_shortcut_label" msgid="5949311515646872071">"ସ୍ପେସ୍‍"</string>
     <string name="menu_enter_shortcut_label" msgid="6709499510082897320">"ଏଣ୍ଟର୍"</string>
     <string name="menu_delete_shortcut_label" msgid="4365787714477739080">"ଡିଲିଟ୍‍"</string>
-    <string name="search_go" msgid="2141477624421347086">"ସର୍ଚ୍ଚ କରନ୍ତୁ"</string>
+    <string name="search_go" msgid="2141477624421347086">"Search"</string>
     <string name="search_hint" msgid="455364685740251925">"ସର୍ଚ୍ଚ…"</string>
-    <string name="searchview_description_search" msgid="1045552007537359343">"ସର୍ଚ୍ଚ କରନ୍ତୁ"</string>
+    <string name="searchview_description_search" msgid="1045552007537359343">"Search"</string>
     <string name="searchview_description_query" msgid="7430242366971716338">"କ୍ୱେରୀ ସର୍ଚ୍ଚ କରନ୍ତୁ"</string>
     <string name="searchview_description_clear" msgid="1989371719192982900">"କ୍ୱେରୀ ଖାଲି କରନ୍ତୁ"</string>
     <string name="searchview_description_submit" msgid="6771060386117334686">"କ୍ୱେରୀ ଦାଖଲ କରନ୍ତୁ"</string>
@@ -1401,7 +1401,7 @@
     <string name="tutorial_double_tap_to_zoom_message_short" msgid="1842872462124648678">"ଜୁମ୍ ନିୟନ୍ତ୍ରଣ ପାଇଁ ଦୁଇଥର ଟାପ୍‌ କରନ୍ତୁ"</string>
     <string name="gadget_host_error_inflating" msgid="2449961590495198720">"ୱିଜେଟ୍‍ ଯୋଡ଼ିପାରିବ ନାହିଁ।"</string>
     <string name="ime_action_go" msgid="5536744546326495436">"ଯାଆନ୍ତୁ"</string>
-    <string name="ime_action_search" msgid="4501435960587287668">"ସର୍ଚ୍ଚ କରନ୍ତୁ"</string>
+    <string name="ime_action_search" msgid="4501435960587287668">"Search"</string>
     <string name="ime_action_send" msgid="8456843745664334138">"ପଠାନ୍ତୁ"</string>
     <string name="ime_action_next" msgid="4169702997635728543">"ପରବର୍ତ୍ତୀ"</string>
     <string name="ime_action_done" msgid="6299921014822891569">"ହୋଇଗଲା"</string>
@@ -1881,7 +1881,7 @@
     <string name="language_picker_section_suggested" msgid="6556199184638990447">"ପ୍ରସ୍ତାବିତ"</string>
     <string name="language_picker_section_all" msgid="1985809075777564284">"ସମସ୍ତ ଭାଷା"</string>
     <string name="region_picker_section_all" msgid="756441309928774155">"ସମସ୍ତ ଅଞ୍ଚଳ"</string>
-    <string name="locale_search_menu" msgid="6258090710176422934">"ସର୍ଚ୍ଚ କରନ୍ତୁ"</string>
+    <string name="locale_search_menu" msgid="6258090710176422934">"Search"</string>
     <string name="app_suspended_title" msgid="888873445010322650">"ଆପ୍‌ ଉପଲବ୍ଧ ନାହିଁ"</string>
     <string name="app_suspended_default_message" msgid="6451215678552004172">"ବର୍ତ୍ତମାନ <xliff:g id="APP_NAME_0">%1$s</xliff:g> ଉପଲବ୍ଧ ନାହିଁ। ଏହା <xliff:g id="APP_NAME_1">%2$s</xliff:g> ଦ୍ଵାରା ପରିଚାଳିତ ହେଉଛି।"</string>
     <string name="app_suspended_more_details" msgid="211260942831587014">"ଅଧିକ ଜାଣନ୍ତୁ"</string>
diff --git a/core/res/res/values-sl/strings.xml b/core/res/res/values-sl/strings.xml
index 9f23a76..3ef51a7 100644
--- a/core/res/res/values-sl/strings.xml
+++ b/core/res/res/values-sl/strings.xml
@@ -1165,7 +1165,7 @@
     <string name="capital_off" msgid="7443704171014626777">"IZKLOPLJENO"</string>
     <string name="checked" msgid="9179896827054513119">"potrjeno"</string>
     <string name="not_checked" msgid="7972320087569023342">"ni potrjeno"</string>
-    <string name="whichApplication" msgid="5432266899591255759">"Dokončanje dejanja z"</string>
+    <string name="whichApplication" msgid="5432266899591255759">"Dokončanje dejanja z aplikacijo"</string>
     <string name="whichApplicationNamed" msgid="6969946041713975681">"Dokončanje dejanja z aplikacijo %1$s"</string>
     <string name="whichApplicationLabel" msgid="7852182961472531728">"Izvedba dejanja"</string>
     <string name="whichViewApplication" msgid="5733194231473132945">"Odpiranje z aplikacijo"</string>
diff --git a/core/res/res/values-sq/strings.xml b/core/res/res/values-sq/strings.xml
index 3e7ca92..37a995a5 100644
--- a/core/res/res/values-sq/strings.xml
+++ b/core/res/res/values-sq/strings.xml
@@ -754,7 +754,7 @@
     <string name="phoneTypeFaxHome" msgid="6678559953115904345">"Faks shtëpie"</string>
     <string name="phoneTypePager" msgid="576402072263522767">"Biper"</string>
     <string name="phoneTypeOther" msgid="6918196243648754715">"Tjetër"</string>
-    <string name="phoneTypeCallback" msgid="3455781500844157767">"Ri-telefono"</string>
+    <string name="phoneTypeCallback" msgid="3455781500844157767">"Kthim telefonate"</string>
     <string name="phoneTypeCar" msgid="4604775148963129195">"Numri i telefonit të makinës"</string>
     <string name="phoneTypeCompanyMain" msgid="4482773154536455441">"Numri kryesor i telefonit të kompanisë"</string>
     <string name="phoneTypeIsdn" msgid="2496238954533998512">"ISDN"</string>
diff --git a/core/res/res/values-te/strings.xml b/core/res/res/values-te/strings.xml
index 854427b..6b9caf1 100644
--- a/core/res/res/values-te/strings.xml
+++ b/core/res/res/values-te/strings.xml
@@ -983,9 +983,9 @@
     <string name="menu_space_shortcut_label" msgid="5949311515646872071">"space"</string>
     <string name="menu_enter_shortcut_label" msgid="6709499510082897320">"enter"</string>
     <string name="menu_delete_shortcut_label" msgid="4365787714477739080">"delete"</string>
-    <string name="search_go" msgid="2141477624421347086">"వెతుకు"</string>
+    <string name="search_go" msgid="2141477624421347086">"సెర్చ్"</string>
     <string name="search_hint" msgid="455364685740251925">"వెతుకు..."</string>
-    <string name="searchview_description_search" msgid="1045552007537359343">"శోధించండి"</string>
+    <string name="searchview_description_search" msgid="1045552007537359343">"సెర్చ్"</string>
     <string name="searchview_description_query" msgid="7430242366971716338">"ప్రశ్నను శోధించండి"</string>
     <string name="searchview_description_clear" msgid="1989371719192982900">"ప్రశ్నను క్లియర్ చేయి"</string>
     <string name="searchview_description_submit" msgid="6771060386117334686">"ప్రశ్నని సమర్పించండి"</string>
@@ -1401,7 +1401,7 @@
     <string name="tutorial_double_tap_to_zoom_message_short" msgid="1842872462124648678">"జూమ్ నియంత్రణ కోసం రెండుసార్లు నొక్కండి"</string>
     <string name="gadget_host_error_inflating" msgid="2449961590495198720">"విడ్జెట్‌ను జోడించడం సాధ్యపడలేదు."</string>
     <string name="ime_action_go" msgid="5536744546326495436">"వెళ్లు"</string>
-    <string name="ime_action_search" msgid="4501435960587287668">"వెతుకు"</string>
+    <string name="ime_action_search" msgid="4501435960587287668">"సెర్చ్"</string>
     <string name="ime_action_send" msgid="8456843745664334138">"పంపు"</string>
     <string name="ime_action_next" msgid="4169702997635728543">"తర్వాత"</string>
     <string name="ime_action_done" msgid="6299921014822891569">"పూర్తయింది"</string>
@@ -1881,7 +1881,7 @@
     <string name="language_picker_section_suggested" msgid="6556199184638990447">"సూచించినవి"</string>
     <string name="language_picker_section_all" msgid="1985809075777564284">"అన్ని భాషలు"</string>
     <string name="region_picker_section_all" msgid="756441309928774155">"అన్ని ప్రాంతాలు"</string>
-    <string name="locale_search_menu" msgid="6258090710176422934">"వెతుకు"</string>
+    <string name="locale_search_menu" msgid="6258090710176422934">"సెర్చ్"</string>
     <string name="app_suspended_title" msgid="888873445010322650">"యాప్ అందుబాటులో లేదు"</string>
     <string name="app_suspended_default_message" msgid="6451215678552004172">"<xliff:g id="APP_NAME_0">%1$s</xliff:g> ప్రస్తుతం అందుబాటులో లేదు. ఇది <xliff:g id="APP_NAME_1">%2$s</xliff:g> ద్వారా నిర్వహించబడుతుంది."</string>
     <string name="app_suspended_more_details" msgid="211260942831587014">"మరింత తెలుసుకోండి"</string>
diff --git a/core/res/res/values-vi/strings.xml b/core/res/res/values-vi/strings.xml
index 3cb264d..1899f8f 100644
--- a/core/res/res/values-vi/strings.xml
+++ b/core/res/res/values-vi/strings.xml
@@ -1131,10 +1131,10 @@
     <string name="whichViewApplication" msgid="5733194231473132945">"Mở bằng"</string>
     <string name="whichViewApplicationNamed" msgid="415164730629690105">"Mở bằng %1$s"</string>
     <string name="whichViewApplicationLabel" msgid="7367556735684742409">"Mở"</string>
-    <string name="whichOpenHostLinksWith" msgid="7645631470199397485">"Mở đường dẫn liên kết <xliff:g id="HOST">%1$s</xliff:g> bằng"</string>
-    <string name="whichOpenLinksWith" msgid="1120936181362907258">"Mở đường dẫn liên kết bằng"</string>
-    <string name="whichOpenLinksWithApp" msgid="6917864367861910086">"Mở đường dẫn liên kết bằng <xliff:g id="APPLICATION">%1$s</xliff:g>"</string>
-    <string name="whichOpenHostLinksWithApp" msgid="2401668560768463004">"Mở đường dẫn liên kết <xliff:g id="HOST">%1$s</xliff:g> bằng <xliff:g id="APPLICATION">%2$s</xliff:g>"</string>
+    <string name="whichOpenHostLinksWith" msgid="7645631470199397485">"Mở đường liên kết <xliff:g id="HOST">%1$s</xliff:g> bằng"</string>
+    <string name="whichOpenLinksWith" msgid="1120936181362907258">"Mở đường liên kết bằng"</string>
+    <string name="whichOpenLinksWithApp" msgid="6917864367861910086">"Mở đường liên kết bằng <xliff:g id="APPLICATION">%1$s</xliff:g>"</string>
+    <string name="whichOpenHostLinksWithApp" msgid="2401668560768463004">"Mở đường liên kết <xliff:g id="HOST">%1$s</xliff:g> bằng <xliff:g id="APPLICATION">%2$s</xliff:g>"</string>
     <string name="whichGiveAccessToApplicationLabel" msgid="7805857277166106236">"Cấp quyền truy cập"</string>
     <string name="whichEditApplication" msgid="6191568491456092812">"Chỉnh sửa bằng"</string>
     <string name="whichEditApplicationNamed" msgid="8096494987978521514">"Chỉnh sửa bằng %1$s"</string>
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 8913e8b..5f2e4f9 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -1823,6 +1823,8 @@
     <string name="config_defaultCallScreening" translatable="false"></string>
     <!-- The name of the package that will hold the system gallery role. -->
     <string name="config_systemGallery" translatable="false">com.android.gallery3d</string>
+    <!-- The name of the package that will hold the system cluster service role. -->
+    <string name="config_systemAutomotiveCluster" translatable="false"></string>
 
     <!-- The name of the package that will be allowed to change its components' label/icon. -->
     <string name="config_overrideComponentUiPackage" translatable="false"></string>
@@ -4187,6 +4189,8 @@
     <string-array name="config_biometric_sensors" translatable="false" >
         <!-- <item>0:2:15</item>  ID0:Fingerprint:Strong -->
     </string-array>
+    <!--If true, allows the device to load udfps components on older HIDL implementations -->
+    <bool name="allow_test_udfps" translatable="false" >false</bool>
 
     <!-- Messages that should not be shown to the user during face auth enrollment. This should be
          used to hide messages that may be too chatty or messages that the user can't do much about.
@@ -4208,15 +4212,20 @@
     </integer-array>
 
     <!-- Messages that should not be shown to the user during face authentication, on
-     BiometricPrompt. This should be used to hide messages that may be too chatty or messages that
-     the user can't do much about. Entries are defined in
-     android.hardware.biometrics.face@1.0 types.hal -->
+         BiometricPrompt. This should be used to hide messages that may be too chatty or messages
+         that the user can't do much about. Entries are defined in
+         android.hardware.biometrics.face@1.0 types.hal -->
     <integer-array name="config_face_acquire_biometricprompt_ignorelist" translatable="false" >
     </integer-array>
     <!-- Same as the above, but are defined by vendorCodes -->
     <integer-array name="config_face_acquire_vendor_biometricprompt_ignorelist" translatable="false" >
     </integer-array>
 
+    <!-- True if the sensor is able to provide self illumination in dark secnarios, without  support
+         from above the HAL. This configuration is only applicable to IBiometricsFace@1.0 and its
+         minor revisions. -->
+    <bool name="config_faceAuthSupportsSelfIllumination">true</bool>
+
     <!-- If face auth sends the user directly to home/last open app, or stays on keyguard -->
     <bool name="config_faceAuthDismissesKeyguard">true</bool>
 
diff --git a/core/res/res/values/cross_profile_apps.xml b/core/res/res/values/cross_profile_apps.xml
index ab6f20d..3688c3e 100644
--- a/core/res/res/values/cross_profile_apps.xml
+++ b/core/res/res/values/cross_profile_apps.xml
@@ -17,7 +17,7 @@
 <resources>
     <!--
     A collection of apps that have been pre-approved for cross-profile communication.
-    These will not require admin consent, but will still require user consent during provisioning.
+    These will not require admin or user consent.
     -->
     <string-array translatable="false" name="cross_profile_apps">
     </string-array>
diff --git a/core/res/res/values/donottranslate.xml b/core/res/res/values/donottranslate.xml
index 3a1679c..f46f70c 100644
--- a/core/res/res/values/donottranslate.xml
+++ b/core/res/res/values/donottranslate.xml
@@ -23,7 +23,7 @@
     <!-- @hide DO NOT TRANSLATE. Control aspect ratio of lock pattern -->
     <string name="lock_pattern_view_aspect">square</string>
     <!-- @hide DO NOT TRANSLATE. ICU pattern for "Mon, 14 January" -->
-    <string name="icu_abbrev_wday_month_day_no_year">eeeMMMMd</string>
+    <string name="icu_abbrev_wday_month_day_no_year">EEEMMMMd</string>
     <!-- @hide DO NOT TRANSLATE. date formatting pattern for system ui.-->
     <string name="system_ui_date_pattern">@string/icu_abbrev_wday_month_day_no_year</string>
     <!-- @hide DO NOT TRANSLATE Spans within this text are applied to style composing regions
diff --git a/core/res/res/values/ids.xml b/core/res/res/values/ids.xml
index bddda1b..f77c6f9 100644
--- a/core/res/res/values/ids.xml
+++ b/core/res/res/values/ids.xml
@@ -197,6 +197,9 @@
   <!-- Marks the "copy to clipboard" button in the ChooserActivity -->
   <item type="id" name="chooser_copy_button" />
 
+  <!-- Marks the "nearby" button in the ChooserActivity -->
+  <item type="id" name="chooser_nearby_button" />
+
   <!-- Accessibility action identifier for {@link android.accessibilityservice.AccessibilityService#GLOBAL_ACTION_BACK}. -->
   <item type="id" name="accessibilitySystemActionBack" />
 
diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml
index 303fde6..1e2d554 100644
--- a/core/res/res/values/public.xml
+++ b/core/res/res/values/public.xml
@@ -3069,7 +3069,8 @@
   </public-group>
 
   <public-group type="string" first-id="0x01040028">
-    <!-- string definitions go here -->
+    <!-- @hide @SystemApi @TestApi -->
+    <public name="config_systemAutomotiveCluster" />
   </public-group>
 
   <public-group type="id" first-id="0x01020055">
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index d5c72da..35ce780 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -2506,6 +2506,7 @@
   <java-symbol type="string" name="face_error_security_update_required" />
 
   <java-symbol type="array" name="config_biometric_sensors" />
+  <java-symbol type="bool" name="allow_test_udfps" />
 
   <java-symbol type="array" name="config_face_acquire_enroll_ignorelist" />
   <java-symbol type="array" name="config_face_acquire_vendor_enroll_ignorelist" />
@@ -2513,6 +2514,7 @@
   <java-symbol type="array" name="config_face_acquire_vendor_keyguard_ignorelist" />
   <java-symbol type="array" name="config_face_acquire_biometricprompt_ignorelist" />
   <java-symbol type="array" name="config_face_acquire_vendor_biometricprompt_ignorelist" />
+  <java-symbol type="bool" name="config_faceAuthSupportsSelfIllumination" />
   <java-symbol type="bool" name="config_faceAuthDismissesKeyguard" />
 
   <!-- Face config -->
@@ -3841,6 +3843,7 @@
   <java-symbol type="layout" name="chooser_dialog_item" />
   <java-symbol type="drawable" name="chooser_dialog_background" />
   <java-symbol type="id" name="chooser_copy_button" />
+  <java-symbol type="id" name="chooser_nearby_button" />
   <java-symbol type="layout" name="chooser_action_button" />
   <java-symbol type="dimen" name="chooser_action_button_icon_size" />
   <java-symbol type="string" name="config_defaultNearbySharingComponent" />
diff --git a/core/res/res/xml/power_profile.xml b/core/res/res/xml/power_profile.xml
index d8ec72f..166edca 100644
--- a/core/res/res/xml/power_profile.xml
+++ b/core/res/res/xml/power_profile.xml
@@ -20,7 +20,7 @@
 <device name="Android">
   <!-- Most values are the incremental current used by a feature,
        in mA (measured at nominal voltage).
-       The default values are deliberately incorrect dummy values.
+       The default values are deliberately incorrect values.
        OEM's must measure and provide actual values before
        shipping a device.
        Example real-world values are given in comments, but they
diff --git a/core/tests/coretests/AndroidManifest.xml b/core/tests/coretests/AndroidManifest.xml
index 3c5d951..e17c312 100644
--- a/core/tests/coretests/AndroidManifest.xml
+++ b/core/tests/coretests/AndroidManifest.xml
@@ -129,6 +129,7 @@
     <!-- virtual display test permissions -->
     <uses-permission android:name="android.permission.CAPTURE_VIDEO_OUTPUT" />
     <uses-permission android:name="android.permission.CAPTURE_SECURE_VIDEO_OUTPUT" />
+    <uses-permission android:name="android.permission.ADD_TRUSTED_DISPLAY" />
 
     <!-- color extraction test permissions -->
     <uses-permission android:name="android.permission.READ_FRAME_BUFFER" />
diff --git a/core/tests/coretests/src/android/app/WindowContextTest.java b/core/tests/coretests/src/android/app/WindowContextTest.java
index 630e16a..0f9bc4b 100644
--- a/core/tests/coretests/src/android/app/WindowContextTest.java
+++ b/core/tests/coretests/src/android/app/WindowContextTest.java
@@ -30,7 +30,6 @@
 import android.view.IWindowManager;
 import android.view.WindowManagerGlobal;
 
-import androidx.test.filters.FlakyTest;
 import androidx.test.filters.SmallTest;
 import androidx.test.platform.app.InstrumentationRegistry;
 import androidx.test.runner.AndroidJUnit4;
@@ -47,7 +46,6 @@
  * <p>This test class is a part of Window Manager Service tests and specified in
  * {@link com.android.server.wm.test.filters.FrameworksTestsFilter}.
  */
-@FlakyTest(bugId = 150812449, detail = "Remove after confirmed it's stable.")
 @RunWith(AndroidJUnit4.class)
 @SmallTest
 @Presubmit
diff --git a/core/tests/coretests/src/android/app/activity/ActivityThreadTest.java b/core/tests/coretests/src/android/app/activity/ActivityThreadTest.java
index 000e870..0a751dd 100644
--- a/core/tests/coretests/src/android/app/activity/ActivityThreadTest.java
+++ b/core/tests/coretests/src/android/app/activity/ActivityThreadTest.java
@@ -408,6 +408,9 @@
             int originalVirtualDisplayOrientation = virtualDisplayContext.getResources()
                     .getConfiguration().orientation;
 
+
+            // Perform global config change and verify there is no config change in derived display
+            // context.
             Configuration newAppConfig = new Configuration(originalAppConfig);
             newAppConfig.seq++;
             newAppConfig.orientation = newAppConfig.orientation == ORIENTATION_PORTRAIT
@@ -417,7 +420,7 @@
             activityThread.handleConfigurationChanged(newAppConfig);
 
             try {
-                assertEquals("Virtual display orientation should not change when process"
+                assertEquals("Virtual display orientation must not change when process"
                                 + " configuration orientation changes.",
                         originalVirtualDisplayOrientation,
                         virtualDisplayContext.getResources().getConfiguration().orientation);
@@ -438,6 +441,50 @@
     }
 
     @Test
+    public void testActivityOrientationChanged_DoesntOverrideVirtualDisplayOrientation() {
+        final TestActivity activity = mActivityTestRule.launchActivity(new Intent());
+        final ActivityThread activityThread = activity.getActivityThread();
+
+        InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
+            Configuration originalActivityConfig =
+                    new Configuration(activity.getResources().getConfiguration());
+            DisplayManager dm = activity.getSystemService(DisplayManager.class);
+
+            int virtualDisplayWidth;
+            int virtualDisplayHeight;
+            if (originalActivityConfig.orientation == ORIENTATION_PORTRAIT) {
+                virtualDisplayWidth = 100;
+                virtualDisplayHeight = 200;
+            } else {
+                virtualDisplayWidth = 200;
+                virtualDisplayHeight = 100;
+            }
+            Display virtualDisplay = dm.createVirtualDisplay("virtual-display",
+                    virtualDisplayWidth, virtualDisplayHeight, 200, null, 0).getDisplay();
+            Context virtualDisplayContext = activity.createDisplayContext(virtualDisplay);
+            int originalVirtualDisplayOrientation = virtualDisplayContext.getResources()
+                    .getConfiguration().orientation;
+
+            // Perform activity config change and verify there is no config change in derived
+            // display context.
+            Configuration newActivityConfig = new Configuration(originalActivityConfig);
+            newActivityConfig.seq++;
+            newActivityConfig.orientation = newActivityConfig.orientation == ORIENTATION_PORTRAIT
+                    ? ORIENTATION_LANDSCAPE : ORIENTATION_PORTRAIT;
+
+            activityThread.updatePendingActivityConfiguration(activity.getActivityToken(),
+                    newActivityConfig);
+            activityThread.handleActivityConfigurationChanged(activity.getActivityToken(),
+                    newActivityConfig, INVALID_DISPLAY);
+
+            assertEquals("Virtual display orientation must not change when activity"
+                            + " configuration orientation changes.",
+                    originalVirtualDisplayOrientation,
+                    virtualDisplayContext.getResources().getConfiguration().orientation);
+        });
+    }
+
+    @Test
     public void testHandleConfigurationChanged_DoesntOverrideActivityConfig() {
         final TestActivity activity = mActivityTestRule.launchActivity(new Intent());
 
diff --git a/core/tests/coretests/src/android/app/backup/BackupAgentTest.java b/core/tests/coretests/src/android/app/backup/BackupAgentTest.java
new file mode 100644
index 0000000..ea903f2
--- /dev/null
+++ b/core/tests/coretests/src/android/app/backup/BackupAgentTest.java
@@ -0,0 +1,104 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app.backup;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.when;
+
+import android.app.backup.BackupAgent.IncludeExcludeRules;
+import android.app.backup.BackupManager.OperationType;
+import android.app.backup.FullBackup.BackupScheme.PathWithRequiredFlags;
+import android.os.ParcelFileDescriptor;
+import android.os.UserHandle;
+import android.platform.test.annotations.Presubmit;
+import android.util.ArraySet;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.io.IOException;
+import java.util.Collections;
+import java.util.Map;
+import java.util.Set;
+
+@Presubmit
+@RunWith(AndroidJUnit4.class)
+public class BackupAgentTest {
+    // An arbitrary user.
+    private static final UserHandle USER_HANDLE = new UserHandle(15);
+
+    @Mock FullBackup.BackupScheme mBackupScheme;
+
+    private BackupAgent mBackupAgent;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+    }
+
+    @Test
+    public void testGetIncludeExcludeRules_isMigration_returnsEmptyRules()  throws Exception {
+        mBackupAgent = getAgentForOperationType(OperationType.MIGRATION);
+
+        IncludeExcludeRules rules = mBackupAgent.getIncludeExcludeRules(mBackupScheme);
+        assertThat(rules).isEqualTo(IncludeExcludeRules.emptyRules());
+    }
+
+    @Test
+    public void testGetIncludeExcludeRules_isNotMigration_returnsRules() throws Exception {
+        PathWithRequiredFlags path = new PathWithRequiredFlags("path", /* requiredFlags */ 0);
+        Map<String, Set<PathWithRequiredFlags>> includePaths = Collections.singletonMap("test",
+                Collections.singleton(path));
+        ArraySet<PathWithRequiredFlags> excludePaths = new ArraySet<>();
+        excludePaths.add(path);
+        IncludeExcludeRules expectedRules = new IncludeExcludeRules(includePaths, excludePaths);
+
+        mBackupAgent = getAgentForOperationType(OperationType.BACKUP);
+        when(mBackupScheme.maybeParseAndGetCanonicalExcludePaths()).thenReturn(excludePaths);
+        when(mBackupScheme.maybeParseAndGetCanonicalIncludePaths()).thenReturn(includePaths);
+
+        IncludeExcludeRules rules = mBackupAgent.getIncludeExcludeRules(mBackupScheme);
+        assertThat(rules).isEqualTo(expectedRules);
+    }
+
+    private BackupAgent getAgentForOperationType(@OperationType int operationType) {
+        BackupAgent agent = new TestFullBackupAgent();
+        agent.onCreate(USER_HANDLE, operationType);
+        return agent;
+    }
+
+    private static class TestFullBackupAgent extends BackupAgent {
+
+        @Override
+        public void onBackup(ParcelFileDescriptor oldState, BackupDataOutput data,
+                ParcelFileDescriptor newState) throws IOException {
+            // Left empty as this is a full backup agent.
+        }
+
+        @Override
+        public void onRestore(BackupDataInput data, int appVersionCode,
+                ParcelFileDescriptor newState) throws IOException {
+            // Left empty as this is a full backup agent.
+        }
+    }
+}
diff --git a/core/tests/coretests/src/android/app/servertransaction/TransactionParcelTests.java b/core/tests/coretests/src/android/app/servertransaction/TransactionParcelTests.java
index f11adef..2bf9848 100644
--- a/core/tests/coretests/src/android/app/servertransaction/TransactionParcelTests.java
+++ b/core/tests/coretests/src/android/app/servertransaction/TransactionParcelTests.java
@@ -24,6 +24,7 @@
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertTrue;
 
+import android.app.ContentProviderHolder;
 import android.app.IApplicationThread;
 import android.app.IInstrumentationWatcher;
 import android.app.IUiAutomationConnection;
@@ -494,7 +495,8 @@
 
         @Override
         public void scheduleCreateBackupAgent(ApplicationInfo applicationInfo,
-                CompatibilityInfo compatibilityInfo, int i, int userId) throws RemoteException {
+                CompatibilityInfo compatibilityInfo, int i, int userId, int operatioType)
+                throws RemoteException {
         }
 
         @Override
@@ -664,5 +666,10 @@
         public void performDirectAction(IBinder activityToken, String actionId, Bundle arguments,
                 RemoteCallback cancellationCallback, RemoteCallback resultCallback) {
         }
+
+        @Override
+        public void notifyContentProviderPublishStatus(ContentProviderHolder holder, String auth,
+                int userId, boolean published) {
+        }
     }
 }
diff --git a/core/tests/coretests/src/android/app/timezonedetector/TimeZoneCapabilitiesTest.java b/core/tests/coretests/src/android/app/timezonedetector/TimeZoneCapabilitiesTest.java
index 8a36f1d..72391f4 100644
--- a/core/tests/coretests/src/android/app/timezonedetector/TimeZoneCapabilitiesTest.java
+++ b/core/tests/coretests/src/android/app/timezonedetector/TimeZoneCapabilitiesTest.java
@@ -33,9 +33,11 @@
     public void testEquals() {
         TimeZoneCapabilities.Builder builder1 = new TimeZoneCapabilities.Builder(ARBITRARY_USER_ID)
                 .setConfigureAutoDetectionEnabled(CAPABILITY_POSSESSED)
+                .setConfigureGeoDetectionEnabled(CAPABILITY_POSSESSED)
                 .setSuggestManualTimeZone(CAPABILITY_POSSESSED);
         TimeZoneCapabilities.Builder builder2 = new TimeZoneCapabilities.Builder(ARBITRARY_USER_ID)
                 .setConfigureAutoDetectionEnabled(CAPABILITY_POSSESSED)
+                .setConfigureGeoDetectionEnabled(CAPABILITY_POSSESSED)
                 .setSuggestManualTimeZone(CAPABILITY_POSSESSED);
         {
             TimeZoneCapabilities one = builder1.build();
@@ -57,6 +59,20 @@
             assertEquals(one, two);
         }
 
+        builder2.setConfigureGeoDetectionEnabled(CAPABILITY_NOT_ALLOWED);
+        {
+            TimeZoneCapabilities one = builder1.build();
+            TimeZoneCapabilities two = builder2.build();
+            assertNotEquals(one, two);
+        }
+
+        builder1.setConfigureGeoDetectionEnabled(CAPABILITY_NOT_ALLOWED);
+        {
+            TimeZoneCapabilities one = builder1.build();
+            TimeZoneCapabilities two = builder2.build();
+            assertEquals(one, two);
+        }
+
         builder2.setSuggestManualTimeZone(CAPABILITY_NOT_ALLOWED);
         {
             TimeZoneCapabilities one = builder1.build();
@@ -76,12 +92,16 @@
     public void testParcelable() {
         TimeZoneCapabilities.Builder builder = new TimeZoneCapabilities.Builder(ARBITRARY_USER_ID)
                 .setConfigureAutoDetectionEnabled(CAPABILITY_POSSESSED)
+                .setConfigureGeoDetectionEnabled(CAPABILITY_POSSESSED)
                 .setSuggestManualTimeZone(CAPABILITY_POSSESSED);
         assertRoundTripParcelable(builder.build());
 
         builder.setConfigureAutoDetectionEnabled(CAPABILITY_NOT_ALLOWED);
         assertRoundTripParcelable(builder.build());
 
+        builder.setConfigureGeoDetectionEnabled(CAPABILITY_NOT_ALLOWED);
+        assertRoundTripParcelable(builder.build());
+
         builder.setSuggestManualTimeZone(CAPABILITY_NOT_ALLOWED);
         assertRoundTripParcelable(builder.build());
     }
diff --git a/core/tests/coretests/src/android/app/timezonedetector/TimeZoneConfigurationTest.java b/core/tests/coretests/src/android/app/timezonedetector/TimeZoneConfigurationTest.java
index ac7e9c4..00dc73e 100644
--- a/core/tests/coretests/src/android/app/timezonedetector/TimeZoneConfigurationTest.java
+++ b/core/tests/coretests/src/android/app/timezonedetector/TimeZoneConfigurationTest.java
@@ -29,8 +29,9 @@
 
     @Test
     public void testBuilder_copyConstructor() {
-        TimeZoneConfiguration.Builder builder1 =
-                new TimeZoneConfiguration.Builder().setAutoDetectionEnabled(true);
+        TimeZoneConfiguration.Builder builder1 = new TimeZoneConfiguration.Builder()
+                .setAutoDetectionEnabled(true)
+                .setGeoDetectionEnabled(true);
         TimeZoneConfiguration configuration1 = builder1.build();
 
         TimeZoneConfiguration configuration2 =
@@ -41,16 +42,15 @@
 
     @Test
     public void testIsComplete() {
-        TimeZoneConfiguration incompleteConfiguration =
-                new TimeZoneConfiguration.Builder()
-                        .build();
-        assertFalse(incompleteConfiguration.isComplete());
+        TimeZoneConfiguration.Builder builder =
+                new TimeZoneConfiguration.Builder();
+        assertFalse(builder.build().isComplete());
 
-        TimeZoneConfiguration completeConfiguration =
-                new TimeZoneConfiguration.Builder()
-                        .setAutoDetectionEnabled(true)
-                        .build();
-        assertTrue(completeConfiguration.isComplete());
+        builder.setAutoDetectionEnabled(true);
+        assertFalse(builder.build().isComplete());
+
+        builder.setGeoDetectionEnabled(true);
+        assertTrue(builder.build().isComplete());
     }
 
     @Test
@@ -122,6 +122,27 @@
             TimeZoneConfiguration two = builder2.build();
             assertEquals(one, two);
         }
+
+        builder1.setGeoDetectionEnabled(true);
+        {
+            TimeZoneConfiguration one = builder1.build();
+            TimeZoneConfiguration two = builder2.build();
+            assertNotEquals(one, two);
+        }
+
+        builder2.setGeoDetectionEnabled(false);
+        {
+            TimeZoneConfiguration one = builder1.build();
+            TimeZoneConfiguration two = builder2.build();
+            assertNotEquals(one, two);
+        }
+
+        builder1.setGeoDetectionEnabled(false);
+        {
+            TimeZoneConfiguration one = builder1.build();
+            TimeZoneConfiguration two = builder2.build();
+            assertEquals(one, two);
+        }
     }
 
     @Test
@@ -135,5 +156,11 @@
 
         builder.setAutoDetectionEnabled(false);
         assertRoundTripParcelable(builder.build());
+
+        builder.setGeoDetectionEnabled(false);
+        assertRoundTripParcelable(builder.build());
+
+        builder.setGeoDetectionEnabled(true);
+        assertRoundTripParcelable(builder.build());
     }
 }
diff --git a/core/tests/coretests/src/android/content/res/ResourcesManagerTest.java b/core/tests/coretests/src/android/content/res/ResourcesManagerTest.java
index efcd458..45adf83 100644
--- a/core/tests/coretests/src/android/content/res/ResourcesManagerTest.java
+++ b/core/tests/coretests/src/android/content/res/ResourcesManagerTest.java
@@ -29,31 +29,50 @@
 
 import junit.framework.TestCase;
 
+import java.util.HashMap;
+import java.util.Map;
+
 public class ResourcesManagerTest extends TestCase {
+    private static final int SECONDARY_DISPLAY_ID = 1;
     private static final String APP_ONE_RES_DIR = "app_one.apk";
     private static final String APP_ONE_RES_SPLIT_DIR = "app_one_split.apk";
     private static final String APP_TWO_RES_DIR = "app_two.apk";
     private static final String LIB_RES_DIR = "lib.apk";
 
     private ResourcesManager mResourcesManager;
-    private DisplayMetrics mDisplayMetrics;
+    private Map<Integer, DisplayMetrics> mDisplayMetricsMap;
 
     @Override
     protected void setUp() throws Exception {
         super.setUp();
 
-        mDisplayMetrics = new DisplayMetrics();
-        mDisplayMetrics.setToDefaults();
+        mDisplayMetricsMap = new HashMap<>();
+
+        DisplayMetrics defaultDisplayMetrics = new DisplayMetrics();
+        defaultDisplayMetrics.setToDefaults();
 
         // Override defaults (which take device specific properties).
-        mDisplayMetrics.density = 1.0f;
-        mDisplayMetrics.densityDpi = DisplayMetrics.DENSITY_DEFAULT;
-        mDisplayMetrics.xdpi = DisplayMetrics.DENSITY_DEFAULT;
-        mDisplayMetrics.ydpi = DisplayMetrics.DENSITY_DEFAULT;
-        mDisplayMetrics.noncompatDensity = mDisplayMetrics.density;
-        mDisplayMetrics.noncompatDensityDpi = mDisplayMetrics.densityDpi;
-        mDisplayMetrics.noncompatXdpi = DisplayMetrics.DENSITY_DEFAULT;
-        mDisplayMetrics.noncompatYdpi = DisplayMetrics.DENSITY_DEFAULT;
+        defaultDisplayMetrics.density = 1.0f;
+        defaultDisplayMetrics.densityDpi = DisplayMetrics.DENSITY_DEFAULT;
+        defaultDisplayMetrics.xdpi = DisplayMetrics.DENSITY_DEFAULT;
+        defaultDisplayMetrics.ydpi = DisplayMetrics.DENSITY_DEFAULT;
+        defaultDisplayMetrics.widthPixels = 1440;
+        defaultDisplayMetrics.heightPixels = 2960;
+        defaultDisplayMetrics.noncompatDensity = defaultDisplayMetrics.density;
+        defaultDisplayMetrics.noncompatDensityDpi = defaultDisplayMetrics.densityDpi;
+        defaultDisplayMetrics.noncompatXdpi = DisplayMetrics.DENSITY_DEFAULT;
+        defaultDisplayMetrics.noncompatYdpi = DisplayMetrics.DENSITY_DEFAULT;
+        defaultDisplayMetrics.noncompatWidthPixels = defaultDisplayMetrics.widthPixels;
+        defaultDisplayMetrics.noncompatHeightPixels = defaultDisplayMetrics.heightPixels;
+        mDisplayMetricsMap.put(Display.DEFAULT_DISPLAY, defaultDisplayMetrics);
+
+        DisplayMetrics secondaryDisplayMetrics = new DisplayMetrics();
+        secondaryDisplayMetrics.setTo(defaultDisplayMetrics);
+        secondaryDisplayMetrics.widthPixels = 50;
+        secondaryDisplayMetrics.heightPixels = 100;
+        secondaryDisplayMetrics.noncompatWidthPixels = secondaryDisplayMetrics.widthPixels;
+        secondaryDisplayMetrics.noncompatHeightPixels = secondaryDisplayMetrics.heightPixels;
+        mDisplayMetricsMap.put(SECONDARY_DISPLAY_ID, secondaryDisplayMetrics);
 
         mResourcesManager = new ResourcesManager() {
             @Override
@@ -63,7 +82,7 @@
 
             @Override
             protected DisplayMetrics getDisplayMetrics(int displayId, DisplayAdjustments daj) {
-                return mDisplayMetrics;
+                return mDisplayMetricsMap.get(displayId);
             }
         };
     }
@@ -71,12 +90,12 @@
     @SmallTest
     public void testMultipleCallsWithIdenticalParametersCacheReference() {
         Resources resources = mResourcesManager.getResources(
-                null, APP_ONE_RES_DIR, null, null, null, Display.DEFAULT_DISPLAY, null,
+                null, APP_ONE_RES_DIR, null, null, null, null, null,
                 CompatibilityInfo.DEFAULT_COMPATIBILITY_INFO, null, null);
         assertNotNull(resources);
 
         Resources newResources = mResourcesManager.getResources(
-                null, APP_ONE_RES_DIR, null, null, null, Display.DEFAULT_DISPLAY, null,
+                null, APP_ONE_RES_DIR, null, null, null, null, null,
                 CompatibilityInfo.DEFAULT_COMPATIBILITY_INFO, null, null);
         assertNotNull(newResources);
         assertSame(resources, newResources);
@@ -85,14 +104,14 @@
     @SmallTest
     public void testMultipleCallsWithDifferentParametersReturnDifferentReferences() {
         Resources resources = mResourcesManager.getResources(
-                null, APP_ONE_RES_DIR, null, null, null, Display.DEFAULT_DISPLAY, null,
+                null, APP_ONE_RES_DIR, null, null, null, null, null,
                 CompatibilityInfo.DEFAULT_COMPATIBILITY_INFO, null, null);
         assertNotNull(resources);
 
         Configuration overrideConfig = new Configuration();
         overrideConfig.smallestScreenWidthDp = 200;
         Resources newResources = mResourcesManager.getResources(
-                null, APP_ONE_RES_DIR, null, null, null, Display.DEFAULT_DISPLAY, overrideConfig,
+                null, APP_ONE_RES_DIR, null, null, null, null, overrideConfig,
                 CompatibilityInfo.DEFAULT_COMPATIBILITY_INFO, null, null);
         assertNotNull(newResources);
         assertNotSame(resources, newResources);
@@ -101,13 +120,13 @@
     @SmallTest
     public void testAddingASplitCreatesANewImpl() {
         Resources resources1 = mResourcesManager.getResources(
-                null, APP_ONE_RES_DIR, null, null, null, Display.DEFAULT_DISPLAY, null,
+                null, APP_ONE_RES_DIR, null, null, null, null, null,
                 CompatibilityInfo.DEFAULT_COMPATIBILITY_INFO, null, null);
         assertNotNull(resources1);
 
         Resources resources2 = mResourcesManager.getResources(
                 null, APP_ONE_RES_DIR, new String[] { APP_ONE_RES_SPLIT_DIR }, null, null,
-                Display.DEFAULT_DISPLAY, null, CompatibilityInfo.DEFAULT_COMPATIBILITY_INFO,null,
+                null, null, CompatibilityInfo.DEFAULT_COMPATIBILITY_INFO, null,
                 null);
         assertNotNull(resources2);
 
@@ -118,12 +137,12 @@
     @SmallTest
     public void testUpdateConfigurationUpdatesAllAssetManagers() {
         Resources resources1 = mResourcesManager.getResources(
-                null, APP_ONE_RES_DIR, null, null, null, Display.DEFAULT_DISPLAY, null,
+                null, APP_ONE_RES_DIR, null, null, null, null, null,
                 CompatibilityInfo.DEFAULT_COMPATIBILITY_INFO, null, null);
         assertNotNull(resources1);
 
         Resources resources2 = mResourcesManager.getResources(
-                null, APP_TWO_RES_DIR, null, null, null, Display.DEFAULT_DISPLAY, null,
+                null, APP_TWO_RES_DIR, null, null, null, null, null,
                 CompatibilityInfo.DEFAULT_COMPATIBILITY_INFO, null, null);
         assertNotNull(resources2);
 
@@ -131,7 +150,7 @@
         final Configuration overrideConfig = new Configuration();
         overrideConfig.orientation = Configuration.ORIENTATION_LANDSCAPE;
         Resources resources3 = mResourcesManager.getResources(
-                activity, APP_ONE_RES_DIR, null, null, null, Display.DEFAULT_DISPLAY,
+                activity, APP_ONE_RES_DIR, null, null, null, null,
                 overrideConfig, CompatibilityInfo.DEFAULT_COMPATIBILITY_INFO, null, null);
         assertNotNull(resources3);
 
@@ -152,7 +171,7 @@
         final Configuration expectedConfig = new Configuration();
         expectedConfig.setToDefaults();
         expectedConfig.setLocales(LocaleList.getAdjustedDefault());
-        expectedConfig.densityDpi = mDisplayMetrics.densityDpi;
+        expectedConfig.densityDpi = mDisplayMetricsMap.get(Display.DEFAULT_DISPLAY).densityDpi;
         expectedConfig.orientation = Configuration.ORIENTATION_LANDSCAPE;
 
         assertEquals(expectedConfig, resources1.getConfiguration());
@@ -164,13 +183,13 @@
     public void testTwoActivitiesWithIdenticalParametersShareImpl() {
         Binder activity1 = new Binder();
         Resources resources1 = mResourcesManager.getResources(
-                activity1, APP_ONE_RES_DIR, null, null, null, Display.DEFAULT_DISPLAY, null,
+                activity1, APP_ONE_RES_DIR, null, null, null, null, null,
                 CompatibilityInfo.DEFAULT_COMPATIBILITY_INFO, null, null);
         assertNotNull(resources1);
 
         Binder activity2 = new Binder();
         Resources resources2 = mResourcesManager.getResources(
-                activity2, APP_ONE_RES_DIR, null, null, null, Display.DEFAULT_DISPLAY, null,
+                activity2, APP_ONE_RES_DIR, null, null, null, null, null,
                 CompatibilityInfo.DEFAULT_COMPATIBILITY_INFO, null, null);
         assertNotNull(resources1);
 
@@ -201,7 +220,7 @@
         final Configuration overrideConfig = new Configuration();
         overrideConfig.orientation = Configuration.ORIENTATION_LANDSCAPE;
         mResourcesManager.updateResourcesForActivity(activity1, overrideConfig,
-                Display.DEFAULT_DISPLAY, false /* movedToDifferentDisplay */);
+                Display.DEFAULT_DISPLAY);
         assertSame(resources1, theme.getResources());
 
         // Make sure we can still access the data.
@@ -226,7 +245,7 @@
         Configuration config2 = new Configuration();
         config2.screenLayout |= Configuration.SCREENLAYOUT_ROUND_YES;
         Resources resources2 = mResourcesManager.getResources(
-                activity1, APP_ONE_RES_DIR, null, null, null, Display.DEFAULT_DISPLAY, config2,
+                activity1, APP_ONE_RES_DIR, null, null, null, null, config2,
                 CompatibilityInfo.DEFAULT_COMPATIBILITY_INFO, null, null);
         assertNotNull(resources2);
 
@@ -250,8 +269,7 @@
 
         // Now update the Activity base override, and both resources should update.
         config1.orientation = Configuration.ORIENTATION_LANDSCAPE;
-        mResourcesManager.updateResourcesForActivity(activity1, config1, Display.DEFAULT_DISPLAY,
-                false /* movedToDifferentDisplay */);
+        mResourcesManager.updateResourcesForActivity(activity1, config1, Display.DEFAULT_DISPLAY);
 
         expectedConfig1.orientation = Configuration.ORIENTATION_LANDSCAPE;
         assertEquals(expectedConfig1, resources1.getConfiguration());
@@ -290,4 +308,41 @@
         assertEquals(originalOverrideDensity,
                 resources.getDisplayAdjustments().getConfiguration().densityDpi);
     }
+
+    @SmallTest
+    public void testChangingActivityDisplayDoesntOverrideDisplayRequestedByResources() {
+        Binder activity = new Binder();
+
+        // Create a base token resources that are based on the default display.
+        Resources activityResources = mResourcesManager.createBaseTokenResources(
+                activity, APP_ONE_RES_DIR, null, null, null, Display.DEFAULT_DISPLAY, null,
+                CompatibilityInfo.DEFAULT_COMPATIBILITY_INFO, null, null);
+        // Create another resources that explicitly override the display of the base token above
+        // and set it to DEFAULT_DISPLAY.
+        Resources defaultDisplayResources = mResourcesManager.getResources(
+                activity, APP_ONE_RES_DIR, null, null, null, Display.DEFAULT_DISPLAY, null,
+                CompatibilityInfo.DEFAULT_COMPATIBILITY_INFO, null, null);
+
+        assertEquals(mDisplayMetricsMap.get(Display.DEFAULT_DISPLAY).widthPixels,
+                activityResources.getDisplayMetrics().widthPixels);
+        assertEquals(mDisplayMetricsMap.get(Display.DEFAULT_DISPLAY).heightPixels,
+                activityResources.getDisplayMetrics().heightPixels);
+        assertEquals(mDisplayMetricsMap.get(Display.DEFAULT_DISPLAY).widthPixels,
+                defaultDisplayResources.getDisplayMetrics().widthPixels);
+        assertEquals(mDisplayMetricsMap.get(Display.DEFAULT_DISPLAY).widthPixels,
+                defaultDisplayResources.getDisplayMetrics().widthPixels);
+
+        // Now change the display of the activity and ensure the activity's display metrics match
+        // the new display, but the other resources remain based on the default display.
+        mResourcesManager.updateResourcesForActivity(activity, null, SECONDARY_DISPLAY_ID);
+
+        assertEquals(mDisplayMetricsMap.get(SECONDARY_DISPLAY_ID).widthPixels,
+                activityResources.getDisplayMetrics().widthPixels);
+        assertEquals(mDisplayMetricsMap.get(SECONDARY_DISPLAY_ID).heightPixels,
+                activityResources.getDisplayMetrics().heightPixels);
+        assertEquals(mDisplayMetricsMap.get(Display.DEFAULT_DISPLAY).widthPixels,
+                defaultDisplayResources.getDisplayMetrics().widthPixels);
+        assertEquals(mDisplayMetricsMap.get(Display.DEFAULT_DISPLAY).widthPixels,
+                defaultDisplayResources.getDisplayMetrics().widthPixels);
+    }
 }
diff --git a/core/tests/coretests/src/android/hardware/display/VirtualDisplayTest.java b/core/tests/coretests/src/android/hardware/display/VirtualDisplayTest.java
index daf6139..0f6284d 100644
--- a/core/tests/coretests/src/android/hardware/display/VirtualDisplayTest.java
+++ b/core/tests/coretests/src/android/hardware/display/VirtualDisplayTest.java
@@ -247,6 +247,25 @@
         assertDisplayUnregistered(display);
     }
 
+    /**
+     * Ensures that an application can create a trusted virtual display with the permission
+     * {@code ADD_TRUSTED_DISPLAY}.
+     */
+    public void testTrustedVirtualDisplay() throws Exception {
+        VirtualDisplay virtualDisplay = mDisplayManager.createVirtualDisplay(NAME,
+                WIDTH, HEIGHT, DENSITY, mSurface,
+                DisplayManager.VIRTUAL_DISPLAY_FLAG_TRUSTED);
+        assertNotNull("virtual display must not be null", virtualDisplay);
+
+        Display display = virtualDisplay.getDisplay();
+        try {
+            assertDisplayRegistered(display, Display.FLAG_PRIVATE | Display.FLAG_TRUSTED);
+        } finally {
+            virtualDisplay.release();
+        }
+        assertDisplayUnregistered(display);
+    }
+
     private void assertDisplayRegistered(Display display, int flags) {
         assertNotNull("display object must not be null", display);
         assertTrue("display must be valid", display.isValid());
diff --git a/core/tests/coretests/src/android/view/InsetsControllerTest.java b/core/tests/coretests/src/android/view/InsetsControllerTest.java
index 801cd4d..de128ad 100644
--- a/core/tests/coretests/src/android/view/InsetsControllerTest.java
+++ b/core/tests/coretests/src/android/view/InsetsControllerTest.java
@@ -27,6 +27,7 @@
 import static android.view.InsetsState.ITYPE_STATUS_BAR;
 import static android.view.ViewRootImpl.NEW_INSETS_MODE_FULL;
 import static android.view.WindowInsets.Type.ime;
+import static android.view.WindowInsets.Type.navigationBars;
 import static android.view.WindowInsets.Type.statusBars;
 import static android.view.WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE;
 
@@ -40,8 +41,11 @@
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.ArgumentMatchers.notNull;
+import static org.mockito.Mockito.clearInvocations;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 
 import android.content.Context;
@@ -124,7 +128,7 @@
             }
             mTestClock = new OffsettableClock();
             mTestHandler = new TestHandler(null, mTestClock);
-            mTestHost = new TestHost(mViewRoot);
+            mTestHost = spy(new TestHost(mViewRoot));
             mController = new InsetsController(mTestHost, (controller, type) -> {
                 if (type == ITYPE_IME) {
                     return new InsetsSourceConsumer(type, controller.getState(),
@@ -742,6 +746,113 @@
             mController.onControlsChanged(createSingletonControl(ITYPE_IME));
             assertEquals(newState.getSource(ITYPE_IME),
                     mTestHost.getModifiedState().peekSource(ITYPE_IME));
+
+            // The modified frames cannot be updated if there is an animation.
+            mController.onControlsChanged(createSingletonControl(ITYPE_NAVIGATION_BAR));
+            mController.hide(navigationBars());
+            newState = new InsetsState(mController.getState(), true /* copySource */);
+            newState.getSource(ITYPE_NAVIGATION_BAR).getFrame().top--;
+            mController.onStateChanged(newState);
+            assertNotEquals(newState.getSource(ITYPE_NAVIGATION_BAR),
+                    mTestHost.getModifiedState().peekSource(ITYPE_NAVIGATION_BAR));
+
+            // The modified frames can be updated while the animation is done.
+            mController.cancelExistingAnimations();
+            assertEquals(newState.getSource(ITYPE_NAVIGATION_BAR),
+                    mTestHost.getModifiedState().peekSource(ITYPE_NAVIGATION_BAR));
+        });
+    }
+
+    @Test
+    public void testInsetsChangedCount_controlSystemBars() {
+        InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
+            prepareControls();
+
+            // Hiding visible system bars should only causes insets change once for each bar.
+            clearInvocations(mTestHost);
+            mController.hide(statusBars() | navigationBars());
+            verify(mTestHost, times(2)).notifyInsetsChanged();
+
+            // Sending the same insets state should not cause insets change.
+            // This simulates the callback from server after hiding system bars.
+            clearInvocations(mTestHost);
+            mController.onStateChanged(mController.getState());
+            verify(mTestHost, never()).notifyInsetsChanged();
+
+            // Showing invisible system bars should only causes insets change once for each bar.
+            clearInvocations(mTestHost);
+            mController.show(statusBars() | navigationBars());
+            verify(mTestHost, times(2)).notifyInsetsChanged();
+
+            // Sending the same insets state should not cause insets change.
+            // This simulates the callback from server after showing system bars.
+            clearInvocations(mTestHost);
+            mController.onStateChanged(mController.getState());
+            verify(mTestHost, never()).notifyInsetsChanged();
+        });
+    }
+
+    @Test
+    public void testInsetsChangedCount_controlIme() {
+        InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
+            prepareControls();
+
+            // Showing invisible ime should only causes insets change once.
+            clearInvocations(mTestHost);
+            mController.show(ime(), true /* fromIme */);
+            verify(mTestHost, times(1)).notifyInsetsChanged();
+
+            // Sending the same insets state should not cause insets change.
+            // This simulates the callback from server after showing ime.
+            clearInvocations(mTestHost);
+            mController.onStateChanged(mController.getState());
+            verify(mTestHost, never()).notifyInsetsChanged();
+
+            // Hiding visible ime should only causes insets change once.
+            clearInvocations(mTestHost);
+            mController.hide(ime());
+            verify(mTestHost, times(1)).notifyInsetsChanged();
+
+            // Sending the same insets state should not cause insets change.
+            // This simulates the callback from server after hiding ime.
+            clearInvocations(mTestHost);
+            mController.onStateChanged(mController.getState());
+            verify(mTestHost, never()).notifyInsetsChanged();
+        });
+    }
+
+    @Test
+    public void testInsetsChangedCount_onStateChanged() {
+        InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
+            final InsetsState localState = mController.getState();
+
+            // Changing status bar frame should cause notifyInsetsChanged.
+            clearInvocations(mTestHost);
+            InsetsState newState = new InsetsState(localState, true /* copySources */);
+            newState.getSource(ITYPE_STATUS_BAR).getFrame().bottom++;
+            mController.onStateChanged(newState);
+            verify(mTestHost, times(1)).notifyInsetsChanged();
+
+            // Changing status bar visibility should cause notifyInsetsChanged.
+            clearInvocations(mTestHost);
+            newState = new InsetsState(localState, true /* copySources */);
+            newState.getSource(ITYPE_STATUS_BAR).setVisible(false);
+            mController.onStateChanged(newState);
+            verify(mTestHost, times(1)).notifyInsetsChanged();
+
+            // Changing invisible IME frame should not cause notifyInsetsChanged.
+            clearInvocations(mTestHost);
+            newState = new InsetsState(localState, true /* copySources */);
+            newState.getSource(ITYPE_IME).getFrame().top--;
+            mController.onStateChanged(newState);
+            verify(mTestHost, never()).notifyInsetsChanged();
+
+            // Changing IME visibility should cause notifyInsetsChanged.
+            clearInvocations(mTestHost);
+            newState = new InsetsState(localState, true /* copySources */);
+            newState.getSource(ITYPE_IME).setVisible(true);
+            mController.onStateChanged(newState);
+            verify(mTestHost, times(1)).notifyInsetsChanged();
         });
     }
 
@@ -777,7 +888,7 @@
         return controls;
     }
 
-    private static class TestHost extends ViewRootInsetsControllerHost {
+    public static class TestHost extends ViewRootInsetsControllerHost {
 
         private InsetsState mModifiedState = new InsetsState();
 
diff --git a/core/tests/coretests/src/android/view/WindowMetricsTest.java b/core/tests/coretests/src/android/view/WindowMetricsTest.java
index ddc977d..96df9dd 100644
--- a/core/tests/coretests/src/android/view/WindowMetricsTest.java
+++ b/core/tests/coretests/src/android/view/WindowMetricsTest.java
@@ -27,7 +27,6 @@
 import android.os.Handler;
 import android.platform.test.annotations.Presubmit;
 
-import androidx.test.filters.FlakyTest;
 import androidx.test.filters.SmallTest;
 import androidx.test.platform.app.InstrumentationRegistry;
 import androidx.test.runner.AndroidJUnit4;
@@ -46,7 +45,6 @@
  * <p>This test class is a part of Window Manager Service tests and specified in
  * {@link com.android.server.wm.test.filters.FrameworksTestsFilter}.
  */
-@FlakyTest(bugId = 148789183, detail = "Remove after confirmed it's stable.")
 @RunWith(AndroidJUnit4.class)
 @SmallTest
 @Presubmit
diff --git a/core/tests/coretests/src/android/widget/EditorCursorDragTest.java b/core/tests/coretests/src/android/widget/EditorCursorDragTest.java
index df2946c..c37a34a 100644
--- a/core/tests/coretests/src/android/widget/EditorCursorDragTest.java
+++ b/core/tests/coretests/src/android/widget/EditorCursorDragTest.java
@@ -201,6 +201,38 @@
     }
 
     @Test
+    public void testCursorDrag_diagonal_thresholdConfig() throws Throwable {
+        TextView tv = mActivity.findViewById(R.id.textview);
+        Editor editor = tv.getEditorForTesting();
+
+        StringBuilder sb = new StringBuilder();
+        for (int i = 1; i <= 9; i++) {
+            sb.append("here is some text").append(i).append("\n");
+        }
+        sb.append(Strings.repeat("abcdefghij\n", 400)).append("Last");
+        String text = sb.toString();
+        onView(withId(R.id.textview)).perform(replaceText(text));
+
+        int index = text.indexOf("text9");
+        onView(withId(R.id.textview)).perform(clickOnTextAtIndex(index));
+        onView(withId(R.id.textview)).check(hasInsertionPointerAtIndex(index));
+
+        // Configure the drag direction threshold to require the drag to be exactly horizontal. With
+        // this set, a swipe that is slightly off horizontal should not trigger cursor drag.
+        editor.setCursorDragMinAngleFromVertical(90);
+        int startIdx = text.indexOf("5");
+        int endIdx = text.indexOf("here is some text3");
+        onView(withId(R.id.textview)).perform(dragOnText(startIdx, endIdx));
+        onView(withId(R.id.textview)).check(hasInsertionPointerAtIndex(index));
+
+        // Configure the drag direction threshold to require the drag to be 45 degrees or more from
+        // vertical. With this set, the same swipe gesture as above should now trigger cursor drag.
+        editor.setCursorDragMinAngleFromVertical(45);
+        onView(withId(R.id.textview)).perform(dragOnText(startIdx, endIdx));
+        onView(withId(R.id.textview)).check(hasInsertionPointerAtIndex(endIdx));
+    }
+
+    @Test
     public void testCursorDrag_vertical_whenTextViewContentsFitOnScreen() throws Throwable {
         String text = "012345_aaa\n"
                 + "0123456789\n"
diff --git a/core/tests/coretests/src/android/widget/EditorTouchStateTest.java b/core/tests/coretests/src/android/widget/EditorTouchStateTest.java
index 35fd4bd..94f43de 100644
--- a/core/tests/coretests/src/android/widget/EditorTouchStateTest.java
+++ b/core/tests/coretests/src/android/widget/EditorTouchStateTest.java
@@ -165,7 +165,7 @@
         long event2Time = 1001;
         MotionEvent event2 = moveEvent(event1Time, event2Time, 200f, 31f);
         mTouchState.update(event2, mConfig);
-        assertDrag(mTouchState, 20f, 30f, 0, 0, false);
+        assertDrag(mTouchState, 20f, 30f, 0, 0, 180f);
 
         // Simulate an ACTION_UP event with a delay that's longer than the double-tap timeout.
         long event3Time = 5000;
@@ -280,7 +280,7 @@
         long event3Time = 1002;
         MotionEvent event3 = moveEvent(event3Time, event3Time, newX, newY);
         mTouchState.update(event3, mConfig);
-        assertDrag(mTouchState, 20f, 30f, 0, 0, false);
+        assertDrag(mTouchState, 20f, 30f, 0, 0, Float.MAX_VALUE);
 
         // Simulate an ACTION_UP event.
         long event4Time = 1003;
@@ -301,15 +301,15 @@
         long event2Time = 1002;
         MotionEvent event2 = moveEvent(event1Time, event2Time, 100f, 174f);
         mTouchState.update(event2, mConfig);
-        assertDrag(mTouchState, 0f, 0f, 0, 0, true);
+        assertDrag(mTouchState, 0f, 0f, 0, 0, 100f / 174f);
 
         // Simulate another ACTION_MOVE event that is horizontal from the original down event.
-        // The value of `isDragCloseToVertical` should NOT change since it should only reflect the
-        // initial direction of movement.
+        // The drag direction ratio should NOT change since it should only reflect the initial
+        // direction of movement.
         long event3Time = 1003;
         MotionEvent event3 = moveEvent(event1Time, event3Time, 200f, 0f);
         mTouchState.update(event3, mConfig);
-        assertDrag(mTouchState, 0f, 0f, 0, 0, true);
+        assertDrag(mTouchState, 0f, 0f, 0, 0, 100f / 174f);
 
         // Simulate an ACTION_UP event.
         long event4Time = 1004;
@@ -330,15 +330,15 @@
         long event2Time = 1002;
         MotionEvent event2 = moveEvent(event1Time, event2Time, 100f, 90f);
         mTouchState.update(event2, mConfig);
-        assertDrag(mTouchState, 0f, 0f, 0, 0, false);
+        assertDrag(mTouchState, 0f, 0f, 0, 0, 100f / 90f);
 
         // Simulate another ACTION_MOVE event that is vertical from the original down event.
-        // The value of `isDragCloseToVertical` should NOT change since it should only reflect the
-        // initial direction of movement.
+        // The drag direction ratio should NOT change since it should only reflect the initial
+        // direction of movement.
         long event3Time = 1003;
         MotionEvent event3 = moveEvent(event1Time, event3Time, 0f, 200f);
         mTouchState.update(event3, mConfig);
-        assertDrag(mTouchState, 0f, 0f, 0, 0, false);
+        assertDrag(mTouchState, 0f, 0f, 0, 0, 100f / 90f);
 
         // Simulate an ACTION_UP event.
         long event4Time = 1004;
@@ -374,7 +374,7 @@
         long event2Time = 1002;
         MotionEvent event2 = moveEvent(event2Time, event2Time, 200f, 30f);
         mTouchState.update(event2, mConfig);
-        assertDrag(mTouchState, 20f, 30f, 0, 0, false);
+        assertDrag(mTouchState, 20f, 30f, 0, 0, Float.MAX_VALUE);
 
         // Simulate an ACTION_CANCEL event.
         long event3Time = 1003;
@@ -411,6 +411,84 @@
         assertSingleTap(mTouchState, 22f, 33f, 20f, 30f);
     }
 
+    @Test
+    public void testGetXYRatio() throws Exception {
+        doTestGetXYRatio(-1, 0.0f);
+        doTestGetXYRatio(0, 0.0f);
+        doTestGetXYRatio(30, 0.58f);
+        doTestGetXYRatio(45, 1.0f);
+        doTestGetXYRatio(60, 1.73f);
+        doTestGetXYRatio(90, Float.MAX_VALUE);
+        doTestGetXYRatio(91, Float.MAX_VALUE);
+    }
+
+    private void doTestGetXYRatio(int angleFromVerticalInDegrees, float expectedXYRatioRounded) {
+        float result = EditorTouchState.getXYRatio(angleFromVerticalInDegrees);
+        String msg = String.format(
+                "%d deg should give an x/y ratio of %f; actual unrounded result is %f",
+                angleFromVerticalInDegrees, expectedXYRatioRounded, result);
+        float roundedResult = (result == 0.0f || result == Float.MAX_VALUE) ? result :
+                Math.round(result * 100) / 100f;
+        assertThat(msg, roundedResult, is(expectedXYRatioRounded));
+    }
+
+    @Test
+    public void testUpdate_dragDirection() throws Exception {
+        // Simulate moving straight up.
+        doTestDragDirection(100f, 100f, 100f, 50f, 0f);
+
+        // Simulate moving straight down.
+        doTestDragDirection(100f, 100f, 100f, 150f, 0f);
+
+        // Simulate moving straight left.
+        doTestDragDirection(100f, 100f, 50f, 100f, Float.MAX_VALUE);
+
+        // Simulate moving straight right.
+        doTestDragDirection(100f, 100f, 150f, 100f, Float.MAX_VALUE);
+
+        // Simulate moving up and right, < 45 deg from vertical.
+        doTestDragDirection(100f, 100f, 110f, 50f, 10f / 50f);
+
+        // Simulate moving up and right, > 45 deg from vertical.
+        doTestDragDirection(100f, 100f, 150f, 90f, 50f / 10f);
+
+        // Simulate moving down and right, < 45 deg from vertical.
+        doTestDragDirection(100f, 100f, 110f, 150f, 10f / 50f);
+
+        // Simulate moving down and right, > 45 deg from vertical.
+        doTestDragDirection(100f, 100f, 150f, 110f, 50f / 10f);
+
+        // Simulate moving down and left, < 45 deg from vertical.
+        doTestDragDirection(100f, 100f, 90f, 150f, 10f / 50f);
+
+        // Simulate moving down and left, > 45 deg from vertical.
+        doTestDragDirection(100f, 100f, 50f, 110f, 50f / 10f);
+
+        // Simulate moving up and left, < 45 deg from vertical.
+        doTestDragDirection(100f, 100f, 90f, 50f, 10f / 50f);
+
+        // Simulate moving up and left, > 45 deg from vertical.
+        doTestDragDirection(100f, 100f, 50f, 90f, 50f / 10f);
+    }
+
+    private void doTestDragDirection(float downX, float downY, float moveX, float moveY,
+            float expectedInitialDragDirectionXYRatio) {
+        EditorTouchState touchState = new EditorTouchState();
+
+        // Simulate an ACTION_DOWN event.
+        long event1Time = 1001;
+        MotionEvent event1 = downEvent(event1Time, event1Time, downX, downY);
+        touchState.update(event1, mConfig);
+
+        // Simulate an ACTION_MOVE event.
+        long event2Time = 1002;
+        MotionEvent event2 = moveEvent(event1Time, event2Time, moveX, moveY);
+        touchState.update(event2, mConfig);
+        String msg = String.format("(%.0f,%.0f)=>(%.0f,%.0f)", downX, downY, moveX, moveY);
+        assertThat(msg, touchState.getInitialDragDirectionXYRatio(),
+                is(expectedInitialDragDirectionXYRatio));
+    }
+
     private static MotionEvent downEvent(long downTime, long eventTime, float x, float y) {
         return MotionEvent.obtain(downTime, eventTime, MotionEvent.ACTION_DOWN, x, y, 0);
     }
@@ -441,7 +519,7 @@
     }
 
     private static void assertDrag(EditorTouchState touchState, float lastDownX,
-            float lastDownY, float lastUpX, float lastUpY, boolean isDragCloseToVertical) {
+            float lastDownY, float lastUpX, float lastUpY, float initialDragDirectionXYRatio) {
         assertThat(touchState.getLastDownX(), is(lastDownX));
         assertThat(touchState.getLastDownY(), is(lastDownY));
         assertThat(touchState.getLastUpX(), is(lastUpX));
@@ -451,7 +529,7 @@
         assertThat(touchState.isMultiTap(), is(false));
         assertThat(touchState.isMultiTapInSameArea(), is(false));
         assertThat(touchState.isMovedEnoughForDrag(), is(true));
-        assertThat(touchState.isDragCloseToVertical(), is(isDragCloseToVertical));
+        assertThat(touchState.getInitialDragDirectionXYRatio(), is(initialDragDirectionXYRatio));
     }
 
     private static void assertMultiTap(EditorTouchState touchState,
@@ -467,6 +545,5 @@
                 || multiTapStatus == MultiTapStatus.TRIPLE_CLICK));
         assertThat(touchState.isMultiTapInSameArea(), is(isMultiTapInSameArea));
         assertThat(touchState.isMovedEnoughForDrag(), is(false));
-        assertThat(touchState.isDragCloseToVertical(), is(false));
     }
 }
diff --git a/core/tests/coretests/src/com/android/internal/app/ChooserActivityTest.java b/core/tests/coretests/src/com/android/internal/app/ChooserActivityTest.java
index 090645f..787879a 100644
--- a/core/tests/coretests/src/com/android/internal/app/ChooserActivityTest.java
+++ b/core/tests/coretests/src/com/android/internal/app/ChooserActivityTest.java
@@ -590,6 +590,54 @@
                 is(1));
     }
 
+
+    @Test
+    public void testNearbyShareLogging() throws Exception {
+        Intent sendIntent = createSendTextIntent();
+        List<ResolvedComponentInfo> resolvedComponentInfos = createResolvedComponentsForTest(2);
+
+        when(ChooserWrapperActivity.sOverrides.resolverListController.getResolversForIntent(
+                Mockito.anyBoolean(),
+                Mockito.anyBoolean(),
+                Mockito.isA(List.class))).thenReturn(resolvedComponentInfos);
+
+        final ChooserWrapperActivity activity = mActivityRule
+                .launchActivity(Intent.createChooser(sendIntent, null));
+        waitForIdle();
+
+        onView(withId(R.id.chooser_nearby_button)).check(matches(isDisplayed()));
+        onView(withId(R.id.chooser_nearby_button)).perform(click());
+
+        ChooserActivityLoggerFake logger =
+                (ChooserActivityLoggerFake) activity.getChooserActivityLogger();
+        assertThat(logger.numCalls(), is(6));
+        // first one should be SHARESHEET_TRIGGERED uievent
+        assertThat(logger.get(0).atomId, is(FrameworkStatsLog.UI_EVENT_REPORTED));
+        assertThat(logger.get(0).event.getId(),
+                is(ChooserActivityLogger.SharesheetStandardEvent.SHARESHEET_TRIGGERED.getId()));
+        // second one should be SHARESHEET_STARTED event
+        assertThat(logger.get(1).atomId, is(FrameworkStatsLog.SHARESHEET_STARTED));
+        assertThat(logger.get(1).intent, is(Intent.ACTION_SEND));
+        assertThat(logger.get(1).mimeType, is("text/plain"));
+        assertThat(logger.get(1).packageName, is("com.android.frameworks.coretests"));
+        assertThat(logger.get(1).appProvidedApp, is(0));
+        assertThat(logger.get(1).appProvidedDirect, is(0));
+        assertThat(logger.get(1).isWorkprofile, is(false));
+        assertThat(logger.get(1).previewType, is(3));
+        // third one should be SHARESHEET_APP_LOAD_COMPLETE uievent
+        assertThat(logger.get(2).atomId, is(FrameworkStatsLog.UI_EVENT_REPORTED));
+        assertThat(logger.get(2).event.getId(),
+                is(ChooserActivityLogger
+                        .SharesheetStandardEvent.SHARESHEET_APP_LOAD_COMPLETE.getId()));
+        // fourth and fifth are just artifacts of test set-up
+        // sixth one should be ranking atom with SHARESHEET_NEARBY_TARGET_SELECTED event
+        assertThat(logger.get(5).atomId, is(FrameworkStatsLog.RANKING_SELECTED));
+        assertThat(logger.get(5).targetType,
+                is(ChooserActivityLogger
+                        .SharesheetTargetSelectedEvent.SHARESHEET_NEARBY_TARGET_SELECTED.getId()));
+    }
+
+
     @Test
     public void oneVisibleImagePreview() throws InterruptedException {
         Uri uri = Uri.parse("android.resource://com.android.frameworks.coretests/"
diff --git a/core/tests/coretests/src/com/android/internal/app/ChooserWrapperActivity.java b/core/tests/coretests/src/com/android/internal/app/ChooserWrapperActivity.java
index d3d5caf..16a2fbd 100644
--- a/core/tests/coretests/src/com/android/internal/app/ChooserWrapperActivity.java
+++ b/core/tests/coretests/src/com/android/internal/app/ChooserWrapperActivity.java
@@ -21,6 +21,7 @@
 
 import android.annotation.Nullable;
 import android.app.usage.UsageStatsManager;
+import android.content.ComponentName;
 import android.content.ContentResolver;
 import android.content.Context;
 import android.content.Intent;
@@ -89,6 +90,18 @@
 
     boolean getIsSelected() { return mIsSuccessfullySelected; }
 
+    @Override
+    protected ComponentName getNearbySharingComponent() {
+        // an arbitrary pre-installed activity that handles this type of intent
+        return ComponentName.unflattenFromString("com.google.android.apps.messaging/"
+                + "com.google.android.apps.messaging.ui.conversationlist.ShareIntentActivity");
+    }
+
+    @Override
+    protected TargetInfo getNearbySharingTarget(Intent originalIntent) {
+        return new ChooserWrapperActivity.EmptyTargetInfo();
+    }
+
     UsageStatsManager getUsageStatsManager() {
         if (mUsm == null) {
             mUsm = (UsageStatsManager) getSystemService(Context.USAGE_STATS_SERVICE);
diff --git a/core/tests/coretests/src/com/android/internal/os/BatteryStatsBinderCallStatsTest.java b/core/tests/coretests/src/com/android/internal/os/BatteryStatsBinderCallStatsTest.java
index 3e67b8b..22c41f3 100644
--- a/core/tests/coretests/src/com/android/internal/os/BatteryStatsBinderCallStatsTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/BatteryStatsBinderCallStatsTest.java
@@ -38,7 +38,8 @@
 @SmallTest
 public class BatteryStatsBinderCallStatsTest extends TestCase {
 
-    private static final int TRANSACTION_CODE = 100;
+    private static final int TRANSACTION_CODE1 = 100;
+    private static final int TRANSACTION_CODE2 = 101;
 
     /**
      * Test BatteryStatsImpl.Uid.noteBinderCallStats.
@@ -53,23 +54,23 @@
 
         Collection<BinderCallsStats.CallStat> callStats = new ArrayList<>();
         BinderCallsStats.CallStat stat1 = new BinderCallsStats.CallStat(callingUid,
-                MockBinder.class, TRANSACTION_CODE, true /*screenInteractive */);
+                MockBinder.class, TRANSACTION_CODE1, true /*screenInteractive */);
         stat1.incrementalCallCount = 21;
         stat1.recordedCallCount = 5;
         stat1.cpuTimeMicros = 1000;
         callStats.add(stat1);
 
-        bi.noteBinderCallStats(workSourceUid, 42, callStats);
+        bi.noteBinderCallStats(workSourceUid, 42, callStats, null);
 
         callStats.clear();
         BinderCallsStats.CallStat stat2 = new BinderCallsStats.CallStat(callingUid,
-                MockBinder.class, TRANSACTION_CODE, true /*screenInteractive */);
+                MockBinder.class, TRANSACTION_CODE1, true /*screenInteractive */);
         stat2.incrementalCallCount = 9;
         stat2.recordedCallCount = 8;
         stat2.cpuTimeMicros = 500;
         callStats.add(stat2);
 
-        bi.noteBinderCallStats(workSourceUid, 8, callStats);
+        bi.noteBinderCallStats(workSourceUid, 8, callStats, null);
 
         BatteryStatsImpl.Uid uid = bi.getUidStatsLocked(workSourceUid);
         assertEquals(42 + 8, uid.getBinderCallCount());
@@ -85,9 +86,62 @@
         assertEquals(500, value.recordedCpuTimeMicros);
     }
 
+
+    @Test
+    public void testProportionalSystemServiceUsage_noStatsForSomeMethods() throws Exception {
+        final MockClocks clocks = new MockClocks(); // holds realtime and uptime in ms
+        MockBatteryStatsImpl bi = new MockBatteryStatsImpl(clocks);
+
+        int callingUid = Process.FIRST_APPLICATION_UID + 1;
+        int workSourceUid1 = Process.FIRST_APPLICATION_UID + 1;
+        int workSourceUid2 = Process.FIRST_APPLICATION_UID + 2;
+        int workSourceUid3 = Process.FIRST_APPLICATION_UID + 3;
+
+        Collection<BinderCallsStats.CallStat> callStats = new ArrayList<>();
+        BinderCallsStats.CallStat stat1a = new BinderCallsStats.CallStat(callingUid,
+                MockBinder.class, TRANSACTION_CODE1, true /*screenInteractive */);
+        stat1a.incrementalCallCount = 10;
+        stat1a.recordedCallCount = 5;
+        stat1a.cpuTimeMicros = 1000;
+        callStats.add(stat1a);
+
+        BinderCallsStats.CallStat stat1b = new BinderCallsStats.CallStat(callingUid,
+                MockBinder.class, TRANSACTION_CODE2, true /*screenInteractive */);
+        stat1b.incrementalCallCount = 30;
+        stat1b.recordedCallCount = 15;
+        stat1b.cpuTimeMicros = 1500;
+        callStats.add(stat1b);
+
+        bi.noteBinderCallStats(workSourceUid1, 65, callStats, null);
+
+        // No recorded stats for some methods. Must use the global average.
+        callStats.clear();
+        BinderCallsStats.CallStat stat2 = new BinderCallsStats.CallStat(callingUid,
+                MockBinder.class, TRANSACTION_CODE1, true /*screenInteractive */);
+        stat2.incrementalCallCount = 10;
+        callStats.add(stat2);
+
+        bi.noteBinderCallStats(workSourceUid2, 40, callStats, null);
+
+        // No stats for any calls. Must use the global average
+        callStats.clear();
+        bi.noteBinderCallStats(workSourceUid3, 50, callStats, null);
+
+        bi.updateSystemServiceCallStats();
+
+        double prop1 = bi.getUidStatsLocked(workSourceUid1).getProportionalSystemServiceUsage();
+        double prop2 = bi.getUidStatsLocked(workSourceUid2).getProportionalSystemServiceUsage();
+        double prop3 = bi.getUidStatsLocked(workSourceUid3).getProportionalSystemServiceUsage();
+
+        assertEquals(0.419, prop1, 0.01);
+        assertEquals(0.258, prop2, 0.01);
+        assertEquals(0.323, prop3, 0.01);
+        assertEquals(1.000, prop1 + prop2 + prop3, 0.01);
+    }
+
     private static class MockBinder extends Binder {
         public static String getDefaultTransactionName(int txCode) {
-            return txCode == TRANSACTION_CODE ? "testMethod" : "unknown";
+            return txCode == TRANSACTION_CODE1 ? "testMethod" : "unknown";
         }
     }
 }
diff --git a/core/tests/coretests/src/com/android/internal/os/BatteryStatsTests.java b/core/tests/coretests/src/com/android/internal/os/BatteryStatsTests.java
index 7807f01..bef27e2 100644
--- a/core/tests/coretests/src/com/android/internal/os/BatteryStatsTests.java
+++ b/core/tests/coretests/src/com/android/internal/os/BatteryStatsTests.java
@@ -39,6 +39,7 @@
         BatteryStatsTimerTest.class,
         BatteryStatsUidTest.class,
         BatteryStatsUserLifecycleTests.class,
+        BstatsCpuTimesValidationTest.class,
         KernelCpuProcStringReaderTest.class,
         KernelCpuUidActiveTimeReaderTest.class,
         KernelCpuUidBpfMapReaderTest.class,
diff --git a/core/tests/coretests/src/com/android/internal/os/BinderCallsStatsTest.java b/core/tests/coretests/src/com/android/internal/os/BinderCallsStatsTest.java
index a5117a3..96250db 100644
--- a/core/tests/coretests/src/com/android/internal/os/BinderCallsStatsTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/BinderCallsStatsTest.java
@@ -20,6 +20,7 @@
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
 
 import android.os.Binder;
 import android.os.Handler;
@@ -44,6 +45,7 @@
 import java.io.StringWriter;
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.Collection;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Random;
@@ -493,7 +495,9 @@
         bcs.callEnded(callSession, REQUEST_SIZE, REPLY_SIZE, WORKSOURCE_UID);
 
         PrintWriter pw = new PrintWriter(new StringWriter());
-        bcs.dump(pw, new AppIdToPackageMap(new HashMap<>()), true);
+        bcs.dump(pw, new AppIdToPackageMap(new HashMap<>()), Process.INVALID_UID, true);
+
+        bcs.dump(pw, new AppIdToPackageMap(new HashMap<>()), WORKSOURCE_UID, true);
     }
 
     @Test
@@ -606,8 +610,8 @@
         assertEquals("-1", callStats.methodName);
         assertEquals("com.android.internal.os.BinderCallsStats$OverflowBinder",
                 callStats.className);
-        assertEquals(false , callStats.screenInteractive);
-        assertEquals(-1 , callStats.callingUid);
+        assertEquals(false, callStats.screenInteractive);
+        assertEquals(-1, callStats.callingUid);
     }
 
     @Test
@@ -768,8 +772,8 @@
 
         final ArrayList<BinderCallsStats.CallStat> callStatsList = new ArrayList<>();
         bcs.setCallStatsObserver(
-                (workSourceUid, incrementalCallCount, callStats) -> callStatsList.addAll(
-                        callStats));
+                (workSourceUid, incrementalCallCount, callStats, binderThreadIds) ->
+                        callStatsList.addAll(callStats));
 
         Binder binder = new Binder();
 
@@ -785,7 +789,7 @@
         bcs.time += 30;
         bcs.callEnded(callSession, REQUEST_SIZE, REPLY_SIZE, WORKSOURCE_UID);
 
-        for (Runnable runnable: mHandler.mRunnables) {
+        for (Runnable runnable : mHandler.mRunnables) {
             // Execute all pending runnables. Ignore the delay.
             runnable.run();
         }
@@ -839,6 +843,53 @@
         assertEquals(3, tids[2]);
     }
 
+    @Test
+    public void testTrackingSpecificWorksourceUid() {
+        mDeviceState.setCharging(true);
+
+        Binder binder = new Binder();
+
+        TestBinderCallsStats bcs = new TestBinderCallsStats();
+        bcs.recordAllCallsForWorkSourceUid(WORKSOURCE_UID);
+
+        int[] transactions = {41, 42, 43, 42, 43, 43};
+        int[] durationsMs = {100, 200, 300, 400, 500, 600};
+
+        for (int i = 0; i < transactions.length; i++) {
+            CallSession callSession = bcs.callStarted(binder, transactions[i], WORKSOURCE_UID);
+            bcs.time += durationsMs[i];
+            bcs.callEnded(callSession, REQUEST_SIZE, REPLY_SIZE, WORKSOURCE_UID);
+        }
+
+        BinderCallsStats.UidEntry uidEntry = bcs.getUidEntries().get(WORKSOURCE_UID);
+        Assert.assertNotNull(uidEntry);
+        assertEquals(6, uidEntry.callCount);
+
+        Collection<BinderCallsStats.CallStat> callStatsList = uidEntry.getCallStatsList();
+        assertEquals(3, callStatsList.size());
+        for (BinderCallsStats.CallStat callStat : callStatsList) {
+            switch (callStat.transactionCode) {
+                case 41:
+                    assertEquals(1, callStat.callCount);
+                    assertEquals(1, callStat.incrementalCallCount);
+                    assertEquals(100, callStat.cpuTimeMicros);
+                    break;
+                case 42:
+                    assertEquals(2, callStat.callCount);
+                    assertEquals(2, callStat.incrementalCallCount);
+                    assertEquals(200 + 400, callStat.cpuTimeMicros);
+                    break;
+                case 43:
+                    assertEquals(3, callStat.callCount);
+                    assertEquals(3, callStat.incrementalCallCount);
+                    assertEquals(300 + 500 + 600, callStat.cpuTimeMicros);
+                    break;
+                default:
+                    fail("Unexpected transaction code: " + callStat.transactionCode);
+            }
+        }
+    }
+
     private static class TestHandler extends Handler {
         ArrayList<Runnable> mRunnables = new ArrayList<>();
 
diff --git a/core/tests/coretests/src/com/android/internal/os/BstatsCpuTimesValidationTest.java b/core/tests/coretests/src/com/android/internal/os/BstatsCpuTimesValidationTest.java
index 01515bd..a80f5a0 100644
--- a/core/tests/coretests/src/com/android/internal/os/BstatsCpuTimesValidationTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/BstatsCpuTimesValidationTest.java
@@ -52,7 +52,9 @@
 import android.provider.Settings;
 import android.support.test.uiautomator.UiDevice;
 import android.text.TextUtils;
+import android.util.ArrayMap;
 import android.util.DebugUtils;
+import android.util.KeyValueListParser;
 import android.util.Log;
 
 import androidx.test.InstrumentationRegistry;
@@ -104,8 +106,6 @@
 
     private static final int WORK_DURATION_MS = 2000;
 
-    private static final String DESIRED_PROC_STATE_CPU_TIMES_DELAY = "0";
-
     private static boolean sBatteryStatsConstsUpdated;
     private static String sOriginalBatteryStatsConsts;
 
@@ -124,10 +124,12 @@
         sContext.getPackageManager().setApplicationEnabledSetting(TEST_PKG,
                 PackageManager.COMPONENT_ENABLED_STATE_ENABLED, 0);
         sTestPkgUid = sContext.getPackageManager().getPackageUid(TEST_PKG, 0);
+
+        final ArrayMap<String, String> desiredConstants = new ArrayMap<>();
+        desiredConstants.put(KEY_TRACK_CPU_TIMES_BY_PROC_STATE, Boolean.toString(true));
+        desiredConstants.put(KEY_PROC_STATE_CPU_TIMES_READ_DELAY_MS, Integer.toString(0));
+        updateBatteryStatsConstants(desiredConstants);
         checkCpuTimesAvailability();
-        if (sPerProcStateTimesAvailable && sCpuFreqTimesAvailable) {
-            setDesiredReadyDelay();
-        }
     }
 
     @AfterClass
@@ -139,26 +141,33 @@
         batteryReset();
     }
 
-    private static void setDesiredReadyDelay() {
+    private static void updateBatteryStatsConstants(ArrayMap<String, String> desiredConstants) {
         sOriginalBatteryStatsConsts = Settings.Global.getString(sContext.getContentResolver(),
                 Settings.Global.BATTERY_STATS_CONSTANTS);
-        String newBatteryStatsConstants;
-        final String newConstant = KEY_PROC_STATE_CPU_TIMES_READ_DELAY_MS
-                + "=" + DESIRED_PROC_STATE_CPU_TIMES_DELAY;
-        if (sOriginalBatteryStatsConsts == null || "null".equals(sOriginalBatteryStatsConsts)) {
-            // battery_stats_constants is initially empty, so just assign the desired value.
-            newBatteryStatsConstants = newConstant;
-        } else if (sOriginalBatteryStatsConsts.contains(KEY_PROC_STATE_CPU_TIMES_READ_DELAY_MS)) {
-            // battery_stats_constants contains delay duration, so replace it
-            // with the desired value.
-            newBatteryStatsConstants = sOriginalBatteryStatsConsts.replaceAll(
-                    KEY_PROC_STATE_CPU_TIMES_READ_DELAY_MS + "=\\d+", newConstant);
-        } else {
-            // battery_stats_constants didn't contain any delay, so append the desired value.
-            newBatteryStatsConstants = sOriginalBatteryStatsConsts + "," + newConstant;
+        final char delimiter = ',';
+        final KeyValueListParser parser = new KeyValueListParser(delimiter);
+        parser.setString(sOriginalBatteryStatsConsts);
+        final StringBuilder sb = new StringBuilder();
+        for (int i = 0, size = parser.size(); i < size; ++i) {
+            final String key = parser.keyAt(i);
+            final String value = desiredConstants.getOrDefault(key,
+                    parser.getString(key, null));
+            if (sb.length() > 0) {
+                sb.append(delimiter);
+            }
+            sb.append(key + "=" + value);
+            desiredConstants.remove(key);
         }
+        desiredConstants.forEach((key, value) -> {
+            if (sb.length() > 0) {
+                sb.append(delimiter);
+            }
+            sb.append(key + '=' + value);
+        });
         Settings.Global.putString(sContext.getContentResolver(),
-                Settings.Global.BATTERY_STATS_CONSTANTS, newBatteryStatsConstants);
+                Settings.Global.BATTERY_STATS_CONSTANTS, sb.toString());
+        Log.d(TAG, "Updated value of '" + Settings.Global.BATTERY_STATS_CONSTANTS + "': "
+                + sb.toString());
         sBatteryStatsConstsUpdated = true;
     }
 
diff --git a/core/tests/coretests/src/com/android/internal/os/MockBatteryStatsImpl.java b/core/tests/coretests/src/com/android/internal/os/MockBatteryStatsImpl.java
index bc0e0a4..75dd7fb 100644
--- a/core/tests/coretests/src/com/android/internal/os/MockBatteryStatsImpl.java
+++ b/core/tests/coretests/src/com/android/internal/os/MockBatteryStatsImpl.java
@@ -133,6 +133,12 @@
         return this;
     }
 
+    public MockBatteryStatsImpl setSystemServerCpuThreadReader(
+            SystemServerCpuThreadReader systemServerCpuThreadReader) {
+        mSystemServerCpuThreadReader = systemServerCpuThreadReader;
+        return this;
+    }
+
     public MockBatteryStatsImpl setUserInfoProvider(UserInfoProvider provider) {
         mUserInfoProvider = provider;
         return this;
diff --git a/core/tests/coretests/src/com/android/internal/os/SystemServerCpuThreadReaderTest.java b/core/tests/coretests/src/com/android/internal/os/SystemServerCpuThreadReaderTest.java
new file mode 100644
index 0000000..10ba548
--- /dev/null
+++ b/core/tests/coretests/src/com/android/internal/os/SystemServerCpuThreadReaderTest.java
@@ -0,0 +1,136 @@
+/*
+ * 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.os;
+
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertTrue;
+
+import android.content.Context;
+import android.os.FileUtils;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.nio.file.Files;
+import java.nio.file.Path;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class SystemServerCpuThreadReaderTest {
+    private File mProcDirectory;
+
+    @Before
+    public void setUp() {
+        Context context = InstrumentationRegistry.getContext();
+        mProcDirectory = context.getDir("proc", Context.MODE_PRIVATE);
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        FileUtils.deleteContents(mProcDirectory);
+    }
+
+    @Test
+    public void testReaderDelta_firstTime() throws IOException {
+        int uid = 42;
+        setupDirectory(
+                mProcDirectory.toPath().resolve(String.valueOf(uid)),
+                new int[]{42, 1, 2, 3},
+                new int[]{1000, 2000},
+                // Units are 10ms aka 10000Us
+                new int[][]{{100, 200}, {0, 200}, {0, 300}, {0, 400}});
+
+        SystemServerCpuThreadReader reader = new SystemServerCpuThreadReader(
+                mProcDirectory.toPath(), uid);
+        reader.setBinderThreadNativeTids(new int[]{1, 3});
+        SystemServerCpuThreadReader.SystemServiceCpuThreadTimes systemServiceCpuThreadTimes =
+                reader.readDelta();
+        assertArrayEquals(new long[]{100 * 10000, 1100 * 10000},
+                systemServiceCpuThreadTimes.threadCpuTimesUs);
+        assertArrayEquals(new long[]{0, 600 * 10000},
+                systemServiceCpuThreadTimes.binderThreadCpuTimesUs);
+    }
+
+    @Test
+    public void testReaderDelta_nextTime() throws IOException {
+        int uid = 42;
+        setupDirectory(
+                mProcDirectory.toPath().resolve(String.valueOf(uid)),
+                new int[]{42, 1, 2, 3},
+                new int[]{1000, 2000},
+                new int[][]{{100, 200}, {0, 200}, {0, 300}, {0, 400}});
+
+        SystemServerCpuThreadReader reader = new SystemServerCpuThreadReader(
+                mProcDirectory.toPath(), uid);
+        reader.setBinderThreadNativeTids(new int[]{1, 3});
+
+        // First time, populate "last" snapshot
+        reader.readDelta();
+
+        FileUtils.deleteContents(mProcDirectory);
+        setupDirectory(
+                mProcDirectory.toPath().resolve(String.valueOf(uid)),
+                new int[]{42, 1, 2, 3},
+                new int[]{1000, 2000},
+                new int[][]{{500, 600}, {700, 800}, {900, 1000}, {1100, 1200}});
+
+        // Second time, get the actual delta
+        SystemServerCpuThreadReader.SystemServiceCpuThreadTimes systemServiceCpuThreadTimes =
+                reader.readDelta();
+
+        assertArrayEquals(new long[]{3100 * 10000, 2500 * 10000},
+                systemServiceCpuThreadTimes.threadCpuTimesUs);
+        assertArrayEquals(new long[]{1800 * 10000, 1400 * 10000},
+                systemServiceCpuThreadTimes.binderThreadCpuTimesUs);
+    }
+
+    private void setupDirectory(Path processPath, int[] threadIds, int[] cpuFrequencies,
+            int[][] cpuTimes) throws IOException {
+        // Make /proc/$PID
+        assertTrue(processPath.toFile().mkdirs());
+
+        // Make /proc/$PID/task
+        final Path selfThreadsPath = processPath.resolve("task");
+        assertTrue(selfThreadsPath.toFile().mkdirs());
+
+        // Make thread directories in reverse order, as they are read in order of creation by
+        // CpuThreadProcReader
+        for (int i = 0; i < threadIds.length; i++) {
+            // Make /proc/$PID/task/$TID
+            final Path threadPath = selfThreadsPath.resolve(String.valueOf(threadIds[i]));
+            assertTrue(threadPath.toFile().mkdirs());
+
+            // Make /proc/$PID/task/$TID/time_in_state
+            final OutputStream timeInStateStream =
+                    Files.newOutputStream(threadPath.resolve("time_in_state"));
+            for (int j = 0; j < cpuFrequencies.length; j++) {
+                final String line = cpuFrequencies[j] + " " + cpuTimes[i][j] + "\n";
+                timeInStateStream.write(line.getBytes());
+            }
+            timeInStateStream.close();
+        }
+    }
+}
diff --git a/core/tests/coretests/src/com/android/internal/os/SystemServicePowerCalculatorTest.java b/core/tests/coretests/src/com/android/internal/os/SystemServicePowerCalculatorTest.java
new file mode 100644
index 0000000..ac5443e
--- /dev/null
+++ b/core/tests/coretests/src/com/android/internal/os/SystemServicePowerCalculatorTest.java
@@ -0,0 +1,173 @@
+/*
+ * 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.os;
+
+import static org.junit.Assert.assertEquals;
+
+import android.content.Context;
+import android.os.BatteryStats;
+import android.os.Binder;
+import android.os.Process;
+
+import androidx.annotation.Nullable;
+import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collection;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class SystemServicePowerCalculatorTest {
+
+    private PowerProfile mProfile;
+    private MockBatteryStatsImpl mMockBatteryStats;
+    private MockKernelCpuUidFreqTimeReader mMockCpuUidFreqTimeReader;
+    private MockServerCpuThreadReader mMockServerCpuThreadReader;
+    private SystemServicePowerCalculator mSystemServicePowerCalculator;
+
+    @Before
+    public void setUp() throws IOException {
+        Context context = InstrumentationRegistry.getContext();
+        mProfile = new PowerProfile(context, true /* forTest */);
+        mMockServerCpuThreadReader = new MockServerCpuThreadReader();
+        mMockCpuUidFreqTimeReader = new MockKernelCpuUidFreqTimeReader();
+        mMockBatteryStats = new MockBatteryStatsImpl(new MockClocks())
+                .setPowerProfile(mProfile)
+                .setSystemServerCpuThreadReader(mMockServerCpuThreadReader)
+                .setKernelCpuUidFreqTimeReader(mMockCpuUidFreqTimeReader)
+                .setUserInfoProvider(new MockUserInfoProvider());
+        mMockBatteryStats.getOnBatteryTimeBase().setRunning(true, 0, 0);
+        mSystemServicePowerCalculator =
+                new SystemServicePowerCalculator(mProfile, mMockBatteryStats);
+    }
+
+    @Test
+    public void testCalculateApp() {
+        // Test Power Profile has two CPU clusters with 3 and 4 speeds, thus 7 freq times total
+        mMockServerCpuThreadReader.setThreadTimes(
+                new long[]{30000, 40000, 50000, 60000, 70000, 80000, 90000},
+                new long[]{20000, 30000, 40000, 50000, 60000, 70000, 80000});
+
+        mMockCpuUidFreqTimeReader.setSystemServerCpuTimes(
+                new long[]{10000, 20000, 30000, 40000, 50000, 60000, 70000}
+        );
+
+        mMockBatteryStats.readKernelUidCpuFreqTimesLocked(null, true, false);
+
+        int workSourceUid1 = 100;
+        int workSourceUid2 = 200;
+        int transactionCode = 42;
+
+        Collection<BinderCallsStats.CallStat> callStats = new ArrayList<>();
+        BinderCallsStats.CallStat stat1 = new BinderCallsStats.CallStat(workSourceUid1,
+                Binder.class, transactionCode, true /*screenInteractive */);
+        stat1.incrementalCallCount = 100;
+        stat1.recordedCallCount = 100;
+        stat1.cpuTimeMicros = 1000000;
+        callStats.add(stat1);
+
+        mMockBatteryStats.noteBinderCallStats(workSourceUid1, 100, callStats, null);
+
+        callStats.clear();
+        BinderCallsStats.CallStat stat2 = new BinderCallsStats.CallStat(workSourceUid2,
+                Binder.class, transactionCode, true /*screenInteractive */);
+        stat2.incrementalCallCount = 100;
+        stat2.recordedCallCount = 100;
+        stat2.cpuTimeMicros = 9000000;
+        callStats.add(stat2);
+
+        mMockBatteryStats.noteBinderCallStats(workSourceUid2, 100, callStats, null);
+
+        mMockBatteryStats.updateSystemServiceCallStats();
+        mMockBatteryStats.updateSystemServerThreadStats();
+
+        BatterySipper app1 = new BatterySipper(BatterySipper.DrainType.APP,
+                mMockBatteryStats.getUidStatsLocked(workSourceUid1), 0);
+        mSystemServicePowerCalculator.calculateApp(app1, app1.uidObj, 0, 0,
+                BatteryStats.STATS_SINCE_CHARGED);
+        assertEquals(0.27162, app1.systemServiceCpuPowerMah, 0.00001);
+
+        BatterySipper app2 = new BatterySipper(BatterySipper.DrainType.APP,
+                mMockBatteryStats.getUidStatsLocked(workSourceUid2), 0);
+        mSystemServicePowerCalculator.calculateApp(app2, app2.uidObj, 0, 0,
+                BatteryStats.STATS_SINCE_CHARGED);
+        assertEquals(2.44458, app2.systemServiceCpuPowerMah, 0.00001);
+    }
+
+    private static class MockKernelCpuUidFreqTimeReader extends
+            KernelCpuUidTimeReader.KernelCpuUidFreqTimeReader {
+        private long[] mSystemServerCpuTimes;
+
+        MockKernelCpuUidFreqTimeReader() {
+            super(/*throttle */false);
+        }
+
+        void setSystemServerCpuTimes(long[] systemServerCpuTimes) {
+            mSystemServerCpuTimes = systemServerCpuTimes;
+        }
+
+        @Override
+        public boolean perClusterTimesAvailable() {
+            return true;
+        }
+
+        @Override
+        public void readDelta(@Nullable Callback<long[]> cb) {
+            if (cb != null) {
+                cb.onUidCpuTime(Process.SYSTEM_UID, mSystemServerCpuTimes);
+            }
+        }
+    }
+
+    private static class MockServerCpuThreadReader extends SystemServerCpuThreadReader {
+        private SystemServiceCpuThreadTimes mThreadTimes = new SystemServiceCpuThreadTimes();
+
+        MockServerCpuThreadReader() {
+            super(null);
+        }
+
+        public void setThreadTimes(long[] threadCpuTimesUs, long[] binderThreadCpuTimesUs) {
+            mThreadTimes.threadCpuTimesUs = threadCpuTimesUs;
+            mThreadTimes.binderThreadCpuTimesUs = binderThreadCpuTimesUs;
+        }
+
+        @Override
+        public SystemServiceCpuThreadTimes readDelta() {
+            return mThreadTimes;
+        }
+    }
+
+    private static class MockUserInfoProvider extends BatteryStatsImpl.UserInfoProvider {
+        @Nullable
+        @Override
+        protected int[] getUserIds() {
+            return new int[0];
+        }
+
+        @Override
+        public boolean exists(int userId) {
+            return true;
+        }
+    }
+}
diff --git a/data/etc/car/Android.bp b/data/etc/car/Android.bp
index 04007c9..e122e00 100644
--- a/data/etc/car/Android.bp
+++ b/data/etc/car/Android.bp
@@ -17,126 +17,126 @@
 // Privapp permission whitelist files
 
 prebuilt_etc {
-    name: "privapp_whitelist_android.car.cluster.loggingrenderer",
+    name: "allowed_privapp_android.car.cluster.loggingrenderer",
     sub_dir: "permissions",
     src: "android.car.cluster.loggingrenderer.xml",
     filename_from_src: true,
 }
 
 prebuilt_etc {
-    name: "privapp_whitelist_android.car.cluster.sample",
+    name: "allowed_privapp_android.car.cluster.sample",
     sub_dir: "permissions",
     src: "android.car.cluster.sample.xml",
     filename_from_src: true,
 }
 
 prebuilt_etc {
-    name: "privapp_whitelist_android.car.cluster",
+    name: "allowed_privapp_android.car.cluster",
     sub_dir: "permissions",
     src: "android.car.cluster.xml",
     filename_from_src: true,
 }
 
 prebuilt_etc {
-    name: "privapp_whitelist_android.car.usb.handler",
+    name: "allowed_privapp_android.car.usb.handler",
     sub_dir: "permissions",
     src: "android.car.usb.handler.xml",
     filename_from_src: true,
 }
 
 prebuilt_etc {
-    name: "privapp_whitelist_com.android.car.carlauncher",
+    name: "allowed_privapp_com.android.car.carlauncher",
     sub_dir: "permissions",
     src: "com.android.car.carlauncher.xml",
     filename_from_src: true,
 }
 
 prebuilt_etc {
-    name: "privapp_whitelist_com.android.car.dialer",
+    name: "allowed_privapp_com.android.car.dialer",
     sub_dir: "permissions",
     src: "com.android.car.dialer.xml",
     filename_from_src: true,
 }
 
 prebuilt_etc {
-    name: "privapp_whitelist_com.android.car.hvac",
+    name: "allowed_privapp_com.android.car.hvac",
     sub_dir: "permissions",
     src: "com.android.car.hvac.xml",
     filename_from_src: true,
 }
 
 prebuilt_etc {
-    name: "privapp_whitelist_com.android.car.media",
+    name: "allowed_privapp_com.android.car.media",
     sub_dir: "permissions",
     src: "com.android.car.media.xml",
     filename_from_src: true,
 }
 
 prebuilt_etc {
-    name: "privapp_whitelist_com.android.car.notification",
+    name: "allowed_privapp_com.android.car.notification",
     sub_dir: "permissions",
     src: "com.android.car.notification.xml",
     filename_from_src: true,
 }
 
 prebuilt_etc {
-    name: "privapp_whitelist_com.android.car.radio",
+    name: "allowed_privapp_com.android.car.radio",
     sub_dir: "permissions",
     src: "com.android.car.radio.xml",
     filename_from_src: true,
 }
 
 prebuilt_etc {
-    name: "privapp_whitelist_com.android.car.secondaryhome",
+    name: "allowed_privapp_com.android.car.secondaryhome",
     sub_dir: "permissions",
     src: "com.android.car.secondaryhome.xml",
     filename_from_src: true,
 }
 
 prebuilt_etc {
-    name: "privapp_whitelist_com.android.car.settings",
+    name: "allowed_privapp_com.android.car.settings",
     sub_dir: "permissions",
     src: "com.android.car.settings.xml",
     filename_from_src: true,
 }
 
 prebuilt_etc {
-    name: "privapp_whitelist_com.android.car.themeplayground",
+    name: "allowed_privapp_com.android.car.themeplayground",
     sub_dir: "permissions",
     src: "com.android.car.themeplayground.xml",
     filename_from_src: true,
 }
 
 prebuilt_etc {
-    name: "privapp_whitelist_com.android.car",
+    name: "allowed_privapp_com.android.car",
     sub_dir: "permissions",
     src: "com.android.car.xml",
     filename_from_src: true,
 }
 
 prebuilt_etc {
-    name: "privapp_whitelist_com.android.car.bugreport",
+    name: "allowed_privapp_com.android.car.bugreport",
     sub_dir: "permissions",
     src: "com.android.car.bugreport.xml",
     filename_from_src: true,
 }
 
 prebuilt_etc {
-    name: "privapp_whitelist_com.android.car.companiondevicesupport",
+    name: "allowed_privapp_com.android.car.companiondevicesupport",
     sub_dir: "permissions",
     src: "com.android.car.companiondevicesupport.xml",
     filename_from_src: true,
 }
 
 prebuilt_etc {
-    name: "privapp_whitelist_com.google.android.car.kitchensink",
+    name: "allowed_privapp_com.google.android.car.kitchensink",
     sub_dir: "permissions",
     src: "com.google.android.car.kitchensink.xml",
     filename_from_src: true,
 }
 
 prebuilt_etc {
-    name: "privapp_whitelist_com.android.car.developeroptions",
+    name: "allowed_privapp_com.android.car.developeroptions",
     sub_dir: "permissions",
     src: "com.android.car.developeroptions.xml",
     filename_from_src: true,
@@ -144,14 +144,7 @@
 }
 
 prebuilt_etc {
-    name: "privapp_whitelist_com.android.car.floatingcardslauncher",
-    sub_dir: "permissions",
-    src: "com.android.car.floatingcardslauncher.xml",
-    filename_from_src: true,
-}
-
-prebuilt_etc {
-    name: "privapp_whitelist_com.android.car.ui.paintbooth",
+    name: "allowed_privapp_com.android.car.ui.paintbooth",
     sub_dir: "permissions",
     src: "com.android.car.ui.paintbooth.xml",
     filename_from_src: true,
diff --git a/data/etc/car/com.android.car.floatingcardslauncher.xml b/data/etc/car/com.android.car.floatingcardslauncher.xml
deleted file mode 100644
index 2755fee..0000000
--- a/data/etc/car/com.android.car.floatingcardslauncher.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-  ~ Copyright (C) 2019 The Android Open Source Project
-  ~
-  ~ Licensed under the Apache License, Version 2.0 (the "License");
-  ~ you may not use this file except in compliance with the License.
-  ~ You may obtain a copy of the License at
-  ~
-  ~      http://www.apache.org/licenses/LICENSE-2.0
-  ~
-  ~ Unless required by applicable law or agreed to in writing, software
-  ~ distributed under the License is distributed on an "AS IS" BASIS,
-  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-  ~ See the License for the specific language governing permissions and
-  ~ limitations under the License
-  -->
-<permissions>
-    <privapp-permissions package="com.android.car.floatingcardslauncher">
-        <permission name="android.permission.ACTIVITY_EMBEDDING"/>
-        <permission name="android.permission.INTERACT_ACROSS_USERS"/>
-        <permission name="android.permission.MANAGE_USERS"/>
-        <permission name="android.permission.MEDIA_CONTENT_CONTROL"/>
-        <permission name="android.permission.MODIFY_PHONE_STATE"/>
-    </privapp-permissions>
-</permissions>
diff --git a/data/etc/com.android.systemui.xml b/data/etc/com.android.systemui.xml
index ada8b00..06f1dae 100644
--- a/data/etc/com.android.systemui.xml
+++ b/data/etc/com.android.systemui.xml
@@ -35,6 +35,7 @@
         <permission name="android.permission.MANAGE_USERS"/>
         <permission name="android.permission.MASTER_CLEAR"/>
         <permission name="android.permission.MEDIA_CONTENT_CONTROL"/>
+        <permission name="android.permission.MODIFY_AUDIO_ROUTING" />
         <permission name="android.permission.MODIFY_DAY_NIGHT_MODE"/>
         <permission name="android.permission.MODIFY_PHONE_STATE"/>
         <permission name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS"/>
diff --git a/data/etc/privapp-permissions-platform.xml b/data/etc/privapp-permissions-platform.xml
index f834ce7..602ccfe 100644
--- a/data/etc/privapp-permissions-platform.xml
+++ b/data/etc/privapp-permissions-platform.xml
@@ -143,6 +143,7 @@
         <permission name="android.permission.SUBSTITUTE_NOTIFICATION_APP_NAME" />
         <permission name="android.permission.PACKAGE_USAGE_STATS" />
         <permission name="android.permission.CHANGE_COMPONENT_ENABLED_STATE" />
+        <permission name="android.permission.MODIFY_AUDIO_ROUTING" />
 
         <!-- For permission hub 2 debugging only -->
         <permission name="android.permission.GET_ACCOUNTS_PRIVILEGED"/>
@@ -432,18 +433,6 @@
         <permission name="android.permission.CAPTURE_AUDIO_OUTPUT" />
         <!-- Permissions required for CTS test - AdbManagerTest -->
         <permission name="android.permission.MANAGE_DEBUGGING" />
-        <!-- Permissions required for ATS tests - AtsCarHostTestCases, AtsCarDeviceApp -->
-        <permission name="android.car.permission.CAR_DRIVING_STATE" />
-        <!-- Permissions required for ATS tests - AtsDeviceInfo, AtsAudioDeviceTestCases -->
-        <permission name="android.car.permission.CAR_CONTROL_AUDIO_VOLUME" />
-        <!-- Permissions required for ATS tests - AtsDeviceInfo -->
-        <permission name="android.car.permission.CAR_DIAGNOSTICS" />
-        <!-- Permissions required for ATS tests - AtsDeviceInfo -->
-        <permission name="android.car.permission.CLEAR_CAR_DIAGNOSTICS" />
-        <!-- Permissions required for ATS tests - AtsCarHostTestCases -->
-        <permission name="android.car.permission.CONTROL_APP_BLOCKING" />
-        <!-- Permissions required for ATS tests - AtsCarHostTestCases -->
-        <permission name="android.car.permission.CAR_UX_RESTRICTIONS_CONFIGURATION" />
     </privapp-permissions>
 
     <privapp-permissions package="com.android.statementservice">
diff --git a/data/keyboards/Generic.kl b/data/keyboards/Generic.kl
index 4cdfbb8..bd2d4af 100644
--- a/data/keyboards/Generic.kl
+++ b/data/keyboards/Generic.kl
@@ -345,10 +345,10 @@
 # key 395 "KEY_LIST"
 # key 396 "KEY_MEMO"
 key 397   CALENDAR
-# key 398 "KEY_RED"
-# key 399 "KEY_GREEN"
-# key 400 "KEY_YELLOW"
-# key 401 "KEY_BLUE"
+key 398   PROG_RED
+key 399   PROG_GREEN
+key 400   PROG_YELLOW
+key 401   PROG_BLUE
 key 402   CHANNEL_UP
 key 403   CHANNEL_DOWN
 # key 404 "KEY_FIRST"
@@ -415,6 +415,7 @@
 key usage 0x0c0067 WINDOW
 key usage 0x0c006F BRIGHTNESS_UP
 key usage 0x0c0070 BRIGHTNESS_DOWN
+key usage 0x0c0173 MEDIA_AUDIO_TRACK
 
 # Joystick and game controller axes.
 # Axes that are not mapped will be assigned generic axis numbers by the input subsystem.
diff --git a/drm/java/android/drm/DrmManagerClient.java b/drm/java/android/drm/DrmManagerClient.java
index ba3ebdd..4ec752a 100644
--- a/drm/java/android/drm/DrmManagerClient.java
+++ b/drm/java/android/drm/DrmManagerClient.java
@@ -751,7 +751,7 @@
 
     /**
      * Removes all the rights information of every DRM plug-in (agent) associated with
-     * the DRM framework. Will be used during a master reset.
+     * the DRM framework.
      *
      * @return ERROR_NONE for success; ERROR_UNKNOWN for failure.
      */
diff --git a/errorprone/java/com/google/errorprone/bugpatterns/android/UidChecker.java b/errorprone/java/com/google/errorprone/bugpatterns/android/UidChecker.java
index 3ffb8ea..a2ee065 100644
--- a/errorprone/java/com/google/errorprone/bugpatterns/android/UidChecker.java
+++ b/errorprone/java/com/google/errorprone/bugpatterns/android/UidChecker.java
@@ -23,12 +23,15 @@
 import com.google.errorprone.VisitorState;
 import com.google.errorprone.bugpatterns.BugChecker;
 import com.google.errorprone.bugpatterns.BugChecker.MethodInvocationTreeMatcher;
+import com.google.errorprone.bugpatterns.BugChecker.NewClassTreeMatcher;
 import com.google.errorprone.matchers.Description;
 import com.google.errorprone.util.ASTHelpers;
 import com.sun.source.tree.ExpressionTree;
 import com.sun.source.tree.IdentifierTree;
 import com.sun.source.tree.MemberSelectTree;
 import com.sun.source.tree.MethodInvocationTree;
+import com.sun.source.tree.NewClassTree;
+import com.sun.source.tree.Tree;
 import com.sun.tools.javac.code.Symbol.VarSymbol;
 
 import java.util.List;
@@ -44,11 +47,20 @@
     name = "AndroidFrameworkUid",
     summary = "Verifies that PID, UID and user ID arguments aren't crossed",
     severity = WARNING)
-public final class UidChecker extends BugChecker implements MethodInvocationTreeMatcher {
+public final class UidChecker extends BugChecker implements MethodInvocationTreeMatcher,
+        NewClassTreeMatcher {
     @Override
     public Description matchMethodInvocation(MethodInvocationTree tree, VisitorState state) {
-        final List<VarSymbol> vars = ASTHelpers.getSymbol(tree).params();
-        final List<? extends ExpressionTree> args = tree.getArguments();
+        return matchArguments(ASTHelpers.getSymbol(tree).params(), tree.getArguments(), tree);
+    }
+
+    @Override
+    public Description matchNewClass(NewClassTree tree, VisitorState state) {
+        return matchArguments(ASTHelpers.getSymbol(tree).params(), tree.getArguments(), tree);
+    }
+
+    private Description matchArguments(List<VarSymbol> vars,
+            List<? extends ExpressionTree> args, Tree tree) {
         for (int i = 0; i < Math.min(vars.size(), args.size()); i++) {
             final Flavor varFlavor = getFlavor(vars.get(i));
             final Flavor argFlavor = getFlavor(args.get(i));
diff --git a/errorprone/tests/java/com/google/errorprone/bugpatterns/android/UidCheckerTest.java b/errorprone/tests/java/com/google/errorprone/bugpatterns/android/UidCheckerTest.java
index 74da947..75341fd 100644
--- a/errorprone/tests/java/com/google/errorprone/bugpatterns/android/UidCheckerTest.java
+++ b/errorprone/tests/java/com/google/errorprone/bugpatterns/android/UidCheckerTest.java
@@ -34,7 +34,7 @@
     }
 
     @Test
-    public void testTypical() {
+    public void testTypical_methodInvocation() {
         compilationHelper
                 .addSourceLines("Example.java",
                         "public abstract class Example {",
@@ -57,7 +57,30 @@
     }
 
     @Test
-    public void testCallingUid() {
+    public void testTypical_newClass() {
+        compilationHelper
+                .addSourceLines("Example.java",
+                        "public abstract class Example {",
+                        "  class Bar { Bar(int pid, int uid, int userId) {} }",
+                        "  abstract int getUserId();",
+                        "  void foo(int pid, int uid, int userId, int unrelated) {",
+                        "    new Bar(0, 0, 0);",
+                        "    new Bar(pid, uid, userId);",
+                        "    new Bar(pid, uid, getUserId());",
+                        "    new Bar(unrelated, unrelated, unrelated);",
+                        "    // BUG: Diagnostic contains:",
+                        "    new Bar(uid, pid, userId);",
+                        "    // BUG: Diagnostic contains:",
+                        "    new Bar(pid, userId, uid);",
+                        "    // BUG: Diagnostic contains:",
+                        "    new Bar(getUserId(), 0, 0);",
+                        "  }",
+                        "}")
+                .doTest();
+    }
+
+    @Test
+    public void testCallingUid_methodInvocation() {
         compilationHelper
                 .addSourceFile("/android/os/Binder.java")
                 .addSourceFile("/android/os/UserHandle.java")
@@ -98,4 +121,48 @@
                         "}")
                 .doTest();
     }
+
+
+    @Test
+    public void testCallingUid_newClass() {
+        compilationHelper
+                .addSourceFile("/android/os/Binder.java")
+                .addSourceFile("/android/os/UserHandle.java")
+                .addSourceLines("Example.java",
+                        "import android.os.Binder;",
+                        "import android.os.UserHandle;",
+                        "public abstract class Example {",
+                        "  int callingUserId;",
+                        "  int callingUid;",
+                        "  class Foo { Foo(int callingUserId) {} }",
+                        "  class Bar { Bar(int callingUid) {} }",
+                        "  void doUserId(int callingUserId) {",
+                        "    new Foo(UserHandle.getUserId(Binder.getCallingUid()));",
+                        "    new Foo(this.callingUserId);",
+                        "    new Foo(callingUserId);",
+                        "    // BUG: Diagnostic contains:",
+                        "    new Foo(Binder.getCallingUid());",
+                        "    // BUG: Diagnostic contains:",
+                        "    new Foo(this.callingUid);",
+                        "    // BUG: Diagnostic contains:",
+                        "    new Foo(callingUid);",
+                        "  }",
+                        "  void doUid(int callingUserId) {",
+                        "    new Bar(Binder.getCallingUid());",
+                        "    new Bar(this.callingUid);",
+                        "    new Bar(callingUid);",
+                        "    // BUG: Diagnostic contains:",
+                        "    new Bar(UserHandle.getUserId(Binder.getCallingUid()));",
+                        "    // BUG: Diagnostic contains:",
+                        "    new Bar(this.callingUserId);",
+                        "    // BUG: Diagnostic contains:",
+                        "    new Bar(callingUserId);",
+                        "  }",
+                        "  void doInner() {",
+                        "    // BUG: Diagnostic contains:",
+                        "    new Foo(UserHandle.getUserId(callingUserId));",
+                        "  }",
+                        "}")
+                .doTest();
+    }
 }
diff --git a/framework-jarjar-rules.txt b/framework-jarjar-rules.txt
index d8af726..70dedb8 100644
--- a/framework-jarjar-rules.txt
+++ b/framework-jarjar-rules.txt
@@ -1,2 +1,6 @@
 rule android.hidl.** android.internal.hidl.@1
 rule android.net.wifi.WifiAnnotations* android.internal.wifi.WifiAnnotations@1
+
+# Hide media mainline module implementation classes to avoid collisions with
+# app-bundled ExoPlayer classes.
+rule com.google.android.exoplayer2.** android.media.internal.exo.@1
diff --git a/graphics/java/android/graphics/BlurShader.java b/graphics/java/android/graphics/BlurShader.java
new file mode 100644
index 0000000..779a890
--- /dev/null
+++ b/graphics/java/android/graphics/BlurShader.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.graphics;
+
+import android.annotation.Nullable;
+
+/**
+ * A subclass of shader that blurs input from another {@link android.graphics.Shader} instance
+ * or all the drawing commands with the {@link android.graphics.Paint} that this shader is
+ * attached to.
+ */
+public final class BlurShader extends Shader {
+
+    private final float mRadiusX;
+    private final float mRadiusY;
+    private final Shader mInputShader;
+
+    private long mNativeInputShader = 0;
+
+    /**
+     * Create a {@link BlurShader} that blurs the contents of the optional input shader
+     * with the specified radius along the x and y axis. If no input shader is provided
+     * then all drawing commands issued with a {@link android.graphics.Paint} that this
+     * shader is installed in will be blurred
+     * @param radiusX Radius of blur along the X axis
+     * @param radiusY Radius of blur along the Y axis
+     * @param inputShader Input shader that provides the content to be blurred
+     */
+    public BlurShader(float radiusX, float radiusY, @Nullable Shader inputShader) {
+        mRadiusX = radiusX;
+        mRadiusY = radiusY;
+        mInputShader = inputShader;
+    }
+
+    /** @hide **/
+    @Override
+    protected long createNativeInstance(long nativeMatrix) {
+        mNativeInputShader = mInputShader != null ? mInputShader.getNativeInstance() : 0;
+        return nativeCreate(nativeMatrix, mRadiusX, mRadiusY, mNativeInputShader);
+    }
+
+    /** @hide **/
+    @Override
+    protected boolean shouldDiscardNativeInstance() {
+        long currentNativeInstance = mInputShader != null ? mInputShader.getNativeInstance() : 0;
+        return mNativeInputShader != currentNativeInstance;
+    }
+
+    private static native long nativeCreate(long nativeMatrix, float radiusX, float radiusY,
+            long inputShader);
+}
diff --git a/graphics/java/android/graphics/HardwareRenderer.java b/graphics/java/android/graphics/HardwareRenderer.java
index b3103fd5..0452933 100644
--- a/graphics/java/android/graphics/HardwareRenderer.java
+++ b/graphics/java/android/graphics/HardwareRenderer.java
@@ -634,6 +634,19 @@
     }
 
     /**
+     * Sets the colormode with the desired SDR white point.
+     *
+     * The white point only applies if the color mode is an HDR mode
+     *
+     * @hide
+     */
+    public void setColorMode(@ActivityInfo.ColorMode int colorMode, float whitePoint) {
+        nSetSdrWhitePoint(mNativeProxy, whitePoint);
+        mColorMode = colorMode;
+        nSetColorMode(mNativeProxy, colorMode);
+    }
+
+    /**
      * Blocks until all previously queued work has completed.
      *
      * TODO: Only used for draw finished listeners, but the FrameCompleteCallback does that
@@ -1227,6 +1240,8 @@
 
     private static native void nSetColorMode(long nativeProxy, int colorMode);
 
+    private static native void nSetSdrWhitePoint(long nativeProxy, float whitePoint);
+
     private static native int nSyncAndDrawFrame(long nativeProxy, long[] frameInfo, int size);
 
     private static native void nDestroy(long nativeProxy, long rootRenderNode);
diff --git a/graphics/java/android/graphics/Paint.java b/graphics/java/android/graphics/Paint.java
index 28d7911..964640b 100644
--- a/graphics/java/android/graphics/Paint.java
+++ b/graphics/java/android/graphics/Paint.java
@@ -3111,7 +3111,7 @@
     @CriticalNative
     private static native boolean nGetFillPath(long paintPtr, long src, long dst);
     @CriticalNative
-    private static native long nSetShader(long paintPtr, long shader);
+    private static native void nSetShader(long paintPtr, long shader);
     @CriticalNative
     private static native long nSetColorFilter(long paintPtr, long filter);
     @CriticalNative
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java
new file mode 100644
index 0000000..1263748
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java
@@ -0,0 +1,161 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell;
+
+import android.app.ActivityManager.RunningTaskInfo;
+import android.util.Log;
+import android.util.Pair;
+import android.util.SparseArray;
+import android.util.SparseIntArray;
+import android.view.Surface;
+import android.view.SurfaceControl;
+import android.window.TaskOrganizer;
+
+import java.util.ArrayList;
+import java.util.LinkedList;
+import java.util.concurrent.CopyOnWriteArrayList;
+
+/**
+ * Unified task organizer for all components in the shell.
+ */
+public class ShellTaskOrganizer extends TaskOrganizer {
+
+    private static final String TAG = "ShellTaskOrganizer";
+
+    /**
+     * Callbacks for when the tasks change in the system.
+     */
+    public interface TaskListener {
+        default void onTaskAppeared(RunningTaskInfo taskInfo, SurfaceControl leash) {}
+        default void onTaskInfoChanged(RunningTaskInfo taskInfo) {}
+        default void onTaskVanished(RunningTaskInfo taskInfo) {}
+        default void onBackPressedOnTaskRoot(RunningTaskInfo taskInfo) {}
+    }
+
+    private final SparseArray<ArrayList<TaskListener>> mListenersByWindowingMode =
+            new SparseArray<>();
+
+    // Keeps track of all the tasks reported to this organizer (changes in windowing mode will
+    // require us to report to both old and new listeners)
+    private final SparseArray<Pair<RunningTaskInfo, SurfaceControl>> mTasks = new SparseArray<>();
+
+    /**
+     * Adds a listener for tasks in a specific windowing mode.
+     */
+    public void addListener(TaskListener listener, int... windowingModes) {
+        for (int winMode : windowingModes) {
+            ArrayList<TaskListener> listeners = mListenersByWindowingMode.get(winMode);
+            if (listeners == null) {
+                listeners = new ArrayList<>();
+                mListenersByWindowingMode.put(winMode, listeners);
+            }
+            if (listeners.contains(listener)) {
+                Log.w(TAG, "Listener already exists");
+                return;
+            }
+            listeners.add(listener);
+
+            // Notify the listener of all existing tasks in that windowing mode
+            for (int i = mTasks.size() - 1; i >= 0; i--) {
+                Pair<RunningTaskInfo, SurfaceControl> data = mTasks.valueAt(i);
+                int taskWinMode = data.first.configuration.windowConfiguration.getWindowingMode();
+                if (taskWinMode == winMode) {
+                    listener.onTaskAppeared(data.first, data.second);
+                }
+            }
+        }
+    }
+
+    /**
+     * Removes a registered listener.
+     */
+    public void removeListener(TaskListener listener) {
+        for (int i = 0; i < mListenersByWindowingMode.size(); i++) {
+            mListenersByWindowingMode.valueAt(i).remove(listener);
+        }
+    }
+
+    @Override
+    public void onTaskAppeared(RunningTaskInfo taskInfo, SurfaceControl leash) {
+        mTasks.put(taskInfo.taskId, new Pair<>(taskInfo, leash));
+        ArrayList<TaskListener> listeners = mListenersByWindowingMode.get(
+                getWindowingMode(taskInfo));
+        if (listeners != null) {
+            for (int i = listeners.size() - 1; i >= 0; i--) {
+                listeners.get(i).onTaskAppeared(taskInfo, leash);
+            }
+        }
+    }
+
+    @Override
+    public void onTaskInfoChanged(RunningTaskInfo taskInfo) {
+        Pair<RunningTaskInfo, SurfaceControl> data = mTasks.get(taskInfo.taskId);
+        int winMode = getWindowingMode(taskInfo);
+        int prevWinMode = getWindowingMode(data.first);
+        if (prevWinMode != -1 && prevWinMode != winMode) {
+            // TODO: We currently send vanished/appeared as the task moves between win modes, but
+            //       we should consider adding a different mode-changed callback
+            ArrayList<TaskListener> listeners = mListenersByWindowingMode.get(prevWinMode);
+            if (listeners != null) {
+                for (int i = listeners.size() - 1; i >= 0; i--) {
+                    listeners.get(i).onTaskVanished(taskInfo);
+                }
+            }
+            listeners = mListenersByWindowingMode.get(winMode);
+            if (listeners != null) {
+                SurfaceControl leash = data.second;
+                for (int i = listeners.size() - 1; i >= 0; i--) {
+                    listeners.get(i).onTaskAppeared(taskInfo, leash);
+                }
+            }
+        } else {
+            ArrayList<TaskListener> listeners = mListenersByWindowingMode.get(winMode);
+            if (listeners != null) {
+                for (int i = listeners.size() - 1; i >= 0; i--) {
+                    listeners.get(i).onTaskInfoChanged(taskInfo);
+                }
+            }
+        }
+    }
+
+    @Override
+    public void onBackPressedOnTaskRoot(RunningTaskInfo taskInfo) {
+        ArrayList<TaskListener> listeners = mListenersByWindowingMode.get(
+                getWindowingMode(taskInfo));
+        if (listeners != null) {
+            for (int i = listeners.size() - 1; i >= 0; i--) {
+                listeners.get(i).onBackPressedOnTaskRoot(taskInfo);
+            }
+        }
+    }
+
+    @Override
+    public void onTaskVanished(RunningTaskInfo taskInfo) {
+        int prevWinMode = getWindowingMode(mTasks.get(taskInfo.taskId).first);
+        mTasks.remove(taskInfo.taskId);
+        ArrayList<TaskListener> listeners = mListenersByWindowingMode.get(prevWinMode);
+        if (listeners != null) {
+            for (int i = listeners.size() - 1; i >= 0; i--) {
+                listeners.get(i).onTaskVanished(taskInfo);
+            }
+        }
+    }
+
+    private int getWindowingMode(RunningTaskInfo taskInfo) {
+        return taskInfo.configuration.windowConfiguration.getWindowingMode();
+    }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayImeController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayImeController.java
index aeda2d9..283fd8d 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayImeController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayImeController.java
@@ -68,7 +68,7 @@
     private final SparseArray<PerDisplay> mImePerDisplay = new SparseArray<>();
     private final ArrayList<ImePositionProcessor> mPositionProcessors = new ArrayList<>();
 
-    protected DisplayImeController(IWindowManager wmService, DisplayController displayController,
+    public DisplayImeController(IWindowManager wmService, DisplayController displayController,
             Handler mainHandler, TransactionPool transactionPool) {
         mHandler = mainHandler;
         mWmService = wmService;
@@ -76,7 +76,8 @@
         mDisplayController = displayController;
     }
 
-    protected void startMonitorDisplays() {
+    /** Starts monitor displays changes and set insets controller for each displays. */
+    public void startMonitorDisplays() {
         mDisplayController.addDisplayWindowListener(this);
     }
 
@@ -493,29 +494,4 @@
         return IInputMethodManager.Stub.asInterface(
                 ServiceManager.getService(Context.INPUT_METHOD_SERVICE));
     }
-
-    /** Builds {@link DisplayImeController} instance. */
-    public static class Builder {
-        private IWindowManager mWmService;
-        private DisplayController mDisplayController;
-        private Handler mHandler;
-        private TransactionPool mTransactionPool;
-
-        public Builder(IWindowManager wmService, DisplayController displayController,
-                Handler handler, TransactionPool transactionPool) {
-            mWmService = wmService;
-            mDisplayController = displayController;
-            mHandler = handler;
-            mTransactionPool = transactionPool;
-        }
-
-        /** Builds and initializes {@link DisplayImeController} instance. */
-        public DisplayImeController build() {
-            DisplayImeController displayImeController = new DisplayImeController(mWmService,
-                    mDisplayController, mHandler, mTransactionPool);
-            // Separates startMonitorDisplays from constructor to prevent circular init issue.
-            displayImeController.startMonitorDisplays();
-            return displayImeController;
-        }
-    }
 }
diff --git a/libs/WindowManager/Shell/tests/README.md b/libs/WindowManager/Shell/tests/README.md
new file mode 100644
index 0000000..c19db76
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/README.md
@@ -0,0 +1,15 @@
+# WM Shell Test
+
+This contains all tests written for WM (WindowManager) Shell and it's currently
+divided into 3 categories
+
+- unittest, tests against individual functions, usually @SmallTest and do not
+  require UI automation nor real device to run
+- integration, this maybe a mix of functional and integration tests. Contains
+  tests verify the WM Shell as a whole, like talking to WM core. This usually
+  involves mocking the window manager service or even talking to the real one.
+  Due to this nature, test cases in this package is normally annotated as
+  @LargeTest and runs with UI automation on real device
+- flicker, similar to functional tests with its sole focus on flickerness. See
+  [WM Shell Flicker Test Package](http://cs/android/framework/base/libs/WindowManager/Shell/tests/flicker/)
+  for more details
diff --git a/libs/WindowManager/Shell/tests/flicker/Android.bp b/libs/WindowManager/Shell/tests/flicker/Android.bp
new file mode 100644
index 0000000..5879022
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/Android.bp
@@ -0,0 +1,34 @@
+//
+// 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.
+//
+
+android_test {
+    name: "WMShellFlickerTests",
+    srcs: ["src/**/*.java", "src/**/*.kt"],
+    manifest: "AndroidManifest.xml",
+    test_config: "AndroidTest.xml",
+    platform_apis: true,
+    certificate: "platform",
+    test_suites: ["device-tests"],
+    libs: ["android.test.runner"],
+    static_libs: [
+        "androidx.test.ext.junit",
+        "flickerlib",
+        "truth-prebuilt",
+        "app-helpers-core",
+        "launcher-helper-lib",
+        "launcher-aosp-tapl"
+    ],
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/AndroidManifest.xml b/libs/WindowManager/Shell/tests/flicker/AndroidManifest.xml
new file mode 100644
index 0000000..8b2f668
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/AndroidManifest.xml
@@ -0,0 +1,43 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2020 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+          package="com.android.wm.shell.flicker">
+
+    <uses-sdk android:minSdkVersion="29" android:targetSdkVersion="29"/>
+    <!-- Read and write traces from external storage -->
+    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
+    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
+    <!-- Write secure settings -->
+    <uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS" />
+    <!-- Capture screen contents -->
+    <uses-permission android:name="android.permission.ACCESS_SURFACE_FLINGER" />
+    <!-- Enable / Disable tracing !-->
+    <uses-permission android:name="android.permission.DUMP" />
+    <!-- Run layers trace -->
+    <uses-permission android:name="android.permission.HARDWARE_TEST"/>
+    <!-- Workaround grant runtime permission exception from b/152733071 -->
+    <uses-permission android:name="android.permission.PACKAGE_USAGE_STATS"/>
+    <uses-permission android:name="android.permission.READ_LOGS"/>
+    <application>
+        <uses-library android:name="android.test.runner"/>
+    </application>
+
+    <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+                     android:targetPackage="com.android.wm.shell.flicker"
+                     android:label="WindowManager Shell Flicker Tests">
+    </instrumentation>
+</manifest>
diff --git a/libs/WindowManager/Shell/tests/flicker/AndroidTest.xml b/libs/WindowManager/Shell/tests/flicker/AndroidTest.xml
new file mode 100644
index 0000000..526fc50
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/AndroidTest.xml
@@ -0,0 +1,40 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ * Copyright 2020 Google Inc. All Rights Reserved.
+ -->
+<configuration description="Runs WindowManager Shell Flicker Tests">
+    <option name="test-tag" value="FlickerTests" />
+    <target_preparer class="com.android.tradefed.targetprep.DeviceSetup">
+        <!-- keeps the screen on during tests -->
+        <option name="screen-always-on" value="on" />
+        <!-- prevents the phone from restarting -->
+        <option name="force-skip-system-props" value="true" />
+        <!-- set WM tracing verbose level to all -->
+        <option name="run-command" value="cmd window tracing level all" />
+        <!-- inform WM to log all transactions -->
+        <option name="run-command" value="cmd window tracing transaction" />
+        <!-- restart launcher to activate TAPL -->
+        <option name="run-command" value="setprop ro.test_harness 1 ; am force-stop com.google.android.apps.nexuslauncher" />
+    </target_preparer>
+    <target_preparer class="com.android.tradefed.targetprep.DeviceCleaner">
+        <!-- reboot the device to teardown any crashed tests -->
+        <option name="cleanup-action" value="REBOOT" />
+    </target_preparer>
+    <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
+        <option name="cleanup-apks" value="true"/>
+        <option name="test-file-name" value="WMShellFlickerTests.apk"/>
+        <option name="test-file-name" value="WMShellFlickerTestApp.apk" />
+    </target_preparer>
+    <test class="com.android.tradefed.testtype.AndroidJUnitTest">
+        <option name="package" value="com.android.wm.shell.flicker"/>
+        <option name="exclude-annotation" value="androidx.test.filters.FlakyTest" />
+        <option name="shell-timeout" value="6600s" />
+        <option name="test-timeout" value="6000s" />
+        <option name="hidden-api-checks" value="false" />
+    </test>
+    <metrics_collector class="com.android.tradefed.device.metric.FilePullerLogCollector">
+        <option name="directory-keys" value="/storage/emulated/0/Android/data/com.android.wm.shell.flicker/files" />
+        <option name="collect-on-run-ended-only" value="true" />
+        <option name="clean-up" value="true" />
+    </metrics_collector>
+</configuration>
diff --git a/libs/WindowManager/Shell/tests/flicker/README.md b/libs/WindowManager/Shell/tests/flicker/README.md
new file mode 100644
index 0000000..4502d49
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/README.md
@@ -0,0 +1,10 @@
+# WM Shell Flicker Test Package
+
+Please reference the following links
+
+- [Introduction to Flicker Test Library](http://cs/android/platform_testing/libraries/flicker/)
+- [Flicker Test in frameworks/base](http://cs/android/frameworks/base/tests/FlickerTests/)
+
+on what is Flicker Test and how to write a Flicker Test
+
+To run the Flicker Tests for WM Shell, simply run `atest WMShellFlickerTests`
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
new file mode 100644
index 0000000..4ff2bfc
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/CommonAssertions.kt
@@ -0,0 +1,153 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker
+
+import com.android.server.wm.flicker.dsl.EventLogAssertion
+import com.android.server.wm.flicker.dsl.LayersAssertion
+import com.android.server.wm.flicker.dsl.WmAssertion
+import com.android.server.wm.flicker.helpers.WindowUtils
+
+@JvmOverloads
+fun WmAssertion.statusBarWindowIsAlwaysVisible(
+    bugId: Int = 0,
+    enabled: Boolean = bugId == 0
+) {
+    all("statusBarWindowIsAlwaysVisible", enabled, bugId) {
+        this.showsAboveAppWindow(FlickerTestBase.STATUS_BAR_WINDOW_TITLE)
+    }
+}
+
+@JvmOverloads
+fun WmAssertion.navBarWindowIsAlwaysVisible(
+    bugId: Int = 0,
+    enabled: Boolean = bugId == 0
+) {
+    all("navBarWindowIsAlwaysVisible", enabled, bugId) {
+        this.showsAboveAppWindow(FlickerTestBase.NAVIGATION_BAR_WINDOW_TITLE)
+    }
+}
+
+@JvmOverloads
+fun LayersAssertion.noUncoveredRegions(
+    beginRotation: Int,
+    endRotation: Int = beginRotation,
+    allStates: Boolean = true,
+    bugId: Int = 0,
+    enabled: Boolean = bugId == 0
+) {
+    val startingBounds = WindowUtils.getDisplayBounds(beginRotation)
+    val endingBounds = WindowUtils.getDisplayBounds(endRotation)
+    if (allStates) {
+        all("noUncoveredRegions", enabled, bugId) {
+            if (startingBounds == endingBounds) {
+                this.coversAtLeastRegion(startingBounds)
+            } else {
+                this.coversAtLeastRegion(startingBounds)
+                        .then()
+                        .coversAtLeastRegion(endingBounds)
+            }
+        }
+    } else {
+        start("noUncoveredRegions_StartingPos") {
+            this.coversAtLeastRegion(startingBounds)
+        }
+        end("noUncoveredRegions_EndingPos") {
+            this.coversAtLeastRegion(endingBounds)
+        }
+    }
+}
+
+@JvmOverloads
+fun LayersAssertion.navBarLayerIsAlwaysVisible(
+    bugId: Int = 0,
+    enabled: Boolean = bugId == 0
+) {
+    all("navBarLayerIsAlwaysVisible", enabled, bugId) {
+        this.showsLayer(FlickerTestBase.NAVIGATION_BAR_WINDOW_TITLE)
+    }
+}
+
+@JvmOverloads
+fun LayersAssertion.statusBarLayerIsAlwaysVisible(
+    bugId: Int = 0,
+    enabled: Boolean = bugId == 0
+) {
+    all("statusBarLayerIsAlwaysVisible", enabled, bugId) {
+        this.showsLayer(FlickerTestBase.STATUS_BAR_WINDOW_TITLE)
+    }
+}
+
+@JvmOverloads
+fun LayersAssertion.navBarLayerRotatesAndScales(
+    beginRotation: Int,
+    endRotation: Int = beginRotation,
+    bugId: Int = 0,
+    enabled: Boolean = bugId == 0
+) {
+    val startingPos = WindowUtils.getNavigationBarPosition(beginRotation)
+    val endingPos = WindowUtils.getNavigationBarPosition(endRotation)
+
+    start("navBarLayerRotatesAndScales_StartingPos", enabled, bugId) {
+        this.hasVisibleRegion(FlickerTestBase.NAVIGATION_BAR_WINDOW_TITLE, startingPos)
+    }
+    end("navBarLayerRotatesAndScales_EndingPost", enabled, bugId) {
+        this.hasVisibleRegion(FlickerTestBase.NAVIGATION_BAR_WINDOW_TITLE, endingPos)
+    }
+
+    if (startingPos == endingPos) {
+        all("navBarLayerRotatesAndScales", enabled, bugId) {
+            this.hasVisibleRegion(FlickerTestBase.NAVIGATION_BAR_WINDOW_TITLE, startingPos)
+        }
+    }
+}
+
+@JvmOverloads
+fun LayersAssertion.statusBarLayerRotatesScales(
+    beginRotation: Int,
+    endRotation: Int = beginRotation,
+    bugId: Int = 0,
+    enabled: Boolean = bugId == 0
+) {
+    val startingPos = WindowUtils.getStatusBarPosition(beginRotation)
+    val endingPos = WindowUtils.getStatusBarPosition(endRotation)
+
+    start("statusBarLayerRotatesScales_StartingPos", enabled, bugId) {
+        this.hasVisibleRegion(FlickerTestBase.STATUS_BAR_WINDOW_TITLE, startingPos)
+    }
+    end("statusBarLayerRotatesScales_EndingPos", enabled, bugId) {
+        this.hasVisibleRegion(FlickerTestBase.STATUS_BAR_WINDOW_TITLE, endingPos)
+    }
+}
+
+fun EventLogAssertion.focusChanges(
+    vararg windows: String,
+    bugId: Int = 0,
+    enabled: Boolean = bugId == 0
+) {
+    all(enabled = enabled, bugId = bugId) {
+        this.focusChanges(windows)
+    }
+}
+
+fun EventLogAssertion.focusDoesNotChange(
+    bugId: Int = 0,
+    enabled: Boolean = bugId == 0
+) {
+    all(enabled = enabled, bugId = bugId) {
+        this.focusDoesNotChange()
+    }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/FlickerTestBase.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/FlickerTestBase.kt
new file mode 100644
index 0000000..99f824b
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/FlickerTestBase.kt
@@ -0,0 +1,130 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker
+
+import android.os.RemoteException
+import android.os.SystemClock
+import android.platform.helpers.IAppHelper
+import android.view.Surface
+import androidx.test.platform.app.InstrumentationRegistry
+import androidx.test.uiautomator.UiDevice
+import com.android.server.wm.flicker.Flicker
+
+/**
+ * Base class of all Flicker test that performs common functions for all flicker tests:
+ *
+ *
+ * - Caches transitions so that a transition is run once and the transition results are used by
+ * tests multiple times. This is needed for parameterized tests which call the BeforeClass methods
+ * multiple times.
+ * - Keeps track of all test artifacts and deletes ones which do not need to be reviewed.
+ * - Fails tests if results are not available for any test due to jank.
+ */
+abstract class FlickerTestBase {
+    val instrumentation by lazy {
+        InstrumentationRegistry.getInstrumentation()
+    }
+    val uiDevice by lazy {
+        UiDevice.getInstance(instrumentation)
+    }
+
+    /**
+     * Build a test tag for the test
+     * @param testName Name of the transition(s) being tested
+     * @param app App being launcher
+     * @param rotation Initial screen rotation
+     *
+     * @return test tag with pattern <NAME>__<APP>__<ROTATION>
+    </ROTATION></APP></NAME> */
+    protected fun buildTestTag(testName: String, app: IAppHelper, rotation: Int): String {
+        return buildTestTag(
+                testName, app, rotation, rotation, app2 = null, extraInfo = "")
+    }
+
+    /**
+     * Build a test tag for the test
+     * @param testName Name of the transition(s) being tested
+     * @param app App being launcher
+     * @param beginRotation Initial screen rotation
+     * @param endRotation End screen rotation (if any, otherwise use same as initial)
+     *
+     * @return test tag with pattern <NAME>__<APP>__<BEGIN_ROTATION>-<END_ROTATION>
+    </END_ROTATION></BEGIN_ROTATION></APP></NAME> */
+    protected fun buildTestTag(
+        testName: String,
+        app: IAppHelper,
+        beginRotation: Int,
+        endRotation: Int
+    ): String {
+        return buildTestTag(
+                testName, app, beginRotation, endRotation, app2 = null, extraInfo = "")
+    }
+
+    /**
+     * Build a test tag for the test
+     * @param testName Name of the transition(s) being tested
+     * @param app App being launcher
+     * @param app2 Second app being launched (if any)
+     * @param beginRotation Initial screen rotation
+     * @param endRotation End screen rotation (if any, otherwise use same as initial)
+     * @param extraInfo Additional information to append to the tag
+     *
+     * @return test tag with pattern <NAME>__<APP></APP>(S)>__<ROTATION></ROTATION>(S)>[__<EXTRA>]
+    </EXTRA></NAME> */
+    protected fun buildTestTag(
+        testName: String,
+        app: IAppHelper,
+        beginRotation: Int,
+        endRotation: Int,
+        app2: IAppHelper?,
+        extraInfo: String
+    ): String {
+        var testTag = "${testName}__${app.launcherName}"
+        if (app2 != null) {
+            testTag += "-${app2.launcherName}"
+        }
+        testTag += "__${Surface.rotationToString(beginRotation)}"
+        if (endRotation != beginRotation) {
+            testTag += "-${Surface.rotationToString(endRotation)}"
+        }
+        if (extraInfo.isNotEmpty()) {
+            testTag += "__$extraInfo"
+        }
+        return testTag
+    }
+
+    protected fun Flicker.setRotation(rotation: Int) {
+        try {
+            when (rotation) {
+                Surface.ROTATION_270 -> device.setOrientationLeft()
+                Surface.ROTATION_90 -> device.setOrientationRight()
+                Surface.ROTATION_0 -> device.setOrientationNatural()
+                else -> device.setOrientationNatural()
+            }
+            // Wait for animation to complete
+            SystemClock.sleep(1000)
+        } catch (e: RemoteException) {
+            throw RuntimeException(e)
+        }
+    }
+
+    companion object {
+        const val NAVIGATION_BAR_WINDOW_TITLE = "NavigationBar"
+        const val STATUS_BAR_WINDOW_TITLE = "StatusBar"
+        const val DOCKED_STACK_DIVIDER = "DockedStackDivider"
+    }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/NonRotationTestBase.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/NonRotationTestBase.kt
new file mode 100644
index 0000000..90334ae
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/NonRotationTestBase.kt
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker
+
+import android.view.Surface
+import org.junit.runners.Parameterized
+
+abstract class NonRotationTestBase(
+    protected val rotationName: String,
+    protected val rotation: Int
+) : FlickerTestBase() {
+    companion object {
+        const val SCREENSHOT_LAYER = "RotationLayer"
+
+        @Parameterized.Parameters(name = "{0}")
+        @JvmStatic
+        fun getParams(): Collection<Array<Any>> {
+            val supportedRotations = intArrayOf(Surface.ROTATION_0, Surface.ROTATION_90)
+            return supportedRotations.map { arrayOf(Surface.rotationToString(it), it) }
+        }
+    }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/FlickerAppHelper.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/FlickerAppHelper.kt
new file mode 100644
index 0000000..308a36e
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/FlickerAppHelper.kt
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker.helpers
+
+import android.app.Instrumentation
+import android.support.test.launcherhelper.ILauncherStrategy
+import com.android.server.wm.flicker.StandardAppHelper
+
+abstract class FlickerAppHelper(
+    instr: Instrumentation,
+    launcherName: String,
+    launcherStrategy: ILauncherStrategy
+) : StandardAppHelper(instr, sFlickerPackage, launcherName, launcherStrategy) {
+    companion object {
+        var sFlickerPackage = "com.android.wm.shell.flicker.testapp"
+    }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/PipAppHelper.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/PipAppHelper.kt
new file mode 100644
index 0000000..5391702
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/PipAppHelper.kt
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker.helpers
+
+import android.app.Instrumentation
+import android.support.test.launcherhelper.ILauncherStrategy
+import android.support.test.launcherhelper.LauncherStrategyFactory
+import androidx.test.uiautomator.By
+import androidx.test.uiautomator.UiDevice
+import com.android.server.wm.flicker.helpers.hasPipWindow
+import com.android.server.wm.flicker.helpers.closePipWindow
+import org.junit.Assert
+
+class PipAppHelper(
+    instr: Instrumentation,
+    launcherStrategy: ILauncherStrategy = LauncherStrategyFactory
+            .getInstance(instr)
+            .launcherStrategy
+) : FlickerAppHelper(instr, "PipApp", launcherStrategy) {
+    fun clickEnterPipButton(device: UiDevice) {
+        val enterPipButton = device.findObject(By.res(getPackage(), "enter_pip"))
+        Assert.assertNotNull("Pip button not found, this usually happens when the device " +
+                "was left in an unknown state (e.g. in split screen)", enterPipButton)
+        enterPipButton.click()
+        device.hasPipWindow()
+    }
+
+    fun closePipWindow(device: UiDevice) {
+        device.closePipWindow()
+    }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipTest.kt
new file mode 100644
index 0000000..4b04449
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipTest.kt
@@ -0,0 +1,121 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker.pip
+
+import android.view.Surface
+import androidx.test.filters.FlakyTest
+import androidx.test.filters.LargeTest
+import com.android.server.wm.flicker.dsl.flicker
+import com.android.server.wm.flicker.helpers.closePipWindow
+import com.android.server.wm.flicker.helpers.expandPipWindow
+import com.android.server.wm.flicker.helpers.hasPipWindow
+import com.android.server.wm.flicker.helpers.wakeUpAndGoToHomeScreen
+import com.android.wm.shell.flicker.navBarLayerIsAlwaysVisible
+import com.android.wm.shell.flicker.navBarLayerRotatesAndScales
+import com.android.wm.shell.flicker.navBarWindowIsAlwaysVisible
+import com.android.wm.shell.flicker.noUncoveredRegions
+import com.android.wm.shell.flicker.statusBarLayerIsAlwaysVisible
+import com.android.wm.shell.flicker.statusBarLayerRotatesScales
+import com.android.wm.shell.flicker.statusBarWindowIsAlwaysVisible
+import org.junit.FixMethodOrder
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.MethodSorters
+import org.junit.runners.Parameterized
+
+/**
+ * Test Pip launch.
+ * To run this test: `atest FlickerTests:PipToAppTest`
+ */
+@LargeTest
+@RunWith(Parameterized::class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+@FlakyTest(bugId = 152738416)
+class EnterPipTest(
+    rotationName: String,
+    rotation: Int
+) : PipTestBase(rotationName, rotation) {
+    @Test
+    fun test() {
+        flicker(instrumentation) {
+            withTag { buildTestTag("enterPip", testApp, rotation) }
+            repeat { 1 }
+            setup {
+                test {
+                    device.wakeUpAndGoToHomeScreen()
+                }
+                eachRun {
+                    device.pressHome()
+                    testApp.open()
+                    this.setRotation(rotation)
+                }
+            }
+            teardown {
+                eachRun {
+                    if (device.hasPipWindow()) {
+                        device.closePipWindow()
+                    }
+                    testApp.exit()
+                    this.setRotation(Surface.ROTATION_0)
+                }
+                test {
+                    if (device.hasPipWindow()) {
+                        device.closePipWindow()
+                    }
+                }
+            }
+            transitions {
+                testApp.clickEnterPipButton(device)
+                device.expandPipWindow()
+            }
+            assertions {
+                windowManagerTrace {
+                    navBarWindowIsAlwaysVisible()
+                    statusBarWindowIsAlwaysVisible()
+                    all("pipWindowBecomesVisible") {
+                        this.showsAppWindow(testApp.`package`)
+                                .then()
+                                .showsAppWindow(sPipWindowTitle)
+                    }
+                }
+
+                layersTrace {
+                    navBarLayerIsAlwaysVisible()
+                    statusBarLayerIsAlwaysVisible()
+                    noUncoveredRegions(rotation, Surface.ROTATION_0, allStates = false)
+                    navBarLayerRotatesAndScales(rotation, Surface.ROTATION_0)
+                    statusBarLayerRotatesScales(rotation, Surface.ROTATION_0)
+
+                    all("pipLayerBecomesVisible") {
+                        this.showsLayer(testApp.launcherName)
+                                .then()
+                                .showsLayer(sPipWindowTitle)
+                    }
+                }
+            }
+        }
+    }
+
+    companion object {
+        @Parameterized.Parameters(name = "{0}")
+        @JvmStatic
+        fun getParams(): Collection<Array<Any>> {
+            val supportedRotations = intArrayOf(Surface.ROTATION_0)
+            return supportedRotations.map { arrayOf(Surface.rotationToString(it), it) }
+        }
+    }
+}
diff --git a/libs/WindowManager/Shell/tests/src/com/android/wm/shell/WindowManagerShellTest.java b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipTestBase.kt
similarity index 60%
rename from libs/WindowManager/Shell/tests/src/com/android/wm/shell/WindowManagerShellTest.java
rename to libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipTestBase.kt
index f1ead3c..3822d69 100644
--- a/libs/WindowManager/Shell/tests/src/com/android/wm/shell/WindowManagerShellTest.java
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipTestBase.kt
@@ -14,25 +14,18 @@
  * limitations under the License.
  */
 
-package com.android.wm.shell;
+package com.android.wm.shell.flicker.pip
 
-import androidx.test.ext.junit.runners.AndroidJUnit4;
-import androidx.test.filters.SmallTest;
+import com.android.wm.shell.flicker.NonRotationTestBase
+import com.android.wm.shell.flicker.helpers.PipAppHelper
 
-import org.junit.Test;
-import org.junit.runner.RunWith;
+abstract class PipTestBase(
+    rotationName: String,
+    rotation: Int
+) : NonRotationTestBase(rotationName, rotation) {
+    protected val testApp = PipAppHelper(instrumentation)
 
-/**
- * Tests for the shell.
- */
-@SmallTest
-@RunWith(AndroidJUnit4.class)
-public class WindowManagerShellTest {
-
-    WindowManagerShell mShell;
-
-    @Test
-    public void testNothing() {
-        // Do nothing
+    companion object {
+        const val sPipWindowTitle = "PipMenuActivity"
     }
 }
diff --git a/libs/WindowManager/Shell/tests/flicker/test-apps/Android.bp b/libs/WindowManager/Shell/tests/flicker/test-apps/Android.bp
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/test-apps/Android.bp
diff --git a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/Android.bp b/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/Android.bp
new file mode 100644
index 0000000..d12b492
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/Android.bp
@@ -0,0 +1,20 @@
+// 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.
+
+android_test {
+    name: "WMShellFlickerTestApp",
+    srcs: ["**/*.java"],
+    sdk_version: "current",
+    test_suites: ["device-tests"],
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/AndroidManifest.xml b/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/AndroidManifest.xml
new file mode 100644
index 0000000..95dc1d4
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/AndroidManifest.xml
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2020 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+     package="com.android.wm.shell.flicker.testapp">
+
+    <uses-sdk android:minSdkVersion="29"
+         android:targetSdkVersion="29"/>
+    <application android:allowBackup="false"
+         android:supportsRtl="true">
+        <activity android:name=".PipActivity"
+             android:resizeableActivity="true"
+             android:supportsPictureInPicture="true"
+             android:configChanges="screenSize|smallestScreenSize|screenLayout|orientation"
+             android:taskAffinity="com.android.wm.shell.flicker.testapp.PipActivity"
+             android:label="PipApp"
+             android:exported="true">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
+            </intent-filter>
+        </activity>
+    </application>
+</manifest>
diff --git a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/res/layout/activity_pip.xml b/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/res/layout/activity_pip.xml
new file mode 100644
index 0000000..e1870d9
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/res/layout/activity_pip.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright 2020 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+     http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<LinearLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:background="@android:color/holo_blue_bright">
+    <Button android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:id="@+id/enter_pip"
+            android:text="Enter PIP"/>
+</LinearLayout>
diff --git a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/src/com/android/wm/shell/flicker/testapp/PipActivity.java b/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/src/com/android/wm/shell/flicker/testapp/PipActivity.java
new file mode 100644
index 0000000..3052816
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/src/com/android/wm/shell/flicker/testapp/PipActivity.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker.testapp;
+
+import android.app.Activity;
+import android.app.PictureInPictureParams;
+import android.graphics.Rect;
+import android.os.Bundle;
+import android.util.Rational;
+import android.view.WindowManager;
+import android.widget.Button;
+
+public class PipActivity extends Activity {
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        WindowManager.LayoutParams p = getWindow().getAttributes();
+        p.layoutInDisplayCutoutMode = WindowManager.LayoutParams
+                .LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES;
+        getWindow().setAttributes(p);
+        setContentView(R.layout.activity_pip);
+        Button enterPip = (Button) findViewById(R.id.enter_pip);
+
+        PictureInPictureParams params = new PictureInPictureParams.Builder()
+                .setAspectRatio(new Rational(1, 1))
+                .setSourceRectHint(new Rect(0, 0, 100, 100))
+                .build();
+
+        enterPip.setOnClickListener((v) -> enterPictureInPictureMode(params));
+    }
+}
diff --git a/libs/WindowManager/Shell/tests/Android.bp b/libs/WindowManager/Shell/tests/unittest/Android.bp
similarity index 96%
rename from libs/WindowManager/Shell/tests/Android.bp
rename to libs/WindowManager/Shell/tests/unittest/Android.bp
index 9868879..692e2fa 100644
--- a/libs/WindowManager/Shell/tests/Android.bp
+++ b/libs/WindowManager/Shell/tests/unittest/Android.bp
@@ -13,7 +13,7 @@
 // limitations under the License.
 
 android_test {
-    name: "WindowManagerShellTests",
+    name: "WMShellUnitTests",
 
     srcs: ["**/*.java"],
 
diff --git a/libs/WindowManager/Shell/tests/AndroidManifest.xml b/libs/WindowManager/Shell/tests/unittest/AndroidManifest.xml
similarity index 100%
rename from libs/WindowManager/Shell/tests/AndroidManifest.xml
rename to libs/WindowManager/Shell/tests/unittest/AndroidManifest.xml
diff --git a/libs/WindowManager/Shell/tests/AndroidTest.xml b/libs/WindowManager/Shell/tests/unittest/AndroidTest.xml
similarity index 90%
rename from libs/WindowManager/Shell/tests/AndroidTest.xml
rename to libs/WindowManager/Shell/tests/unittest/AndroidTest.xml
index 4dce4db..21ed2c0 100644
--- a/libs/WindowManager/Shell/tests/AndroidTest.xml
+++ b/libs/WindowManager/Shell/tests/unittest/AndroidTest.xml
@@ -17,12 +17,12 @@
     <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
         <option name="cleanup-apks" value="true" />
         <option name="install-arg" value="-t" />
-        <option name="test-file-name" value="WindowManagerShellTests.apk" />
+        <option name="test-file-name" value="WMShellUnitTests.apk" />
     </target_preparer>
 
     <option name="test-suite-tag" value="apct" />
     <option name="test-suite-tag" value="framework-base-presubmit" />
-    <option name="test-tag" value="WindowManagerShellTests" />
+    <option name="test-tag" value="WMShellUnitTests" />
     <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
         <option name="package" value="com.android.wm.shell.tests" />
         <option name="runner" value="androidx.test.runner.AndroidJUnitRunner" />
diff --git a/libs/WindowManager/Shell/tests/res/values/config.xml b/libs/WindowManager/Shell/tests/unittest/res/values/config.xml
similarity index 100%
rename from libs/WindowManager/Shell/tests/res/values/config.xml
rename to libs/WindowManager/Shell/tests/unittest/res/values/config.xml
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/ShellTaskOrganizerTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/ShellTaskOrganizerTests.java
new file mode 100644
index 0000000..10672c8
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/ShellTaskOrganizerTests.java
@@ -0,0 +1,121 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell;
+
+import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
+import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
+
+import static org.junit.Assert.assertTrue;
+
+import android.app.ActivityManager.RunningTaskInfo;
+import android.content.res.Configuration;
+import android.view.SurfaceControl;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.ArrayList;
+
+/**
+ * Tests for the shell task organizer.
+ */
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class ShellTaskOrganizerTests {
+
+    ShellTaskOrganizer mOrganizer;
+
+    private class TrackingTaskListener implements ShellTaskOrganizer.TaskListener {
+        final ArrayList<RunningTaskInfo> appeared = new ArrayList<>();
+        final ArrayList<RunningTaskInfo> vanished = new ArrayList<>();
+        final ArrayList<RunningTaskInfo> infoChanged = new ArrayList<>();
+
+        @Override
+        public void onTaskAppeared(RunningTaskInfo taskInfo, SurfaceControl leash) {
+            appeared.add(taskInfo);
+        }
+
+        @Override
+        public void onTaskInfoChanged(RunningTaskInfo taskInfo) {
+            infoChanged.add(taskInfo);
+        }
+
+        @Override
+        public void onTaskVanished(RunningTaskInfo taskInfo) {
+            vanished.add(taskInfo);
+        }
+
+        @Override
+        public void onBackPressedOnTaskRoot(RunningTaskInfo taskInfo) {
+            // Not currently used
+        }
+    }
+
+    @Before
+    public void setUp() {
+        mOrganizer = new ShellTaskOrganizer();
+    }
+
+    @Test
+    public void testAppearedVanished() {
+        RunningTaskInfo taskInfo = createTaskInfo(WINDOWING_MODE_MULTI_WINDOW);
+        TrackingTaskListener listener = new TrackingTaskListener();
+        mOrganizer.addListener(listener, WINDOWING_MODE_MULTI_WINDOW);
+        mOrganizer.onTaskAppeared(taskInfo, null);
+        assertTrue(listener.appeared.contains(taskInfo));
+
+        mOrganizer.onTaskVanished(taskInfo);
+        assertTrue(listener.vanished.contains(taskInfo));
+    }
+
+    @Test
+    public void testAddListenerExistingTasks() {
+        RunningTaskInfo taskInfo = createTaskInfo(WINDOWING_MODE_MULTI_WINDOW);
+        mOrganizer.onTaskAppeared(taskInfo, null);
+
+        TrackingTaskListener listener = new TrackingTaskListener();
+        mOrganizer.addListener(listener, WINDOWING_MODE_MULTI_WINDOW);
+        assertTrue(listener.appeared.contains(taskInfo));
+    }
+
+    @Test
+    public void testWindowingModeChange() {
+        RunningTaskInfo taskInfo = createTaskInfo(WINDOWING_MODE_MULTI_WINDOW);
+        TrackingTaskListener mwListener = new TrackingTaskListener();
+        TrackingTaskListener pipListener = new TrackingTaskListener();
+        mOrganizer.addListener(mwListener, WINDOWING_MODE_MULTI_WINDOW);
+        mOrganizer.addListener(pipListener, WINDOWING_MODE_PINNED);
+        mOrganizer.onTaskAppeared(taskInfo, null);
+        assertTrue(mwListener.appeared.contains(taskInfo));
+        assertTrue(pipListener.appeared.isEmpty());
+
+        taskInfo = createTaskInfo(WINDOWING_MODE_PINNED);
+        mOrganizer.onTaskInfoChanged(taskInfo);
+        assertTrue(mwListener.vanished.contains(taskInfo));
+        assertTrue(pipListener.appeared.contains(taskInfo));
+    }
+
+    private RunningTaskInfo createTaskInfo(int windowingMode) {
+        RunningTaskInfo taskInfo = new RunningTaskInfo();
+        taskInfo.configuration.windowConfiguration.setWindowingMode(windowingMode);
+        return taskInfo;
+    }
+}
diff --git a/libs/WindowManager/Shell/tests/src/com/android/wm/shell/common/DisplayLayoutTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/DisplayLayoutTest.java
similarity index 100%
rename from libs/WindowManager/Shell/tests/src/com/android/wm/shell/common/DisplayLayoutTest.java
rename to libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/DisplayLayoutTest.java
diff --git a/libs/androidfw/ResourceTypes.cpp b/libs/androidfw/ResourceTypes.cpp
index 4d7e5df..dfb4009 100644
--- a/libs/androidfw/ResourceTypes.cpp
+++ b/libs/androidfw/ResourceTypes.cpp
@@ -37,7 +37,6 @@
 #include <androidfw/TypeWrappers.h>
 #include <cutils/atomic.h>
 #include <utils/ByteOrder.h>
-#include <utils/Debug.h>
 #include <utils/Log.h>
 #include <utils/String16.h>
 #include <utils/String8.h>
diff --git a/libs/androidfw/include/androidfw/ResourceTypes.h b/libs/androidfw/include/androidfw/ResourceTypes.h
index e351a46..e10a7f3 100644
--- a/libs/androidfw/include/androidfw/ResourceTypes.h
+++ b/libs/androidfw/include/androidfw/ResourceTypes.h
@@ -1717,6 +1717,10 @@
     // The overlay must be signed with the same signature as the actor declared for the target
     // resource
     ACTOR_SIGNATURE = 0x00000080,
+
+    // The overlay must be signed with the same signature as the reference package declared
+    // in the SystemConfig
+    CONFIG_SIGNATURE = 0x00000100,
   };
 
   using PolicyBitmask = uint32_t;
diff --git a/libs/hostgraphics/Android.bp b/libs/hostgraphics/Android.bp
index e713b98..9d83e49 100644
--- a/libs/hostgraphics/Android.bp
+++ b/libs/hostgraphics/Android.bp
@@ -5,6 +5,10 @@
         "-Wno-unused-parameter",
     ],
 
+    static_libs: [
+        "libbase",
+    ],
+
     srcs: [
         ":libui_host_common",
         "Fence.cpp",
@@ -28,4 +32,4 @@
             enabled: true,
         }
     },
-}
\ No newline at end of file
+}
diff --git a/libs/hwui/Android.bp b/libs/hwui/Android.bp
index 9ae5ad9..90d2537 100644
--- a/libs/hwui/Android.bp
+++ b/libs/hwui/Android.bp
@@ -462,6 +462,14 @@
         "RenderNode.cpp",
         "RenderProperties.cpp",
         "RootRenderNode.cpp",
+        "shader/Shader.cpp",
+        "shader/BitmapShader.cpp",
+        "shader/BlurShader.cpp",
+        "shader/ComposeShader.cpp",
+        "shader/LinearGradientShader.cpp",
+        "shader/RadialGradientShader.cpp",
+        "shader/RuntimeShader.cpp",
+        "shader/SweepGradientShader.cpp",
         "SkiaCanvas.cpp",
         "VectorDrawable.cpp",
     ],
diff --git a/libs/hwui/HardwareBitmapUploader.cpp b/libs/hwui/HardwareBitmapUploader.cpp
index 8724442..ab9b8b5 100644
--- a/libs/hwui/HardwareBitmapUploader.cpp
+++ b/libs/hwui/HardwareBitmapUploader.cpp
@@ -56,12 +56,6 @@
 public:
     virtual ~AHBUploader() {}
 
-    // Called to start creation of the Vulkan and EGL contexts on another thread before we actually
-    // need to do an upload.
-    void initialize() {
-        onInitialize();
-    }
-
     void destroy() {
         std::lock_guard _lock{mLock};
         LOG_ALWAYS_FATAL_IF(mPendingUploads, "terminate called while uploads in progress");
@@ -91,7 +85,6 @@
     sp<ThreadBase> mUploadThread = nullptr;
 
 private:
-    virtual void onInitialize() = 0;
     virtual void onIdle() = 0;
     virtual void onDestroy() = 0;
 
@@ -141,7 +134,6 @@
 
 class EGLUploader : public AHBUploader {
 private:
-    void onInitialize() override {}
     void onDestroy() override {
         mEglManager.destroy();
     }
@@ -231,62 +223,67 @@
 
 class VkUploader : public AHBUploader {
 private:
-    void onInitialize() override {
-        std::lock_guard _lock{mLock};
-        if (!mUploadThread) {
-            mUploadThread = new ThreadBase{};
-        }
-        if (!mUploadThread->isRunning()) {
-            mUploadThread->start("GrallocUploadThread");
-        }
-
-        mUploadThread->queue().post([this]() {
-            std::lock_guard _lock{mVkLock};
-            if (!mVulkanManager.hasVkContext()) {
-                mVulkanManager.initialize();
-            }
-        });
-    }
     void onDestroy() override {
+        std::lock_guard _lock{mVkLock};
         mGrContext.reset();
-        mVulkanManager.destroy();
+        mVulkanManagerStrong.clear();
     }
     void onIdle() override {
-        mGrContext.reset();
+        onDestroy();
     }
 
-    void onBeginUpload() override {
-        {
-            std::lock_guard _lock{mVkLock};
-            if (!mVulkanManager.hasVkContext()) {
-                LOG_ALWAYS_FATAL_IF(mGrContext,
-                    "GrContext exists with no VulkanManager for vulkan uploads");
-                mUploadThread->queue().runSync([this]() {
-                    mVulkanManager.initialize();
-                });
-            }
-        }
-        if (!mGrContext) {
-            GrContextOptions options;
-            mGrContext = mVulkanManager.createContext(options);
-            LOG_ALWAYS_FATAL_IF(!mGrContext, "failed to create GrContext for vulkan uploads");
-            this->postIdleTimeoutCheck();
-        }
-    }
+    void onBeginUpload() override {}
 
     bool onUploadHardwareBitmap(const SkBitmap& bitmap, const FormatInfo& format,
                                 AHardwareBuffer* ahb) override {
-        ATRACE_CALL();
+        bool uploadSucceeded = false;
+        mUploadThread->queue().runSync([this, &uploadSucceeded, bitmap, ahb]() {
+          ATRACE_CALL();
+          std::lock_guard _lock{mVkLock};
 
-        std::lock_guard _lock{mLock};
+          renderthread::VulkanManager* vkManager = getVulkanManager();
+          if (!vkManager->hasVkContext()) {
+              LOG_ALWAYS_FATAL_IF(mGrContext,
+                                  "GrContext exists with no VulkanManager for vulkan uploads");
+              vkManager->initialize();
+          }
 
-        sk_sp<SkImage> image =
-                SkImage::MakeFromAHardwareBufferWithData(mGrContext.get(), bitmap.pixmap(), ahb);
-        return (image.get() != nullptr);
+          if (!mGrContext) {
+              GrContextOptions options;
+              mGrContext = vkManager->createContext(options,
+                      renderthread::VulkanManager::ContextType::kUploadThread);
+              LOG_ALWAYS_FATAL_IF(!mGrContext, "failed to create GrContext for vulkan uploads");
+              this->postIdleTimeoutCheck();
+          }
+
+          sk_sp<SkImage> image =
+              SkImage::MakeFromAHardwareBufferWithData(mGrContext.get(), bitmap.pixmap(), ahb);
+          mGrContext->submit(true);
+
+          uploadSucceeded = (image.get() != nullptr);
+        });
+        return uploadSucceeded;
+    }
+
+    /* must be called on the upload thread after the vkLock has been acquired  */
+    renderthread::VulkanManager* getVulkanManager() {
+        if (!mVulkanManagerStrong) {
+            mVulkanManagerStrong = mVulkanManagerWeak.promote();
+
+            // create a new manager if we couldn't promote the weak ref
+            if (!mVulkanManagerStrong) {
+                mVulkanManagerStrong = renderthread::VulkanManager::getInstance();
+                mGrContext.reset();
+                mVulkanManagerWeak = mVulkanManagerStrong;
+            }
+        }
+
+        return mVulkanManagerStrong.get();
     }
 
     sk_sp<GrDirectContext> mGrContext;
-    renderthread::VulkanManager mVulkanManager;
+    sp<renderthread::VulkanManager> mVulkanManagerStrong;
+    wp<renderthread::VulkanManager> mVulkanManagerWeak;
     std::mutex mVkLock;
 };
 
@@ -428,7 +425,6 @@
     bool usingGL = uirenderer::Properties::getRenderPipelineType() ==
             uirenderer::RenderPipelineType::SkiaGL;
     createUploader(usingGL);
-    sUploader->initialize();
 }
 
 void HardwareBitmapUploader::terminate() {
diff --git a/libs/hwui/OWNERS b/libs/hwui/OWNERS
index 936ba5c..c232d13 100644
--- a/libs/hwui/OWNERS
+++ b/libs/hwui/OWNERS
@@ -1,6 +1,7 @@
+alecmouri@google.com
+djsollen@google.com
 jreck@google.com
 njawad@google.com
-djsollen@google.com
-stani@google.com
-scroggo@google.com
 reed@google.com
+scroggo@google.com
+stani@google.com
diff --git a/libs/hwui/Properties.cpp b/libs/hwui/Properties.cpp
index 446e81e..ba44d05 100644
--- a/libs/hwui/Properties.cpp
+++ b/libs/hwui/Properties.cpp
@@ -78,6 +78,7 @@
 
 int Properties::contextPriority = 0;
 int Properties::defaultRenderAhead = -1;
+float Properties::defaultSdrWhitePoint = 200.f;
 
 bool Properties::load() {
     bool prevDebugLayersUpdates = debugLayersUpdates;
diff --git a/libs/hwui/Properties.h b/libs/hwui/Properties.h
index c8f6b3b..85a0f4a 100644
--- a/libs/hwui/Properties.h
+++ b/libs/hwui/Properties.h
@@ -249,6 +249,8 @@
 
     static int defaultRenderAhead;
 
+    static float defaultSdrWhitePoint;
+
 private:
     static ProfileType sProfileType;
     static bool sDisableProfileBars;
diff --git a/libs/hwui/SkiaCanvas.cpp b/libs/hwui/SkiaCanvas.cpp
index 242b8b0..cfba5d4 100644
--- a/libs/hwui/SkiaCanvas.cpp
+++ b/libs/hwui/SkiaCanvas.cpp
@@ -42,6 +42,8 @@
 #include <SkTextBlob.h>
 #include <SkVertices.h>
 
+#include <shader/BitmapShader.h>
+
 #include <memory>
 #include <optional>
 #include <utility>
@@ -49,6 +51,7 @@
 namespace android {
 
 using uirenderer::PaintUtils;
+using uirenderer::BitmapShader;
 
 Canvas* Canvas::create_canvas(const SkBitmap& bitmap) {
     return new SkiaCanvas(bitmap);
@@ -681,7 +684,9 @@
     if (paint) {
         pnt = *paint;
     }
-    pnt.setShader(bitmap.makeImage()->makeShader());
+
+    pnt.setShader(sk_ref_sp(new BitmapShader(
+            bitmap.makeImage(), SkTileMode::kClamp, SkTileMode::kClamp, nullptr)));
     auto v = builder.detach();
     apply_looper(&pnt, [&](const SkPaint& p) {
         mCanvas->drawVertices(v, SkBlendMode::kModulate, p);
diff --git a/libs/hwui/VectorDrawable.cpp b/libs/hwui/VectorDrawable.cpp
index cd908354..0f566e4 100644
--- a/libs/hwui/VectorDrawable.cpp
+++ b/libs/hwui/VectorDrawable.cpp
@@ -23,7 +23,6 @@
 #include "PathParser.h"
 #include "SkColorFilter.h"
 #include "SkImageInfo.h"
-#include "SkShader.h"
 #include "hwui/Paint.h"
 
 #ifdef __ANDROID__
@@ -159,10 +158,10 @@
 
     // Draw path's fill, if fill color or gradient is valid
     bool needsFill = false;
-    SkPaint paint;
+    Paint paint;
     if (properties.getFillGradient() != nullptr) {
         paint.setColor(applyAlpha(SK_ColorBLACK, properties.getFillAlpha()));
-        paint.setShader(sk_sp<SkShader>(SkSafeRef(properties.getFillGradient())));
+        paint.setShader(sk_sp<Shader>(SkSafeRef(properties.getFillGradient())));
         needsFill = true;
     } else if (properties.getFillColor() != SK_ColorTRANSPARENT) {
         paint.setColor(applyAlpha(properties.getFillColor(), properties.getFillAlpha()));
@@ -179,7 +178,7 @@
     bool needsStroke = false;
     if (properties.getStrokeGradient() != nullptr) {
         paint.setColor(applyAlpha(SK_ColorBLACK, properties.getStrokeAlpha()));
-        paint.setShader(sk_sp<SkShader>(SkSafeRef(properties.getStrokeGradient())));
+        paint.setShader(sk_sp<Shader>(SkSafeRef(properties.getStrokeGradient())));
         needsStroke = true;
     } else if (properties.getStrokeColor() != SK_ColorTRANSPARENT) {
         paint.setColor(applyAlpha(properties.getStrokeColor(), properties.getStrokeAlpha()));
diff --git a/libs/hwui/VectorDrawable.h b/libs/hwui/VectorDrawable.h
index ac7d41e..d4086f1 100644
--- a/libs/hwui/VectorDrawable.h
+++ b/libs/hwui/VectorDrawable.h
@@ -31,8 +31,8 @@
 #include <SkPath.h>
 #include <SkPathMeasure.h>
 #include <SkRect.h>
-#include <SkShader.h>
 #include <SkSurface.h>
+#include <shader/Shader.h>
 
 #include <cutils/compiler.h>
 #include <stddef.h>
@@ -227,20 +227,20 @@
             strokeGradient = prop.strokeGradient;
             onPropertyChanged();
         }
-        void setFillGradient(SkShader* gradient) {
+        void setFillGradient(Shader* gradient) {
             if (fillGradient.get() != gradient) {
                 fillGradient = sk_ref_sp(gradient);
                 onPropertyChanged();
             }
         }
-        void setStrokeGradient(SkShader* gradient) {
+        void setStrokeGradient(Shader* gradient) {
             if (strokeGradient.get() != gradient) {
                 strokeGradient = sk_ref_sp(gradient);
                 onPropertyChanged();
             }
         }
-        SkShader* getFillGradient() const { return fillGradient.get(); }
-        SkShader* getStrokeGradient() const { return strokeGradient.get(); }
+        Shader* getFillGradient() const { return fillGradient.get(); }
+        Shader* getStrokeGradient() const { return strokeGradient.get(); }
         float getStrokeWidth() const { return mPrimitiveFields.strokeWidth; }
         void setStrokeWidth(float strokeWidth) {
             VD_SET_PRIMITIVE_FIELD_AND_NOTIFY(strokeWidth, strokeWidth);
@@ -320,8 +320,8 @@
             count,
         };
         PrimitiveFields mPrimitiveFields;
-        sk_sp<SkShader> fillGradient;
-        sk_sp<SkShader> strokeGradient;
+        sk_sp<Shader> fillGradient;
+        sk_sp<Shader> strokeGradient;
     };
 
     // Called from UI thread
diff --git a/libs/hwui/apex/jni_runtime.cpp b/libs/hwui/apex/jni_runtime.cpp
index b933813..12e2e81 100644
--- a/libs/hwui/apex/jni_runtime.cpp
+++ b/libs/hwui/apex/jni_runtime.cpp
@@ -16,14 +16,14 @@
 
 #include "android/graphics/jni_runtime.h"
 
-#include <android/log.h>
-#include <nativehelper/JNIHelp.h>
-#include <sys/cdefs.h>
-
 #include <EGL/egl.h>
 #include <GraphicsJNI.h>
 #include <Properties.h>
 #include <SkGraphics.h>
+#include <android/log.h>
+#include <nativehelper/JNIHelp.h>
+#include <sys/cdefs.h>
+#include <vulkan/vulkan.h>
 
 #undef LOG_TAG
 #define LOG_TAG "AndroidGraphicsJNI"
@@ -172,6 +172,11 @@
 
 void zygote_preload_graphics() {
     if (Properties::peekRenderPipelineType() == RenderPipelineType::SkiaGL) {
+        // Preload GL driver if HWUI renders with GL backend.
         eglGetDisplay(EGL_DEFAULT_DISPLAY);
+    } else {
+        // Preload Vulkan driver if HWUI renders with Vulkan backend.
+        uint32_t apiVersion;
+        vkEnumerateInstanceVersion(&apiVersion);
     }
-}
\ No newline at end of file
+}
diff --git a/libs/hwui/hwui/Canvas.cpp b/libs/hwui/hwui/Canvas.cpp
index c138a32..2a377bb 100644
--- a/libs/hwui/hwui/Canvas.cpp
+++ b/libs/hwui/hwui/Canvas.cpp
@@ -73,7 +73,7 @@
 
 static void simplifyPaint(int color, Paint* paint) {
     paint->setColor(color);
-    paint->setShader(nullptr);
+    paint->setShader((sk_sp<uirenderer::Shader>)nullptr);
     paint->setColorFilter(nullptr);
     paint->setLooper(nullptr);
     paint->setStrokeWidth(4 + 0.04 * paint->getSkFont().getSize());
diff --git a/libs/hwui/hwui/Paint.h b/libs/hwui/hwui/Paint.h
index e75e9e7..0bb689c 100644
--- a/libs/hwui/hwui/Paint.h
+++ b/libs/hwui/hwui/Paint.h
@@ -30,6 +30,8 @@
 #include <minikin/FamilyVariant.h>
 #include <minikin/Hyphenator.h>
 
+#include <shader/Shader.h>
+
 namespace android {
 
 class Paint : public SkPaint {
@@ -149,8 +151,14 @@
     // The only respected flags are : [ antialias, dither, filterBitmap ]
     static uint32_t GetSkPaintJavaFlags(const SkPaint&);
     static void SetSkPaintJavaFlags(SkPaint*, uint32_t flags);
+
+    void setShader(sk_sp<uirenderer::Shader> shader);
  
 private:
+
+    using SkPaint::setShader;
+    using SkPaint::setImageFilter;
+
     SkFont mFont;
     sk_sp<SkDrawLooper> mLooper;
 
@@ -169,6 +177,7 @@
     bool mStrikeThru = false;
     bool mUnderline = false;
     bool mDevKern = false;
+    sk_sp<uirenderer::Shader> mShader;
 };
 
 }  // namespace android
diff --git a/libs/hwui/hwui/PaintImpl.cpp b/libs/hwui/hwui/PaintImpl.cpp
index fa2674f..21f60fd 100644
--- a/libs/hwui/hwui/PaintImpl.cpp
+++ b/libs/hwui/hwui/PaintImpl.cpp
@@ -24,7 +24,8 @@
         , mWordSpacing(0)
         , mFontFeatureSettings()
         , mMinikinLocaleListId(0)
-        , mFamilyVariant(minikin::FamilyVariant::DEFAULT) {
+        , mFamilyVariant(minikin::FamilyVariant::DEFAULT)
+        , mShader(nullptr) {
     // SkPaint::antialiasing defaults to false, but
     // SkFont::edging defaults to kAntiAlias. To keep them
     // insync, we manually set the font to kAilas.
@@ -45,7 +46,8 @@
         , mAlign(paint.mAlign)
         , mStrikeThru(paint.mStrikeThru)
         , mUnderline(paint.mUnderline)
-        , mDevKern(paint.mDevKern) {}
+        , mDevKern(paint.mDevKern)
+        , mShader(paint.mShader){}
 
 
 Paint::~Paint() {}
@@ -65,9 +67,30 @@
     mStrikeThru = other.mStrikeThru;
     mUnderline = other.mUnderline;
     mDevKern = other.mDevKern;
+    mShader = other.mShader;
     return *this;
 }
 
+void Paint::setShader(sk_sp<uirenderer::Shader> shader) {
+    if (shader) {
+        // If there is an SkShader compatible shader, apply it
+        sk_sp<SkShader> skShader = shader->asSkShader();
+        if (skShader.get()) {
+            SkPaint::setShader(skShader);
+            SkPaint::setImageFilter(nullptr);
+        } else {
+            // ... otherwise the specified shader can only be represented as an ImageFilter
+            SkPaint::setShader(nullptr);
+            SkPaint::setImageFilter(shader->asSkImageFilter());
+        }
+    } else {
+        // No shader is provided at all, clear out both the SkShader and SkImageFilter slots
+        SkPaint::setShader(nullptr);
+        SkPaint::setImageFilter(nullptr);
+    }
+    mShader = shader;
+}
+
 bool operator==(const Paint& a, const Paint& b) {
     return static_cast<const SkPaint&>(a) == static_cast<const SkPaint&>(b) &&
            a.mFont == b.mFont &&
diff --git a/libs/hwui/jni/Paint.cpp b/libs/hwui/jni/Paint.cpp
index df8635a..554674a 100644
--- a/libs/hwui/jni/Paint.cpp
+++ b/libs/hwui/jni/Paint.cpp
@@ -47,6 +47,7 @@
 #include <minikin/LocaleList.h>
 #include <minikin/Measurement.h>
 #include <minikin/MinikinPaint.h>
+#include <shader/Shader.h>
 #include <unicode/utf16.h>
 
 #include <cassert>
@@ -54,6 +55,8 @@
 #include <memory>
 #include <vector>
 
+using namespace android::uirenderer;
+
 namespace android {
 
 struct JMetricsID {
@@ -782,11 +785,10 @@
         return obj->getFillPath(*src, dst) ? JNI_TRUE : JNI_FALSE;
     }
 
-    static jlong setShader(CRITICAL_JNI_PARAMS_COMMA jlong objHandle, jlong shaderHandle) {
-        Paint* obj = reinterpret_cast<Paint*>(objHandle);
-        SkShader* shader = reinterpret_cast<SkShader*>(shaderHandle);
-        obj->setShader(sk_ref_sp(shader));
-        return reinterpret_cast<jlong>(obj->getShader());
+    static void setShader(CRITICAL_JNI_PARAMS_COMMA jlong objHandle, jlong shaderHandle) {
+        auto* paint = reinterpret_cast<Paint*>(objHandle);
+        auto* shader = reinterpret_cast<Shader*>(shaderHandle);
+        paint->setShader(sk_ref_sp(shader));
     }
 
     static jlong setColorFilter(CRITICAL_JNI_PARAMS_COMMA jlong objHandle, jlong filterHandle) {
@@ -1097,7 +1099,7 @@
     {"nGetStrokeJoin","(J)I", (void*) PaintGlue::getStrokeJoin},
     {"nSetStrokeJoin","(JI)V", (void*) PaintGlue::setStrokeJoin},
     {"nGetFillPath","(JJJ)Z", (void*) PaintGlue::getFillPath},
-    {"nSetShader","(JJ)J", (void*) PaintGlue::setShader},
+    {"nSetShader","(JJ)V", (void*) PaintGlue::setShader},
     {"nSetColorFilter","(JJ)J", (void*) PaintGlue::setColorFilter},
     {"nSetXfermode","(JI)V", (void*) PaintGlue::setXfermode},
     {"nSetPathEffect","(JJ)J", (void*) PaintGlue::setPathEffect},
diff --git a/libs/hwui/jni/Shader.cpp b/libs/hwui/jni/Shader.cpp
index 0f68376..7cb7723 100644
--- a/libs/hwui/jni/Shader.cpp
+++ b/libs/hwui/jni/Shader.cpp
@@ -5,6 +5,14 @@
 #include "SkShader.h"
 #include "SkBlendMode.h"
 #include "include/effects/SkRuntimeEffect.h"
+#include "shader/Shader.h"
+#include "shader/BitmapShader.h"
+#include "shader/BlurShader.h"
+#include "shader/ComposeShader.h"
+#include "shader/LinearGradientShader.h"
+#include "shader/RadialGradientShader.h"
+#include "shader/RuntimeShader.h"
+#include "shader/SweepGradientShader.h"
 
 #include <vector>
 
@@ -50,7 +58,7 @@
 
 ///////////////////////////////////////////////////////////////////////////////////////////////
 
-static void Shader_safeUnref(SkShader* shader) {
+static void Shader_safeUnref(Shader* shader) {
     SkSafeUnref(shader);
 }
 
@@ -74,15 +82,15 @@
         SkBitmap bitmap;
         image = SkMakeImageFromRasterBitmap(bitmap, kNever_SkCopyPixelsMode);
     }
-    sk_sp<SkShader> shader = image->makeShader(
-            (SkTileMode)tileModeX, (SkTileMode)tileModeY);
-    ThrowIAE_IfNull(env, shader.get());
 
-    if (matrix) {
-        shader = shader->makeWithLocalMatrix(*matrix);
-    }
+    auto* shader = new BitmapShader(
+            image,
+            static_cast<SkTileMode>(tileModeX),
+            static_cast<SkTileMode>(tileModeY),
+            matrix
+        );
 
-    return reinterpret_cast<jlong>(shader.release());
+    return reinterpret_cast<jlong>(shader);
 }
 
 ///////////////////////////////////////////////////////////////////////////////////////////////
@@ -118,17 +126,18 @@
     #error Need to convert float array to SkScalar array before calling the following function.
 #endif
 
-    sk_sp<SkShader> shader(SkGradientShader::MakeLinear(pts, &colors[0],
-                GraphicsJNI::getNativeColorSpace(colorSpaceHandle), pos, colors.size(),
-                static_cast<SkTileMode>(tileMode), sGradientShaderFlags, nullptr));
-    ThrowIAE_IfNull(env, shader);
+    auto* matrix = reinterpret_cast<const SkMatrix*>(matrixPtr);
+    auto* shader = new LinearGradientShader(
+                pts,
+                colors,
+                GraphicsJNI::getNativeColorSpace(colorSpaceHandle),
+                pos,
+                static_cast<SkTileMode>(tileMode),
+                sGradientShaderFlags,
+                matrix
+            );
 
-    const SkMatrix* matrix = reinterpret_cast<const SkMatrix*>(matrixPtr);
-    if (matrix) {
-        shader = shader->makeWithLocalMatrix(*matrix);
-    }
-
-    return reinterpret_cast<jlong>(shader.release());
+    return reinterpret_cast<jlong>(shader);
 }
 
 ///////////////////////////////////////////////////////////////////////////////////////////////
@@ -148,17 +157,20 @@
     #error Need to convert float array to SkScalar array before calling the following function.
 #endif
 
-    sk_sp<SkShader> shader = SkGradientShader::MakeRadial(center, radius, &colors[0],
-            GraphicsJNI::getNativeColorSpace(colorSpaceHandle), pos, colors.size(),
-            static_cast<SkTileMode>(tileMode), sGradientShaderFlags, nullptr);
-    ThrowIAE_IfNull(env, shader);
+    auto* matrix = reinterpret_cast<const SkMatrix*>(matrixPtr);
 
-    const SkMatrix* matrix = reinterpret_cast<const SkMatrix*>(matrixPtr);
-    if (matrix) {
-        shader = shader->makeWithLocalMatrix(*matrix);
-    }
+    auto* shader = new RadialGradientShader(
+                center,
+                radius,
+                colors,
+                GraphicsJNI::getNativeColorSpace(colorSpaceHandle),
+                pos,
+                static_cast<SkTileMode>(tileMode),
+                sGradientShaderFlags,
+                matrix
+            );
 
-    return reinterpret_cast<jlong>(shader.release());
+    return reinterpret_cast<jlong>(shader);
 }
 
 ///////////////////////////////////////////////////////////////////////////////
@@ -174,74 +186,93 @@
     #error Need to convert float array to SkScalar array before calling the following function.
 #endif
 
-    sk_sp<SkShader> shader = SkGradientShader::MakeSweep(x, y, &colors[0],
-            GraphicsJNI::getNativeColorSpace(colorSpaceHandle), pos, colors.size(),
-            sGradientShaderFlags, nullptr);
-    ThrowIAE_IfNull(env, shader);
+    auto* matrix = reinterpret_cast<const SkMatrix*>(matrixPtr);
 
-    const SkMatrix* matrix = reinterpret_cast<const SkMatrix*>(matrixPtr);
-    if (matrix) {
-        shader = shader->makeWithLocalMatrix(*matrix);
-    }
+    auto* shader = new SweepGradientShader(
+                x,
+                y,
+                colors,
+                GraphicsJNI::getNativeColorSpace(colorSpaceHandle),
+                pos,
+                sGradientShaderFlags,
+                matrix
+            );
 
-    return reinterpret_cast<jlong>(shader.release());
+    return reinterpret_cast<jlong>(shader);
 }
 
 ///////////////////////////////////////////////////////////////////////////////////////////////
 
 static jlong ComposeShader_create(JNIEnv* env, jobject o, jlong matrixPtr,
         jlong shaderAHandle, jlong shaderBHandle, jint xfermodeHandle) {
-    const SkMatrix* matrix = reinterpret_cast<const SkMatrix*>(matrixPtr);
-    SkShader* shaderA = reinterpret_cast<SkShader *>(shaderAHandle);
-    SkShader* shaderB = reinterpret_cast<SkShader *>(shaderBHandle);
-    SkBlendMode mode = static_cast<SkBlendMode>(xfermodeHandle);
-    sk_sp<SkShader> baseShader(SkShaders::Blend(mode,
-            sk_ref_sp(shaderA), sk_ref_sp(shaderB)));
+    auto* matrix = reinterpret_cast<const SkMatrix*>(matrixPtr);
+    auto* shaderA = reinterpret_cast<Shader*>(shaderAHandle);
+    auto* shaderB = reinterpret_cast<Shader*>(shaderBHandle);
 
-    SkShader* shader;
+    auto mode = static_cast<SkBlendMode>(xfermodeHandle);
 
-    if (matrix) {
-        shader = baseShader->makeWithLocalMatrix(*matrix).release();
-    } else {
-        shader = baseShader.release();
-    }
-    return reinterpret_cast<jlong>(shader);
+    auto* composeShader = new ComposeShader(
+            *shaderA,
+            *shaderB,
+            mode,
+            matrix
+        );
+
+    return reinterpret_cast<jlong>(composeShader);
+}
+
+///////////////////////////////////////////////////////////////////////////////////////////////
+
+static jlong BlurShader_create(JNIEnv* env , jobject o, jlong matrixPtr, jfloat sigmaX,
+        jfloat sigmaY, jlong shaderHandle) {
+    auto* matrix = reinterpret_cast<const SkMatrix*>(matrixPtr);
+    auto* inputShader = reinterpret_cast<Shader*>(shaderHandle);
+
+    auto* blurShader = new BlurShader(
+                sigmaX,
+                sigmaY,
+                inputShader,
+                matrix
+            );
+    return reinterpret_cast<jlong>(blurShader);
 }
 
 ///////////////////////////////////////////////////////////////////////////////////////////////
 
 static jlong RuntimeShader_create(JNIEnv* env, jobject, jlong shaderFactory, jlong matrixPtr,
         jbyteArray inputs, jlong colorSpaceHandle, jboolean isOpaque) {
-    SkRuntimeEffect* effect = reinterpret_cast<SkRuntimeEffect*>(shaderFactory);
+    auto* effect = reinterpret_cast<SkRuntimeEffect*>(shaderFactory);
     AutoJavaByteArray arInputs(env, inputs);
 
-    sk_sp<SkData> fData;
-    fData = SkData::MakeWithCopy(arInputs.ptr(), arInputs.length());
-    const SkMatrix* matrix = reinterpret_cast<const SkMatrix*>(matrixPtr);
-    sk_sp<SkShader> shader = effect->makeShader(fData, nullptr, 0, matrix, isOpaque == JNI_TRUE);
-    ThrowIAE_IfNull(env, shader);
+    auto data = SkData::MakeWithCopy(arInputs.ptr(), arInputs.length());
+    auto* matrix = reinterpret_cast<const SkMatrix*>(matrixPtr);
 
-    return reinterpret_cast<jlong>(shader.release());
+    auto* shader = new RuntimeShader(
+            *effect,
+            std::move(data),
+            isOpaque == JNI_TRUE,
+            matrix
+        );
+    return reinterpret_cast<jlong>(shader);
 }
 
 ///////////////////////////////////////////////////////////////////////////////////////////////
 
 static jlong RuntimeShader_createShaderFactory(JNIEnv* env, jobject, jstring sksl) {
     ScopedUtfChars strSksl(env, sksl);
-    sk_sp<SkRuntimeEffect> effect = std::get<0>(SkRuntimeEffect::Make(SkString(strSksl.c_str())));
-    ThrowIAE_IfNull(env, effect);
-
+    auto result = SkRuntimeEffect::Make(SkString(strSksl.c_str()));
+    sk_sp<SkRuntimeEffect> effect = std::get<0>(result);
+    if (!effect) {
+        const auto& err = std::get<1>(result);
+        doThrowIAE(env, err.c_str());
+    }
     return reinterpret_cast<jlong>(effect.release());
 }
 
 ///////////////////////////////////////////////////////////////////////////////////////////////
 
-static void Effect_safeUnref(SkRuntimeEffect* effect) {
-    SkSafeUnref(effect);
-}
-
 static jlong RuntimeShader_getNativeFinalizer(JNIEnv*, jobject) {
-    return static_cast<jlong>(reinterpret_cast<uintptr_t>(&Effect_safeUnref));
+    return static_cast<jlong>(reinterpret_cast<uintptr_t>(&Shader_safeUnref));
 }
 
 ///////////////////////////////////////////////////////////////////////////////////////////////
@@ -259,6 +290,10 @@
     { "nativeCreate",      "(JJII)J",  (void*)BitmapShader_constructor },
 };
 
+static const JNINativeMethod gBlurShaderMethods[] = {
+    { "nativeCreate",      "(JFFJ)J", (void*)BlurShader_create }
+};
+
 static const JNINativeMethod gLinearGradientMethods[] = {
     { "nativeCreate",     "(JFFFF[J[FIJ)J",  (void*)LinearGradient_create     },
 };
@@ -290,6 +325,8 @@
                                   NELEM(gShaderMethods));
     android::RegisterMethodsOrDie(env, "android/graphics/BitmapShader", gBitmapShaderMethods,
                                   NELEM(gBitmapShaderMethods));
+    android::RegisterMethodsOrDie(env, "android/graphics/BlurShader", gBlurShaderMethods,
+                                  NELEM(gBlurShaderMethods));
     android::RegisterMethodsOrDie(env, "android/graphics/LinearGradient", gLinearGradientMethods,
                                   NELEM(gLinearGradientMethods));
     android::RegisterMethodsOrDie(env, "android/graphics/RadialGradient", gRadialGradientMethods,
diff --git a/libs/hwui/jni/android_graphics_HardwareRenderer.cpp b/libs/hwui/jni/android_graphics_HardwareRenderer.cpp
index 7d6875f..e817ca7 100644
--- a/libs/hwui/jni/android_graphics_HardwareRenderer.cpp
+++ b/libs/hwui/jni/android_graphics_HardwareRenderer.cpp
@@ -184,7 +184,9 @@
         proxy->setSwapBehavior(SwapBehavior::kSwap_discardBuffer);
     }
     proxy->setSurface(window, enableTimeout);
-    ANativeWindow_release(window);
+    if (window) {
+        ANativeWindow_release(window);
+    }
 }
 
 static jboolean android_view_ThreadedRenderer_pause(JNIEnv* env, jobject clazz,
@@ -223,6 +225,11 @@
     proxy->setColorMode(static_cast<ColorMode>(colorMode));
 }
 
+static void android_view_ThreadedRenderer_setSdrWhitePoint(JNIEnv* env, jobject clazz,
+        jlong proxyPtr, jfloat sdrWhitePoint) {
+    Properties::defaultSdrWhitePoint = sdrWhitePoint;
+}
+
 static int android_view_ThreadedRenderer_syncAndDrawFrame(JNIEnv* env, jobject clazz,
         jlong proxyPtr, jlongArray frameInfo, jint frameInfoSize) {
     LOG_ALWAYS_FATAL_IF(frameInfoSize != UI_THREAD_FRAME_INFO_SIZE,
@@ -671,6 +678,7 @@
         {"nSetLightGeometry", "(JFFFF)V", (void*)android_view_ThreadedRenderer_setLightGeometry},
         {"nSetOpaque", "(JZ)V", (void*)android_view_ThreadedRenderer_setOpaque},
         {"nSetColorMode", "(JI)V", (void*)android_view_ThreadedRenderer_setColorMode},
+        {"nSetSdrWhitePoint", "(JF)V", (void*)android_view_ThreadedRenderer_setSdrWhitePoint},
         {"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/jni/android_graphics_drawable_VectorDrawable.cpp b/libs/hwui/jni/android_graphics_drawable_VectorDrawable.cpp
index 9cffceb..a1adcb3 100644
--- a/libs/hwui/jni/android_graphics_drawable_VectorDrawable.cpp
+++ b/libs/hwui/jni/android_graphics_drawable_VectorDrawable.cpp
@@ -143,13 +143,13 @@
 
 static void updateFullPathFillGradient(JNIEnv*, jobject, jlong pathPtr, jlong fillGradientPtr) {
     VectorDrawable::FullPath* path = reinterpret_cast<VectorDrawable::FullPath*>(pathPtr);
-    SkShader* fillShader = reinterpret_cast<SkShader*>(fillGradientPtr);
+    auto* fillShader = reinterpret_cast<Shader*>(fillGradientPtr);
     path->mutateStagingProperties()->setFillGradient(fillShader);
 }
 
 static void updateFullPathStrokeGradient(JNIEnv*, jobject, jlong pathPtr, jlong strokeGradientPtr) {
     VectorDrawable::FullPath* path = reinterpret_cast<VectorDrawable::FullPath*>(pathPtr);
-    SkShader* strokeShader = reinterpret_cast<SkShader*>(strokeGradientPtr);
+    auto* strokeShader = reinterpret_cast<Shader*>(strokeGradientPtr);
     path->mutateStagingProperties()->setStrokeGradient(strokeShader);
 }
 
diff --git a/libs/hwui/renderthread/EglManager.cpp b/libs/hwui/renderthread/EglManager.cpp
index 2a8aa8c..a116781 100644
--- a/libs/hwui/renderthread/EglManager.cpp
+++ b/libs/hwui/renderthread/EglManager.cpp
@@ -238,12 +238,8 @@
     return config;
 }
 
-extern "C" EGLAPI const char* eglQueryStringImplementationANDROID(EGLDisplay dpy, EGLint name);
-
 void EglManager::initExtensions() {
     auto extensions = StringUtils::split(eglQueryString(mEglDisplay, EGL_EXTENSIONS));
-    auto extensionsAndroid =
-            StringUtils::split(eglQueryStringImplementationANDROID(mEglDisplay, EGL_EXTENSIONS));
 
     // For our purposes we don't care if EGL_BUFFER_AGE is a result of
     // EGL_EXT_buffer_age or EGL_KHR_partial_update as our usage is covered
@@ -265,10 +261,7 @@
     EglExtensions.surfacelessContext = extensions.has("EGL_KHR_surfaceless_context");
     EglExtensions.fenceSync = extensions.has("EGL_KHR_fence_sync");
     EglExtensions.waitSync = extensions.has("EGL_KHR_wait_sync");
-
-    // EGL_ANDROID_native_fence_sync is not exposed to applications, so access
-    // this through the private Android-specific query instead.
-    EglExtensions.nativeFenceSync = extensionsAndroid.has("EGL_ANDROID_native_fence_sync");
+    EglExtensions.nativeFenceSync = extensions.has("EGL_ANDROID_native_fence_sync");
 }
 
 bool EglManager::hasEglContext() {
diff --git a/libs/hwui/renderthread/RenderProxy.cpp b/libs/hwui/renderthread/RenderProxy.cpp
index aad0cca..b51f6dc 100644
--- a/libs/hwui/renderthread/RenderProxy.cpp
+++ b/libs/hwui/renderthread/RenderProxy.cpp
@@ -77,10 +77,10 @@
 }
 
 void RenderProxy::setSurface(ANativeWindow* window, bool enableTimeout) {
-    ANativeWindow_acquire(window);
+    if (window) { ANativeWindow_acquire(window); }
     mRenderThread.queue().post([this, win = window, enableTimeout]() mutable {
         mContext->setSurface(win, enableTimeout);
-        ANativeWindow_release(win);
+        if (win) { ANativeWindow_release(win); }
     });
 }
 
diff --git a/libs/hwui/renderthread/RenderThread.cpp b/libs/hwui/renderthread/RenderThread.cpp
index 565fb61..4dcbc44 100644
--- a/libs/hwui/renderthread/RenderThread.cpp
+++ b/libs/hwui/renderthread/RenderThread.cpp
@@ -131,8 +131,7 @@
         , mFrameCallbackTaskPending(false)
         , mRenderState(nullptr)
         , mEglManager(nullptr)
-        , mFunctorManager(WebViewFunctorManager::instance())
-        , mVkManager(nullptr) {
+        , mFunctorManager(WebViewFunctorManager::instance()) {
     Properties::load();
     start("RenderThread");
 }
@@ -166,7 +165,7 @@
     initializeChoreographer();
     mEglManager = new EglManager();
     mRenderState = new RenderState(*this);
-    mVkManager = new VulkanManager();
+    mVkManager = VulkanManager::getInstance();
     mCacheManager = new CacheManager();
 }
 
@@ -196,7 +195,8 @@
 }
 
 void RenderThread::requireVkContext() {
-    if (mVkManager->hasVkContext()) {
+    // the getter creates the context in the event it had been destroyed by destroyRenderingContext
+    if (vulkanManager().hasVkContext()) {
         return;
     }
     mVkManager->initialize();
@@ -222,13 +222,18 @@
             mEglManager->destroy();
         }
     } else {
-        if (vulkanManager().hasVkContext()) {
-            setGrContext(nullptr);
-            vulkanManager().destroy();
-        }
+        setGrContext(nullptr);
+        mVkManager.clear();
     }
 }
 
+VulkanManager& RenderThread::vulkanManager() {
+    if (!mVkManager.get()) {
+        mVkManager = VulkanManager::getInstance();
+    }
+    return *mVkManager.get();
+}
+
 void RenderThread::dumpGraphicsMemory(int fd) {
     globalProfileData()->dump(fd);
 
diff --git a/libs/hwui/renderthread/RenderThread.h b/libs/hwui/renderthread/RenderThread.h
index b8ce556..d7dc00b 100644
--- a/libs/hwui/renderthread/RenderThread.h
+++ b/libs/hwui/renderthread/RenderThread.h
@@ -110,7 +110,7 @@
     void setGrContext(sk_sp<GrDirectContext> cxt);
 
     CacheManager& cacheManager() { return *mCacheManager; }
-    VulkanManager& vulkanManager() { return *mVkManager; }
+    VulkanManager& vulkanManager();
 
     sk_sp<Bitmap> allocateHardwareBitmap(SkBitmap& skBitmap);
     void dumpGraphicsMemory(int fd);
@@ -188,7 +188,7 @@
 
     sk_sp<GrDirectContext> mGrContext;
     CacheManager* mCacheManager;
-    VulkanManager* mVkManager;
+    sp<VulkanManager> mVkManager;
 };
 
 } /* namespace renderthread */
diff --git a/libs/hwui/renderthread/VulkanManager.cpp b/libs/hwui/renderthread/VulkanManager.cpp
index 249936e..0c5cf682 100644
--- a/libs/hwui/renderthread/VulkanManager.cpp
+++ b/libs/hwui/renderthread/VulkanManager.cpp
@@ -57,12 +57,22 @@
 #define GET_INST_PROC(F) m##F = (PFN_vk##F)vkGetInstanceProcAddr(mInstance, "vk" #F)
 #define GET_DEV_PROC(F) m##F = (PFN_vk##F)vkGetDeviceProcAddr(mDevice, "vk" #F)
 
-void VulkanManager::destroy() {
-    if (VK_NULL_HANDLE != mCommandPool) {
-        mDestroyCommandPool(mDevice, mCommandPool, nullptr);
-        mCommandPool = VK_NULL_HANDLE;
+sp<VulkanManager> VulkanManager::getInstance() {
+    // cache a weakptr to the context to enable a second thread to share the same vulkan state
+    static wp<VulkanManager> sWeakInstance = nullptr;
+    static std::mutex sLock;
+
+    std::lock_guard _lock{sLock};
+    sp<VulkanManager> vulkanManager = sWeakInstance.promote();
+    if (!vulkanManager.get()) {
+        vulkanManager = new VulkanManager();
+        sWeakInstance = vulkanManager;
     }
 
+    return vulkanManager;
+}
+
+VulkanManager::~VulkanManager() {
     if (mDevice != VK_NULL_HANDLE) {
         mDeviceWaitIdle(mDevice);
         mDestroyDevice(mDevice, nullptr);
@@ -73,6 +83,7 @@
     }
 
     mGraphicsQueue = VK_NULL_HANDLE;
+    mAHBUploadQueue = VK_NULL_HANDLE;
     mPresentQueue = VK_NULL_HANDLE;
     mDevice = VK_NULL_HANDLE;
     mPhysicalDevice = VK_NULL_HANDLE;
@@ -175,6 +186,7 @@
     for (uint32_t i = 0; i < queueCount; i++) {
         if (queueProps[i].queueFlags & VK_QUEUE_GRAPHICS_BIT) {
             mGraphicsQueueIndex = i;
+            LOG_ALWAYS_FATAL_IF(queueProps[i].queueCount < 2);
             break;
         }
     }
@@ -283,7 +295,7 @@
                     queueNextPtr,                                // pNext
                     0,                                           // VkDeviceQueueCreateFlags
                     mGraphicsQueueIndex,                         // queueFamilyIndex
-                    1,                                           // queueCount
+                    2,                                           // queueCount
                     queuePriorities,                             // pQueuePriorities
             },
             {
@@ -347,6 +359,7 @@
     this->setupDevice(mExtensions, mPhysicalDeviceFeatures2);
 
     mGetDeviceQueue(mDevice, mGraphicsQueueIndex, 0, &mGraphicsQueue);
+    mGetDeviceQueue(mDevice, mGraphicsQueueIndex, 1, &mAHBUploadQueue);
 
     // create the command pool for the command buffers
     if (VK_NULL_HANDLE == mCommandPool) {
@@ -369,7 +382,8 @@
     }
 }
 
-sk_sp<GrDirectContext> VulkanManager::createContext(const GrContextOptions& options) {
+sk_sp<GrDirectContext> VulkanManager::createContext(const GrContextOptions& options,
+                                                    ContextType contextType) {
     auto getProc = [](const char* proc_name, VkInstance instance, VkDevice device) {
         if (device != VK_NULL_HANDLE) {
             return vkGetDeviceProcAddr(device, proc_name);
@@ -381,7 +395,8 @@
     backendContext.fInstance = mInstance;
     backendContext.fPhysicalDevice = mPhysicalDevice;
     backendContext.fDevice = mDevice;
-    backendContext.fQueue = mGraphicsQueue;
+    backendContext.fQueue = (contextType == ContextType::kRenderThread) ? mGraphicsQueue
+                                                                        : mAHBUploadQueue;
     backendContext.fGraphicsQueueIndex = mGraphicsQueueIndex;
     backendContext.fMaxAPIVersion = mAPIVersion;
     backendContext.fVkExtensions = &mExtensions;
diff --git a/libs/hwui/renderthread/VulkanManager.h b/libs/hwui/renderthread/VulkanManager.h
index 3f2df8d..13335f3 100644
--- a/libs/hwui/renderthread/VulkanManager.h
+++ b/libs/hwui/renderthread/VulkanManager.h
@@ -43,10 +43,9 @@
 // This class contains the shared global Vulkan objects, such as VkInstance, VkDevice and VkQueue,
 // which are re-used by CanvasContext. This class is created once and should be used by all vulkan
 // windowing contexts. The VulkanManager must be initialized before use.
-class VulkanManager {
+class VulkanManager final : public RefBase {
 public:
-    explicit VulkanManager() {}
-    ~VulkanManager() { destroy(); }
+    static sp<VulkanManager> getInstance();
 
     // Sets up the vulkan context that is shared amonst all clients of the VulkanManager. This must
     // be call once before use of the VulkanManager. Multiple calls after the first will simiply
@@ -68,9 +67,6 @@
     Frame dequeueNextBuffer(VulkanSurface* surface);
     void swapBuffers(VulkanSurface* surface, const SkRect& dirtyRect);
 
-    // Cleans up all the global state in the VulkanManger.
-    void destroy();
-
     // Inserts a wait on fence command into the Vulkan command buffer.
     status_t fenceWait(int fence, GrDirectContext* grContext);
 
@@ -83,12 +79,24 @@
     // the internal state of VulkanManager: VulkanManager must be alive to use the returned value.
     VkFunctorInitParams getVkFunctorInitParams() const;
 
-    sk_sp<GrDirectContext> createContext(const GrContextOptions& options);
+
+    enum class ContextType {
+        kRenderThread,
+        kUploadThread
+    };
+
+    // returns a Skia graphic context used to draw content on the specified thread
+    sk_sp<GrDirectContext> createContext(const GrContextOptions& options,
+                                         ContextType contextType = ContextType::kRenderThread);
 
     uint32_t getDriverVersion() const { return mDriverVersion; }
 
 private:
     friend class VulkanSurface;
+
+    explicit VulkanManager() {}
+    ~VulkanManager();
+
     // Sets up the VkInstance and VkDevice objects. Also fills out the passed in
     // VkPhysicalDeviceFeatures struct.
     void setupDevice(GrVkExtensions&, VkPhysicalDeviceFeatures2&);
@@ -154,6 +162,7 @@
 
     uint32_t mGraphicsQueueIndex;
     VkQueue mGraphicsQueue = VK_NULL_HANDLE;
+    VkQueue mAHBUploadQueue = VK_NULL_HANDLE;
     uint32_t mPresentQueueIndex;
     VkQueue mPresentQueue = VK_NULL_HANDLE;
     VkCommandPool mCommandPool = VK_NULL_HANDLE;
diff --git a/libs/hwui/service/GraphicsStatsService.cpp b/libs/hwui/service/GraphicsStatsService.cpp
index 644d5fb..e419801 100644
--- a/libs/hwui/service/GraphicsStatsService.cpp
+++ b/libs/hwui/service/GraphicsStatsService.cpp
@@ -559,6 +559,7 @@
         AStatsEvent_writeBool(event, !lastFullDay);
         AStatsEvent_build(event);
     }
+    delete dump;
 }
 
 
diff --git a/libs/hwui/shader/BitmapShader.cpp b/libs/hwui/shader/BitmapShader.cpp
new file mode 100644
index 0000000..fe653e8
--- /dev/null
+++ b/libs/hwui/shader/BitmapShader.cpp
@@ -0,0 +1,31 @@
+/*
+ * 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.
+ */
+
+#include "BitmapShader.h"
+
+#include "SkImagePriv.h"
+
+namespace android::uirenderer {
+BitmapShader::BitmapShader(const sk_sp<SkImage>& image, const SkTileMode tileModeX,
+                           const SkTileMode tileModeY, const SkMatrix* matrix)
+        : Shader(matrix), skShader(image->makeShader(tileModeX, tileModeY)) {}
+
+sk_sp<SkShader> BitmapShader::makeSkShader() {
+    return skShader;
+}
+
+BitmapShader::~BitmapShader() {}
+}  // namespace android::uirenderer
\ No newline at end of file
diff --git a/libs/hwui/shader/BitmapShader.h b/libs/hwui/shader/BitmapShader.h
new file mode 100644
index 0000000..ed6a6e6
--- /dev/null
+++ b/libs/hwui/shader/BitmapShader.h
@@ -0,0 +1,39 @@
+/*
+ * 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.
+ */
+
+#pragma once
+
+#include "Shader.h"
+#include "SkShader.h"
+
+namespace android::uirenderer {
+
+/**
+ * Shader implementation that renders a Bitmap as either a SkShader or SkImageFilter
+ */
+class BitmapShader : public Shader {
+public:
+    BitmapShader(const sk_sp<SkImage>& image, const SkTileMode tileModeX,
+                 const SkTileMode tileModeY, const SkMatrix* matrix);
+    ~BitmapShader() override;
+
+protected:
+    sk_sp<SkShader> makeSkShader() override;
+
+private:
+    sk_sp<SkShader> skShader;
+};
+}  // namespace android::uirenderer
\ No newline at end of file
diff --git a/libs/hwui/shader/BlurShader.cpp b/libs/hwui/shader/BlurShader.cpp
new file mode 100644
index 0000000..4d18cdd
--- /dev/null
+++ b/libs/hwui/shader/BlurShader.cpp
@@ -0,0 +1,38 @@
+/*
+ * 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.
+ */
+
+#include "BlurShader.h"
+#include "SkImageFilters.h"
+#include "SkRefCnt.h"
+#include "utils/Blur.h"
+
+namespace android::uirenderer {
+BlurShader::BlurShader(float radiusX, float radiusY, Shader* inputShader, const SkMatrix* matrix)
+    : Shader(matrix)
+    , skImageFilter(
+            SkImageFilters::Blur(
+                    Blur::convertRadiusToSigma(radiusX),
+                    Blur::convertRadiusToSigma(radiusY),
+                    inputShader ? inputShader->asSkImageFilter() : nullptr)
+            ) { }
+
+sk_sp<SkImageFilter> BlurShader::makeSkImageFilter() {
+    return skImageFilter;
+}
+
+BlurShader::~BlurShader() {}
+
+} // namespace android::uirenderer
\ No newline at end of file
diff --git a/libs/hwui/shader/BlurShader.h b/libs/hwui/shader/BlurShader.h
new file mode 100644
index 0000000..9eb22bd
--- /dev/null
+++ b/libs/hwui/shader/BlurShader.h
@@ -0,0 +1,42 @@
+/*
+ * 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.
+ */
+
+#pragma once
+#include "Shader.h"
+
+namespace android::uirenderer {
+
+/**
+ * Shader implementation that blurs another Shader instance or the source bitmap
+ */
+class BlurShader : public Shader {
+public:
+    /**
+     * Creates a BlurShader instance with the provided radius values to blur along the x and y
+     * axis accordingly.
+     *
+     * This will blur the contents of the provided input shader if it is non-null, otherwise
+     * the source bitmap will be blurred instead.
+     */
+    BlurShader(float radiusX, float radiusY, Shader* inputShader, const SkMatrix* matrix);
+    ~BlurShader() override;
+protected:
+    sk_sp<SkImageFilter> makeSkImageFilter() override;
+private:
+    sk_sp<SkImageFilter> skImageFilter;
+};
+
+} // namespace android::uirenderer
\ No newline at end of file
diff --git a/libs/hwui/shader/ComposeShader.cpp b/libs/hwui/shader/ComposeShader.cpp
new file mode 100644
index 0000000..3765489
--- /dev/null
+++ b/libs/hwui/shader/ComposeShader.cpp
@@ -0,0 +1,51 @@
+/*
+ * 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.
+ */
+
+#include "ComposeShader.h"
+
+#include "SkImageFilters.h"
+#include "SkShader.h"
+
+namespace android::uirenderer {
+
+ComposeShader::ComposeShader(Shader& shaderA, Shader& shaderB, const SkBlendMode blendMode,
+                             const SkMatrix* matrix)
+        : Shader(matrix) {
+    // If both Shaders can be represented as SkShaders then use those, if not
+    // create an SkImageFilter from both Shaders and create the equivalent SkImageFilter
+    sk_sp<SkShader> skShaderA = shaderA.asSkShader();
+    sk_sp<SkShader> skShaderB = shaderB.asSkShader();
+    if (skShaderA.get() && skShaderB.get()) {
+        skShader = SkShaders::Blend(blendMode, skShaderA, skShaderB);
+        skImageFilter = nullptr;
+    } else {
+        sk_sp<SkImageFilter> skImageFilterA = shaderA.asSkImageFilter();
+        sk_sp<SkImageFilter> skImageFilterB = shaderB.asSkImageFilter();
+        skShader = nullptr;
+        skImageFilter = SkImageFilters::Xfermode(blendMode, skImageFilterA, skImageFilterB);
+    }
+}
+
+sk_sp<SkShader> ComposeShader::makeSkShader() {
+    return skShader;
+}
+
+sk_sp<SkImageFilter> ComposeShader::makeSkImageFilter() {
+    return skImageFilter;
+}
+
+ComposeShader::~ComposeShader() {}
+}  // namespace android::uirenderer
diff --git a/libs/hwui/shader/ComposeShader.h b/libs/hwui/shader/ComposeShader.h
new file mode 100644
index 0000000..a246b52
--- /dev/null
+++ b/libs/hwui/shader/ComposeShader.h
@@ -0,0 +1,43 @@
+/*
+ * 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.
+ */
+
+#pragma once
+
+#include "Shader.h"
+#include "SkShader.h"
+
+namespace android::uirenderer {
+
+/**
+ * Shader implementation that can composite 2 Shaders together with the specified blend mode.
+ * This implementation can appropriately convert the composed result to either an SkShader or
+ * SkImageFilter depending on the inputs
+ */
+class ComposeShader : public Shader {
+public:
+    ComposeShader(Shader& shaderA, Shader& shaderB, const SkBlendMode blendMode,
+                  const SkMatrix* matrix);
+    ~ComposeShader() override;
+
+protected:
+    sk_sp<SkShader> makeSkShader() override;
+    sk_sp<SkImageFilter> makeSkImageFilter() override;
+
+private:
+    sk_sp<SkShader> skShader;
+    sk_sp<SkImageFilter> skImageFilter;
+};
+}  // namespace android::uirenderer
diff --git a/libs/hwui/shader/LinearGradientShader.cpp b/libs/hwui/shader/LinearGradientShader.cpp
new file mode 100644
index 0000000..868fa44
--- /dev/null
+++ b/libs/hwui/shader/LinearGradientShader.cpp
@@ -0,0 +1,39 @@
+/*
+ * 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.
+ */
+
+#include "LinearGradientShader.h"
+
+#include <vector>
+
+#include "SkGradientShader.h"
+
+namespace android::uirenderer {
+
+LinearGradientShader::LinearGradientShader(const SkPoint pts[2],
+                                           const std::vector<SkColor4f>& colors,
+                                           sk_sp<SkColorSpace> colorspace, const SkScalar pos[],
+                                           const SkTileMode tileMode, const uint32_t shaderFlags,
+                                           const SkMatrix* matrix)
+        : Shader(matrix)
+        , skShader(SkGradientShader::MakeLinear(pts, colors.data(), colorspace, pos, colors.size(),
+                                                tileMode, shaderFlags, nullptr)) {}
+
+sk_sp<SkShader> LinearGradientShader::makeSkShader() {
+    return skShader;
+}
+
+LinearGradientShader::~LinearGradientShader() {}
+}  // namespace android::uirenderer
\ No newline at end of file
diff --git a/libs/hwui/shader/LinearGradientShader.h b/libs/hwui/shader/LinearGradientShader.h
new file mode 100644
index 0000000..596f4e0
--- /dev/null
+++ b/libs/hwui/shader/LinearGradientShader.h
@@ -0,0 +1,42 @@
+/*
+ * 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.
+ */
+
+#pragma once
+
+#include "Shader.h"
+#include "SkShader.h"
+
+namespace android::uirenderer {
+
+/**
+ * Shader implementation that renders a color ramp of colors to either as either SkShader or
+ * SkImageFilter
+ */
+class LinearGradientShader : public Shader {
+public:
+    LinearGradientShader(const SkPoint pts[2], const std::vector<SkColor4f>& colors,
+                         sk_sp<SkColorSpace> colorspace, const SkScalar pos[],
+                         const SkTileMode tileMode, const uint32_t shaderFlags,
+                         const SkMatrix* matrix);
+    ~LinearGradientShader() override;
+
+protected:
+    sk_sp<SkShader> makeSkShader() override;
+
+private:
+    sk_sp<SkShader> skShader;
+};
+}  // namespace android::uirenderer
diff --git a/libs/hwui/shader/RadialGradientShader.cpp b/libs/hwui/shader/RadialGradientShader.cpp
new file mode 100644
index 0000000..21ff56f
--- /dev/null
+++ b/libs/hwui/shader/RadialGradientShader.cpp
@@ -0,0 +1,38 @@
+/*
+ * 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.
+ */
+#include "RadialGradientShader.h"
+
+#include <vector>
+
+#include "SkGradientShader.h"
+
+namespace android::uirenderer {
+
+RadialGradientShader::RadialGradientShader(const SkPoint& center, const float radius,
+                                           const std::vector<SkColor4f>& colors,
+                                           sk_sp<SkColorSpace> colorspace, const SkScalar pos[],
+                                           const SkTileMode tileMode, const uint32_t shaderFlags,
+                                           const SkMatrix* matrix)
+        : Shader(matrix)
+        , skShader(SkGradientShader::MakeRadial(center, radius, colors.data(), colorspace, pos,
+                                                colors.size(), tileMode, shaderFlags, nullptr)) {}
+
+sk_sp<SkShader> RadialGradientShader::makeSkShader() {
+    return skShader;
+}
+
+RadialGradientShader::~RadialGradientShader() {}
+}  // namespace android::uirenderer
\ No newline at end of file
diff --git a/libs/hwui/shader/RadialGradientShader.h b/libs/hwui/shader/RadialGradientShader.h
new file mode 100644
index 0000000..9a2ff13
--- /dev/null
+++ b/libs/hwui/shader/RadialGradientShader.h
@@ -0,0 +1,42 @@
+/*
+ * 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.
+ */
+
+#pragma once
+
+#include "Shader.h"
+#include "SkShader.h"
+
+namespace android::uirenderer {
+
+/**
+ * Shader implementation that renders a color ramp from the center outward to either as either
+ * a SkShader or SkImageFilter
+ */
+class RadialGradientShader : public Shader {
+public:
+    RadialGradientShader(const SkPoint& center, const float radius,
+                         const std::vector<SkColor4f>& colors, sk_sp<SkColorSpace> colorSpace,
+                         const SkScalar pos[], const SkTileMode tileMode, const uint32_t shaderFlags,
+                         const SkMatrix* matrix);
+    ~RadialGradientShader() override;
+
+protected:
+    sk_sp<SkShader> makeSkShader() override;
+
+private:
+    sk_sp<SkShader> skShader;
+};
+}  // namespace android::uirenderer
\ No newline at end of file
diff --git a/libs/hwui/shader/RuntimeShader.cpp b/libs/hwui/shader/RuntimeShader.cpp
new file mode 100644
index 0000000..dd0b698
--- /dev/null
+++ b/libs/hwui/shader/RuntimeShader.cpp
@@ -0,0 +1,36 @@
+/*
+ * 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.
+ */
+
+#include "RuntimeShader.h"
+
+#include "SkShader.h"
+#include "include/effects/SkRuntimeEffect.h"
+
+namespace android::uirenderer {
+
+RuntimeShader::RuntimeShader(SkRuntimeEffect& effect, sk_sp<SkData> data, bool isOpaque,
+                             const SkMatrix* matrix)
+        : Shader(nullptr)
+        ,  // Explicitly passing null as RuntimeShader is created with the
+           // matrix directly
+        skShader(effect.makeShader(std::move(data), nullptr, 0, matrix, isOpaque)) {}
+
+sk_sp<SkShader> RuntimeShader::makeSkShader() {
+    return skShader;
+}
+
+RuntimeShader::~RuntimeShader() {}
+}  // namespace android::uirenderer
\ No newline at end of file
diff --git a/libs/hwui/shader/RuntimeShader.h b/libs/hwui/shader/RuntimeShader.h
new file mode 100644
index 0000000..7fe0b02
--- /dev/null
+++ b/libs/hwui/shader/RuntimeShader.h
@@ -0,0 +1,40 @@
+/*
+ * 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.
+ */
+
+#pragma once
+
+#include "Shader.h"
+#include "SkShader.h"
+#include "include/effects/SkRuntimeEffect.h"
+
+namespace android::uirenderer {
+
+/**
+ * RuntimeShader implementation that can map to either a SkShader or SkImageFilter
+ */
+class RuntimeShader : public Shader {
+public:
+    RuntimeShader(SkRuntimeEffect& effect, sk_sp<SkData> data, bool isOpaque,
+                  const SkMatrix* matrix);
+    ~RuntimeShader() override;
+
+protected:
+    sk_sp<SkShader> makeSkShader() override;
+
+private:
+    sk_sp<SkShader> skShader;
+};
+}  // namespace android::uirenderer
diff --git a/libs/hwui/shader/Shader.cpp b/libs/hwui/shader/Shader.cpp
new file mode 100644
index 0000000..45123dd
--- /dev/null
+++ b/libs/hwui/shader/Shader.cpp
@@ -0,0 +1,87 @@
+/*
+ * 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.
+ */
+
+#include "Shader.h"
+
+#include "SkImageFilters.h"
+#include "SkPaint.h"
+#include "SkRefCnt.h"
+
+namespace android::uirenderer {
+
+Shader::Shader(const SkMatrix* matrix)
+        : localMatrix(matrix ? *matrix : SkMatrix::I())
+        , skShader(nullptr)
+        , skImageFilter(nullptr) {}
+
+Shader::~Shader() {}
+
+sk_sp<SkShader> Shader::asSkShader() {
+    // If we already have created a shader with these parameters just return the existing
+    // shader we have already created
+    if (!this->skShader.get()) {
+        this->skShader = makeSkShader();
+        if (this->skShader.get()) {
+            if (!localMatrix.isIdentity()) {
+                this->skShader = this->skShader->makeWithLocalMatrix(localMatrix);
+            }
+        }
+    }
+    return this->skShader;
+}
+
+/**
+ * By default return null as we cannot convert all visual effects to SkShader instances
+ */
+sk_sp<SkShader> Shader::makeSkShader() {
+    return nullptr;
+}
+
+sk_sp<SkImageFilter> Shader::asSkImageFilter() {
+    // If we already have created an ImageFilter with these parameters just return the existing
+    // ImageFilter we have already created
+    if (!this->skImageFilter.get()) {
+        // Attempt to create an SkImageFilter from the current Shader implementation
+        this->skImageFilter = makeSkImageFilter();
+        if (this->skImageFilter) {
+            if (!localMatrix.isIdentity()) {
+                // If we have created an SkImageFilter and we have a transformation, wrap
+                // the created SkImageFilter to apply the given matrix
+                this->skImageFilter = SkImageFilters::MatrixTransform(
+                    localMatrix, kMedium_SkFilterQuality, this->skImageFilter);
+            }
+        } else {
+            // Otherwise if no SkImageFilter implementation is provided, create one from
+            // the result of asSkShader. Note the matrix is already applied to the shader in
+            // this case so just convert it to an SkImageFilter using SkImageFilters::Paint
+            SkPaint paint;
+            paint.setShader(asSkShader());
+            sk_sp<SkImageFilter> paintFilter = SkImageFilters::Paint(paint);
+            this->skImageFilter = SkImageFilters::Xfermode(SkBlendMode::kDstIn,
+                    std::move(paintFilter));
+        }
+    }
+    return this->skImageFilter;
+}
+
+/**
+ * By default return null for subclasses to implement. If there is not a direct SkImageFilter
+ * conversion
+ */
+sk_sp<SkImageFilter> Shader::makeSkImageFilter() {
+    return nullptr;
+}
+}  // namespace android::uirenderer
\ No newline at end of file
diff --git a/libs/hwui/shader/Shader.h b/libs/hwui/shader/Shader.h
new file mode 100644
index 0000000..6403e11
--- /dev/null
+++ b/libs/hwui/shader/Shader.h
@@ -0,0 +1,84 @@
+/*
+ * 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.
+ */
+
+#pragma once
+
+#include "SkImageFilter.h"
+#include "SkShader.h"
+#include "SkPaint.h"
+#include "SkRefCnt.h"
+
+class SkMatrix;
+
+namespace android::uirenderer {
+
+/**
+ * Shader class that can optionally wrap an SkShader or SkImageFilter depending
+ * on the implementation
+ */
+class Shader: public SkRefCnt {
+public:
+    /**
+     * Creates a Shader instance with an optional transformation matrix. The transformation matrix
+     * is copied internally and ownership is unchanged. It is the responsibility of the caller to
+     * deallocate it appropriately.
+     * @param matrix Optional matrix to transform the underlying SkShader or SkImageFilter
+     */
+    Shader(const SkMatrix* matrix);
+    virtual ~Shader();
+
+    /**
+     * Create an SkShader from the current Shader instance or return a previously
+     * created instance. This can be null if no SkShader could be created from this
+     * Shader instance.
+     */
+    sk_sp<SkShader> asSkShader();
+
+    /**
+     * Create an SkImageFilter from the current Shader instance or return a previously
+     * created instance. Unlike asSkShader, this method cannot return null.
+     */
+    sk_sp<SkImageFilter> asSkImageFilter();
+
+protected:
+    /**
+     * Create a new SkShader instance based on this Shader instance
+     */
+    virtual sk_sp<SkShader> makeSkShader();
+
+    /**
+     * Create a new SkImageFilter instance based on this Shader instance. If no SkImageFilter
+     * can be created then return nullptr
+     */
+    virtual sk_sp<SkImageFilter> makeSkImageFilter();
+
+private:
+    /**
+     * Optional matrix transform
+     */
+    const SkMatrix localMatrix;
+
+    /**
+     * Cached SkShader instance to be returned on subsequent queries
+     */
+    sk_sp<SkShader> skShader;
+
+    /**
+     * Cached SkImageFilter instance to be returned on subsequent queries
+     */
+    sk_sp<SkImageFilter> skImageFilter;
+};
+}  // namespace android::uirenderer
diff --git a/libs/hwui/shader/SweepGradientShader.cpp b/libs/hwui/shader/SweepGradientShader.cpp
new file mode 100644
index 0000000..3b1f37f
--- /dev/null
+++ b/libs/hwui/shader/SweepGradientShader.cpp
@@ -0,0 +1,38 @@
+/*
+ * 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.
+ */
+
+#include "SweepGradientShader.h"
+
+#include <vector>
+
+#include "SkGradientShader.h"
+#include "SkImageFilters.h"
+
+namespace android::uirenderer {
+
+SweepGradientShader::SweepGradientShader(float x, float y, const std::vector<SkColor4f>& colors,
+                                         const sk_sp<SkColorSpace>& colorspace, const SkScalar pos[],
+                                         const uint32_t shaderFlags, const SkMatrix* matrix)
+        : Shader(matrix)
+        , skShader(SkGradientShader::MakeSweep(x, y, colors.data(), colorspace, pos, colors.size(),
+                                               shaderFlags, nullptr)) {}
+
+sk_sp<SkShader> SweepGradientShader::makeSkShader() {
+    return skShader;
+}
+
+SweepGradientShader::~SweepGradientShader() {}
+}  // namespace android::uirenderer
\ No newline at end of file
diff --git a/libs/hwui/shader/SweepGradientShader.h b/libs/hwui/shader/SweepGradientShader.h
new file mode 100644
index 0000000..dad3ef0
--- /dev/null
+++ b/libs/hwui/shader/SweepGradientShader.h
@@ -0,0 +1,41 @@
+/*
+ * 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.
+ */
+
+#pragma once
+
+#include "Shader.h"
+#include "SkShader.h"
+
+namespace android::uirenderer {
+
+/**
+ * Shader implementation that renders a color ramp clockwise such that the start and end colors
+ * are visible at 3 o'clock. This handles converting to either an SkShader or SkImageFilter
+ */
+class SweepGradientShader : public Shader {
+public:
+    SweepGradientShader(float x, float y, const std::vector<SkColor4f>& colors,
+                        const sk_sp<SkColorSpace>& colorspace, const SkScalar pos[],
+                        const uint32_t shaderFlags, const SkMatrix* matrix);
+    virtual ~SweepGradientShader() override;
+
+protected:
+    virtual sk_sp<SkShader> makeSkShader() override;
+
+private:
+    sk_sp<SkShader> skShader;
+};
+}  // namespace android::uirenderer
diff --git a/libs/hwui/tests/common/scenes/BitmapShaders.cpp b/libs/hwui/tests/common/scenes/BitmapShaders.cpp
index c4067af..e2c1651 100644
--- a/libs/hwui/tests/common/scenes/BitmapShaders.cpp
+++ b/libs/hwui/tests/common/scenes/BitmapShaders.cpp
@@ -18,6 +18,7 @@
 #include "hwui/Paint.h"
 #include "TestSceneBase.h"
 #include "tests/common/BitmapAllocationTestUtils.h"
+#include <shader/BitmapShader.h>
 #include "utils/Color.h"
 
 class BitmapShaders;
@@ -45,15 +46,24 @@
                 });
 
         Paint paint;
+        sk_sp<BitmapShader> bitmapShader = sk_make_sp<BitmapShader>(
+                    hwuiBitmap->makeImage(),
+                    SkTileMode::kRepeat,
+                    SkTileMode::kRepeat,
+                    nullptr
+                );
+
         sk_sp<SkImage> image = hwuiBitmap->makeImage();
-        sk_sp<SkShader> repeatShader =
-                image->makeShader(SkTileMode::kRepeat, SkTileMode::kRepeat);
-        paint.setShader(std::move(repeatShader));
+        paint.setShader(std::move(bitmapShader));
         canvas.drawRoundRect(0, 0, 500, 500, 50.0f, 50.0f, paint);
 
-        sk_sp<SkShader> mirrorShader =
-                image->makeShader(SkTileMode::kMirror, SkTileMode::kMirror);
-        paint.setShader(std::move(mirrorShader));
+        sk_sp<BitmapShader> mirrorBitmapShader = sk_make_sp<BitmapShader>(
+                    image,
+                    SkTileMode::kMirror,
+                    SkTileMode::kMirror,
+                    nullptr
+                );
+        paint.setShader(std::move(mirrorBitmapShader));
         canvas.drawRoundRect(0, 600, 500, 1100, 50.0f, 50.0f, paint);
     }
 
diff --git a/libs/hwui/tests/common/scenes/HwBitmapInCompositeShader.cpp b/libs/hwui/tests/common/scenes/HwBitmapInCompositeShader.cpp
index 5886ea3..d37bc3c 100644
--- a/libs/hwui/tests/common/scenes/HwBitmapInCompositeShader.cpp
+++ b/libs/hwui/tests/common/scenes/HwBitmapInCompositeShader.cpp
@@ -20,6 +20,10 @@
 #include <SkGradientShader.h>
 #include <SkImagePriv.h>
 #include <ui/PixelFormat.h>
+#include <shader/BitmapShader.h>
+#include <shader/LinearGradientShader.h>
+#include <shader/RadialGradientShader.h>
+#include <shader/ComposeShader.h>
 
 class HwBitmapInCompositeShader;
 
@@ -50,20 +54,41 @@
             pixels[4000 + 4 * i + 3] = 255;
         }
         buffer->unlock();
-        sk_sp<Bitmap> hardwareBitmap(Bitmap::createFrom(buffer->toAHardwareBuffer(),
-                                                        SkColorSpace::MakeSRGB()));
-        sk_sp<SkShader> hardwareShader(createBitmapShader(*hardwareBitmap));
+
+        sk_sp<BitmapShader> bitmapShader = sk_make_sp<BitmapShader>(
+                Bitmap::createFrom(
+                        buffer->toAHardwareBuffer(),
+                        SkColorSpace::MakeSRGB()
+                )->makeImage(),
+                SkTileMode::kClamp,
+                SkTileMode::kClamp,
+                nullptr
+        );
 
         SkPoint center;
         center.set(50, 50);
-        SkColor colors[2];
-        colors[0] = Color::Black;
-        colors[1] = Color::White;
-        sk_sp<SkShader> gradientShader = SkGradientShader::MakeRadial(
-                center, 50, colors, nullptr, 2, SkTileMode::kRepeat);
 
-        sk_sp<SkShader> compositeShader(
-                SkShaders::Blend(SkBlendMode::kDstATop, hardwareShader, gradientShader));
+        std::vector<SkColor4f> vColors(2);
+        vColors[0] = SkColors::kBlack;
+        vColors[1] = SkColors::kWhite;
+
+        sk_sp<RadialGradientShader> radialShader = sk_make_sp<RadialGradientShader>(
+                center,
+                50,
+                vColors,
+                SkColorSpace::MakeSRGB(),
+                nullptr,
+                SkTileMode::kRepeat,
+                0,
+                nullptr
+            );
+
+        sk_sp<ComposeShader> compositeShader = sk_make_sp<ComposeShader>(
+                    *bitmapShader.get(),
+                    *radialShader.get(),
+                    SkBlendMode::kDstATop,
+                    nullptr
+                );
 
         Paint paint;
         paint.setShader(std::move(compositeShader));
diff --git a/libs/hwui/tests/common/scenes/ListOfFadedTextAnimation.cpp b/libs/hwui/tests/common/scenes/ListOfFadedTextAnimation.cpp
index a9449b6..76e39de 100644
--- a/libs/hwui/tests/common/scenes/ListOfFadedTextAnimation.cpp
+++ b/libs/hwui/tests/common/scenes/ListOfFadedTextAnimation.cpp
@@ -17,7 +17,8 @@
 #include "TestSceneBase.h"
 #include "tests/common/TestListViewSceneBase.h"
 #include "hwui/Paint.h"
-#include <SkGradientShader.h>
+#include "SkColor.h"
+#include <shader/LinearGradientShader.h>
 
 class ListOfFadedTextAnimation;
 
@@ -42,15 +43,26 @@
         pts[0].set(0, 0);
         pts[1].set(0, 1);
 
-        SkColor colors[2] = {Color::Black, Color::Transparent};
-        sk_sp<SkShader> s(
-                SkGradientShader::MakeLinear(pts, colors, NULL, 2, SkTileMode::kClamp));
-
         SkMatrix matrix;
         matrix.setScale(1, length);
         matrix.postRotate(-90);
+
+        std::vector<SkColor4f> vColors(2);
+        vColors[0] = SkColors::kBlack;
+        vColors[1] = SkColors::kTransparent;
+
+        sk_sp<LinearGradientShader> linearGradientShader = sk_make_sp<LinearGradientShader>(
+                    pts,
+                    vColors,
+                    SkColorSpace::MakeSRGB(),
+                    nullptr,
+                    SkTileMode::kClamp,
+                    0,
+                    &matrix
+                );
+
         Paint fadingPaint;
-        fadingPaint.setShader(s->makeWithLocalMatrix(matrix));
+        fadingPaint.setShader(linearGradientShader);
         fadingPaint.setBlendMode(SkBlendMode::kDstOut);
         canvas.drawRect(0, 0, length, itemHeight, fadingPaint);
         canvas.restore();
diff --git a/libs/hwui/tests/common/scenes/SimpleColorMatrixAnimation.cpp b/libs/hwui/tests/common/scenes/SimpleColorMatrixAnimation.cpp
index a0bc5aa..bdc157f 100644
--- a/libs/hwui/tests/common/scenes/SimpleColorMatrixAnimation.cpp
+++ b/libs/hwui/tests/common/scenes/SimpleColorMatrixAnimation.cpp
@@ -17,7 +17,7 @@
 #include "TestSceneBase.h"
 
 #include <SkColorMatrixFilter.h>
-#include <SkGradientShader.h>
+#include <shader/LinearGradientShader.h>
 
 class SimpleColorMatrixAnimation;
 
@@ -65,9 +65,12 @@
                     // enough renderer might apply it directly to the paint color)
                     float pos[] = {0, 1};
                     SkPoint pts[] = {SkPoint::Make(0, 0), SkPoint::Make(width, height)};
-                    SkColor colors[2] = {Color::DeepPurple_500, Color::DeepOrange_500};
-                    paint.setShader(SkGradientShader::MakeLinear(pts, colors, pos, 2,
-                                                                 SkTileMode::kClamp));
+                    std::vector<SkColor4f> colors(2);
+                    colors[0] = SkColor4f::FromColor(Color::DeepPurple_500);
+                    colors[1] = SkColor4f::FromColor(Color::DeepOrange_500);
+                    paint.setShader(sk_make_sp<LinearGradientShader>(
+                            pts, colors, SkColorSpace::MakeSRGB(), pos, SkTileMode::kClamp,
+                            0, nullptr));
 
                     // overdraw several times to emphasize shader cost
                     for (int i = 0; i < 10; i++) {
diff --git a/libs/hwui/tests/common/scenes/SimpleGradientAnimation.cpp b/libs/hwui/tests/common/scenes/SimpleGradientAnimation.cpp
index 57a260c..9a15c9d 100644
--- a/libs/hwui/tests/common/scenes/SimpleGradientAnimation.cpp
+++ b/libs/hwui/tests/common/scenes/SimpleGradientAnimation.cpp
@@ -17,6 +17,7 @@
 #include "TestSceneBase.h"
 
 #include <SkGradientShader.h>
+#include <shader/LinearGradientShader.h>
 
 class SimpleGradientAnimation;
 
@@ -55,9 +56,24 @@
                     // overdraw several times to emphasize shader cost
                     for (int i = 0; i < 10; i++) {
                         // use i%2 start position to pick 2 color combo with black in it
-                        SkColor colors[3] = {Color::Transparent, Color::Black, Color::Cyan_500};
-                        paint.setShader(SkGradientShader::MakeLinear(pts, colors + (i % 2), pos, 2,
-                                                                     SkTileMode::kClamp));
+                        std::vector<SkColor4f> vColors(2);
+                        vColors[0] = ((i % 2) == 0) ?
+                                SkColor4f::FromColor(Color::Transparent) :
+                                SkColor4f::FromColor(Color::Black);
+                        vColors[1] = (((i + 1) % 2) == 0) ?
+                                SkColor4f::FromColor(Color::Black) :
+                                SkColor4f::FromColor(Color::Cyan_500);
+
+                        sk_sp<LinearGradientShader> gradient = sk_make_sp<LinearGradientShader>(
+                                    pts,
+                                    vColors,
+                                    SkColorSpace::MakeSRGB(),
+                                    pos,
+                                    SkTileMode::kClamp,
+                                    0,
+                                    nullptr
+                                );
+                        paint.setShader(gradient);
                         canvas.drawRect(i, i, width, height, paint);
                     }
                 });
diff --git a/libs/hwui/tests/unit/VectorDrawableTests.cpp b/libs/hwui/tests/unit/VectorDrawableTests.cpp
index 6d4c574..5e56b26 100644
--- a/libs/hwui/tests/unit/VectorDrawableTests.cpp
+++ b/libs/hwui/tests/unit/VectorDrawableTests.cpp
@@ -17,9 +17,14 @@
 #include <gtest/gtest.h>
 
 #include "PathParser.h"
+#include "GraphicsJNI.h"
+#include "SkGradientShader.h"
+#include "SkShader.h"
 #include "VectorDrawable.h"
 #include "utils/MathUtils.h"
 #include "utils/VectorDrawableUtils.h"
+#include <shader/Shader.h>
+#include <shader/LinearGradientShader.h>
 
 #include <functional>
 
@@ -395,7 +400,21 @@
     bitmap.allocN32Pixels(5, 5, false);
     SkCanvas canvas(bitmap);
 
-    sk_sp<SkShader> shader = SkShaders::Color(SK_ColorBLACK);
+    SkPoint pts[2];
+    pts[0].set(0, 0);
+    pts[1].set(0, 0);
+
+    std::vector<SkColor4f> colors(2);
+    colors[0] = SkColors::kBlack;
+    colors[1] = SkColors::kBlack;
+
+    sk_sp<LinearGradientShader> shader = sk_sp(new LinearGradientShader(pts,
+            colors,
+            SkColorSpace::MakeSRGB(),
+            nullptr,
+            SkTileMode::kClamp,
+            SkGradientShader::kInterpolateColorsInPremul_Flag,
+            nullptr));
     // Initial ref count is 1
     EXPECT_TRUE(shader->unique());
 
diff --git a/libs/hwui/utils/Color.cpp b/libs/hwui/utils/Color.cpp
index eff34a8..87512f0 100644
--- a/libs/hwui/utils/Color.cpp
+++ b/libs/hwui/utils/Color.cpp
@@ -26,6 +26,7 @@
 
 #include <algorithm>
 #include <cmath>
+#include <Properties.h>
 
 namespace android {
 namespace uirenderer {
@@ -344,13 +345,9 @@
             static_cast<uint8_t>(rgb.b * 255));
 }
 
-// Note that SkColorSpace doesn't have the notion of an unspecified SDR white
-// level.
-static constexpr float kDefaultSDRWhiteLevel = 150.f;
-
 skcms_TransferFunction GetPQSkTransferFunction(float sdr_white_level) {
     if (sdr_white_level <= 0.f) {
-        sdr_white_level = kDefaultSDRWhiteLevel;
+        sdr_white_level = Properties::defaultSdrWhitePoint;
     }
     // The generic PQ transfer function produces normalized luminance values i.e.
     // the range 0-1 represents 0-10000 nits for the reference display, but we
diff --git a/libs/input/Android.bp b/libs/input/Android.bp
index 5252cd0..dca3501 100644
--- a/libs/input/Android.bp
+++ b/libs/input/Android.bp
@@ -16,6 +16,9 @@
     name: "libinputservice",
     srcs: [
         "PointerController.cpp",
+        "PointerControllerContext.cpp",
+        "MouseCursorController.cpp",
+        "TouchSpotController.cpp",
         "SpriteController.cpp",
         "SpriteIcon.cpp",
     ],
diff --git a/libs/input/MouseCursorController.cpp b/libs/input/MouseCursorController.cpp
new file mode 100644
index 0000000..80b555b
--- /dev/null
+++ b/libs/input/MouseCursorController.cpp
@@ -0,0 +1,460 @@
+/*
+ * 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.
+ */
+
+#define LOG_TAG "MouseCursorController"
+//#define LOG_NDEBUG 0
+
+// Log debug messages about pointer updates
+#define DEBUG_MOUSE_CURSOR_UPDATES 0
+
+#include "MouseCursorController.h"
+
+#include <log/log.h>
+
+#include <SkBitmap.h>
+#include <SkBlendMode.h>
+#include <SkCanvas.h>
+#include <SkColor.h>
+#include <SkPaint.h>
+
+namespace {
+// Time to spend fading out the pointer completely.
+const nsecs_t POINTER_FADE_DURATION = 500 * 1000000LL; // 500 ms
+} // namespace
+
+namespace android {
+
+// --- MouseCursorController ---
+
+MouseCursorController::MouseCursorController(PointerControllerContext& context)
+      : mContext(context) {
+    std::scoped_lock lock(mLock);
+
+    mLocked.animationFrameIndex = 0;
+    mLocked.lastFrameUpdatedTime = 0;
+
+    mLocked.pointerFadeDirection = 0;
+    mLocked.pointerX = 0;
+    mLocked.pointerY = 0;
+    mLocked.pointerAlpha = 0.0f; // pointer is initially faded
+    mLocked.pointerSprite = mContext.getSpriteController()->createSprite();
+    mLocked.updatePointerIcon = false;
+    mLocked.requestedPointerType = mContext.getPolicy()->getDefaultPointerIconId();
+
+    mLocked.resourcesLoaded = false;
+
+    mLocked.buttonState = 0;
+}
+
+MouseCursorController::~MouseCursorController() {
+    std::scoped_lock lock(mLock);
+
+    mLocked.pointerSprite.clear();
+}
+
+bool MouseCursorController::getBounds(float* outMinX, float* outMinY, float* outMaxX,
+                                      float* outMaxY) const {
+    std::scoped_lock lock(mLock);
+
+    return getBoundsLocked(outMinX, outMinY, outMaxX, outMaxY);
+}
+
+bool MouseCursorController::getBoundsLocked(float* outMinX, float* outMinY, float* outMaxX,
+                                            float* outMaxY) const REQUIRES(mLock) {
+    if (!mLocked.viewport.isValid()) {
+        return false;
+    }
+
+    *outMinX = mLocked.viewport.logicalLeft;
+    *outMinY = mLocked.viewport.logicalTop;
+    *outMaxX = mLocked.viewport.logicalRight - 1;
+    *outMaxY = mLocked.viewport.logicalBottom - 1;
+    return true;
+}
+
+void MouseCursorController::move(float deltaX, float deltaY) {
+#if DEBUG_MOUSE_CURSOR_UPDATES
+    ALOGD("Move pointer by deltaX=%0.3f, deltaY=%0.3f", deltaX, deltaY);
+#endif
+    if (deltaX == 0.0f && deltaY == 0.0f) {
+        return;
+    }
+
+    std::scoped_lock lock(mLock);
+
+    setPositionLocked(mLocked.pointerX + deltaX, mLocked.pointerY + deltaY);
+}
+
+void MouseCursorController::setButtonState(int32_t buttonState) {
+#if DEBUG_MOUSE_CURSOR_UPDATES
+    ALOGD("Set button state 0x%08x", buttonState);
+#endif
+    std::scoped_lock lock(mLock);
+
+    if (mLocked.buttonState != buttonState) {
+        mLocked.buttonState = buttonState;
+    }
+}
+
+int32_t MouseCursorController::getButtonState() const {
+    std::scoped_lock lock(mLock);
+    return mLocked.buttonState;
+}
+
+void MouseCursorController::setPosition(float x, float y) {
+#if DEBUG_MOUSE_CURSOR_UPDATES
+    ALOGD("Set pointer position to x=%0.3f, y=%0.3f", x, y);
+#endif
+    std::scoped_lock lock(mLock);
+    setPositionLocked(x, y);
+}
+
+void MouseCursorController::setPositionLocked(float x, float y) REQUIRES(mLock) {
+    float minX, minY, maxX, maxY;
+    if (getBoundsLocked(&minX, &minY, &maxX, &maxY)) {
+        if (x <= minX) {
+            mLocked.pointerX = minX;
+        } else if (x >= maxX) {
+            mLocked.pointerX = maxX;
+        } else {
+            mLocked.pointerX = x;
+        }
+        if (y <= minY) {
+            mLocked.pointerY = minY;
+        } else if (y >= maxY) {
+            mLocked.pointerY = maxY;
+        } else {
+            mLocked.pointerY = y;
+        }
+        updatePointerLocked();
+    }
+}
+
+void MouseCursorController::getPosition(float* outX, float* outY) const {
+    std::scoped_lock lock(mLock);
+
+    *outX = mLocked.pointerX;
+    *outY = mLocked.pointerY;
+}
+
+int32_t MouseCursorController::getDisplayId() const {
+    std::scoped_lock lock(mLock);
+    return mLocked.viewport.displayId;
+}
+
+void MouseCursorController::fade(PointerControllerInterface::Transition transition) {
+    std::scoped_lock lock(mLock);
+
+    // Remove the inactivity timeout, since we are fading now.
+    mContext.removeInactivityTimeout();
+
+    // Start fading.
+    if (transition == PointerControllerInterface::Transition::IMMEDIATE) {
+        mLocked.pointerFadeDirection = 0;
+        mLocked.pointerAlpha = 0.0f;
+        updatePointerLocked();
+    } else {
+        mLocked.pointerFadeDirection = -1;
+        mContext.startAnimation();
+    }
+}
+
+void MouseCursorController::unfade(PointerControllerInterface::Transition transition) {
+    std::scoped_lock lock(mLock);
+
+    // Always reset the inactivity timer.
+    mContext.resetInactivityTimeout();
+
+    // Start unfading.
+    if (transition == PointerControllerInterface::Transition::IMMEDIATE) {
+        mLocked.pointerFadeDirection = 0;
+        mLocked.pointerAlpha = 1.0f;
+        updatePointerLocked();
+    } else {
+        mLocked.pointerFadeDirection = 1;
+        mContext.startAnimation();
+    }
+}
+
+void MouseCursorController::reloadPointerResources(bool getAdditionalMouseResources) {
+    std::scoped_lock lock(mLock);
+
+    loadResourcesLocked(getAdditionalMouseResources);
+    updatePointerLocked();
+}
+
+/**
+ * The viewport values for deviceHeight and deviceWidth have already been adjusted for rotation,
+ * so here we are getting the dimensions in the original, unrotated orientation (orientation 0).
+ */
+static void getNonRotatedSize(const DisplayViewport& viewport, int32_t& width, int32_t& height) {
+    width = viewport.deviceWidth;
+    height = viewport.deviceHeight;
+
+    if (viewport.orientation == DISPLAY_ORIENTATION_90 ||
+        viewport.orientation == DISPLAY_ORIENTATION_270) {
+        std::swap(width, height);
+    }
+}
+
+void MouseCursorController::setDisplayViewport(const DisplayViewport& viewport,
+                                               bool getAdditionalMouseResources) {
+    std::scoped_lock lock(mLock);
+
+    if (viewport == mLocked.viewport) {
+        return;
+    }
+
+    const DisplayViewport oldViewport = mLocked.viewport;
+    mLocked.viewport = viewport;
+
+    int32_t oldDisplayWidth, oldDisplayHeight;
+    getNonRotatedSize(oldViewport, oldDisplayWidth, oldDisplayHeight);
+    int32_t newDisplayWidth, newDisplayHeight;
+    getNonRotatedSize(viewport, newDisplayWidth, newDisplayHeight);
+
+    // Reset cursor position to center if size or display changed.
+    if (oldViewport.displayId != viewport.displayId || oldDisplayWidth != newDisplayWidth ||
+        oldDisplayHeight != newDisplayHeight) {
+        float minX, minY, maxX, maxY;
+        if (getBoundsLocked(&minX, &minY, &maxX, &maxY)) {
+            mLocked.pointerX = (minX + maxX) * 0.5f;
+            mLocked.pointerY = (minY + maxY) * 0.5f;
+            // Reload icon resources for density may be changed.
+            loadResourcesLocked(getAdditionalMouseResources);
+        } else {
+            mLocked.pointerX = 0;
+            mLocked.pointerY = 0;
+        }
+    } else if (oldViewport.orientation != viewport.orientation) {
+        // Apply offsets to convert from the pixel top-left corner position to the pixel center.
+        // This creates an invariant frame of reference that we can easily rotate when
+        // taking into account that the pointer may be located at fractional pixel offsets.
+        float x = mLocked.pointerX + 0.5f;
+        float y = mLocked.pointerY + 0.5f;
+        float temp;
+
+        // Undo the previous rotation.
+        switch (oldViewport.orientation) {
+            case DISPLAY_ORIENTATION_90:
+                temp = x;
+                x = oldViewport.deviceHeight - y;
+                y = temp;
+                break;
+            case DISPLAY_ORIENTATION_180:
+                x = oldViewport.deviceWidth - x;
+                y = oldViewport.deviceHeight - y;
+                break;
+            case DISPLAY_ORIENTATION_270:
+                temp = x;
+                x = y;
+                y = oldViewport.deviceWidth - temp;
+                break;
+        }
+
+        // Perform the new rotation.
+        switch (viewport.orientation) {
+            case DISPLAY_ORIENTATION_90:
+                temp = x;
+                x = y;
+                y = viewport.deviceHeight - temp;
+                break;
+            case DISPLAY_ORIENTATION_180:
+                x = viewport.deviceWidth - x;
+                y = viewport.deviceHeight - y;
+                break;
+            case DISPLAY_ORIENTATION_270:
+                temp = x;
+                x = viewport.deviceWidth - y;
+                y = temp;
+                break;
+        }
+
+        // Apply offsets to convert from the pixel center to the pixel top-left corner position
+        // and save the results.
+        mLocked.pointerX = x - 0.5f;
+        mLocked.pointerY = y - 0.5f;
+    }
+
+    updatePointerLocked();
+}
+
+void MouseCursorController::updatePointerIcon(int32_t iconId) {
+    std::scoped_lock lock(mLock);
+
+    if (mLocked.requestedPointerType != iconId) {
+        mLocked.requestedPointerType = iconId;
+        mLocked.updatePointerIcon = true;
+        updatePointerLocked();
+    }
+}
+
+void MouseCursorController::setCustomPointerIcon(const SpriteIcon& icon) {
+    std::scoped_lock lock(mLock);
+
+    const int32_t iconId = mContext.getPolicy()->getCustomPointerIconId();
+    mLocked.additionalMouseResources[iconId] = icon;
+    mLocked.requestedPointerType = iconId;
+    mLocked.updatePointerIcon = true;
+    updatePointerLocked();
+}
+
+bool MouseCursorController::doFadingAnimation(nsecs_t timestamp, bool keepAnimating) {
+    nsecs_t frameDelay = timestamp - mContext.getAnimationTime();
+
+    std::scoped_lock lock(mLock);
+
+    // Animate pointer fade.
+    if (mLocked.pointerFadeDirection < 0) {
+        mLocked.pointerAlpha -= float(frameDelay) / POINTER_FADE_DURATION;
+        if (mLocked.pointerAlpha <= 0.0f) {
+            mLocked.pointerAlpha = 0.0f;
+            mLocked.pointerFadeDirection = 0;
+        } else {
+            keepAnimating = true;
+        }
+        updatePointerLocked();
+    } else if (mLocked.pointerFadeDirection > 0) {
+        mLocked.pointerAlpha += float(frameDelay) / POINTER_FADE_DURATION;
+        if (mLocked.pointerAlpha >= 1.0f) {
+            mLocked.pointerAlpha = 1.0f;
+            mLocked.pointerFadeDirection = 0;
+        } else {
+            keepAnimating = true;
+        }
+        updatePointerLocked();
+    }
+
+    return keepAnimating;
+}
+
+bool MouseCursorController::doBitmapAnimation(nsecs_t timestamp) {
+    std::scoped_lock lock(mLock);
+
+    std::map<int32_t, PointerAnimation>::const_iterator iter =
+            mLocked.animationResources.find(mLocked.requestedPointerType);
+    if (iter == mLocked.animationResources.end()) {
+        return false;
+    }
+
+    if (timestamp - mLocked.lastFrameUpdatedTime > iter->second.durationPerFrame) {
+        sp<SpriteController> spriteController = mContext.getSpriteController();
+        spriteController->openTransaction();
+
+        int incr = (timestamp - mLocked.lastFrameUpdatedTime) / iter->second.durationPerFrame;
+        mLocked.animationFrameIndex += incr;
+        mLocked.lastFrameUpdatedTime += iter->second.durationPerFrame * incr;
+        while (mLocked.animationFrameIndex >= iter->second.animationFrames.size()) {
+            mLocked.animationFrameIndex -= iter->second.animationFrames.size();
+        }
+        mLocked.pointerSprite->setIcon(iter->second.animationFrames[mLocked.animationFrameIndex]);
+
+        spriteController->closeTransaction();
+    }
+
+    // Keep animating.
+    return true;
+}
+
+void MouseCursorController::updatePointerLocked() REQUIRES(mLock) {
+    if (!mLocked.viewport.isValid()) {
+        return;
+    }
+    sp<SpriteController> spriteController = mContext.getSpriteController();
+    spriteController->openTransaction();
+
+    mLocked.pointerSprite->setLayer(Sprite::BASE_LAYER_POINTER);
+    mLocked.pointerSprite->setPosition(mLocked.pointerX, mLocked.pointerY);
+    mLocked.pointerSprite->setDisplayId(mLocked.viewport.displayId);
+
+    if (mLocked.pointerAlpha > 0) {
+        mLocked.pointerSprite->setAlpha(mLocked.pointerAlpha);
+        mLocked.pointerSprite->setVisible(true);
+    } else {
+        mLocked.pointerSprite->setVisible(false);
+    }
+
+    if (mLocked.updatePointerIcon) {
+        if (mLocked.requestedPointerType == mContext.getPolicy()->getDefaultPointerIconId()) {
+            mLocked.pointerSprite->setIcon(mLocked.pointerIcon);
+        } else {
+            std::map<int32_t, SpriteIcon>::const_iterator iter =
+                    mLocked.additionalMouseResources.find(mLocked.requestedPointerType);
+            if (iter != mLocked.additionalMouseResources.end()) {
+                std::map<int32_t, PointerAnimation>::const_iterator anim_iter =
+                        mLocked.animationResources.find(mLocked.requestedPointerType);
+                if (anim_iter != mLocked.animationResources.end()) {
+                    mLocked.animationFrameIndex = 0;
+                    mLocked.lastFrameUpdatedTime = systemTime(SYSTEM_TIME_MONOTONIC);
+                    mContext.startAnimation();
+                }
+                mLocked.pointerSprite->setIcon(iter->second);
+            } else {
+                ALOGW("Can't find the resource for icon id %d", mLocked.requestedPointerType);
+                mLocked.pointerSprite->setIcon(mLocked.pointerIcon);
+            }
+        }
+        mLocked.updatePointerIcon = false;
+    }
+
+    spriteController->closeTransaction();
+}
+
+void MouseCursorController::loadResourcesLocked(bool getAdditionalMouseResources) REQUIRES(mLock) {
+    if (!mLocked.viewport.isValid()) {
+        return;
+    }
+
+    if (!mLocked.resourcesLoaded) mLocked.resourcesLoaded = true;
+
+    sp<PointerControllerPolicyInterface> policy = mContext.getPolicy();
+    policy->loadPointerResources(&mResources, mLocked.viewport.displayId);
+    policy->loadPointerIcon(&mLocked.pointerIcon, mLocked.viewport.displayId);
+
+    mLocked.additionalMouseResources.clear();
+    mLocked.animationResources.clear();
+    if (getAdditionalMouseResources) {
+        policy->loadAdditionalMouseResources(&mLocked.additionalMouseResources,
+                                             &mLocked.animationResources,
+                                             mLocked.viewport.displayId);
+    }
+
+    mLocked.updatePointerIcon = true;
+}
+
+bool MouseCursorController::isViewportValid() {
+    std::scoped_lock lock(mLock);
+    return mLocked.viewport.isValid();
+}
+
+void MouseCursorController::getAdditionalMouseResources() {
+    std::scoped_lock lock(mLock);
+
+    if (mLocked.additionalMouseResources.empty()) {
+        mContext.getPolicy()->loadAdditionalMouseResources(&mLocked.additionalMouseResources,
+                                                           &mLocked.animationResources,
+                                                           mLocked.viewport.displayId);
+    }
+    mLocked.updatePointerIcon = true;
+    updatePointerLocked();
+}
+
+bool MouseCursorController::resourcesLoaded() {
+    std::scoped_lock lock(mLock);
+    return mLocked.resourcesLoaded;
+}
+
+} // namespace android
diff --git a/libs/input/MouseCursorController.h b/libs/input/MouseCursorController.h
new file mode 100644
index 0000000..448165b
--- /dev/null
+++ b/libs/input/MouseCursorController.h
@@ -0,0 +1,111 @@
+/*
+ * 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.
+ */
+
+#ifndef _UI_MOUSE_CURSOR_CONTROLLER_H
+#define _UI_MOUSE_CURSOR_CONTROLLER_H
+
+#include <gui/DisplayEventReceiver.h>
+#include <input/DisplayViewport.h>
+#include <input/Input.h>
+#include <ui/DisplayInfo.h>
+#include <utils/BitSet.h>
+#include <utils/Looper.h>
+#include <utils/RefBase.h>
+
+#include <map>
+#include <memory>
+#include <vector>
+
+#include "PointerControllerContext.h"
+#include "SpriteController.h"
+
+namespace android {
+
+/*
+ * Helper class for PointerController that specifically handles
+ * mouse cursor resources and actions.
+ */
+class MouseCursorController {
+public:
+    MouseCursorController(PointerControllerContext& context);
+    ~MouseCursorController();
+
+    bool getBounds(float* outMinX, float* outMinY, float* outMaxX, float* outMaxY) const;
+    void move(float deltaX, float deltaY);
+    void setButtonState(int32_t buttonState);
+    int32_t getButtonState() const;
+    void setPosition(float x, float y);
+    void getPosition(float* outX, float* outY) const;
+    int32_t getDisplayId() const;
+    void fade(PointerControllerInterface::Transition transition);
+    void unfade(PointerControllerInterface::Transition transition);
+    void setDisplayViewport(const DisplayViewport& viewport, bool getAdditionalMouseResources);
+
+    void updatePointerIcon(int32_t iconId);
+    void setCustomPointerIcon(const SpriteIcon& icon);
+    void reloadPointerResources(bool getAdditionalMouseResources);
+
+    void getAdditionalMouseResources();
+    bool isViewportValid();
+
+    bool doBitmapAnimation(nsecs_t timestamp);
+    bool doFadingAnimation(nsecs_t timestamp, bool keepAnimating);
+
+    bool resourcesLoaded();
+
+private:
+    mutable std::mutex mLock;
+
+    PointerResources mResources;
+
+    PointerControllerContext& mContext;
+
+    struct Locked {
+        DisplayViewport viewport;
+
+        size_t animationFrameIndex;
+        nsecs_t lastFrameUpdatedTime;
+
+        int32_t pointerFadeDirection;
+        float pointerX;
+        float pointerY;
+        float pointerAlpha;
+        sp<Sprite> pointerSprite;
+        SpriteIcon pointerIcon;
+        bool updatePointerIcon;
+
+        bool resourcesLoaded;
+
+        std::map<int32_t, SpriteIcon> additionalMouseResources;
+        std::map<int32_t, PointerAnimation> animationResources;
+
+        int32_t requestedPointerType;
+
+        int32_t buttonState;
+
+    } mLocked GUARDED_BY(mLock);
+
+    bool getBoundsLocked(float* outMinX, float* outMinY, float* outMaxX, float* outMaxY) const;
+    void setPositionLocked(float x, float y);
+
+    void updatePointerLocked();
+
+    void loadResourcesLocked(bool getAdditionalMouseResources);
+};
+
+} // namespace android
+
+#endif // _UI_MOUSE_CURSOR_CONTROLLER_H
diff --git a/libs/input/PointerController.cpp b/libs/input/PointerController.cpp
index 5e480a6..14c96ce 100644
--- a/libs/input/PointerController.cpp
+++ b/libs/input/PointerController.cpp
@@ -21,31 +21,26 @@
 #define DEBUG_POINTER_UPDATES 0
 
 #include "PointerController.h"
+#include "MouseCursorController.h"
+#include "PointerControllerContext.h"
+#include "TouchSpotController.h"
 
 #include <log/log.h>
 
-#include <memory>
+#include <SkBitmap.h>
+#include <SkBlendMode.h>
+#include <SkCanvas.h>
+#include <SkColor.h>
+#include <SkPaint.h>
 
 namespace android {
 
 // --- PointerController ---
 
-// Time to wait before starting the fade when the pointer is inactive.
-static const nsecs_t INACTIVITY_TIMEOUT_DELAY_TIME_NORMAL = 15 * 1000 * 1000000LL; // 15 seconds
-static const nsecs_t INACTIVITY_TIMEOUT_DELAY_TIME_SHORT = 3 * 1000 * 1000000LL; // 3 seconds
-
-// Time to spend fading out the spot completely.
-static const nsecs_t SPOT_FADE_DURATION = 200 * 1000000LL; // 200 ms
-
-// Time to spend fading out the pointer completely.
-static const nsecs_t POINTER_FADE_DURATION = 500 * 1000000LL; // 500 ms
-
-// The number of events to be read at once for DisplayEventReceiver.
-static const int EVENT_BUFFER_SIZE = 100;
-
 std::shared_ptr<PointerController> PointerController::create(
         const sp<PointerControllerPolicyInterface>& policy, const sp<Looper>& looper,
         const sp<SpriteController>& spriteController) {
+    // using 'new' to access non-public constructor
     std::shared_ptr<PointerController> controller = std::shared_ptr<PointerController>(
             new PointerController(policy, looper, spriteController));
 
@@ -60,758 +55,175 @@
      * weak_ptr's which themselves cannot be constructed until there's at least one shared_ptr.
      */
 
-    controller->mHandler->pointerController = controller;
-    controller->mCallback->pointerController = controller;
-    if (controller->mDisplayEventReceiver.initCheck() == NO_ERROR) {
-        controller->mLooper->addFd(controller->mDisplayEventReceiver.getFd(), Looper::POLL_CALLBACK,
-                                   Looper::EVENT_INPUT, controller->mCallback, nullptr);
-    } else {
-        ALOGE("Failed to initialize DisplayEventReceiver.");
-    }
+    controller->mContext.setHandlerController(controller);
+    controller->mContext.setCallbackController(controller);
+    controller->mContext.initializeDisplayEventReceiver();
     return controller;
 }
 
 PointerController::PointerController(const sp<PointerControllerPolicyInterface>& policy,
                                      const sp<Looper>& looper,
                                      const sp<SpriteController>& spriteController)
-      : mPolicy(policy),
-        mLooper(looper),
-        mSpriteController(spriteController),
-        mHandler(new MessageHandler()),
-        mCallback(new LooperCallback()) {
-    AutoMutex _l(mLock);
-
-    mLocked.animationPending = false;
-
-    mLocked.presentation = Presentation::POINTER;
-    mLocked.presentationChanged = false;
-
-    mLocked.inactivityTimeout = InactivityTimeout::NORMAL;
-
-    mLocked.pointerFadeDirection = 0;
-    mLocked.pointerX = 0;
-    mLocked.pointerY = 0;
-    mLocked.pointerAlpha = 0.0f; // pointer is initially faded
-    mLocked.pointerSprite = mSpriteController->createSprite();
-    mLocked.pointerIconChanged = false;
-    mLocked.requestedPointerType = mPolicy->getDefaultPointerIconId();
-
-    mLocked.animationFrameIndex = 0;
-    mLocked.lastFrameUpdatedTime = 0;
-
-    mLocked.buttonState = 0;
+      : mContext(policy, looper, spriteController, *this), mCursorController(mContext) {
+    std::scoped_lock lock(mLock);
+    mLocked.presentation = Presentation::SPOT;
 }
 
-PointerController::~PointerController() {
-    mLooper->removeMessages(mHandler);
-
-    AutoMutex _l(mLock);
-
-    mLocked.pointerSprite.clear();
-
-    for (auto& it : mLocked.spotsByDisplay) {
-        const std::vector<Spot*>& spots = it.second;
-        size_t numSpots = spots.size();
-        for (size_t i = 0; i < numSpots; i++) {
-            delete spots[i];
-        }
-    }
-    mLocked.spotsByDisplay.clear();
-    mLocked.recycledSprites.clear();
-}
-
-bool PointerController::getBounds(float* outMinX, float* outMinY,
-        float* outMaxX, float* outMaxY) const {
-    AutoMutex _l(mLock);
-
-    return getBoundsLocked(outMinX, outMinY, outMaxX, outMaxY);
-}
-
-bool PointerController::getBoundsLocked(float* outMinX, float* outMinY,
-        float* outMaxX, float* outMaxY) const {
-
-    if (!mLocked.viewport.isValid()) {
-        return false;
-    }
-
-    *outMinX = mLocked.viewport.logicalLeft;
-    *outMinY = mLocked.viewport.logicalTop;
-    *outMaxX = mLocked.viewport.logicalRight - 1;
-    *outMaxY = mLocked.viewport.logicalBottom - 1;
-    return true;
+bool PointerController::getBounds(float* outMinX, float* outMinY, float* outMaxX,
+                                  float* outMaxY) const {
+    return mCursorController.getBounds(outMinX, outMinY, outMaxX, outMaxY);
 }
 
 void PointerController::move(float deltaX, float deltaY) {
-#if DEBUG_POINTER_UPDATES
-    ALOGD("Move pointer by deltaX=%0.3f, deltaY=%0.3f", deltaX, deltaY);
-#endif
-    if (deltaX == 0.0f && deltaY == 0.0f) {
-        return;
-    }
-
-    AutoMutex _l(mLock);
-
-    setPositionLocked(mLocked.pointerX + deltaX, mLocked.pointerY + deltaY);
+    mCursorController.move(deltaX, deltaY);
 }
 
 void PointerController::setButtonState(int32_t buttonState) {
-#if DEBUG_POINTER_UPDATES
-    ALOGD("Set button state 0x%08x", buttonState);
-#endif
-    AutoMutex _l(mLock);
-
-    if (mLocked.buttonState != buttonState) {
-        mLocked.buttonState = buttonState;
-    }
+    mCursorController.setButtonState(buttonState);
 }
 
 int32_t PointerController::getButtonState() const {
-    AutoMutex _l(mLock);
-
-    return mLocked.buttonState;
+    return mCursorController.getButtonState();
 }
 
 void PointerController::setPosition(float x, float y) {
-#if DEBUG_POINTER_UPDATES
-    ALOGD("Set pointer position to x=%0.3f, y=%0.3f", x, y);
-#endif
-    AutoMutex _l(mLock);
-
-    setPositionLocked(x, y);
-}
-
-void PointerController::setPositionLocked(float x, float y) {
-    float minX, minY, maxX, maxY;
-    if (getBoundsLocked(&minX, &minY, &maxX, &maxY)) {
-        if (x <= minX) {
-            mLocked.pointerX = minX;
-        } else if (x >= maxX) {
-            mLocked.pointerX = maxX;
-        } else {
-            mLocked.pointerX = x;
-        }
-        if (y <= minY) {
-            mLocked.pointerY = minY;
-        } else if (y >= maxY) {
-            mLocked.pointerY = maxY;
-        } else {
-            mLocked.pointerY = y;
-        }
-        updatePointerLocked();
-    }
+    std::scoped_lock lock(mLock);
+    mCursorController.setPosition(x, y);
 }
 
 void PointerController::getPosition(float* outX, float* outY) const {
-    AutoMutex _l(mLock);
-
-    *outX = mLocked.pointerX;
-    *outY = mLocked.pointerY;
+    mCursorController.getPosition(outX, outY);
 }
 
 int32_t PointerController::getDisplayId() const {
-    AutoMutex _l(mLock);
-
-    return mLocked.viewport.displayId;
+    return mCursorController.getDisplayId();
 }
 
 void PointerController::fade(Transition transition) {
-    AutoMutex _l(mLock);
-
-    // Remove the inactivity timeout, since we are fading now.
-    removeInactivityTimeoutLocked();
-
-    // Start fading.
-    if (transition == Transition::IMMEDIATE) {
-        mLocked.pointerFadeDirection = 0;
-        mLocked.pointerAlpha = 0.0f;
-        updatePointerLocked();
-    } else {
-        mLocked.pointerFadeDirection = -1;
-        startAnimationLocked();
-    }
+    std::scoped_lock lock(mLock);
+    mCursorController.fade(transition);
 }
 
 void PointerController::unfade(Transition transition) {
-    AutoMutex _l(mLock);
-
-    // Always reset the inactivity timer.
-    resetInactivityTimeoutLocked();
-
-    // Start unfading.
-    if (transition == Transition::IMMEDIATE) {
-        mLocked.pointerFadeDirection = 0;
-        mLocked.pointerAlpha = 1.0f;
-        updatePointerLocked();
-    } else {
-        mLocked.pointerFadeDirection = 1;
-        startAnimationLocked();
-    }
+    std::scoped_lock lock(mLock);
+    mCursorController.unfade(transition);
 }
 
 void PointerController::setPresentation(Presentation presentation) {
-    AutoMutex _l(mLock);
+    std::scoped_lock lock(mLock);
 
     if (mLocked.presentation == presentation) {
         return;
     }
 
     mLocked.presentation = presentation;
-    mLocked.presentationChanged = true;
 
-    if (!mLocked.viewport.isValid()) {
+    if (!mCursorController.isViewportValid()) {
         return;
     }
 
     if (presentation == Presentation::POINTER) {
-        if (mLocked.additionalMouseResources.empty()) {
-            mPolicy->loadAdditionalMouseResources(&mLocked.additionalMouseResources,
-                                                  &mLocked.animationResources,
-                                                  mLocked.viewport.displayId);
-        }
-        fadeOutAndReleaseAllSpotsLocked();
-        updatePointerLocked();
+        mCursorController.getAdditionalMouseResources();
+        clearSpotsLocked();
     }
 }
 
-void PointerController::setSpots(const PointerCoords* spotCoords,
-        const uint32_t* spotIdToIndex, BitSet32 spotIdBits, int32_t displayId) {
-#if DEBUG_POINTER_UPDATES
-    ALOGD("setSpots: idBits=%08x", spotIdBits.value);
-    for (BitSet32 idBits(spotIdBits); !idBits.isEmpty(); ) {
-        uint32_t id = idBits.firstMarkedBit();
-        idBits.clearBit(id);
-        const PointerCoords& c = spotCoords[spotIdToIndex[id]];
-        ALOGD(" spot %d: position=(%0.3f, %0.3f), pressure=%0.3f, displayId=%" PRId32 ".", id,
-                c.getAxisValue(AMOTION_EVENT_AXIS_X),
-                c.getAxisValue(AMOTION_EVENT_AXIS_Y),
-                c.getAxisValue(AMOTION_EVENT_AXIS_PRESSURE),
-                displayId);
+void PointerController::setSpots(const PointerCoords* spotCoords, const uint32_t* spotIdToIndex,
+                                 BitSet32 spotIdBits, int32_t displayId) {
+    std::scoped_lock lock(mLock);
+    auto it = mLocked.spotControllers.find(displayId);
+    if (it == mLocked.spotControllers.end()) {
+        mLocked.spotControllers.try_emplace(displayId, displayId, mContext);
     }
-#endif
-
-    AutoMutex _l(mLock);
-    if (!mLocked.viewport.isValid()) {
-        return;
-    }
-
-    std::vector<Spot*> newSpots;
-    std::map<int32_t, std::vector<Spot*>>::const_iterator iter =
-            mLocked.spotsByDisplay.find(displayId);
-    if (iter != mLocked.spotsByDisplay.end()) {
-        newSpots = iter->second;
-    }
-
-    mSpriteController->openTransaction();
-
-    // Add or move spots for fingers that are down.
-    for (BitSet32 idBits(spotIdBits); !idBits.isEmpty(); ) {
-        uint32_t id = idBits.clearFirstMarkedBit();
-        const PointerCoords& c = spotCoords[spotIdToIndex[id]];
-        const SpriteIcon& icon = c.getAxisValue(AMOTION_EVENT_AXIS_PRESSURE) > 0
-                ? mResources.spotTouch : mResources.spotHover;
-        float x = c.getAxisValue(AMOTION_EVENT_AXIS_X);
-        float y = c.getAxisValue(AMOTION_EVENT_AXIS_Y);
-
-        Spot* spot = getSpot(id, newSpots);
-        if (!spot) {
-            spot = createAndAddSpotLocked(id, newSpots);
-        }
-
-        spot->updateSprite(&icon, x, y, displayId);
-    }
-
-    // Remove spots for fingers that went up.
-    for (size_t i = 0; i < newSpots.size(); i++) {
-        Spot* spot = newSpots[i];
-        if (spot->id != Spot::INVALID_ID
-                && !spotIdBits.hasBit(spot->id)) {
-            fadeOutAndReleaseSpotLocked(spot);
-        }
-    }
-
-    mSpriteController->closeTransaction();
-    mLocked.spotsByDisplay[displayId] = newSpots;
+    mLocked.spotControllers.at(displayId).setSpots(spotCoords, spotIdToIndex, spotIdBits);
 }
 
 void PointerController::clearSpots() {
-#if DEBUG_POINTER_UPDATES
-    ALOGD("clearSpots");
-#endif
+    std::scoped_lock lock(mLock);
+    clearSpotsLocked();
+}
 
-    AutoMutex _l(mLock);
-    if (!mLocked.viewport.isValid()) {
-        return;
+void PointerController::clearSpotsLocked() REQUIRES(mLock) {
+    for (auto& [displayID, spotController] : mLocked.spotControllers) {
+        spotController.clearSpots();
     }
-
-    fadeOutAndReleaseAllSpotsLocked();
 }
 
 void PointerController::setInactivityTimeout(InactivityTimeout inactivityTimeout) {
-    AutoMutex _l(mLock);
-
-    if (mLocked.inactivityTimeout != inactivityTimeout) {
-        mLocked.inactivityTimeout = inactivityTimeout;
-        resetInactivityTimeoutLocked();
-    }
+    mContext.setInactivityTimeout(inactivityTimeout);
 }
 
 void PointerController::reloadPointerResources() {
-    AutoMutex _l(mLock);
+    std::scoped_lock lock(mLock);
 
-    loadResourcesLocked();
-    updatePointerLocked();
-}
+    for (auto& [displayID, spotController] : mLocked.spotControllers) {
+        spotController.reloadSpotResources();
+    }
 
-/**
- * The viewport values for deviceHeight and deviceWidth have already been adjusted for rotation,
- * so here we are getting the dimensions in the original, unrotated orientation (orientation 0).
- */
-static void getNonRotatedSize(const DisplayViewport& viewport, int32_t& width, int32_t& height) {
-    width = viewport.deviceWidth;
-    height = viewport.deviceHeight;
-
-    if (viewport.orientation == DISPLAY_ORIENTATION_90
-            || viewport.orientation == DISPLAY_ORIENTATION_270) {
-        std::swap(width, height);
+    if (mCursorController.resourcesLoaded()) {
+        bool getAdditionalMouseResources = false;
+        if (mLocked.presentation == PointerController::Presentation::POINTER) {
+            getAdditionalMouseResources = true;
+        }
+        mCursorController.reloadPointerResources(getAdditionalMouseResources);
     }
 }
 
 void PointerController::setDisplayViewport(const DisplayViewport& viewport) {
-    AutoMutex _l(mLock);
-    if (viewport == mLocked.viewport) {
-        return;
+    std::scoped_lock lock(mLock);
+
+    bool getAdditionalMouseResources = false;
+    if (mLocked.presentation == PointerController::Presentation::POINTER) {
+        getAdditionalMouseResources = true;
     }
-
-    const DisplayViewport oldViewport = mLocked.viewport;
-    mLocked.viewport = viewport;
-
-    int32_t oldDisplayWidth, oldDisplayHeight;
-    getNonRotatedSize(oldViewport, oldDisplayWidth, oldDisplayHeight);
-    int32_t newDisplayWidth, newDisplayHeight;
-    getNonRotatedSize(viewport, newDisplayWidth, newDisplayHeight);
-
-    // Reset cursor position to center if size or display changed.
-    if (oldViewport.displayId != viewport.displayId
-            || oldDisplayWidth != newDisplayWidth
-            || oldDisplayHeight != newDisplayHeight) {
-
-        float minX, minY, maxX, maxY;
-        if (getBoundsLocked(&minX, &minY, &maxX, &maxY)) {
-            mLocked.pointerX = (minX + maxX) * 0.5f;
-            mLocked.pointerY = (minY + maxY) * 0.5f;
-            // Reload icon resources for density may be changed.
-            loadResourcesLocked();
-        } else {
-            mLocked.pointerX = 0;
-            mLocked.pointerY = 0;
-        }
-
-        fadeOutAndReleaseAllSpotsLocked();
-    } else if (oldViewport.orientation != viewport.orientation) {
-        // Apply offsets to convert from the pixel top-left corner position to the pixel center.
-        // This creates an invariant frame of reference that we can easily rotate when
-        // taking into account that the pointer may be located at fractional pixel offsets.
-        float x = mLocked.pointerX + 0.5f;
-        float y = mLocked.pointerY + 0.5f;
-        float temp;
-
-        // Undo the previous rotation.
-        switch (oldViewport.orientation) {
-        case DISPLAY_ORIENTATION_90:
-            temp = x;
-            x =  oldViewport.deviceHeight - y;
-            y = temp;
-            break;
-        case DISPLAY_ORIENTATION_180:
-            x = oldViewport.deviceWidth - x;
-            y = oldViewport.deviceHeight - y;
-            break;
-        case DISPLAY_ORIENTATION_270:
-            temp = x;
-            x = y;
-            y = oldViewport.deviceWidth - temp;
-            break;
-        }
-
-        // Perform the new rotation.
-        switch (viewport.orientation) {
-        case DISPLAY_ORIENTATION_90:
-            temp = x;
-            x = y;
-            y = viewport.deviceHeight - temp;
-            break;
-        case DISPLAY_ORIENTATION_180:
-            x = viewport.deviceWidth - x;
-            y = viewport.deviceHeight - y;
-            break;
-        case DISPLAY_ORIENTATION_270:
-            temp = x;
-            x = viewport.deviceWidth - y;
-            y = temp;
-            break;
-        }
-
-        // Apply offsets to convert from the pixel center to the pixel top-left corner position
-        // and save the results.
-        mLocked.pointerX = x - 0.5f;
-        mLocked.pointerY = y - 0.5f;
-    }
-
-    updatePointerLocked();
+    mCursorController.setDisplayViewport(viewport, getAdditionalMouseResources);
 }
 
 void PointerController::updatePointerIcon(int32_t iconId) {
-    AutoMutex _l(mLock);
-    if (mLocked.requestedPointerType != iconId) {
-        mLocked.requestedPointerType = iconId;
-        mLocked.presentationChanged = true;
-        updatePointerLocked();
-    }
+    std::scoped_lock lock(mLock);
+    mCursorController.updatePointerIcon(iconId);
 }
 
 void PointerController::setCustomPointerIcon(const SpriteIcon& icon) {
-    AutoMutex _l(mLock);
-
-    const int32_t iconId = mPolicy->getCustomPointerIconId();
-    mLocked.additionalMouseResources[iconId] = icon;
-    mLocked.requestedPointerType = iconId;
-    mLocked.presentationChanged = true;
-
-    updatePointerLocked();
-}
-
-void PointerController::MessageHandler::handleMessage(const Message& message) {
-    std::shared_ptr<PointerController> controller = pointerController.lock();
-
-    if (controller == nullptr) {
-        ALOGE("PointerController instance was released before processing message: what=%d",
-              message.what);
-        return;
-    }
-    switch (message.what) {
-    case MSG_INACTIVITY_TIMEOUT:
-        controller->doInactivityTimeout();
-        break;
-    }
-}
-
-int PointerController::LooperCallback::handleEvent(int /* fd */, int events, void* /* data */) {
-    std::shared_ptr<PointerController> controller = pointerController.lock();
-    if (controller == nullptr) {
-        ALOGW("PointerController instance was released with pending callbacks.  events=0x%x",
-              events);
-        return 0; // Remove the callback, the PointerController is gone anyways
-    }
-    if (events & (Looper::EVENT_ERROR | Looper::EVENT_HANGUP)) {
-        ALOGE("Display event receiver pipe was closed or an error occurred.  events=0x%x", events);
-        return 0; // remove the callback
-    }
-
-    if (!(events & Looper::EVENT_INPUT)) {
-        ALOGW("Received spurious callback for unhandled poll event.  events=0x%x", events);
-        return 1; // keep the callback
-    }
-
-    bool gotVsync = false;
-    ssize_t n;
-    nsecs_t timestamp;
-    DisplayEventReceiver::Event buf[EVENT_BUFFER_SIZE];
-    while ((n = controller->mDisplayEventReceiver.getEvents(buf, EVENT_BUFFER_SIZE)) > 0) {
-        for (size_t i = 0; i < static_cast<size_t>(n); ++i) {
-            if (buf[i].header.type == DisplayEventReceiver::DISPLAY_EVENT_VSYNC) {
-                timestamp = buf[i].header.timestamp;
-                gotVsync = true;
-            }
-        }
-    }
-    if (gotVsync) {
-        controller->doAnimate(timestamp);
-    }
-    return 1;  // keep the callback
+    std::scoped_lock lock(mLock);
+    mCursorController.setCustomPointerIcon(icon);
 }
 
 void PointerController::doAnimate(nsecs_t timestamp) {
-    AutoMutex _l(mLock);
+    std::scoped_lock lock(mLock);
 
-    mLocked.animationPending = false;
+    mContext.setAnimationPending(false);
 
-    bool keepFading = doFadingAnimationLocked(timestamp);
-    bool keepBitmapFlipping = doBitmapAnimationLocked(timestamp);
+    bool keepFading = false;
+    keepFading = mCursorController.doFadingAnimation(timestamp, keepFading);
+
+    for (auto& [displayID, spotController] : mLocked.spotControllers) {
+        keepFading = spotController.doFadingAnimation(timestamp, keepFading);
+    }
+
+    bool keepBitmapFlipping = mCursorController.doBitmapAnimation(timestamp);
     if (keepFading || keepBitmapFlipping) {
-        startAnimationLocked();
+        mContext.startAnimation();
     }
 }
 
-bool PointerController::doFadingAnimationLocked(nsecs_t timestamp) {
-    bool keepAnimating = false;
-    nsecs_t frameDelay = timestamp - mLocked.animationTime;
-
-    // Animate pointer fade.
-    if (mLocked.pointerFadeDirection < 0) {
-        mLocked.pointerAlpha -= float(frameDelay) / POINTER_FADE_DURATION;
-        if (mLocked.pointerAlpha <= 0.0f) {
-            mLocked.pointerAlpha = 0.0f;
-            mLocked.pointerFadeDirection = 0;
-        } else {
-            keepAnimating = true;
-        }
-        updatePointerLocked();
-    } else if (mLocked.pointerFadeDirection > 0) {
-        mLocked.pointerAlpha += float(frameDelay) / POINTER_FADE_DURATION;
-        if (mLocked.pointerAlpha >= 1.0f) {
-            mLocked.pointerAlpha = 1.0f;
-            mLocked.pointerFadeDirection = 0;
-        } else {
-            keepAnimating = true;
-        }
-        updatePointerLocked();
-    }
-
-    // Animate spots that are fading out and being removed.
-    for(auto it = mLocked.spotsByDisplay.begin(); it != mLocked.spotsByDisplay.end();) {
-        std::vector<Spot*>& spots = it->second;
-        size_t numSpots = spots.size();
-        for (size_t i = 0; i < numSpots;) {
-            Spot* spot = spots[i];
-            if (spot->id == Spot::INVALID_ID) {
-                spot->alpha -= float(frameDelay) / SPOT_FADE_DURATION;
-                if (spot->alpha <= 0) {
-                    spots.erase(spots.begin() + i);
-                    releaseSpotLocked(spot);
-                    numSpots--;
-                    continue;
-                } else {
-                    spot->sprite->setAlpha(spot->alpha);
-                    keepAnimating = true;
-                }
-            }
-            ++i;
-        }
-
-        if (spots.size() == 0) {
-            it = mLocked.spotsByDisplay.erase(it);
-        } else {
-            ++it;
-        }
-    }
-
-    return keepAnimating;
-}
-
-bool PointerController::doBitmapAnimationLocked(nsecs_t timestamp) {
-    std::map<int32_t, PointerAnimation>::const_iterator iter = mLocked.animationResources.find(
-            mLocked.requestedPointerType);
-    if (iter == mLocked.animationResources.end()) {
-        return false;
-    }
-
-    if (timestamp - mLocked.lastFrameUpdatedTime > iter->second.durationPerFrame) {
-        mSpriteController->openTransaction();
-
-        int incr = (timestamp - mLocked.lastFrameUpdatedTime) / iter->second.durationPerFrame;
-        mLocked.animationFrameIndex += incr;
-        mLocked.lastFrameUpdatedTime += iter->second.durationPerFrame * incr;
-        while (mLocked.animationFrameIndex >= iter->second.animationFrames.size()) {
-            mLocked.animationFrameIndex -= iter->second.animationFrames.size();
-        }
-        mLocked.pointerSprite->setIcon(iter->second.animationFrames[mLocked.animationFrameIndex]);
-
-        mSpriteController->closeTransaction();
-    }
-
-    // Keep animating.
-    return true;
-}
-
 void PointerController::doInactivityTimeout() {
     fade(Transition::GRADUAL);
 }
 
-void PointerController::startAnimationLocked() {
-    if (!mLocked.animationPending) {
-        mLocked.animationPending = true;
-        mLocked.animationTime = systemTime(SYSTEM_TIME_MONOTONIC);
-        mDisplayEventReceiver.requestNextVsync();
-    }
-}
-
-void PointerController::resetInactivityTimeoutLocked() {
-    mLooper->removeMessages(mHandler, MSG_INACTIVITY_TIMEOUT);
-
-    nsecs_t timeout = mLocked.inactivityTimeout == InactivityTimeout::SHORT
-            ? INACTIVITY_TIMEOUT_DELAY_TIME_SHORT
-            : INACTIVITY_TIMEOUT_DELAY_TIME_NORMAL;
-    mLooper->sendMessageDelayed(timeout, mHandler, MSG_INACTIVITY_TIMEOUT);
-}
-
-void PointerController::removeInactivityTimeoutLocked() {
-    mLooper->removeMessages(mHandler, MSG_INACTIVITY_TIMEOUT);
-}
-
-void PointerController::updatePointerLocked() REQUIRES(mLock) {
-    if (!mLocked.viewport.isValid()) {
-        return;
+void PointerController::onDisplayViewportsUpdated(std::vector<DisplayViewport>& viewports) {
+    std::unordered_set<int32_t> displayIdSet;
+    for (DisplayViewport viewport : viewports) {
+        displayIdSet.insert(viewport.displayId);
     }
 
-    mSpriteController->openTransaction();
-
-    mLocked.pointerSprite->setLayer(Sprite::BASE_LAYER_POINTER);
-    mLocked.pointerSprite->setPosition(mLocked.pointerX, mLocked.pointerY);
-    mLocked.pointerSprite->setDisplayId(mLocked.viewport.displayId);
-
-    if (mLocked.pointerAlpha > 0) {
-        mLocked.pointerSprite->setAlpha(mLocked.pointerAlpha);
-        mLocked.pointerSprite->setVisible(true);
-    } else {
-        mLocked.pointerSprite->setVisible(false);
-    }
-
-    if (mLocked.pointerIconChanged || mLocked.presentationChanged) {
-        if (mLocked.presentation == Presentation::POINTER) {
-            if (mLocked.requestedPointerType == mPolicy->getDefaultPointerIconId()) {
-                mLocked.pointerSprite->setIcon(mLocked.pointerIcon);
-            } else {
-                std::map<int32_t, SpriteIcon>::const_iterator iter =
-                    mLocked.additionalMouseResources.find(mLocked.requestedPointerType);
-                if (iter != mLocked.additionalMouseResources.end()) {
-                    std::map<int32_t, PointerAnimation>::const_iterator anim_iter =
-                            mLocked.animationResources.find(mLocked.requestedPointerType);
-                    if (anim_iter != mLocked.animationResources.end()) {
-                        mLocked.animationFrameIndex = 0;
-                        mLocked.lastFrameUpdatedTime = systemTime(SYSTEM_TIME_MONOTONIC);
-                        startAnimationLocked();
-                    }
-                    mLocked.pointerSprite->setIcon(iter->second);
-                } else {
-                    ALOGW("Can't find the resource for icon id %d", mLocked.requestedPointerType);
-                    mLocked.pointerSprite->setIcon(mLocked.pointerIcon);
-                }
-            }
+    std::scoped_lock lock(mLock);
+    for (auto it = mLocked.spotControllers.begin(); it != mLocked.spotControllers.end();) {
+        int32_t displayID = it->first;
+        if (!displayIdSet.count(displayID)) {
+            it = mLocked.spotControllers.erase(it);
         } else {
-            mLocked.pointerSprite->setIcon(mResources.spotAnchor);
-        }
-        mLocked.pointerIconChanged = false;
-        mLocked.presentationChanged = false;
-    }
-
-    mSpriteController->closeTransaction();
-}
-
-PointerController::Spot* PointerController::getSpot(uint32_t id, const std::vector<Spot*>& spots) {
-    for (size_t i = 0; i < spots.size(); i++) {
-        Spot* spot = spots[i];
-        if (spot->id == id) {
-            return spot;
-        }
-    }
-
-    return nullptr;
-}
-
-PointerController::Spot* PointerController::createAndAddSpotLocked(uint32_t id,
-        std::vector<Spot*>& spots) {
-    // Remove spots until we have fewer than MAX_SPOTS remaining.
-    while (spots.size() >= MAX_SPOTS) {
-        Spot* spot = removeFirstFadingSpotLocked(spots);
-        if (!spot) {
-            spot = spots[0];
-            spots.erase(spots.begin());
-        }
-        releaseSpotLocked(spot);
-    }
-
-    // Obtain a sprite from the recycled pool.
-    sp<Sprite> sprite;
-    if (! mLocked.recycledSprites.empty()) {
-        sprite = mLocked.recycledSprites.back();
-        mLocked.recycledSprites.pop_back();
-    } else {
-        sprite = mSpriteController->createSprite();
-    }
-
-    // Return the new spot.
-    Spot* spot = new Spot(id, sprite);
-    spots.push_back(spot);
-    return spot;
-}
-
-PointerController::Spot* PointerController::removeFirstFadingSpotLocked(std::vector<Spot*>& spots) {
-    for (size_t i = 0; i < spots.size(); i++) {
-        Spot* spot = spots[i];
-        if (spot->id == Spot::INVALID_ID) {
-            spots.erase(spots.begin() + i);
-            return spot;
-        }
-    }
-    return nullptr;
-}
-
-void PointerController::releaseSpotLocked(Spot* spot) {
-    spot->sprite->clearIcon();
-
-    if (mLocked.recycledSprites.size() < MAX_RECYCLED_SPRITES) {
-        mLocked.recycledSprites.push_back(spot->sprite);
-    }
-
-    delete spot;
-}
-
-void PointerController::fadeOutAndReleaseSpotLocked(Spot* spot) {
-    if (spot->id != Spot::INVALID_ID) {
-        spot->id = Spot::INVALID_ID;
-        startAnimationLocked();
-    }
-}
-
-void PointerController::fadeOutAndReleaseAllSpotsLocked() {
-    for (auto& it : mLocked.spotsByDisplay) {
-        const std::vector<Spot*>& spots = it.second;
-        size_t numSpots = spots.size();
-        for (size_t i = 0; i < numSpots; i++) {
-            Spot* spot = spots[i];
-            fadeOutAndReleaseSpotLocked(spot);
-        }
-    }
-}
-
-void PointerController::loadResourcesLocked() REQUIRES(mLock) {
-    if (!mLocked.viewport.isValid()) {
-        return;
-    }
-
-    mPolicy->loadPointerResources(&mResources, mLocked.viewport.displayId);
-    mPolicy->loadPointerIcon(&mLocked.pointerIcon, mLocked.viewport.displayId);
-
-    mLocked.additionalMouseResources.clear();
-    mLocked.animationResources.clear();
-    if (mLocked.presentation == Presentation::POINTER) {
-        mPolicy->loadAdditionalMouseResources(&mLocked.additionalMouseResources,
-                &mLocked.animationResources, mLocked.viewport.displayId);
-    }
-
-    mLocked.pointerIconChanged = true;
-}
-
-
-// --- PointerController::Spot ---
-
-void PointerController::Spot::updateSprite(const SpriteIcon* icon, float x, float y,
-        int32_t displayId) {
-    sprite->setLayer(Sprite::BASE_LAYER_SPOT + id);
-    sprite->setAlpha(alpha);
-    sprite->setTransformationMatrix(SpriteTransformationMatrix(scale, 0.0f, 0.0f, scale));
-    sprite->setPosition(x, y);
-    sprite->setDisplayId(displayId);
-    this->x = x;
-    this->y = y;
-
-    if (icon != lastIcon) {
-        lastIcon = icon;
-        if (icon) {
-            sprite->setIcon(*icon);
-            sprite->setVisible(true);
-        } else {
-            sprite->setVisible(false);
+            ++it;
         }
     }
 }
diff --git a/libs/input/PointerController.h b/libs/input/PointerController.h
index 14c0679..1f561da 100644
--- a/libs/input/PointerController.h
+++ b/libs/input/PointerController.h
@@ -30,48 +30,14 @@
 #include <memory>
 #include <vector>
 
+#include "MouseCursorController.h"
+#include "PointerControllerContext.h"
 #include "SpriteController.h"
+#include "TouchSpotController.h"
 
 namespace android {
 
 /*
- * Pointer resources.
- */
-struct PointerResources {
-    SpriteIcon spotHover;
-    SpriteIcon spotTouch;
-    SpriteIcon spotAnchor;
-};
-
-struct PointerAnimation {
-    std::vector<SpriteIcon> animationFrames;
-    nsecs_t durationPerFrame;
-};
-
-/*
- * Pointer controller policy interface.
- *
- * The pointer controller policy is used by the pointer controller to interact with
- * the Window Manager and other system components.
- *
- * The actual implementation is partially supported by callbacks into the DVM
- * via JNI.  This interface is also mocked in the unit tests.
- */
-class PointerControllerPolicyInterface : public virtual RefBase {
-protected:
-    PointerControllerPolicyInterface() { }
-    virtual ~PointerControllerPolicyInterface() { }
-
-public:
-    virtual void loadPointerIcon(SpriteIcon* icon, int32_t displayId) = 0;
-    virtual void loadPointerResources(PointerResources* outResources, int32_t displayId) = 0;
-    virtual void loadAdditionalMouseResources(std::map<int32_t, SpriteIcon>* outResources,
-            std::map<int32_t, PointerAnimation>* outAnimationResources, int32_t displayId) = 0;
-    virtual int32_t getDefaultPointerIconId() = 0;
-    virtual int32_t getCustomPointerIconId() = 0;
-};
-
-/*
  * Tracks pointer movements and draws the pointer sprite to a surface.
  *
  * Handles pointer acceleration and animation.
@@ -81,15 +47,10 @@
     static std::shared_ptr<PointerController> create(
             const sp<PointerControllerPolicyInterface>& policy, const sp<Looper>& looper,
             const sp<SpriteController>& spriteController);
-    enum class InactivityTimeout {
-        NORMAL = 0,
-        SHORT = 1,
-    };
 
-    virtual ~PointerController();
+    virtual ~PointerController() = default;
 
-    virtual bool getBounds(float* outMinX, float* outMinY,
-            float* outMaxX, float* outMaxY) const;
+    virtual bool getBounds(float* outMinX, float* outMinY, float* outMaxX, float* outMaxY) const;
     virtual void move(float deltaX, float deltaY);
     virtual void setButtonState(int32_t buttonState);
     virtual int32_t getButtonState() const;
@@ -101,129 +62,37 @@
     virtual void setDisplayViewport(const DisplayViewport& viewport);
 
     virtual void setPresentation(Presentation presentation);
-    virtual void setSpots(const PointerCoords* spotCoords,
-            const uint32_t* spotIdToIndex, BitSet32 spotIdBits, int32_t displayId);
+    virtual void setSpots(const PointerCoords* spotCoords, const uint32_t* spotIdToIndex,
+                          BitSet32 spotIdBits, int32_t displayId);
     virtual void clearSpots();
 
     void updatePointerIcon(int32_t iconId);
     void setCustomPointerIcon(const SpriteIcon& icon);
     void setInactivityTimeout(InactivityTimeout inactivityTimeout);
+    void doInactivityTimeout();
+    void doAnimate(nsecs_t timestamp);
     void reloadPointerResources();
+    void onDisplayViewportsUpdated(std::vector<DisplayViewport>& viewports);
 
 private:
-    static constexpr size_t MAX_RECYCLED_SPRITES = 12;
-    static constexpr size_t MAX_SPOTS = 12;
+    friend PointerControllerContext::LooperCallback;
+    friend PointerControllerContext::MessageHandler;
 
-    enum {
-        MSG_INACTIVITY_TIMEOUT,
-    };
+    mutable std::mutex mLock;
 
-    struct Spot {
-        static const uint32_t INVALID_ID = 0xffffffff;
+    PointerControllerContext mContext;
 
-        uint32_t id;
-        sp<Sprite> sprite;
-        float alpha;
-        float scale;
-        float x, y;
-
-        inline Spot(uint32_t id, const sp<Sprite>& sprite)
-              : id(id),
-                sprite(sprite),
-                alpha(1.0f),
-                scale(1.0f),
-                x(0.0f),
-                y(0.0f),
-                lastIcon(nullptr) {}
-
-        void updateSprite(const SpriteIcon* icon, float x, float y, int32_t displayId);
-
-    private:
-        const SpriteIcon* lastIcon;
-    };
-
-    class MessageHandler : public virtual android::MessageHandler {
-    public:
-        void handleMessage(const Message& message) override;
-        std::weak_ptr<PointerController> pointerController;
-    };
-
-    class LooperCallback : public virtual android::LooperCallback {
-    public:
-        int handleEvent(int fd, int events, void* data) override;
-        std::weak_ptr<PointerController> pointerController;
-    };
-
-    mutable Mutex mLock;
-
-    sp<PointerControllerPolicyInterface> mPolicy;
-    sp<Looper> mLooper;
-    sp<SpriteController> mSpriteController;
-    sp<MessageHandler> mHandler;
-    sp<LooperCallback> mCallback;
-
-    DisplayEventReceiver mDisplayEventReceiver;
-
-    PointerResources mResources;
+    MouseCursorController mCursorController;
 
     struct Locked {
-        bool animationPending;
-        nsecs_t animationTime;
-
-        size_t animationFrameIndex;
-        nsecs_t lastFrameUpdatedTime;
-
-        DisplayViewport viewport;
-
-        InactivityTimeout inactivityTimeout;
-
         Presentation presentation;
-        bool presentationChanged;
 
-        int32_t pointerFadeDirection;
-        float pointerX;
-        float pointerY;
-        float pointerAlpha;
-        sp<Sprite> pointerSprite;
-        SpriteIcon pointerIcon;
-        bool pointerIconChanged;
-
-        std::map<int32_t, SpriteIcon> additionalMouseResources;
-        std::map<int32_t, PointerAnimation> animationResources;
-
-        int32_t requestedPointerType;
-
-        int32_t buttonState;
-
-        std::map<int32_t /* displayId */, std::vector<Spot*>> spotsByDisplay;
-        std::vector<sp<Sprite>> recycledSprites;
+        std::unordered_map<int32_t /* displayId */, TouchSpotController> spotControllers;
     } mLocked GUARDED_BY(mLock);
 
     PointerController(const sp<PointerControllerPolicyInterface>& policy, const sp<Looper>& looper,
                       const sp<SpriteController>& spriteController);
-
-    bool getBoundsLocked(float* outMinX, float* outMinY, float* outMaxX, float* outMaxY) const;
-    void setPositionLocked(float x, float y);
-
-    void doAnimate(nsecs_t timestamp);
-    bool doFadingAnimationLocked(nsecs_t timestamp);
-    bool doBitmapAnimationLocked(nsecs_t timestamp);
-    void doInactivityTimeout();
-
-    void startAnimationLocked();
-
-    void resetInactivityTimeoutLocked();
-    void removeInactivityTimeoutLocked();
-    void updatePointerLocked();
-
-    Spot* getSpot(uint32_t id, const std::vector<Spot*>& spots);
-    Spot* createAndAddSpotLocked(uint32_t id, std::vector<Spot*>& spots);
-    Spot* removeFirstFadingSpotLocked(std::vector<Spot*>& spots);
-    void releaseSpotLocked(Spot* spot);
-    void fadeOutAndReleaseSpotLocked(Spot* spot);
-    void fadeOutAndReleaseAllSpotsLocked();
-
-    void loadResourcesLocked();
+    void clearSpotsLocked();
 };
 
 } // namespace android
diff --git a/libs/input/PointerControllerContext.cpp b/libs/input/PointerControllerContext.cpp
new file mode 100644
index 0000000..2d7e22b
--- /dev/null
+++ b/libs/input/PointerControllerContext.cpp
@@ -0,0 +1,179 @@
+/*
+ * 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.
+ */
+
+#include "PointerControllerContext.h"
+#include "PointerController.h"
+
+namespace {
+// Time to wait before starting the fade when the pointer is inactive.
+const nsecs_t INACTIVITY_TIMEOUT_DELAY_TIME_NORMAL = 15 * 1000 * 1000000LL; // 15 seconds
+const nsecs_t INACTIVITY_TIMEOUT_DELAY_TIME_SHORT = 3 * 1000 * 1000000LL;   // 3 seconds
+
+// The number of events to be read at once for DisplayEventReceiver.
+const int EVENT_BUFFER_SIZE = 100;
+} // namespace
+
+namespace android {
+
+// --- PointerControllerContext ---
+
+PointerControllerContext::PointerControllerContext(
+        const sp<PointerControllerPolicyInterface>& policy, const sp<Looper>& looper,
+        const sp<SpriteController>& spriteController, PointerController& controller)
+      : mPolicy(policy),
+        mLooper(looper),
+        mSpriteController(spriteController),
+        mHandler(new MessageHandler()),
+        mCallback(new LooperCallback()),
+        mController(controller) {
+    std::scoped_lock lock(mLock);
+    mLocked.inactivityTimeout = InactivityTimeout::NORMAL;
+    mLocked.animationPending = false;
+}
+
+PointerControllerContext::~PointerControllerContext() {
+    mLooper->removeMessages(mHandler);
+}
+
+void PointerControllerContext::setInactivityTimeout(InactivityTimeout inactivityTimeout) {
+    std::scoped_lock lock(mLock);
+
+    if (mLocked.inactivityTimeout != inactivityTimeout) {
+        mLocked.inactivityTimeout = inactivityTimeout;
+        resetInactivityTimeoutLocked();
+    }
+}
+
+void PointerControllerContext::startAnimation() {
+    std::scoped_lock lock(mLock);
+    if (!mLocked.animationPending) {
+        mLocked.animationPending = true;
+        mLocked.animationTime = systemTime(SYSTEM_TIME_MONOTONIC);
+        mDisplayEventReceiver.requestNextVsync();
+    }
+}
+
+void PointerControllerContext::resetInactivityTimeout() {
+    std::scoped_lock lock(mLock);
+    resetInactivityTimeoutLocked();
+}
+
+void PointerControllerContext::resetInactivityTimeoutLocked() REQUIRES(mLock) {
+    mLooper->removeMessages(mHandler, MessageHandler::MSG_INACTIVITY_TIMEOUT);
+
+    nsecs_t timeout = mLocked.inactivityTimeout == InactivityTimeout::SHORT
+            ? INACTIVITY_TIMEOUT_DELAY_TIME_SHORT
+            : INACTIVITY_TIMEOUT_DELAY_TIME_NORMAL;
+    mLooper->sendMessageDelayed(timeout, mHandler, MessageHandler::MSG_INACTIVITY_TIMEOUT);
+}
+
+void PointerControllerContext::removeInactivityTimeout() {
+    std::scoped_lock lock(mLock);
+    mLooper->removeMessages(mHandler, MessageHandler::MSG_INACTIVITY_TIMEOUT);
+}
+
+void PointerControllerContext::setAnimationPending(bool animationPending) {
+    std::scoped_lock lock(mLock);
+    mLocked.animationPending = animationPending;
+}
+
+nsecs_t PointerControllerContext::getAnimationTime() {
+    std::scoped_lock lock(mLock);
+    return mLocked.animationTime;
+}
+
+void PointerControllerContext::setHandlerController(std::shared_ptr<PointerController> controller) {
+    mHandler->pointerController = controller;
+}
+
+void PointerControllerContext::setCallbackController(
+        std::shared_ptr<PointerController> controller) {
+    mCallback->pointerController = controller;
+}
+
+sp<PointerControllerPolicyInterface> PointerControllerContext::getPolicy() {
+    return mPolicy;
+}
+
+sp<SpriteController> PointerControllerContext::getSpriteController() {
+    return mSpriteController;
+}
+
+void PointerControllerContext::initializeDisplayEventReceiver() {
+    if (mDisplayEventReceiver.initCheck() == NO_ERROR) {
+        mLooper->addFd(mDisplayEventReceiver.getFd(), Looper::POLL_CALLBACK, Looper::EVENT_INPUT,
+                       mCallback, nullptr);
+    } else {
+        ALOGE("Failed to initialize DisplayEventReceiver.");
+    }
+}
+
+void PointerControllerContext::handleDisplayEvents() {
+    bool gotVsync = false;
+    ssize_t n;
+    nsecs_t timestamp;
+    DisplayEventReceiver::Event buf[EVENT_BUFFER_SIZE];
+    while ((n = mDisplayEventReceiver.getEvents(buf, EVENT_BUFFER_SIZE)) > 0) {
+        for (size_t i = 0; i < static_cast<size_t>(n); ++i) {
+            if (buf[i].header.type == DisplayEventReceiver::DISPLAY_EVENT_VSYNC) {
+                timestamp = buf[i].header.timestamp;
+                gotVsync = true;
+            }
+        }
+    }
+    if (gotVsync) {
+        mController.doAnimate(timestamp);
+    }
+}
+
+void PointerControllerContext::MessageHandler::handleMessage(const Message& message) {
+    std::shared_ptr<PointerController> controller = pointerController.lock();
+
+    if (controller == nullptr) {
+        ALOGE("PointerController instance was released before processing message: what=%d",
+              message.what);
+        return;
+    }
+    switch (message.what) {
+        case MSG_INACTIVITY_TIMEOUT:
+            controller->doInactivityTimeout();
+            break;
+    }
+}
+
+int PointerControllerContext::LooperCallback::handleEvent(int /* fd */, int events,
+                                                          void* /* data */) {
+    std::shared_ptr<PointerController> controller = pointerController.lock();
+    if (controller == nullptr) {
+        ALOGW("PointerController instance was released with pending callbacks.  events=0x%x",
+              events);
+        return 0; // Remove the callback, the PointerController is gone anyways
+    }
+    if (events & (Looper::EVENT_ERROR | Looper::EVENT_HANGUP)) {
+        ALOGE("Display event receiver pipe was closed or an error occurred.  events=0x%x", events);
+        return 0; // remove the callback
+    }
+
+    if (!(events & Looper::EVENT_INPUT)) {
+        ALOGW("Received spurious callback for unhandled poll event.  events=0x%x", events);
+        return 1; // keep the callback
+    }
+
+    controller->mContext.handleDisplayEvents();
+    return 1; // keep the callback
+}
+
+} // namespace android
diff --git a/libs/input/PointerControllerContext.h b/libs/input/PointerControllerContext.h
new file mode 100644
index 0000000..92e1bda
--- /dev/null
+++ b/libs/input/PointerControllerContext.h
@@ -0,0 +1,154 @@
+/*
+ * 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.
+ */
+
+#ifndef _UI_POINTER_CONTROLLER_CONTEXT_H
+#define _UI_POINTER_CONTROLLER_CONTEXT_H
+
+#include <PointerControllerInterface.h>
+#include <gui/DisplayEventReceiver.h>
+#include <input/DisplayViewport.h>
+#include <input/Input.h>
+#include <ui/DisplayInfo.h>
+#include <utils/BitSet.h>
+#include <utils/Looper.h>
+#include <utils/RefBase.h>
+
+#include <map>
+#include <memory>
+#include <vector>
+
+#include "SpriteController.h"
+
+namespace android {
+
+class PointerController;
+
+/*
+ * Pointer resources.
+ */
+struct PointerResources {
+    SpriteIcon spotHover;
+    SpriteIcon spotTouch;
+    SpriteIcon spotAnchor;
+};
+
+struct PointerAnimation {
+    std::vector<SpriteIcon> animationFrames;
+    nsecs_t durationPerFrame;
+};
+
+enum class InactivityTimeout {
+    NORMAL = 0,
+    SHORT = 1,
+};
+
+/*
+ * Pointer controller policy interface.
+ *
+ * The pointer controller policy is used by the pointer controller to interact with
+ * the Window Manager and other system components.
+ *
+ * The actual implementation is partially supported by callbacks into the DVM
+ * via JNI.  This interface is also mocked in the unit tests.
+ */
+class PointerControllerPolicyInterface : public virtual RefBase {
+protected:
+    PointerControllerPolicyInterface() {}
+    virtual ~PointerControllerPolicyInterface() {}
+
+public:
+    virtual void loadPointerIcon(SpriteIcon* icon, int32_t displayId) = 0;
+    virtual void loadPointerResources(PointerResources* outResources, int32_t displayId) = 0;
+    virtual void loadAdditionalMouseResources(
+            std::map<int32_t, SpriteIcon>* outResources,
+            std::map<int32_t, PointerAnimation>* outAnimationResources, int32_t displayId) = 0;
+    virtual int32_t getDefaultPointerIconId() = 0;
+    virtual int32_t getCustomPointerIconId() = 0;
+};
+
+/*
+ * Contains logic and resources shared among PointerController,
+ * MouseCursorController, and TouchSpotController.
+ */
+
+class PointerControllerContext {
+public:
+    PointerControllerContext(const sp<PointerControllerPolicyInterface>& policy,
+                             const sp<Looper>& looper, const sp<SpriteController>& spriteController,
+                             PointerController& controller);
+    ~PointerControllerContext();
+
+    void removeInactivityTimeout();
+    void resetInactivityTimeout();
+    void startAnimation();
+    void setInactivityTimeout(InactivityTimeout inactivityTimeout);
+
+    void setAnimationPending(bool animationPending);
+    nsecs_t getAnimationTime();
+
+    void clearSpotsByDisplay(int32_t displayId);
+
+    void setHandlerController(std::shared_ptr<PointerController> controller);
+    void setCallbackController(std::shared_ptr<PointerController> controller);
+
+    sp<PointerControllerPolicyInterface> getPolicy();
+    sp<SpriteController> getSpriteController();
+
+    void initializeDisplayEventReceiver();
+    void handleDisplayEvents();
+
+    class MessageHandler : public virtual android::MessageHandler {
+    public:
+        enum {
+            MSG_INACTIVITY_TIMEOUT,
+        };
+
+        void handleMessage(const Message& message) override;
+        std::weak_ptr<PointerController> pointerController;
+    };
+
+    class LooperCallback : public virtual android::LooperCallback {
+    public:
+        int handleEvent(int fd, int events, void* data) override;
+        std::weak_ptr<PointerController> pointerController;
+    };
+
+private:
+    sp<PointerControllerPolicyInterface> mPolicy;
+    sp<Looper> mLooper;
+    sp<SpriteController> mSpriteController;
+    sp<MessageHandler> mHandler;
+    sp<LooperCallback> mCallback;
+
+    DisplayEventReceiver mDisplayEventReceiver;
+
+    PointerController& mController;
+
+    mutable std::mutex mLock;
+
+    struct Locked {
+        bool animationPending;
+        nsecs_t animationTime;
+
+        InactivityTimeout inactivityTimeout;
+    } mLocked GUARDED_BY(mLock);
+
+    void resetInactivityTimeoutLocked();
+};
+
+} // namespace android
+
+#endif // _UI_POINTER_CONTROLLER_CONTEXT_H
diff --git a/libs/input/TouchSpotController.cpp b/libs/input/TouchSpotController.cpp
new file mode 100644
index 0000000..c7430ce
--- /dev/null
+++ b/libs/input/TouchSpotController.cpp
@@ -0,0 +1,236 @@
+/*
+ * 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.
+ */
+
+#define LOG_TAG "TouchSpotController"
+
+// Log debug messages about pointer updates
+#define DEBUG_SPOT_UPDATES 0
+
+#include "TouchSpotController.h"
+
+#include <log/log.h>
+
+#include <SkBitmap.h>
+#include <SkBlendMode.h>
+#include <SkCanvas.h>
+#include <SkColor.h>
+#include <SkPaint.h>
+
+namespace {
+// Time to spend fading out the spot completely.
+const nsecs_t SPOT_FADE_DURATION = 200 * 1000000LL; // 200 ms
+} // namespace
+
+namespace android {
+
+// --- Spot ---
+
+void TouchSpotController::Spot::updateSprite(const SpriteIcon* icon, float x, float y,
+                                             int32_t displayId) {
+    sprite->setLayer(Sprite::BASE_LAYER_SPOT + id);
+    sprite->setAlpha(alpha);
+    sprite->setTransformationMatrix(SpriteTransformationMatrix(scale, 0.0f, 0.0f, scale));
+    sprite->setPosition(x, y);
+    sprite->setDisplayId(displayId);
+    this->x = x;
+    this->y = y;
+
+    if (icon != mLastIcon) {
+        mLastIcon = icon;
+        if (icon) {
+            sprite->setIcon(*icon);
+            sprite->setVisible(true);
+        } else {
+            sprite->setVisible(false);
+        }
+    }
+}
+
+// --- TouchSpotController ---
+
+TouchSpotController::TouchSpotController(int32_t displayId, PointerControllerContext& context)
+      : mDisplayId(displayId), mContext(context) {
+    mContext.getPolicy()->loadPointerResources(&mResources, mDisplayId);
+}
+
+TouchSpotController::~TouchSpotController() {
+    std::scoped_lock lock(mLock);
+
+    size_t numSpots = mLocked.displaySpots.size();
+    for (size_t i = 0; i < numSpots; i++) {
+        delete mLocked.displaySpots[i];
+    }
+    mLocked.displaySpots.clear();
+}
+
+void TouchSpotController::setSpots(const PointerCoords* spotCoords, const uint32_t* spotIdToIndex,
+                                   BitSet32 spotIdBits) {
+#if DEBUG_SPOT_UPDATES
+    ALOGD("setSpots: idBits=%08x", spotIdBits.value);
+    for (BitSet32 idBits(spotIdBits); !idBits.isEmpty();) {
+        uint32_t id = idBits.firstMarkedBit();
+        idBits.clearBit(id);
+        const PointerCoords& c = spotCoords[spotIdToIndex[id]];
+        ALOGD(" spot %d: position=(%0.3f, %0.3f), pressure=%0.3f, displayId=%" PRId32 ".", id,
+              c.getAxisValue(AMOTION_EVENT_AXIS_X), c.getAxisValue(AMOTION_EVENT_AXIS_Y),
+              c.getAxisValue(AMOTION_EVENT_AXIS_PRESSURE), displayId);
+    }
+#endif
+
+    std::scoped_lock lock(mLock);
+    sp<SpriteController> spriteController = mContext.getSpriteController();
+    spriteController->openTransaction();
+
+    // Add or move spots for fingers that are down.
+    for (BitSet32 idBits(spotIdBits); !idBits.isEmpty();) {
+        uint32_t id = idBits.clearFirstMarkedBit();
+        const PointerCoords& c = spotCoords[spotIdToIndex[id]];
+        const SpriteIcon& icon = c.getAxisValue(AMOTION_EVENT_AXIS_PRESSURE) > 0
+                ? mResources.spotTouch
+                : mResources.spotHover;
+        float x = c.getAxisValue(AMOTION_EVENT_AXIS_X);
+        float y = c.getAxisValue(AMOTION_EVENT_AXIS_Y);
+
+        Spot* spot = getSpot(id, mLocked.displaySpots);
+        if (!spot) {
+            spot = createAndAddSpotLocked(id, mLocked.displaySpots);
+        }
+
+        spot->updateSprite(&icon, x, y, mDisplayId);
+    }
+
+    for (Spot* spot : mLocked.displaySpots) {
+        if (spot->id != Spot::INVALID_ID && !spotIdBits.hasBit(spot->id)) {
+            fadeOutAndReleaseSpotLocked(spot);
+        }
+    }
+
+    spriteController->closeTransaction();
+}
+
+void TouchSpotController::clearSpots() {
+#if DEBUG_SPOT_UPDATES
+    ALOGD("clearSpots");
+#endif
+
+    std::scoped_lock lock(mLock);
+    fadeOutAndReleaseAllSpotsLocked();
+}
+
+TouchSpotController::Spot* TouchSpotController::getSpot(uint32_t id,
+                                                        const std::vector<Spot*>& spots) {
+    for (size_t i = 0; i < spots.size(); i++) {
+        Spot* spot = spots[i];
+        if (spot->id == id) {
+            return spot;
+        }
+    }
+    return nullptr;
+}
+
+TouchSpotController::Spot* TouchSpotController::createAndAddSpotLocked(uint32_t id,
+                                                                       std::vector<Spot*>& spots) {
+    // Remove spots until we have fewer than MAX_SPOTS remaining.
+    while (spots.size() >= MAX_SPOTS) {
+        Spot* spot = removeFirstFadingSpotLocked(spots);
+        if (!spot) {
+            spot = spots[0];
+            spots.erase(spots.begin());
+        }
+        releaseSpotLocked(spot);
+    }
+
+    // Obtain a sprite from the recycled pool.
+    sp<Sprite> sprite;
+    if (!mLocked.recycledSprites.empty()) {
+        sprite = mLocked.recycledSprites.back();
+        mLocked.recycledSprites.pop_back();
+    } else {
+        sprite = mContext.getSpriteController()->createSprite();
+    }
+
+    // Return the new spot.
+    Spot* spot = new Spot(id, sprite);
+    spots.push_back(spot);
+    return spot;
+}
+
+TouchSpotController::Spot* TouchSpotController::removeFirstFadingSpotLocked(
+        std::vector<Spot*>& spots) REQUIRES(mLock) {
+    for (size_t i = 0; i < spots.size(); i++) {
+        Spot* spot = spots[i];
+        if (spot->id == Spot::INVALID_ID) {
+            spots.erase(spots.begin() + i);
+            return spot;
+        }
+    }
+    return NULL;
+}
+
+void TouchSpotController::releaseSpotLocked(Spot* spot) REQUIRES(mLock) {
+    spot->sprite->clearIcon();
+
+    if (mLocked.recycledSprites.size() < MAX_RECYCLED_SPRITES) {
+        mLocked.recycledSprites.push_back(spot->sprite);
+    }
+
+    delete spot;
+}
+
+void TouchSpotController::fadeOutAndReleaseSpotLocked(Spot* spot) REQUIRES(mLock) {
+    if (spot->id != Spot::INVALID_ID) {
+        spot->id = Spot::INVALID_ID;
+        mContext.startAnimation();
+    }
+}
+
+void TouchSpotController::fadeOutAndReleaseAllSpotsLocked() REQUIRES(mLock) {
+    size_t numSpots = mLocked.displaySpots.size();
+    for (size_t i = 0; i < numSpots; i++) {
+        Spot* spot = mLocked.displaySpots[i];
+        fadeOutAndReleaseSpotLocked(spot);
+    }
+}
+
+void TouchSpotController::reloadSpotResources() {
+    mContext.getPolicy()->loadPointerResources(&mResources, mDisplayId);
+}
+
+bool TouchSpotController::doFadingAnimation(nsecs_t timestamp, bool keepAnimating) {
+    std::scoped_lock lock(mLock);
+    nsecs_t animationTime = mContext.getAnimationTime();
+    nsecs_t frameDelay = timestamp - animationTime;
+    size_t numSpots = mLocked.displaySpots.size();
+    for (size_t i = 0; i < numSpots;) {
+        Spot* spot = mLocked.displaySpots[i];
+        if (spot->id == Spot::INVALID_ID) {
+            spot->alpha -= float(frameDelay) / SPOT_FADE_DURATION;
+            if (spot->alpha <= 0) {
+                mLocked.displaySpots.erase(mLocked.displaySpots.begin() + i);
+                releaseSpotLocked(spot);
+                numSpots--;
+                continue;
+            } else {
+                spot->sprite->setAlpha(spot->alpha);
+                keepAnimating = true;
+            }
+        }
+        ++i;
+    }
+    return keepAnimating;
+}
+
+} // namespace android
diff --git a/libs/input/TouchSpotController.h b/libs/input/TouchSpotController.h
new file mode 100644
index 0000000..f3b3550
--- /dev/null
+++ b/libs/input/TouchSpotController.h
@@ -0,0 +1,91 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef _UI_TOUCH_SPOT_CONTROLLER_H
+#define _UI_TOUCH_SPOT_CONTROLLER_H
+
+#include "PointerControllerContext.h"
+
+namespace android {
+
+/*
+ * Helper class for PointerController that specifically handles
+ * touch spot resources and actions for a single display.
+ */
+class TouchSpotController {
+public:
+    TouchSpotController(int32_t displayId, PointerControllerContext& context);
+    ~TouchSpotController();
+    void setSpots(const PointerCoords* spotCoords, const uint32_t* spotIdToIndex,
+                  BitSet32 spotIdBits);
+    void clearSpots();
+
+    void reloadSpotResources();
+    bool doFadingAnimation(nsecs_t timestamp, bool keepAnimating);
+
+private:
+    struct Spot {
+        static const uint32_t INVALID_ID = 0xffffffff;
+
+        uint32_t id;
+        sp<Sprite> sprite;
+        float alpha;
+        float scale;
+        float x, y;
+
+        inline Spot(uint32_t id, const sp<Sprite>& sprite)
+              : id(id),
+                sprite(sprite),
+                alpha(1.0f),
+                scale(1.0f),
+                x(0.0f),
+                y(0.0f),
+                mLastIcon(nullptr) {}
+
+        void updateSprite(const SpriteIcon* icon, float x, float y, int32_t displayId);
+
+    private:
+        const SpriteIcon* mLastIcon;
+    };
+
+    int32_t mDisplayId;
+
+    mutable std::mutex mLock;
+
+    PointerResources mResources;
+
+    PointerControllerContext& mContext;
+
+    static constexpr size_t MAX_RECYCLED_SPRITES = 12;
+    static constexpr size_t MAX_SPOTS = 12;
+
+    struct Locked {
+        std::vector<Spot*> displaySpots;
+        std::vector<sp<Sprite>> recycledSprites;
+
+    } mLocked GUARDED_BY(mLock);
+
+    Spot* getSpot(uint32_t id, const std::vector<Spot*>& spots);
+    Spot* createAndAddSpotLocked(uint32_t id, std::vector<Spot*>& spots);
+    Spot* removeFirstFadingSpotLocked(std::vector<Spot*>& spots);
+    void releaseSpotLocked(Spot* spot);
+    void fadeOutAndReleaseSpotLocked(Spot* spot);
+    void fadeOutAndReleaseAllSpotsLocked();
+};
+
+} // namespace android
+
+#endif // _UI_TOUCH_SPOT_CONTROLLER_H
diff --git a/libs/input/tests/PointerController_test.cpp b/libs/input/tests/PointerController_test.cpp
index 6e129a0..b67088a 100644
--- a/libs/input/tests/PointerController_test.cpp
+++ b/libs/input/tests/PointerController_test.cpp
@@ -178,9 +178,6 @@
     viewport.deviceWidth = 400;
     viewport.deviceHeight = 300;
     mPointerController->setDisplayViewport(viewport);
-
-    // The first call to setDisplayViewport should trigger the loading of the necessary resources.
-    EXPECT_TRUE(mPolicy->allResourcesAreLoaded());
 }
 
 void PointerControllerTest::loopThread() {
@@ -208,6 +205,7 @@
 
 TEST_F(PointerControllerTest, updatePointerIcon) {
     ensureDisplayViewportIsSet();
+    mPointerController->setPresentation(PointerController::Presentation::POINTER);
     mPointerController->unfade(PointerController::Transition::IMMEDIATE);
 
     int32_t type = CURSOR_TYPE_ADDITIONAL;
@@ -247,8 +245,6 @@
 
 TEST_F(PointerControllerTest, doesNotGetResourcesBeforeSettingViewport) {
     mPointerController->setPresentation(PointerController::Presentation::POINTER);
-    mPointerController->setSpots(nullptr, nullptr, BitSet32(), -1);
-    mPointerController->clearSpots();
     mPointerController->setPosition(1.0f, 1.0f);
     mPointerController->move(1.0f, 1.0f);
     mPointerController->unfade(PointerController::Transition::IMMEDIATE);
diff --git a/location/java/android/location/GnssAntennaInfo.java b/location/java/android/location/GnssAntennaInfo.java
index b2f9a0f..23977f1 100644
--- a/location/java/android/location/GnssAntennaInfo.java
+++ b/location/java/android/location/GnssAntennaInfo.java
@@ -53,7 +53,7 @@
      * Class containing information about the antenna phase center offset (PCO). PCO is defined with
      * respect to the origin of the Android sensor coordinate system, e.g., center of primary screen
      * for mobiles - see sensor or form factor documents for details. Uncertainties are reported
-     *  to 1-sigma.
+     * to 1-sigma.
      */
     public static final class PhaseCenterOffset implements Parcelable {
         private final double mOffsetXMm;
@@ -95,31 +95,55 @@
                     }
                 };
 
+        /**
+         * Returns the x-axis offset of the phase center from the origin of the Android sensor
+         * coordinate system, in millimeters.
+         */
         @FloatRange()
         public double getXOffsetMm() {
             return mOffsetXMm;
         }
 
+        /**
+         * Returns the 1-sigma uncertainty of the x-axis offset of the phase center from the origin
+         * of the Android sensor coordinate system, in millimeters.
+         */
         @FloatRange()
         public double getXOffsetUncertaintyMm() {
             return mOffsetXUncertaintyMm;
         }
 
+        /**
+         * Returns the y-axis offset of the phase center from the origin of the Android sensor
+         * coordinate system, in millimeters.
+         */
         @FloatRange()
         public double getYOffsetMm() {
             return mOffsetYMm;
         }
 
+        /**
+         * Returns the 1-sigma uncertainty of the y-axis offset of the phase center from the origin
+         * of the Android sensor coordinate system, in millimeters.
+         */
         @FloatRange()
         public double getYOffsetUncertaintyMm() {
             return mOffsetYUncertaintyMm;
         }
 
+        /**
+         * Returns the z-axis offset of the phase center from the origin of the Android sensor
+         * coordinate system, in millimeters.
+         */
         @FloatRange()
         public double getZOffsetMm() {
             return mOffsetZMm;
         }
 
+        /**
+         * Returns the 1-sigma uncertainty of the z-axis offset of the phase center from the origin
+         * of the Android sensor coordinate system, in millimeters.
+         */
         @FloatRange()
         public double getZOffsetUncertaintyMm() {
             return mOffsetZUncertaintyMm;
@@ -165,7 +189,7 @@
      * at 180 degrees. They are separated by deltaPhi, the regular spacing between zenith angles,
      * i.e., deltaPhi = 180 / (number of columns - 1).
      */
-    public static final class SphericalCorrections implements Parcelable{
+    public static final class SphericalCorrections implements Parcelable {
         private final double[][] mCorrections;
         private final double[][] mCorrectionUncertainties;
         private final double mDeltaTheta;
@@ -296,10 +320,10 @@
         public void writeToParcel(@NonNull Parcel dest, int flags) {
             dest.writeInt(mNumRows);
             dest.writeInt(mNumColumns);
-            for (double[] row: mCorrections) {
+            for (double[] row : mCorrections) {
                 dest.writeDoubleArray(row);
             }
-            for (double[] row: mCorrectionUncertainties) {
+            for (double[] row : mCorrectionUncertainties) {
                 dest.writeDoubleArray(row);
             }
         }
@@ -340,6 +364,7 @@
 
         /**
          * Set antenna carrier frequency (MHz).
+         *
          * @param carrierFrequencyMHz antenna carrier frequency (MHz)
          * @return Builder builder object
          */
@@ -351,6 +376,7 @@
 
         /**
          * Set antenna phase center offset.
+         *
          * @param phaseCenterOffset phase center offset object
          * @return Builder builder object
          */
@@ -362,6 +388,7 @@
 
         /**
          * Set phase center variation corrections.
+         *
          * @param phaseCenterVariationCorrections phase center variation corrections object
          * @return Builder builder object
          */
@@ -374,6 +401,7 @@
 
         /**
          * Set signal gain corrections.
+         *
          * @param signalGainCorrections signal gain corrections object
          * @return Builder builder object
          */
@@ -386,6 +414,7 @@
 
         /**
          * Build GnssAntennaInfo object.
+         *
          * @return instance of GnssAntennaInfo
          */
         @NonNull
@@ -400,47 +429,65 @@
         return mCarrierFrequencyMHz;
     }
 
+    /**
+     * Returns a {@link PhaseCenterOffset} object encapsulating the phase center offset and
+     * corresponding uncertainties in millimeters.
+     *
+     * @return {@link PhaseCenterOffset}
+     */
     @NonNull
     public PhaseCenterOffset getPhaseCenterOffset() {
         return mPhaseCenterOffset;
     }
 
+    /**
+     * Returns a {@link SphericalCorrections} object encapsulating the phase center variation
+     * corrections and corresponding uncertainties in millimeters.
+     *
+     * @return phase center variation corrections as {@link SphericalCorrections}
+     */
     @Nullable
     public SphericalCorrections getPhaseCenterVariationCorrections() {
         return mPhaseCenterVariationCorrections;
     }
 
+    /**
+     * Returns a {@link SphericalCorrections} object encapsulating the signal gain
+     * corrections and corresponding uncertainties in dBi.
+     *
+     * @return signal gain corrections as {@link SphericalCorrections}
+     */
     @Nullable
     public SphericalCorrections getSignalGainCorrections() {
         return mSignalGainCorrections;
     }
 
-    public static final @android.annotation.NonNull
-                    Creator<GnssAntennaInfo> CREATOR = new Creator<GnssAntennaInfo>() {
-                            @Override
-                            public GnssAntennaInfo createFromParcel(Parcel in) {
-                                double carrierFrequencyMHz = in.readDouble();
+    public static final @android.annotation.NonNull Creator<GnssAntennaInfo> CREATOR =
+            new Creator<GnssAntennaInfo>() {
+                @Override
+                public GnssAntennaInfo createFromParcel(Parcel in) {
+                    double carrierFrequencyMHz = in.readDouble();
 
-                                ClassLoader classLoader = getClass().getClassLoader();
-                                PhaseCenterOffset phaseCenterOffset =
-                                        in.readParcelable(classLoader);
-                                SphericalCorrections phaseCenterVariationCorrections =
-                                        in.readParcelable(classLoader);
-                                SphericalCorrections signalGainCorrections =
-                                        in.readParcelable(classLoader);
+                    ClassLoader classLoader = getClass().getClassLoader();
+                    PhaseCenterOffset phaseCenterOffset =
+                            in.readParcelable(classLoader);
+                    SphericalCorrections phaseCenterVariationCorrections =
+                            in.readParcelable(classLoader);
+                    SphericalCorrections signalGainCorrections =
+                            in.readParcelable(classLoader);
 
-                                return new GnssAntennaInfo(
-                                            carrierFrequencyMHz,
-                                            phaseCenterOffset,
-                                            phaseCenterVariationCorrections,
-                                            signalGainCorrections);
-                            }
+                    return new GnssAntennaInfo(
+                            carrierFrequencyMHz,
+                            phaseCenterOffset,
+                            phaseCenterVariationCorrections,
+                            signalGainCorrections);
+                }
 
-                            @Override
-                            public GnssAntennaInfo[] newArray(int size) {
-                                return new GnssAntennaInfo[size];
-                            }
-                    };
+                @Override
+                public GnssAntennaInfo[] newArray(int size) {
+                    return new GnssAntennaInfo[size];
+                }
+            };
 
     @Override
     public int describeContents() {
diff --git a/location/java/android/location/LocationManagerInternal.java b/location/java/android/location/LocationManagerInternal.java
index 542737b4..ef68814 100644
--- a/location/java/android/location/LocationManagerInternal.java
+++ b/location/java/android/location/LocationManagerInternal.java
@@ -21,6 +21,8 @@
 import android.annotation.Nullable;
 import android.location.util.identity.CallerIdentity;
 
+import java.util.List;
+
 /**
  * Location manager local system service interface.
  *
@@ -29,6 +31,16 @@
 public abstract class LocationManagerInternal {
 
     /**
+     * Listener for changes in provider enabled state.
+     */
+    public interface ProviderEnabledListener {
+        /**
+         * Called when the provider enabled state changes for a particular user.
+         */
+        void onProviderEnabledChanged(String provider, int userId, boolean enabled);
+    }
+
+    /**
      * Returns true if the given provider is enabled for the given user.
      *
      * @param provider A location provider as listed by {@link LocationManager#getAllProviders()}
@@ -38,6 +50,24 @@
     public abstract boolean isProviderEnabledForUser(@NonNull String provider, int userId);
 
     /**
+     * Adds a provider enabled listener. The given provider must exist.
+     *
+     * @param provider The provider to listen for changes
+     * @param listener The listener
+     */
+    public abstract void addProviderEnabledListener(String provider,
+            ProviderEnabledListener listener);
+
+    /**
+     * Removes a provider enabled listener. The given provider must exist.
+     *
+     * @param provider The provider to listen for changes
+     * @param listener The listener
+     */
+    public abstract void removeProviderEnabledListener(String provider,
+            ProviderEnabledListener listener);
+
+    /**
      * Returns true if the given identity is a location provider.
      *
      * @param provider The provider to check, or null to check every provider
@@ -52,4 +82,10 @@
      */
     // TODO: there is no reason for this to exist as part of any API. move all the logic into gnss
     public abstract void sendNiResponse(int notifId, int userResponse);
+
+    /**
+     * Should only be used by GNSS code.
+     */
+    // TODO: there is no reason for this to exist as part of any API. create a real batching API
+    public abstract void reportGnssBatchLocations(List<Location> locations);
 }
diff --git a/location/java/android/location/LocationRequest.java b/location/java/android/location/LocationRequest.java
index bb36c2a..280bd05 100644
--- a/location/java/android/location/LocationRequest.java
+++ b/location/java/android/location/LocationRequest.java
@@ -32,6 +32,8 @@
 
 import com.android.internal.util.Preconditions;
 
+import java.util.Objects;
+
 
 /**
  * A data object that contains quality of service parameters for requests
@@ -150,8 +152,6 @@
 
     @UnsupportedAppUsage
     private String mProvider;
-    // if true, client requests coarse location, if false, client requests fine location
-    private boolean mCoarseLocation;
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
     private int mQuality;
     @UnsupportedAppUsage
@@ -257,7 +257,6 @@
     public LocationRequest() {
         this(
                 /* provider= */ LocationManager.FUSED_PROVIDER,
-                /* coarseLocation= */ false,
                 /* quality= */ POWER_LOW,
                 /* interval= */ DEFAULT_INTERVAL_MS,
                 /* fastestInterval= */ (long) (DEFAULT_INTERVAL_MS / FASTEST_INTERVAL_FACTOR),
@@ -276,7 +275,6 @@
     public LocationRequest(LocationRequest src) {
         this(
                 src.mProvider,
-                src.mCoarseLocation,
                 src.mQuality,
                 src.mInterval,
                 src.mFastestInterval,
@@ -293,7 +291,6 @@
 
     private LocationRequest(
             @NonNull String provider,
-            boolean coarseLocation,
             int quality,
             long intervalMs,
             long fastestIntervalMs,
@@ -310,7 +307,6 @@
         checkQuality(quality);
 
         mProvider = provider;
-        mCoarseLocation = coarseLocation;
         mQuality = quality;
         mInterval = intervalMs;
         mFastestInterval = fastestIntervalMs;
@@ -327,20 +323,6 @@
     }
 
     /**
-     * @hide
-     */
-    public boolean isCoarse() {
-        return mCoarseLocation;
-    }
-
-    /**
-     * @hide
-     */
-    public void setCoarse(boolean coarse) {
-        mCoarseLocation = coarse;
-    }
-
-    /**
      * Set the quality of the request.
      *
      * <p>Use with a accuracy constant such as {@link #ACCURACY_FINE}, or a power
@@ -720,7 +702,6 @@
                 public LocationRequest createFromParcel(Parcel in) {
                     return new LocationRequest(
                             /* provider= */ in.readString(),
-                            /* coarseLocation= */ in.readBoolean(),
                             /* quality= */ in.readInt(),
                             /* interval= */ in.readLong(),
                             /* fastestInterval= */ in.readLong(),
@@ -749,7 +730,6 @@
     @Override
     public void writeToParcel(Parcel parcel, int flags) {
         parcel.writeString(mProvider);
-        parcel.writeBoolean(mCoarseLocation);
         parcel.writeInt(mQuality);
         parcel.writeLong(mInterval);
         parcel.writeLong(mFastestInterval);
@@ -784,6 +764,36 @@
         }
     }
 
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) {
+            return true;
+        }
+        if (o == null || getClass() != o.getClass()) {
+            return false;
+        }
+
+        LocationRequest that = (LocationRequest) o;
+        return mQuality == that.mQuality
+                && mInterval == that.mInterval
+                && mFastestInterval == that.mFastestInterval
+                && mExplicitFastestInterval == that.mExplicitFastestInterval
+                && mExpireAt == that.mExpireAt
+                && mExpireIn == that.mExpireIn
+                && mNumUpdates == that.mNumUpdates
+                && Float.compare(that.mSmallestDisplacement, mSmallestDisplacement) == 0
+                && mHideFromAppOps == that.mHideFromAppOps
+                && mLocationSettingsIgnored == that.mLocationSettingsIgnored
+                && mLowPowerMode == that.mLowPowerMode
+                && mProvider.equals(that.mProvider)
+                && Objects.equals(mWorkSource, that.mWorkSource);
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(mProvider, mInterval, mWorkSource);
+    }
+
     @NonNull
     @Override
     public String toString() {
@@ -794,7 +804,7 @@
         if (mQuality != POWER_NONE) {
             s.append(" interval=");
             TimeUtils.formatDuration(mInterval, s);
-            if (mExplicitFastestInterval) {
+            if (mExplicitFastestInterval && mFastestInterval != mInterval) {
                 s.append(" fastestInterval=");
                 TimeUtils.formatDuration(mFastestInterval, s);
             }
diff --git a/location/java/android/location/timezone/LocationTimeZoneEvent.java b/location/java/android/location/timezone/LocationTimeZoneEvent.java
index ea3353c..540bdff 100644
--- a/location/java/android/location/timezone/LocationTimeZoneEvent.java
+++ b/location/java/android/location/timezone/LocationTimeZoneEvent.java
@@ -21,6 +21,7 @@
 import android.annotation.Nullable;
 import android.os.Parcel;
 import android.os.Parcelable;
+import android.os.UserHandle;
 
 import java.util.ArrayList;
 import java.util.Collections;
@@ -37,7 +38,7 @@
     @IntDef({ EVENT_TYPE_UNKNOWN, EVENT_TYPE_SUCCESS, EVENT_TYPE_SUCCESS })
     @interface EventType {}
 
-    /** Uninitialized value for {@link #mEventType} */
+    /** Uninitialized value for {@link #mEventType} - must not be used for real events. */
     private static final int EVENT_TYPE_UNKNOWN = 0;
 
     /**
@@ -60,6 +61,9 @@
 
     private static final int EVENT_TYPE_MAX = EVENT_TYPE_UNCERTAIN;
 
+    @NonNull
+    private final UserHandle mUserHandle;
+
     @EventType
     private final int mEventType;
 
@@ -68,8 +72,9 @@
 
     private final long mElapsedRealtimeNanos;
 
-    private LocationTimeZoneEvent(@EventType int eventType, @NonNull List<String> timeZoneIds,
-            long elapsedRealtimeNanos) {
+    private LocationTimeZoneEvent(@NonNull UserHandle userHandle, @EventType int eventType,
+            @NonNull List<String> timeZoneIds, long elapsedRealtimeNanos) {
+        mUserHandle = Objects.requireNonNull(userHandle);
         mEventType = checkValidEventType(eventType);
         mTimeZoneIds = immutableList(timeZoneIds);
 
@@ -83,6 +88,14 @@
     }
 
     /**
+     * Returns the current user when the event was generated.
+     */
+    @NonNull
+    public UserHandle getUserHandle() {
+        return mUserHandle;
+    }
+
+    /**
      * Returns the time of this fix, in elapsed real-time since system boot.
      *
      * <p>This value can be reliably compared to {@link
@@ -117,7 +130,8 @@
     @Override
     public String toString() {
         return "LocationTimeZoneEvent{"
-                + "mEventType=" + mEventType
+                + "mUserHandle=" + mUserHandle
+                + ", mEventType=" + mEventType
                 + ", mTimeZoneIds=" + mTimeZoneIds
                 + ", mElapsedRealtimeNanos=" + mElapsedRealtimeNanos
                 + '}';
@@ -127,12 +141,14 @@
             new Parcelable.Creator<LocationTimeZoneEvent>() {
                 @Override
                 public LocationTimeZoneEvent createFromParcel(Parcel in) {
+                    UserHandle userHandle = UserHandle.readFromParcel(in);
                     int eventType = in.readInt();
                     @SuppressWarnings("unchecked")
                     ArrayList<String> timeZoneIds =
                             (ArrayList<String>) in.readArrayList(null /* classLoader */);
                     long elapsedRealtimeNanos = in.readLong();
-                    return new LocationTimeZoneEvent(eventType, timeZoneIds, elapsedRealtimeNanos);
+                    return new LocationTimeZoneEvent(
+                            userHandle, eventType, timeZoneIds, elapsedRealtimeNanos);
                 }
 
                 @Override
@@ -148,6 +164,7 @@
 
     @Override
     public void writeToParcel(Parcel parcel, int flags) {
+        mUserHandle.writeToParcel(parcel, flags);
         parcel.writeInt(mEventType);
         parcel.writeList(mTimeZoneIds);
         parcel.writeLong(mElapsedRealtimeNanos);
@@ -162,19 +179,21 @@
             return false;
         }
         LocationTimeZoneEvent that = (LocationTimeZoneEvent) o;
-        return mEventType == that.mEventType
+        return mUserHandle.equals(that.mUserHandle)
+                && mEventType == that.mEventType
                 && mElapsedRealtimeNanos == that.mElapsedRealtimeNanos
                 && mTimeZoneIds.equals(that.mTimeZoneIds);
     }
 
     @Override
     public int hashCode() {
-        return Objects.hash(mEventType, mTimeZoneIds, mElapsedRealtimeNanos);
+        return Objects.hash(mUserHandle, mEventType, mTimeZoneIds, mElapsedRealtimeNanos);
     }
 
     /** @hide */
     public static final class Builder {
 
+        private UserHandle mUserHandle;
         private @EventType int mEventType = EVENT_TYPE_UNKNOWN;
         private @NonNull List<String> mTimeZoneIds = Collections.emptyList();
         private long mElapsedRealtimeNanos;
@@ -186,13 +205,22 @@
          * Sets the contents of this from the supplied instance.
          */
         public Builder(@NonNull LocationTimeZoneEvent ltz) {
+            mUserHandle = ltz.mUserHandle;
             mEventType = ltz.mEventType;
             mTimeZoneIds = ltz.mTimeZoneIds;
             mElapsedRealtimeNanos = ltz.mElapsedRealtimeNanos;
         }
 
         /**
-         * Set the time zone ID of this fix.
+         * Set the current user when this event was generated.
+         */
+        public Builder setUserHandle(@NonNull UserHandle userHandle) {
+            mUserHandle = Objects.requireNonNull(userHandle);
+            return this;
+        }
+
+        /**
+         * Set the time zone ID of this event.
          */
         public Builder setEventType(@EventType int eventType) {
             checkValidEventType(eventType);
@@ -201,7 +229,7 @@
         }
 
         /**
-         * Sets the time zone IDs of this fix.
+         * Sets the time zone IDs of this event.
          */
         public Builder setTimeZoneIds(@NonNull List<String> timeZoneIds) {
             mTimeZoneIds = Objects.requireNonNull(timeZoneIds);
@@ -209,9 +237,7 @@
         }
 
         /**
-         * Sets the time of this fix, in elapsed real-time since system boot.
-         *
-         * @param time elapsed real-time of fix, in nanoseconds since system boot.
+         * Sets the time of this event, in elapsed real-time since system boot.
          */
         public Builder setElapsedRealtimeNanos(long time) {
             mElapsedRealtimeNanos = time;
@@ -222,8 +248,8 @@
          * Builds a {@link LocationTimeZoneEvent} instance.
          */
         public LocationTimeZoneEvent build() {
-            return new LocationTimeZoneEvent(this.mEventType, this.mTimeZoneIds,
-                    this.mElapsedRealtimeNanos);
+            return new LocationTimeZoneEvent(
+                    mUserHandle, mEventType, mTimeZoneIds, mElapsedRealtimeNanos);
         }
     }
 
diff --git a/location/lib/java/com/android/location/timezone/provider/LocationTimeZoneProviderBase.java b/location/lib/java/com/android/location/timezone/provider/LocationTimeZoneProviderBase.java
index 0143c88..c533c20 100644
--- a/location/lib/java/com/android/location/timezone/provider/LocationTimeZoneProviderBase.java
+++ b/location/lib/java/com/android/location/timezone/provider/LocationTimeZoneProviderBase.java
@@ -32,7 +32,7 @@
 /**
  * Base class for location time zone providers implemented as unbundled services.
  *
- * TODO Provide details of the expected service actions.
+ * TODO(b/152744911): Provide details of the expected service actions and threading.
  *
  * <p>IMPORTANT: This class is effectively a public API for unbundled applications, and must remain
  * API stable.
diff --git a/location/lib/java/com/android/location/timezone/provider/LocationTimeZoneProviderRequestUnbundled.java b/location/lib/java/com/android/location/timezone/provider/LocationTimeZoneProviderRequestUnbundled.java
index dd80466..e898bbf 100644
--- a/location/lib/java/com/android/location/timezone/provider/LocationTimeZoneProviderRequestUnbundled.java
+++ b/location/lib/java/com/android/location/timezone/provider/LocationTimeZoneProviderRequestUnbundled.java
@@ -44,6 +44,24 @@
     }
 
     @Override
+    public boolean equals(Object o) {
+        if (this == o) {
+            return true;
+        }
+        if (o == null || getClass() != o.getClass()) {
+            return false;
+        }
+        LocationTimeZoneProviderRequestUnbundled that =
+                (LocationTimeZoneProviderRequestUnbundled) o;
+        return mRequest.equals(that.mRequest);
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(mRequest);
+    }
+
+    @Override
     public String toString() {
         return mRequest.toString();
     }
diff --git a/media/java/android/media/AudioDeviceInfo.java b/media/java/android/media/AudioDeviceInfo.java
index 6e3fb19..d4fb1be 100644
--- a/media/java/android/media/AudioDeviceInfo.java
+++ b/media/java/android/media/AudioDeviceInfo.java
@@ -147,6 +147,19 @@
     // {@link android.media.audiopolicy.AudioMix#ROUTE_FLAG_LOOP_BACK} flag.
     public static final int TYPE_REMOTE_SUBMIX = 25;
 
+    /**
+     * A device type describing a Bluetooth Low Energy (BLE) audio headset or headphones.
+     * Headphones are grouped with headsets when the device is a sink:
+     * the features of headsets and headphones with regard to playback are the same.
+     */
+    public static final int TYPE_BLE_HEADSET   = 26;
+
+    /**
+     * A device type describing a Bluetooth Low Energy (BLE) audio speaker.
+     */
+    public static final int TYPE_BLE_SPEAKER   = 27;
+
+
     /** @hide */
     @IntDef(flag = false, prefix = "TYPE", value = {
             TYPE_BUILTIN_EARPIECE,
@@ -171,7 +184,9 @@
             TYPE_HEARING_AID,
             TYPE_BUILTIN_MIC,
             TYPE_FM_TUNER,
-            TYPE_TV_TUNER }
+            TYPE_TV_TUNER,
+            TYPE_BLE_HEADSET,
+            TYPE_BLE_SPEAKER}
     )
     @Retention(RetentionPolicy.SOURCE)
     public @interface AudioDeviceType {}
@@ -193,7 +208,8 @@
             TYPE_LINE_ANALOG,
             TYPE_LINE_DIGITAL,
             TYPE_IP,
-            TYPE_BUS }
+            TYPE_BUS,
+            TYPE_BLE_HEADSET}
     )
     @Retention(RetentionPolicy.SOURCE)
     public @interface AudioDeviceTypeIn {}
@@ -219,7 +235,9 @@
             TYPE_AUX_LINE,
             TYPE_IP,
             TYPE_BUS,
-            TYPE_HEARING_AID }
+            TYPE_HEARING_AID,
+            TYPE_BLE_HEADSET,
+            TYPE_BLE_SPEAKER}
     )
     @Retention(RetentionPolicy.SOURCE)
     public @interface AudioDeviceTypeOut {}
@@ -248,7 +266,8 @@
             case TYPE_BUS:
             case TYPE_HEARING_AID:
             case TYPE_BUILTIN_SPEAKER_SAFE:
-            case TYPE_REMOTE_SUBMIX:
+            case TYPE_BLE_HEADSET:
+            case TYPE_BLE_SPEAKER:
                 return true;
             default:
                 return false;
@@ -275,6 +294,7 @@
             case TYPE_IP:
             case TYPE_BUS:
             case TYPE_REMOTE_SUBMIX:
+            case TYPE_BLE_HEADSET:
                 return true;
             default:
                 return false;
@@ -527,6 +547,8 @@
         INT_TO_EXT_DEVICE_MAPPING.put(AudioSystem.DEVICE_OUT_SPEAKER_SAFE,
                 TYPE_BUILTIN_SPEAKER_SAFE);
         INT_TO_EXT_DEVICE_MAPPING.put(AudioSystem.DEVICE_OUT_REMOTE_SUBMIX, TYPE_REMOTE_SUBMIX);
+        INT_TO_EXT_DEVICE_MAPPING.put(AudioSystem.DEVICE_OUT_BLE_HEADSET, TYPE_BLE_HEADSET);
+        INT_TO_EXT_DEVICE_MAPPING.put(AudioSystem.DEVICE_OUT_BLE_SPEAKER, TYPE_BLE_SPEAKER);
 
         INT_TO_EXT_DEVICE_MAPPING.put(AudioSystem.DEVICE_IN_BUILTIN_MIC, TYPE_BUILTIN_MIC);
         INT_TO_EXT_DEVICE_MAPPING.put(AudioSystem.DEVICE_IN_BLUETOOTH_SCO_HEADSET, TYPE_BLUETOOTH_SCO);
@@ -547,6 +569,7 @@
         INT_TO_EXT_DEVICE_MAPPING.put(AudioSystem.DEVICE_IN_IP, TYPE_IP);
         INT_TO_EXT_DEVICE_MAPPING.put(AudioSystem.DEVICE_IN_BUS, TYPE_BUS);
         INT_TO_EXT_DEVICE_MAPPING.put(AudioSystem.DEVICE_IN_REMOTE_SUBMIX, TYPE_REMOTE_SUBMIX);
+        INT_TO_EXT_DEVICE_MAPPING.put(AudioSystem.DEVICE_IN_BLE_HEADSET, TYPE_BLE_HEADSET);
 
         // privileges mapping to output device
         EXT_TO_INT_DEVICE_MAPPING = new SparseIntArray();
@@ -576,6 +599,8 @@
         EXT_TO_INT_DEVICE_MAPPING.put(TYPE_BUILTIN_SPEAKER_SAFE,
                 AudioSystem.DEVICE_OUT_SPEAKER_SAFE);
         EXT_TO_INT_DEVICE_MAPPING.put(TYPE_REMOTE_SUBMIX, AudioSystem.DEVICE_OUT_REMOTE_SUBMIX);
+        EXT_TO_INT_DEVICE_MAPPING.put(TYPE_BLE_HEADSET, AudioSystem.DEVICE_OUT_BLE_HEADSET);
+        EXT_TO_INT_DEVICE_MAPPING.put(TYPE_BLE_SPEAKER, AudioSystem.DEVICE_OUT_BLE_SPEAKER);
     }
 }
 
diff --git a/media/java/android/media/AudioDevicePort.java b/media/java/android/media/AudioDevicePort.java
index 42d0f0c..f6b0454 100644
--- a/media/java/android/media/AudioDevicePort.java
+++ b/media/java/android/media/AudioDevicePort.java
@@ -70,7 +70,9 @@
      * {@link AudioManager#DEVICE_IN_USB_DEVICE}) use an address composed of the ALSA card number
      * and device number: "card=2;device=1"
      * - Bluetooth devices ({@link AudioManager#DEVICE_OUT_BLUETOOTH_SCO},
-     * {@link AudioManager#DEVICE_OUT_BLUETOOTH_SCO}, {@link AudioManager#DEVICE_OUT_BLUETOOTH_A2DP})
+     * {@link AudioManager#DEVICE_OUT_BLUETOOTH_SCO},
+     * {@link AudioManager#DEVICE_OUT_BLUETOOTH_A2DP}),
+     * {@link AudioManager#DEVICE_OUT_BLE_HEADSET}, {@link AudioManager#DEVICE_OUT_BLE_SPEAKER})
      * use the MAC address of the bluetooth device in the form "00:11:22:AA:BB:CC" as reported by
      * {@link BluetoothDevice#getAddress()}.
      * - Deivces that do not have an address will indicate an empty string "".
diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java
index 928fb62..a16e063 100755
--- a/media/java/android/media/AudioManager.java
+++ b/media/java/android/media/AudioManager.java
@@ -75,6 +75,7 @@
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.Iterator;
@@ -564,6 +565,7 @@
      * request is from a hardware key press. (e.g. {@link MediaController}).
      * @hide
      */
+    @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
     public static final int FLAG_FROM_KEY = 1 << 12;
 
     // The iterator of TreeMap#entrySet() returns the entries in ascending key order.
@@ -1598,21 +1600,14 @@
     @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING)
     public boolean setPreferredDeviceForStrategy(@NonNull AudioProductStrategy strategy,
             @NonNull AudioDeviceAttributes device) {
-        Objects.requireNonNull(strategy);
-        Objects.requireNonNull(device);
-        try {
-            final int status =
-                    getService().setPreferredDeviceForStrategy(strategy.getId(), device);
-            return status == AudioSystem.SUCCESS;
-        } catch (RemoteException e) {
-            throw e.rethrowFromSystemServer();
-        }
+        return setPreferredDevicesForStrategy(strategy, Arrays.asList(device));
     }
 
     /**
      * @hide
-     * Removes the preferred audio device previously set with
-     * {@link #setPreferredDeviceForStrategy(AudioProductStrategy, AudioDeviceAttributes)}.
+     * Removes the preferred audio device(s) previously set with
+     * {@link #setPreferredDeviceForStrategy(AudioProductStrategy, AudioDeviceAttributes)} or
+     * {@link #setPreferredDevicesForStrategy(AudioProductStrategy, List<AudioDeviceAttributes>)}.
      * @param strategy the audio strategy whose routing will be affected
      * @return true if the operation was successful, false otherwise (invalid strategy, or no
      *     device set for example)
@@ -1623,7 +1618,7 @@
         Objects.requireNonNull(strategy);
         try {
             final int status =
-                    getService().removePreferredDeviceForStrategy(strategy.getId());
+                    getService().removePreferredDevicesForStrategy(strategy.getId());
             return status == AudioSystem.SUCCESS;
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
@@ -1633,18 +1628,74 @@
     /**
      * @hide
      * Return the preferred device for an audio strategy, previously set with
+     * {@link #setPreferredDeviceForStrategy(AudioProductStrategy, AudioDeviceAttributes)} or
+     * {@link #setPreferredDevicesForStrategy(AudioProductStrategy, List<AudioDeviceAttributes>)}
+     * @param strategy the strategy to query
+     * @return the preferred device for that strategy, if multiple devices are set as preferred
+     *    devices, the first one in the list will be returned. Null will be returned if none was
+     *    ever set or if the strategy is invalid
+     */
+    @SystemApi
+    @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING)
+    @Nullable
+    public AudioDeviceAttributes getPreferredDeviceForStrategy(
+            @NonNull AudioProductStrategy strategy) {
+        List<AudioDeviceAttributes> devices = getPreferredDevicesForStrategy(strategy);
+        return devices.isEmpty() ? null : devices.get(0);
+    }
+
+    /**
+     * @hide
+     * Set the preferred devices for a given strategy, i.e. the audio routing to be used by
+     * this audio strategy. Note that the devices may not be available at the time the preferred
+     * devices is set, but it will be used once made available.
+     * <p>Use {@link #removePreferredDeviceForStrategy(AudioProductStrategy)} to cancel setting
+     * this preference for this strategy.</p>
+     * Note that the list of devices is not a list ranked by preference, but a list of one or more
+     * devices used simultaneously to output the same audio signal.
+     * @param strategy the audio strategy whose routing will be affected
+     * @param devices a non-empty list of the audio devices to route to when available
+     * @return true if the operation was successful, false otherwise
+     */
+    @SystemApi
+    @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING)
+    public boolean setPreferredDevicesForStrategy(@NonNull AudioProductStrategy strategy,
+                                                  @NonNull List<AudioDeviceAttributes> devices) {
+        Objects.requireNonNull(strategy);
+        Objects.requireNonNull(devices);
+        if (devices.isEmpty()) {
+            throw new IllegalArgumentException(
+                    "Tried to set preferred devices for strategy with a empty list");
+        }
+        for (AudioDeviceAttributes device : devices) {
+            Objects.requireNonNull(device);
+        }
+        try {
+            final int status =
+                    getService().setPreferredDevicesForStrategy(strategy.getId(), devices);
+            return status == AudioSystem.SUCCESS;
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * @hide
+     * Return the preferred devices for an audio strategy, previously set with
      * {@link #setPreferredDeviceForStrategy(AudioProductStrategy, AudioDeviceAttributes)}
+     * {@link #setPreferredDevicesForStrategy(AudioProductStrategy, List<AudioDeviceAttributes>)}
      * @param strategy the strategy to query
      * @return the preferred device for that strategy, or null if none was ever set or if the
      *    strategy is invalid
      */
     @SystemApi
     @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING)
-    public @Nullable AudioDeviceAttributes getPreferredDeviceForStrategy(
+    @NonNull
+    public List<AudioDeviceAttributes> getPreferredDevicesForStrategy(
             @NonNull AudioProductStrategy strategy) {
         Objects.requireNonNull(strategy);
         try {
-            return getService().getPreferredDeviceForStrategy(strategy.getId());
+            return getService().getPreferredDevicesForStrategy(strategy.getId());
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
@@ -1656,6 +1707,7 @@
      * strategy.
      * <p>Note that this listener will only be invoked whenever
      * {@link #setPreferredDeviceForStrategy(AudioProductStrategy, AudioDeviceAttributes)} or
+     * {@link #setPreferredDevicesForStrategy(AudioProductStrategy, List<AudioDeviceAttributes>)}
      * {@link #removePreferredDeviceForStrategy(AudioProductStrategy)} causes a change in
      * preferred device. It will not be invoked directly after registration with
      * {@link #addOnPreferredDeviceForStrategyChangedListener(Executor, OnPreferredDeviceForStrategyChangedListener)}
@@ -1663,8 +1715,10 @@
      * @see #setPreferredDeviceForStrategy(AudioProductStrategy, AudioDeviceAttributes)
      * @see #removePreferredDeviceForStrategy(AudioProductStrategy)
      * @see #getPreferredDeviceForStrategy(AudioProductStrategy)
+     * @deprecated use #OnPreferredDevicesForStrategyChangedListener
      */
     @SystemApi
+    @Deprecated
     public interface OnPreferredDeviceForStrategyChangedListener {
         /**
          * Called on the listener to indicate that the preferred audio device for the given
@@ -1679,6 +1733,70 @@
 
     /**
      * @hide
+     * Interface to be notified of changes in the preferred audio devices set for a given audio
+     * strategy.
+     * <p>Note that this listener will only be invoked whenever
+     * {@link #setPreferredDeviceForStrategy(AudioProductStrategy, AudioDeviceAttributes)} or
+     * {@link #setPreferredDevicesForStrategy(AudioProductStrategy, List<AudioDeviceAttributes>)}
+     * {@link #removePreferredDeviceForStrategy(AudioProductStrategy)} causes a change in
+     * preferred device(s). It will not be invoked directly after registration with
+     * {@link #addOnPreferredDevicesForStrategyChangedListener(
+     * Executor, OnPreferredDevicesForStrategyChangedListener)}
+     * to indicate which strategies had preferred devices at the time of registration.</p>
+     * @see #setPreferredDeviceForStrategy(AudioProductStrategy, AudioDeviceAttributes)
+     * @see #setPreferredDevicesForStrategy(AudioProductStrategy, List)
+     * @see #removePreferredDeviceForStrategy(AudioProductStrategy)
+     * @see #getPreferredDeviceForStrategy(AudioProductStrategy)
+     * @see #getPreferredDevicesForStrategy(AudioProductStrategy)
+     */
+    @SystemApi
+    public interface OnPreferredDevicesForStrategyChangedListener {
+        /**
+         * Called on the listener to indicate that the preferred audio devices for the given
+         * strategy has changed.
+         * @param strategy the {@link AudioProductStrategy} whose preferred device changed
+         * @param devices a list of newly set preferred audio devices
+         */
+        void onPreferredDevicesForStrategyChanged(@NonNull AudioProductStrategy strategy,
+                                                  @NonNull List<AudioDeviceAttributes> devices);
+    }
+
+    /**
+     * @hide
+     * Adds a listener for being notified of changes to the strategy-preferred audio device.
+     * @param executor
+     * @param listener
+     * @throws SecurityException if the caller doesn't hold the required permission
+     * @deprecated use {@link #addOnPreferredDevicesForStrategyChangedListener(
+     *             Executor, AudioManager.OnPreferredDevicesForStrategyChangedListener)} instead
+     */
+    @SystemApi
+    @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING)
+    @Deprecated
+    public void addOnPreferredDeviceForStrategyChangedListener(
+            @NonNull @CallbackExecutor Executor executor,
+            @NonNull OnPreferredDeviceForStrategyChangedListener listener)
+            throws SecurityException {
+        // No-op, the method is deprecated.
+    }
+
+    /**
+     * @hide
+     * Removes a previously added listener of changes to the strategy-preferred audio device.
+     * @param listener
+     * @deprecated use {@link #removeOnPreferredDevicesForStrategyChangedListener(
+     *             AudioManager.OnPreferredDevicesForStrategyChangedListener)} instead
+     */
+    @SystemApi
+    @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING)
+    @Deprecated
+    public void removeOnPreferredDeviceForStrategyChangedListener(
+            @NonNull OnPreferredDeviceForStrategyChangedListener listener) {
+        // No-op, the method is deprecated.
+    }
+
+    /**
+     * @hide
      * Adds a listener for being notified of changes to the strategy-preferred audio device.
      * @param executor
      * @param listener
@@ -1686,16 +1804,16 @@
      */
     @SystemApi
     @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING)
-    public void addOnPreferredDeviceForStrategyChangedListener(
+    public void addOnPreferredDevicesForStrategyChangedListener(
             @NonNull @CallbackExecutor Executor executor,
-            @NonNull OnPreferredDeviceForStrategyChangedListener listener)
+            @NonNull OnPreferredDevicesForStrategyChangedListener listener)
             throws SecurityException {
         Objects.requireNonNull(executor);
         Objects.requireNonNull(listener);
         synchronized (mPrefDevListenerLock) {
             if (hasPrefDevListener(listener)) {
                 throw new IllegalArgumentException(
-                        "attempt to call addOnPreferredDeviceForStrategyChangedListener() "
+                        "attempt to call addOnPreferredDevicesForStrategyChangedListener() "
                                 + "on a previously registered listener");
             }
             // lazy initialization of the list of strategy-preferred device listener
@@ -1707,10 +1825,10 @@
             if (oldCbCount == 0 && mPrefDevListeners.size() > 0) {
                 // register binder for callbacks
                 if (mPrefDevDispatcherStub == null) {
-                    mPrefDevDispatcherStub = new StrategyPreferredDeviceDispatcherStub();
+                    mPrefDevDispatcherStub = new StrategyPreferredDevicesDispatcherStub();
                 }
                 try {
-                    getService().registerStrategyPreferredDeviceDispatcher(mPrefDevDispatcherStub);
+                    getService().registerStrategyPreferredDevicesDispatcher(mPrefDevDispatcherStub);
                 } catch (RemoteException e) {
                     throw e.rethrowFromSystemServer();
                 }
@@ -1725,8 +1843,8 @@
      */
     @SystemApi
     @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING)
-    public void removeOnPreferredDeviceForStrategyChangedListener(
-            @NonNull OnPreferredDeviceForStrategyChangedListener listener) {
+    public void removeOnPreferredDevicesForStrategyChangedListener(
+            @NonNull OnPreferredDevicesForStrategyChangedListener listener) {
         Objects.requireNonNull(listener);
         synchronized (mPrefDevListenerLock) {
             if (!removePrefDevListener(listener)) {
@@ -1737,7 +1855,7 @@
             if (mPrefDevListeners.size() == 0) {
                 // unregister binder for callbacks
                 try {
-                    getService().unregisterStrategyPreferredDeviceDispatcher(
+                    getService().unregisterStrategyPreferredDevicesDispatcher(
                             mPrefDevDispatcherStub);
                 } catch (RemoteException e) {
                     throw e.rethrowFromSystemServer();
@@ -1759,23 +1877,23 @@
     private @Nullable ArrayList<PrefDevListenerInfo> mPrefDevListeners;
 
     private static class PrefDevListenerInfo {
-        final @NonNull OnPreferredDeviceForStrategyChangedListener mListener;
+        final @NonNull OnPreferredDevicesForStrategyChangedListener mListener;
         final @NonNull Executor mExecutor;
-        PrefDevListenerInfo(OnPreferredDeviceForStrategyChangedListener listener, Executor exe) {
+        PrefDevListenerInfo(OnPreferredDevicesForStrategyChangedListener listener, Executor exe) {
             mListener = listener;
             mExecutor = exe;
         }
     }
 
     @GuardedBy("mPrefDevListenerLock")
-    private StrategyPreferredDeviceDispatcherStub mPrefDevDispatcherStub;
+    private StrategyPreferredDevicesDispatcherStub mPrefDevDispatcherStub;
 
-    private final class StrategyPreferredDeviceDispatcherStub
-            extends IStrategyPreferredDeviceDispatcher.Stub {
+    private final class StrategyPreferredDevicesDispatcherStub
+            extends IStrategyPreferredDevicesDispatcher.Stub {
 
         @Override
-        public void dispatchPrefDeviceChanged(int strategyId,
-                                              @Nullable AudioDeviceAttributes device) {
+        public void dispatchPrefDevicesChanged(int strategyId,
+                                               @NonNull List<AudioDeviceAttributes> devices) {
             // make a shallow copy of listeners so callback is not executed under lock
             final ArrayList<PrefDevListenerInfo> prefDevListeners;
             synchronized (mPrefDevListenerLock) {
@@ -1790,7 +1908,7 @@
             try {
                 for (PrefDevListenerInfo info : prefDevListeners) {
                     info.mExecutor.execute(() ->
-                            info.mListener.onPreferredDeviceForStrategyChanged(strategy, device));
+                            info.mListener.onPreferredDevicesForStrategyChanged(strategy, devices));
                 }
             } finally {
                 Binder.restoreCallingIdentity(ident);
@@ -1800,7 +1918,7 @@
 
     @GuardedBy("mPrefDevListenerLock")
     private @Nullable PrefDevListenerInfo getPrefDevListenerInfo(
-            OnPreferredDeviceForStrategyChangedListener listener) {
+            OnPreferredDevicesForStrategyChangedListener listener) {
         if (mPrefDevListeners == null) {
             return null;
         }
@@ -1813,7 +1931,7 @@
     }
 
     @GuardedBy("mPrefDevListenerLock")
-    private boolean hasPrefDevListener(OnPreferredDeviceForStrategyChangedListener listener) {
+    private boolean hasPrefDevListener(OnPreferredDevicesForStrategyChangedListener listener) {
         return getPrefDevListenerInfo(listener) != null;
     }
 
@@ -1821,7 +1939,7 @@
     /**
      * @return true if the listener was removed from the list
      */
-    private boolean removePrefDevListener(OnPreferredDeviceForStrategyChangedListener listener) {
+    private boolean removePrefDevListener(OnPreferredDevicesForStrategyChangedListener listener) {
         final PrefDevListenerInfo infoToRemove = getPrefDevListenerInfo(listener);
         if (infoToRemove != null) {
             mPrefDevListeners.remove(infoToRemove);
@@ -4412,6 +4530,18 @@
      */
     public static final int DEVICE_OUT_FM = AudioSystem.DEVICE_OUT_FM;
     /** @hide
+     * The audio output device code for echo reference injection point.
+     */
+    public static final int DEVICE_OUT_ECHO_CANCELLER = AudioSystem.DEVICE_OUT_ECHO_CANCELLER;
+    /** @hide
+     * The audio output device code for a BLE audio headset.
+     */
+    public static final int DEVICE_OUT_BLE_HEADSET = AudioSystem.DEVICE_OUT_BLE_HEADSET;
+    /** @hide
+     * The audio output device code for a BLE audio speaker.
+     */
+    public static final int DEVICE_OUT_BLE_SPEAKER = AudioSystem.DEVICE_OUT_BLE_SPEAKER;
+    /** @hide
      * This is not used as a returned value from {@link #getDevicesForStream}, but could be
      *  used in the future in a set method to select whatever default device is chosen by the
      *  platform-specific implementation.
@@ -4495,6 +4625,14 @@
      * The audio input device code for audio loopback
      */
     public static final int DEVICE_IN_LOOPBACK = AudioSystem.DEVICE_IN_LOOPBACK;
+    /** @hide
+     * The audio input device code for an echo reference capture point.
+     */
+    public static final int DEVICE_IN_ECHO_REFERENCE = AudioSystem.DEVICE_IN_ECHO_REFERENCE;
+    /** @hide
+     * The audio input device code for a BLE audio headset.
+     */
+    public static final int DEVICE_IN_BLE_HEADSET = AudioSystem.DEVICE_IN_BLE_HEADSET;
 
     /**
      * Return true if the device code corresponds to an output device.
@@ -4600,36 +4738,41 @@
     /**
      * @hide
      * Volume behavior for an audio device that has no particular volume behavior set. Invalid as
-     * an argument to {@link #setDeviceVolumeBehavior(int, String, int)}.
+     * an argument to {@link #setDeviceVolumeBehavior(AudioDeviceAttributes, int)} and should not
+     * be returned by {@link #getDeviceVolumeBehavior(AudioDeviceAttributes)}.
      */
     public static final int DEVICE_VOLUME_BEHAVIOR_UNSET = -1;
     /**
      * @hide
      * Volume behavior for an audio device where a software attenuation is applied
-     * @see #setDeviceVolumeBehavior(int, String, int)
+     * @see #setDeviceVolumeBehavior(AudioDeviceAttributes, int)
      */
+    @SystemApi
     public static final int DEVICE_VOLUME_BEHAVIOR_VARIABLE = 0;
     /**
      * @hide
      * Volume behavior for an audio device where the volume is always set to provide no attenuation
      *     nor gain (e.g. unit gain).
-     * @see #setDeviceVolumeBehavior(int, String, int)
+     * @see #setDeviceVolumeBehavior(AudioDeviceAttributes, int)
      */
+    @SystemApi
     public static final int DEVICE_VOLUME_BEHAVIOR_FULL = 1;
     /**
      * @hide
      * Volume behavior for an audio device where the volume is either set to muted, or to provide
      *     no attenuation nor gain (e.g. unit gain).
-     * @see #setDeviceVolumeBehavior(int, String, int)
+     * @see #setDeviceVolumeBehavior(AudioDeviceAttributes, int)
      */
+    @SystemApi
     public static final int DEVICE_VOLUME_BEHAVIOR_FIXED = 2;
     /**
      * @hide
      * Volume behavior for an audio device where no software attenuation is applied, and
      *     the volume is kept synchronized between the host and the device itself through a
      *     device-specific protocol such as BT AVRCP.
-     * @see #setDeviceVolumeBehavior(int, String, int)
+     * @see #setDeviceVolumeBehavior(AudioDeviceAttributes, int)
      */
+    @SystemApi
     public static final int DEVICE_VOLUME_BEHAVIOR_ABSOLUTE = 3;
     /**
      * @hide
@@ -4638,8 +4781,9 @@
      *     device-specific protocol (such as for hearing aids), based on the audio mode (e.g.
      *     normal vs in phone call).
      * @see #setMode(int)
-     * @see #setDeviceVolumeBehavior(int, String, int)
+     * @see #setDeviceVolumeBehavior(AudioDeviceAttributes, int)
      */
+    @SystemApi
     public static final int DEVICE_VOLUME_BEHAVIOR_ABSOLUTE_MULTI_MODE = 4;
 
     /** @hide */
@@ -4686,27 +4830,15 @@
     /**
      * @hide
      * Sets the volume behavior for an audio output device.
-     * @param deviceType the type of audio device to be affected. Currently only supports
-     *     {@link AudioDeviceInfo#TYPE_HDMI}, {@link AudioDeviceInfo#TYPE_HDMI_ARC},
-     *     {@link AudioDeviceInfo#TYPE_LINE_DIGITAL} and {@link AudioDeviceInfo#TYPE_AUX_LINE}
-     * @param deviceAddress the address of the device, if any
+     * @see #DEVICE_VOLUME_BEHAVIOR_VARIABLE
+     * @see #DEVICE_VOLUME_BEHAVIOR_FULL
+     * @see #DEVICE_VOLUME_BEHAVIOR_FIXED
+     * @see #DEVICE_VOLUME_BEHAVIOR_ABSOLUTE
+     * @see #DEVICE_VOLUME_BEHAVIOR_ABSOLUTE_MULTI_MODE
+     * @param device the device to be affected
      * @param deviceVolumeBehavior one of the device behaviors
      */
-    @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING)
-    public void setDeviceVolumeBehavior(int deviceType, @Nullable String deviceAddress,
-            @DeviceVolumeBehavior int deviceVolumeBehavior) {
-        setDeviceVolumeBehavior(new AudioDeviceAttributes(AudioDeviceAttributes.ROLE_OUTPUT,
-                deviceType, deviceAddress), deviceVolumeBehavior);
-    }
-
-    /**
-     * @hide
-     * Sets the volume behavior for an audio output device.
-     * @param device the device to be affected. Currently only supports devices of type
-     *     {@link AudioDeviceInfo#TYPE_HDMI}, {@link AudioDeviceInfo#TYPE_HDMI_ARC},
-     *     {@link AudioDeviceInfo#TYPE_LINE_DIGITAL} and {@link AudioDeviceInfo#TYPE_AUX_LINE}
-     * @param deviceVolumeBehavior one of the device behaviors
-     */
+    @SystemApi
     @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING)
     public void setDeviceVolumeBehavior(@NonNull AudioDeviceAttributes device,
             @DeviceVolumeBehavior int deviceVolumeBehavior) {
@@ -4725,29 +4857,14 @@
 
     /**
      * @hide
-     * Returns the volume device behavior for the given device type and address
-     * @param deviceType an audio output device type, as defined in {@link AudioDeviceInfo}
-     * @param deviceAddress the address of the audio device, if any.
-     * @return the volume behavior for the device
-     */
-    @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING)
-    public @DeviceVolumeBehaviorState int getDeviceVolumeBehavior(int deviceType,
-            @Nullable String deviceAddress) {
-        // verify arguments
-        AudioDeviceInfo.enforceValidAudioDeviceTypeOut(deviceType);
-        return getDeviceVolumeBehavior(new AudioDeviceAttributes(AudioDeviceAttributes.ROLE_OUTPUT,
-                deviceType, deviceAddress));
-    }
-
-    /**
-     * @hide
      * Returns the volume device behavior for the given audio device
      * @param device the audio device
      * @return the volume behavior for the device
      */
+    @SystemApi
     @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING)
-    public @DeviceVolumeBehaviorState int getDeviceVolumeBehavior(
-            @NonNull AudioDeviceAttributes device) {
+    public @DeviceVolumeBehavior
+    int getDeviceVolumeBehavior(@NonNull AudioDeviceAttributes device) {
         // verify arguments (validity of device type is enforced in server)
         Objects.requireNonNull(device);
         // communicate with service
@@ -6200,6 +6317,24 @@
         }
     }
 
+
+    /**
+     * Retrieves the Hardware A/V synchronization ID corresponding to the given audio session ID.
+     * For more details on Hardware A/V synchronization please refer to
+     *  <a href="https://source.android.com/devices/tv/multimedia-tunneling/">
+     * media tunneling documentation</a>.
+     * @param sessionId the audio session ID for which the HW A/V sync ID is retrieved.
+     * @return the HW A/V sync ID for this audio session (an integer different from 0).
+     * @throws UnsupportedOperationException if HW A/V synchronization is not supported.
+     */
+    public int getAudioHwSyncForSession(int sessionId) {
+        int hwSyncId = AudioSystem.getAudioHwSyncForSession(sessionId);
+        if (hwSyncId == AudioSystem.AUDIO_HW_SYNC_INVALID) {
+            throw new UnsupportedOperationException("HW A/V synchronization is not supported.");
+        }
+        return hwSyncId;
+    }
+
     //---------------------------------------------------------
     // Inner classes
     //--------------------
diff --git a/media/java/android/media/AudioSystem.java b/media/java/android/media/AudioSystem.java
index da52cfe..243ec1f 100644
--- a/media/java/android/media/AudioSystem.java
+++ b/media/java/android/media/AudioSystem.java
@@ -32,6 +32,7 @@
 import java.lang.annotation.RetentionPolicy;
 import java.util.ArrayList;
 import java.util.HashSet;
+import java.util.List;
 import java.util.Map;
 import java.util.Objects;
 import java.util.Set;
@@ -866,6 +867,12 @@
     public static final int DEVICE_OUT_USB_HEADSET = 0x4000000;
     /** @hide */
     public static final int DEVICE_OUT_HEARING_AID = 0x8000000;
+    /** @hide */
+    public static final int DEVICE_OUT_ECHO_CANCELLER = 0x10000000;
+    /** @hide */
+    public static final int DEVICE_OUT_BLE_HEADSET = 0x20000000;
+    /** @hide */
+    public static final int DEVICE_OUT_BLE_SPEAKER = 0x20000001;
 
     /** @hide */
     public static final int DEVICE_OUT_DEFAULT = DEVICE_BIT_DEFAULT;
@@ -890,6 +897,8 @@
     public static final Set<Integer> DEVICE_OUT_ALL_HDMI_SYSTEM_AUDIO_SET;
     /** @hide */
     public static final Set<Integer> DEVICE_ALL_HDMI_SYSTEM_AUDIO_AND_SPEAKER_SET;
+    /** @hide */
+    public static final Set<Integer> DEVICE_OUT_ALL_BLE_SET;
     static {
         DEVICE_OUT_ALL_SET = new HashSet<>();
         DEVICE_OUT_ALL_SET.add(DEVICE_OUT_EARPIECE);
@@ -920,6 +929,9 @@
         DEVICE_OUT_ALL_SET.add(DEVICE_OUT_PROXY);
         DEVICE_OUT_ALL_SET.add(DEVICE_OUT_USB_HEADSET);
         DEVICE_OUT_ALL_SET.add(DEVICE_OUT_HEARING_AID);
+        DEVICE_OUT_ALL_SET.add(DEVICE_OUT_ECHO_CANCELLER);
+        DEVICE_OUT_ALL_SET.add(DEVICE_OUT_BLE_HEADSET);
+        DEVICE_OUT_ALL_SET.add(DEVICE_OUT_BLE_SPEAKER);
         DEVICE_OUT_ALL_SET.add(DEVICE_OUT_DEFAULT);
 
         DEVICE_OUT_ALL_A2DP_SET = new HashSet<>();
@@ -945,6 +957,10 @@
         DEVICE_ALL_HDMI_SYSTEM_AUDIO_AND_SPEAKER_SET = new HashSet<>();
         DEVICE_ALL_HDMI_SYSTEM_AUDIO_AND_SPEAKER_SET.addAll(DEVICE_OUT_ALL_HDMI_SYSTEM_AUDIO_SET);
         DEVICE_ALL_HDMI_SYSTEM_AUDIO_AND_SPEAKER_SET.add(DEVICE_OUT_SPEAKER);
+
+        DEVICE_OUT_ALL_BLE_SET = new HashSet<>();
+        DEVICE_OUT_ALL_BLE_SET.add(DEVICE_OUT_BLE_HEADSET);
+        DEVICE_OUT_ALL_BLE_SET.add(DEVICE_OUT_BLE_SPEAKER);
     }
 
     // input devices
@@ -1019,6 +1035,8 @@
     /** @hide */
     public static final int DEVICE_IN_ECHO_REFERENCE = DEVICE_BIT_IN | 0x10000000;
     /** @hide */
+    public static final int DEVICE_IN_BLE_HEADSET = DEVICE_BIT_IN | 0x20000000;
+    /** @hide */
     @UnsupportedAppUsage
     public static final int DEVICE_IN_DEFAULT = DEVICE_BIT_IN | DEVICE_BIT_DEFAULT;
 
@@ -1056,6 +1074,7 @@
         DEVICE_IN_ALL_SET.add(DEVICE_IN_BLUETOOTH_BLE);
         DEVICE_IN_ALL_SET.add(DEVICE_IN_HDMI_ARC);
         DEVICE_IN_ALL_SET.add(DEVICE_IN_ECHO_REFERENCE);
+        DEVICE_IN_ALL_SET.add(DEVICE_IN_BLE_HEADSET);
         DEVICE_IN_ALL_SET.add(DEVICE_IN_DEFAULT);
 
         DEVICE_IN_ALL_SCO_SET = new HashSet<>();
@@ -1118,6 +1137,9 @@
     /** @hide */ public static final String DEVICE_OUT_PROXY_NAME = "proxy";
     /** @hide */ public static final String DEVICE_OUT_USB_HEADSET_NAME = "usb_headset";
     /** @hide */ public static final String DEVICE_OUT_HEARING_AID_NAME = "hearing_aid_out";
+    /** @hide */ public static final String DEVICE_OUT_ECHO_CANCELLER_NAME = "echo_canceller";
+    /** @hide */ public static final String DEVICE_OUT_BLE_HEADSET_NAME = "ble_headset";
+    /** @hide */ public static final String DEVICE_OUT_BLE_SPEAKER_NAME = "ble_speaker";
 
     /** @hide */ public static final String DEVICE_IN_COMMUNICATION_NAME = "communication";
     /** @hide */ public static final String DEVICE_IN_AMBIENT_NAME = "ambient";
@@ -1145,6 +1167,7 @@
     /** @hide */ public static final String DEVICE_IN_BLUETOOTH_BLE_NAME = "bt_ble";
     /** @hide */ public static final String DEVICE_IN_ECHO_REFERENCE_NAME = "echo_reference";
     /** @hide */ public static final String DEVICE_IN_HDMI_ARC_NAME = "hdmi_arc";
+    /** @hide */ public static final String DEVICE_IN_BLE_HEADSET_NAME = "ble_headset";
 
     /** @hide */
     @UnsupportedAppUsage
@@ -1207,6 +1230,12 @@
             return DEVICE_OUT_USB_HEADSET_NAME;
         case DEVICE_OUT_HEARING_AID:
             return DEVICE_OUT_HEARING_AID_NAME;
+        case DEVICE_OUT_ECHO_CANCELLER:
+            return DEVICE_OUT_ECHO_CANCELLER_NAME;
+        case DEVICE_OUT_BLE_HEADSET:
+            return DEVICE_OUT_BLE_HEADSET_NAME;
+        case DEVICE_OUT_BLE_SPEAKER:
+            return DEVICE_OUT_BLE_SPEAKER_NAME;
         case DEVICE_OUT_DEFAULT:
         default:
             return Integer.toString(device);
@@ -1269,6 +1298,8 @@
             return DEVICE_IN_ECHO_REFERENCE_NAME;
         case DEVICE_IN_HDMI_ARC:
             return DEVICE_IN_HDMI_ARC_NAME;
+        case DEVICE_IN_BLE_HEADSET:
+            return DEVICE_IN_BLE_HEADSET_NAME;
         case DEVICE_IN_DEFAULT:
         default:
             return Integer.toString(device);
@@ -1347,6 +1378,11 @@
     /** @hide */ public static final int FOR_VIBRATE_RINGING = 7;
     private static final int NUM_FORCE_USE = 8;
 
+    // Device role in audio policy
+    public static final int DEVICE_ROLE_NONE = 0;
+    public static final int DEVICE_ROLE_PREFERRED = 1;
+    public static final int DEVICE_ROLE_DISABLED = 2;
+
     /** @hide */
     public static String forceUseUsageToString(int usage) {
         switch (usage) {
@@ -1667,47 +1703,58 @@
 
     /**
      * @hide
-     * Sets the preferred device to use for a given audio strategy in the audio policy engine
+     * Set device as role for product strategy.
      * @param strategy the id of the strategy to configure
-     * @param device the device type and address to route to when available
+     * @param role the role of the devices
+     * @param devices the list of devices to be set as role for the given strategy
      * @return {@link #SUCCESS} if successfully set
      */
-    public static int setPreferredDeviceForStrategy(
-            int strategy, @NonNull AudioDeviceAttributes device) {
-        return setPreferredDeviceForStrategy(strategy,
-                AudioDeviceInfo.convertDeviceTypeToInternalDevice(device.getType()),
-                device.getAddress());
+    public static int setDevicesRoleForStrategy(
+            int strategy, int role, @NonNull List<AudioDeviceAttributes> devices) {
+        if (devices.isEmpty()) {
+            return BAD_VALUE;
+        }
+        int[] types = new int[devices.size()];
+        String[] addresses = new String[devices.size()];
+        for (int i = 0; i < devices.size(); ++i) {
+            types[i] = AudioDeviceInfo.convertDeviceTypeToInternalDevice(devices.get(i).getType());
+            addresses[i] = devices.get(i).getAddress();
+        }
+        return setDevicesRoleForStrategy(strategy, role, types, addresses);
     }
-    /**
-     * @hide
-     * Set device routing per product strategy.
-     * @param strategy the id of the strategy to configure
-     * @param deviceType the native device type, NOT AudioDeviceInfo types
-     * @param deviceAddress the address of the device
-     * @return {@link #SUCCESS} if successfully set
-     */
-    private static native int setPreferredDeviceForStrategy(
-            int strategy, int deviceType, String deviceAddress);
 
     /**
      * @hide
-     * Remove preferred routing for the strategy
+     * Set device as role for product strategy.
      * @param strategy the id of the strategy to configure
+     * @param role the role of the devices
+     * @param types all device types
+     * @param addresses all device addresses
+     * @return {@link #SUCCESS} if successfully set
+     */
+    private static native int setDevicesRoleForStrategy(
+            int strategy, int role, @NonNull int[] types, @NonNull String[] addresses);
+
+    /**
+     * @hide
+     * Remove devices as role for the strategy
+     * @param strategy the id of the strategy to configure
+     * @param role the role of the devices
      * @return {@link #SUCCESS} if successfully removed
      */
-    public static native int removePreferredDeviceForStrategy(int strategy);
+    public static native int removeDevicesRoleForStrategy(int strategy, int role);
 
     /**
      * @hide
-     * Query previously set preferred device for a strategy
+     * Query previously set devices as role for a strategy
      * @param strategy the id of the strategy to query for
-     * @param device an array of size 1 that will contain the preferred device, or null if
-     *               none was set
+     * @param role the role of the devices
+     * @param devices a list that will contain the devices of role
      * @return {@link #SUCCESS} if there is a preferred device and it was successfully retrieved
      *     and written to the array
      */
-    public static native int getPreferredDeviceForStrategy(int strategy,
-                                                           AudioDeviceAttributes[] device);
+    public static native int getDevicesForRoleAndStrategy(
+            int strategy, int role, @NonNull List<AudioDeviceAttributes> devices);
 
     // Items shared with audio service
 
diff --git a/media/java/android/media/IAudioService.aidl b/media/java/android/media/IAudioService.aidl
index 4cf236a..ef8b0ed 100755
--- a/media/java/android/media/IAudioService.aidl
+++ b/media/java/android/media/IAudioService.aidl
@@ -29,7 +29,7 @@
 import android.media.IPlaybackConfigDispatcher;
 import android.media.IRecordingConfigDispatcher;
 import android.media.IRingtonePlayer;
-import android.media.IStrategyPreferredDeviceDispatcher;
+import android.media.IStrategyPreferredDevicesDispatcher;
 import android.media.IVolumeController;
 import android.media.IVolumeController;
 import android.media.PlayerBase;
@@ -279,11 +279,11 @@
 
     boolean isCallScreeningModeSupported();
 
-    int setPreferredDeviceForStrategy(in int strategy, in AudioDeviceAttributes device);
+    int setPreferredDevicesForStrategy(in int strategy, in List<AudioDeviceAttributes> device);
 
-    int removePreferredDeviceForStrategy(in int strategy);
+    int removePreferredDevicesForStrategy(in int strategy);
 
-    AudioDeviceAttributes getPreferredDeviceForStrategy(in int strategy);
+    List<AudioDeviceAttributes> getPreferredDevicesForStrategy(in int strategy);
 
     List<AudioDeviceAttributes> getDevicesForAttributes(in AudioAttributes attributes);
 
@@ -291,10 +291,10 @@
 
     int getAllowedCapturePolicy();
 
-    void registerStrategyPreferredDeviceDispatcher(IStrategyPreferredDeviceDispatcher dispatcher);
+    void registerStrategyPreferredDevicesDispatcher(IStrategyPreferredDevicesDispatcher dispatcher);
 
-    oneway void unregisterStrategyPreferredDeviceDispatcher(
-            IStrategyPreferredDeviceDispatcher dispatcher);
+    oneway void unregisterStrategyPreferredDevicesDispatcher(
+            IStrategyPreferredDevicesDispatcher dispatcher);
 
     oneway void setRttEnabled(in boolean rttEnabled);
 
diff --git a/media/java/android/media/IStrategyPreferredDeviceDispatcher.aidl b/media/java/android/media/IStrategyPreferredDevicesDispatcher.aidl
similarity index 82%
rename from media/java/android/media/IStrategyPreferredDeviceDispatcher.aidl
rename to media/java/android/media/IStrategyPreferredDevicesDispatcher.aidl
index b1f99e6..db674c3 100644
--- a/media/java/android/media/IStrategyPreferredDeviceDispatcher.aidl
+++ b/media/java/android/media/IStrategyPreferredDevicesDispatcher.aidl
@@ -19,12 +19,12 @@
 import android.media.AudioDeviceAttributes;
 
 /**
- * AIDL for AudioService to signal audio strategy-preferred device updates.
+ * AIDL for AudioService to signal audio strategy-preferred devices updates.
  *
  * {@hide}
  */
-oneway interface IStrategyPreferredDeviceDispatcher {
+oneway interface IStrategyPreferredDevicesDispatcher {
 
-    void dispatchPrefDeviceChanged(int strategyId, in AudioDeviceAttributes device);
+    void dispatchPrefDevicesChanged(int strategyId, in List<AudioDeviceAttributes> devices);
 
 }
diff --git a/media/java/android/media/MediaRouter.java b/media/java/android/media/MediaRouter.java
index 6fa3787..2b3f420c 100644
--- a/media/java/android/media/MediaRouter.java
+++ b/media/java/android/media/MediaRouter.java
@@ -1616,9 +1616,9 @@
         Drawable mIcon;
         // playback information
         int mPlaybackType = PLAYBACK_TYPE_LOCAL;
-        int mVolumeMax = RemoteControlClient.DEFAULT_PLAYBACK_VOLUME;
-        int mVolume = RemoteControlClient.DEFAULT_PLAYBACK_VOLUME;
-        int mVolumeHandling = RemoteControlClient.DEFAULT_PLAYBACK_VOLUME_HANDLING;
+        int mVolumeMax = DEFAULT_PLAYBACK_MAX_VOLUME;
+        int mVolume = DEFAULT_PLAYBACK_VOLUME;
+        int mVolumeHandling = PLAYBACK_VOLUME_VARIABLE;
         int mPlaybackStream = AudioManager.STREAM_MUSIC;
         VolumeCallbackInfo mVcb;
         Display mPresentationDisplay;
@@ -1722,6 +1722,21 @@
          */
         public final static int PLAYBACK_VOLUME_VARIABLE = 1;
 
+        /**
+         * Default playback max volume if not set.
+         * Hard-coded to the same number of steps as AudioService.MAX_STREAM_VOLUME[STREAM_MUSIC]
+         *
+         * @see #getVolumeMax()
+         */
+        private static final int DEFAULT_PLAYBACK_MAX_VOLUME = 15;
+
+        /**
+         * Default playback volume if not set.
+         *
+         * @see #getVolume()
+         */
+        private static final int DEFAULT_PLAYBACK_VOLUME = DEFAULT_PLAYBACK_MAX_VOLUME;
+
         RouteInfo(RouteCategory category) {
             mCategory = category;
             mDeviceType = DEVICE_TYPE_UNKNOWN;
@@ -2430,13 +2445,13 @@
                 }
                 return;
             }
-            if (mPlaybackType == RemoteControlClient.PLAYBACK_TYPE_REMOTE) {
+            if (mPlaybackType == PLAYBACK_TYPE_REMOTE) {
                 int volumeControl = VolumeProvider.VOLUME_CONTROL_FIXED;
                 switch (mVolumeHandling) {
-                    case RemoteControlClient.PLAYBACK_VOLUME_VARIABLE:
+                    case PLAYBACK_VOLUME_VARIABLE:
                         volumeControl = VolumeProvider.VOLUME_CONTROL_ABSOLUTE;
                         break;
-                    case RemoteControlClient.PLAYBACK_VOLUME_FIXED:
+                    case PLAYBACK_VOLUME_FIXED:
                     default:
                         break;
                 }
diff --git a/media/java/android/media/MediaTranscodeManager.java b/media/java/android/media/MediaTranscodeManager.java
index e959e8e..e1bff03 100644
--- a/media/java/android/media/MediaTranscodeManager.java
+++ b/media/java/android/media/MediaTranscodeManager.java
@@ -225,13 +225,50 @@
             job.updateStatusAndResult(TranscodingJob.STATUS_FINISHED,
                     TranscodingJob.RESULT_ERROR);
 
-            // Notifies client the job is done.
+            // Notifies client the job failed.
             if (job.mListener != null && job.mListenerExecutor != null) {
                 job.mListenerExecutor.execute(() -> job.mListener.onTranscodingFinished(job));
             }
         }
     }
 
+    private void handleTranscodingProgressUpdate(int jobId, int newProgress) {
+        synchronized (mPendingTranscodingJobs) {
+            // Gets the job associated with the jobId.
+            final TranscodingJob job = mPendingTranscodingJobs.get(jobId);
+
+            if (job == null) {
+                // This should not happen in reality.
+                Log.e(TAG, "Job " + jobId + " is not in PendingJobs");
+                return;
+            }
+
+            // Updates the job progress.
+            job.updateProgress(newProgress);
+
+            // Notifies client the progress update.
+            if (job.mProgressUpdateExecutor != null && job.mProgressUpdateListener != null) {
+                job.mProgressUpdateExecutor.execute(
+                        () -> job.mProgressUpdateListener.onProgressUpdate(newProgress));
+            }
+        }
+    }
+
+    private void updateStatus(int jobId, int status) {
+        synchronized (mPendingTranscodingJobs) {
+            final TranscodingJob job = mPendingTranscodingJobs.get(jobId);
+
+            if (job == null) {
+                // This should not happen in reality.
+                Log.e(TAG, "Job " + jobId + " is not in PendingJobs");
+                return;
+            }
+
+            // Updates the job status.
+            job.updateStatus(status);
+        }
+    }
+
     // Just forwards all the events to the event handler.
     private ITranscodingClientCallback mTranscodingClientCallback =
             new ITranscodingClientCallback.Stub() {
@@ -263,17 +300,17 @@
 
                 @Override
                 public void onTranscodingStarted(int jobId) throws RemoteException {
-
+                    updateStatus(jobId, TranscodingJob.STATUS_RUNNING);
                 }
 
                 @Override
                 public void onTranscodingPaused(int jobId) throws RemoteException {
-
+                    updateStatus(jobId, TranscodingJob.STATUS_PAUSED);
                 }
 
                 @Override
                 public void onTranscodingResumed(int jobId) throws RemoteException {
-
+                    updateStatus(jobId, TranscodingJob.STATUS_RUNNING);
                 }
 
                 @Override
@@ -294,8 +331,8 @@
                 }
 
                 @Override
-                public void onProgressUpdate(int jobId, int progress) throws RemoteException {
-                    //TODO(hkuang): Implement this.
+                public void onProgressUpdate(int jobId, int newProgress) throws RemoteException {
+                    handleTranscodingProgressUpdate(jobId, newProgress);
                 }
             };
 
@@ -661,11 +698,14 @@
         public static final int STATUS_RUNNING = 2;
         /** The job is finished. */
         public static final int STATUS_FINISHED = 3;
+        /** The job is paused. */
+        public static final int STATUS_PAUSED = 4;
 
         @IntDef(prefix = { "STATUS_" }, value = {
                 STATUS_PENDING,
                 STATUS_RUNNING,
                 STATUS_FINISHED,
+                STATUS_PAUSED,
         })
         @Retention(RetentionPolicy.SOURCE)
         public @interface Status {}
@@ -821,17 +861,12 @@
             return mResult;
         }
 
-        private void setJobProgress(int newProgress) {
-            synchronized (this) {
-                mProgress = newProgress;
-            }
+        private synchronized void updateProgress(int newProgress) {
+            mProgress = newProgress;
+        }
 
-            // Notify listener.
-            OnProgressUpdateListener onProgressUpdateListener = mProgressUpdateListener;
-            if (mProgressUpdateListener != null) {
-                mProgressUpdateExecutor.execute(
-                        () -> onProgressUpdateListener.onProgressUpdate(mProgress));
-            }
+        private synchronized void updateStatus(int newStatus) {
+            mStatus = newStatus;
         }
     }
 
diff --git a/media/java/android/media/audiopolicy/AudioPolicyConfig.java b/media/java/android/media/audiopolicy/AudioPolicyConfig.java
index 561a884..697d80c 100644
--- a/media/java/android/media/audiopolicy/AudioPolicyConfig.java
+++ b/media/java/android/media/audiopolicy/AudioPolicyConfig.java
@@ -220,6 +220,34 @@
         return textDump;
     }
 
+    /**
+     * Very short dump of configuration
+     * @return a condensed dump of configuration, uniquely identifies a policy in a log
+     */
+    public String toCompactLogString() {
+        String compactDump = "reg:" + mRegistrationId;
+        int mixNum = 0;
+        for (AudioMix mix : mMixes) {
+            compactDump += " Mix:" + mixNum + "-Typ:" + mixTypePrefix(mix.getMixType())
+                    + "-Rul:" + mix.getRule().getCriteria().size();
+            mixNum++;
+        }
+        return compactDump;
+    }
+
+    private static String mixTypePrefix(int mixType) {
+        switch (mixType) {
+            case AudioMix.MIX_TYPE_PLAYERS:
+                return "p";
+            case AudioMix.MIX_TYPE_RECORDERS:
+                return "r";
+            case AudioMix.MIX_TYPE_INVALID:
+            default:
+                return "#";
+
+        }
+    }
+
     protected void reset() {
         mMixCounter = 0;
     }
diff --git a/media/java/android/media/session/ISession.aidl b/media/java/android/media/session/ISession.aidl
index 21378c8..77f7b54 100644
--- a/media/java/android/media/session/ISession.aidl
+++ b/media/java/android/media/session/ISession.aidl
@@ -41,7 +41,8 @@
     // These commands are for the TransportPerformer
     void setMetadata(in MediaMetadata metadata, long duration, String metadataDescription);
     void setPlaybackState(in PlaybackState state);
-    void setQueue(in ParceledListSlice queue);
+    void resetQueue();
+    IBinder getBinderForSetQueue();
     void setQueueTitle(CharSequence title);
     void setExtras(in Bundle extras);
     void setRatingType(int type);
diff --git a/media/java/android/media/session/MediaSession.java b/media/java/android/media/session/MediaSession.java
index 8bf462c..6c41f7b 100644
--- a/media/java/android/media/session/MediaSession.java
+++ b/media/java/android/media/session/MediaSession.java
@@ -19,12 +19,12 @@
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.annotation.SystemApi;
 import android.app.Activity;
 import android.app.PendingIntent;
 import android.compat.annotation.UnsupportedAppUsage;
 import android.content.Context;
 import android.content.Intent;
-import android.content.pm.ParceledListSlice;
 import android.media.AudioAttributes;
 import android.media.MediaDescription;
 import android.media.MediaMetadata;
@@ -35,6 +35,7 @@
 import android.os.BadParcelableException;
 import android.os.Bundle;
 import android.os.Handler;
+import android.os.IBinder;
 import android.os.Looper;
 import android.os.Message;
 import android.os.Parcel;
@@ -105,6 +106,7 @@
      *
      * @hide
      */
+    @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
     public static final int FLAG_EXCLUSIVE_GLOBAL_PRIORITY = 1 << 16;
 
     /**
@@ -489,7 +491,12 @@
      */
     public void setQueue(@Nullable List<QueueItem> queue) {
         try {
-            mBinder.setQueue(queue == null ? null : new ParceledListSlice(queue));
+            if (queue == null) {
+                mBinder.resetQueue();
+            } else {
+                IBinder binder = mBinder.getBinderForSetQueue();
+                ParcelableListBinder.send(binder, queue);
+            }
         } catch (RemoteException e) {
             Log.wtf("Dead object in setQueue.", e);
         }
diff --git a/media/java/android/media/session/MediaSessionManager.java b/media/java/android/media/session/MediaSessionManager.java
index 6976a35..2000693 100644
--- a/media/java/android/media/session/MediaSessionManager.java
+++ b/media/java/android/media/session/MediaSessionManager.java
@@ -523,7 +523,8 @@
      * @param keyEvent The KeyEvent to send.
      * @hide
      */
-    public void dispatchMediaKeyEventAsSystemService(KeyEvent keyEvent) {
+    @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+    public void dispatchMediaKeyEventAsSystemService(@NonNull KeyEvent keyEvent) {
         dispatchMediaKeyEventInternal(true, keyEvent, false);
     }
 
@@ -548,6 +549,7 @@
      * @return {@code true} if the event was sent to the session, {@code false} otherwise
      * @hide
      */
+    @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
     public boolean dispatchMediaKeyEventAsSystemService(@NonNull MediaSession.Token sessionToken,
             @NonNull KeyEvent keyEvent) {
         if (sessionToken == null) {
@@ -586,10 +588,15 @@
      * Should be only called by the {@link com.android.internal.policy.PhoneWindow} or
      * {@link android.view.FallbackEventHandler} when the foreground activity didn't consume the key
      * from the hardware devices.
+     * <p>
+     * Valid stream types include {@link AudioManager.PublicStreamTypes} and
+     * {@link AudioManager#USE_DEFAULT_STREAM_TYPE}.
      *
-     * @param keyEvent The KeyEvent to send.
+     * @param keyEvent volume key event
+     * @param streamType type of stream
      * @hide
      */
+    @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
     public void dispatchVolumeKeyEventAsSystemService(@NonNull KeyEvent keyEvent, int streamType) {
         dispatchVolumeKeyEventInternal(true, keyEvent, streamType, false);
     }
@@ -614,6 +621,7 @@
      * @param keyEvent volume key event
      * @hide
      */
+    @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
     public void dispatchVolumeKeyEventAsSystemService(@NonNull MediaSession.Token sessionToken,
             @NonNull KeyEvent keyEvent) {
         if (sessionToken == null) {
diff --git a/media/java/android/media/session/ParcelableListBinder.java b/media/java/android/media/session/ParcelableListBinder.java
new file mode 100644
index 0000000..a7aacf2
--- /dev/null
+++ b/media/java/android/media/session/ParcelableListBinder.java
@@ -0,0 +1,131 @@
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.session;
+
+import android.annotation.NonNull;
+import android.os.Binder;
+import android.os.IBinder;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.os.RemoteException;
+
+import com.android.internal.annotations.GuardedBy;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.function.Consumer;
+
+/**
+ * Binder to receive a list that has a large number of {@link Parcelable} items.
+ *
+ * It's similar to {@link android.content.pm.ParceledListSlice}, but transactions are performed in
+ * the opposite direction.
+ *
+ * @param <T> the type of {@link Parcelable}
+ * @hide
+ */
+public class ParcelableListBinder<T extends Parcelable> extends Binder {
+
+    private static final int SUGGESTED_MAX_IPC_SIZE = IBinder.getSuggestedMaxIpcSizeBytes();
+
+    private static final int END_OF_PARCEL = 0;
+    private static final int ITEM_CONTINUED = 1;
+
+    private final Consumer<List<T>> mConsumer;
+
+    private final Object mLock = new Object();
+
+    @GuardedBy("mLock")
+    private final List<T> mList = new ArrayList<>();
+
+    @GuardedBy("mLock")
+    private int mCount;
+
+    @GuardedBy("mLock")
+    private boolean mConsumed;
+
+    /**
+     * Creates an instance.
+     *
+     * @param consumer a consumer that consumes the list received
+     */
+    public ParcelableListBinder(@NonNull Consumer<List<T>> consumer) {
+        mConsumer = consumer;
+    }
+
+    @Override
+    protected boolean onTransact(int code, Parcel data, Parcel reply, int flags)
+            throws RemoteException {
+        if (code != FIRST_CALL_TRANSACTION) {
+            return super.onTransact(code, data, reply, flags);
+        }
+        List<T> listToBeConsumed;
+        synchronized (mLock) {
+            if (mConsumed) {
+                return false;
+            }
+            int i = mList.size();
+            if (i == 0) {
+                mCount = data.readInt();
+            }
+            while (i < mCount && data.readInt() != END_OF_PARCEL) {
+                mList.add(data.readParcelable(null));
+                i++;
+            }
+            if (i >= mCount) {
+                listToBeConsumed = mList;
+                mConsumed = true;
+            } else {
+                listToBeConsumed = null;
+            }
+        }
+        if (listToBeConsumed != null) {
+            mConsumer.accept(listToBeConsumed);
+        }
+        return true;
+    }
+
+    /**
+     * Sends a list of {@link Parcelable} to a binder.
+     *
+     * @param binder a binder interface backed by {@link ParcelableListBinder}
+     * @param list a list to send
+     */
+    public static <T extends Parcelable> void send(@NonNull IBinder binder, @NonNull List<T> list)
+            throws RemoteException {
+        int count = list.size();
+        int i = 0;
+        while (i < count) {
+            Parcel data = Parcel.obtain();
+            Parcel reply = Parcel.obtain();
+            if (i == 0) {
+                data.writeInt(count);
+            }
+            while (i < count && data.dataSize() < SUGGESTED_MAX_IPC_SIZE) {
+                data.writeInt(ITEM_CONTINUED);
+                data.writeParcelable(list.get(i), 0);
+                i++;
+            }
+            if (i < count) {
+                data.writeInt(END_OF_PARCEL);
+            }
+            binder.transact(FIRST_CALL_TRANSACTION, data, reply, 0);
+            reply.recycle();
+            data.recycle();
+        }
+    }
+}
diff --git a/media/java/android/media/tv/tuner/Tuner.java b/media/java/android/media/tv/tuner/Tuner.java
index 8bf688d..e148d0e 100644
--- a/media/java/android/media/tv/tuner/Tuner.java
+++ b/media/java/android/media/tv/tuner/Tuner.java
@@ -362,6 +362,11 @@
      */
     @Override
     public void close() {
+        releaseAll();
+        TunerUtils.throwExceptionForResult(nativeClose(), "failed to close tuner");
+    }
+
+    private void releaseAll() {
         if (mFrontendHandle != null) {
             int res = nativeCloseFrontend(mFrontendHandle);
             if (res != Tuner.RESULT_SUCCESS) {
@@ -396,9 +401,9 @@
                 TunerUtils.throwExceptionForResult(res, "failed to close demux");
             }
             mTunerResourceManager.releaseDemux(mDemuxHandle, mClientId);
-            mFrontendHandle = null;
+            mDemuxHandle = null;
         }
-        TunerUtils.throwExceptionForResult(nativeClose(), "failed to close tuner");
+
     }
 
     /**
@@ -495,6 +500,7 @@
                     break;
                 }
                 case MSG_RESOURCE_LOST: {
+                    releaseAll();
                     if (mOnResourceLostListener != null
                                 && mOnResourceLostListenerExecutor != null) {
                         mOnResourceLostListenerExecutor.execute(
diff --git a/media/java/android/service/media/MediaBrowserService.java b/media/java/android/service/media/MediaBrowserService.java
index 39c7682..1386cba 100644
--- a/media/java/android/service/media/MediaBrowserService.java
+++ b/media/java/android/service/media/MediaBrowserService.java
@@ -684,8 +684,15 @@
                 List<MediaBrowser.MediaItem> filteredList =
                         (flag & RESULT_FLAG_OPTION_NOT_HANDLED) != 0
                                 ? applyOptions(list, options) : list;
-                final ParceledListSlice<MediaBrowser.MediaItem> pls =
-                        filteredList == null ? null : new ParceledListSlice<>(filteredList);
+                final ParceledListSlice<MediaBrowser.MediaItem> pls;
+                if (filteredList == null) {
+                    pls = null;
+                } else {
+                    pls = new ParceledListSlice<>(filteredList);
+                    // Limit the size of initial Parcel to prevent binder buffer overflow
+                    // as onLoadChildren is an async binder call.
+                    pls.setInlineCountLimit(1);
+                }
                 try {
                     connection.callbacks.onLoadChildren(parentId, pls, options);
                 } catch (RemoteException ex) {
diff --git a/media/jni/android_media_tv_Tuner.cpp b/media/jni/android_media_tv_Tuner.cpp
index 3cd4081..5db6729 100644
--- a/media/jni/android_media_tv_Tuner.cpp
+++ b/media/jni/android_media_tv_Tuner.cpp
@@ -291,8 +291,9 @@
 C2DataIdInfo::C2DataIdInfo(uint32_t index, uint64_t value) : C2Param(kParamSize, index) {
     CHECK(isGlobal());
     CHECK_EQ(C2Param::INFO, kind());
-    DummyInfo info{value};
-    memcpy(this + 1, static_cast<C2Param *>(&info) + 1, kParamSize - sizeof(C2Param));
+    mInfo = StubInfo(value);
+    memcpy(static_cast<C2Param *>(this) + 1, static_cast<C2Param *>(&mInfo) + 1,
+            kParamSize - sizeof(C2Param));
 }
 
 /////////////// MediaEvent ///////////////////////
@@ -2059,7 +2060,7 @@
                     env->GetIntField(settings, env->GetFieldID(clazz, "mModulation", "I")));
     FrontendInnerFec innerFec =
             static_cast<FrontendInnerFec>(
-                    env->GetLongField(settings, env->GetFieldID(clazz, "mFec", "J")));
+                    env->GetLongField(settings, env->GetFieldID(clazz, "mInnerFec", "J")));
     uint32_t symbolRate =
             static_cast<uint32_t>(
                     env->GetIntField(settings, env->GetFieldID(clazz, "mSymbolRate", "I")));
@@ -2068,7 +2069,7 @@
                     env->GetIntField(settings, env->GetFieldID(clazz, "mOuterFec", "I")));
     FrontendDvbcAnnex annex =
             static_cast<FrontendDvbcAnnex>(
-                    env->GetByteField(settings, env->GetFieldID(clazz, "mAnnex", "B")));
+                    env->GetIntField(settings, env->GetFieldID(clazz, "mAnnex", "I")));
     FrontendDvbcSpectralInversion spectralInversion =
             static_cast<FrontendDvbcSpectralInversion>(
                     env->GetIntField(
diff --git a/media/jni/android_media_tv_Tuner.h b/media/jni/android_media_tv_Tuner.h
index 83e9db7..fd29959 100644
--- a/media/jni/android_media_tv_Tuner.h
+++ b/media/jni/android_media_tv_Tuner.h
@@ -250,8 +250,9 @@
 public:
     C2DataIdInfo(uint32_t index, uint64_t value);
 private:
-    typedef C2GlobalParam<C2Info, C2Int64Value, 0> DummyInfo;
-    static const size_t kParamSize = sizeof(DummyInfo);
+    typedef C2GlobalParam<C2Info, C2Int64Value, 0> StubInfo;
+    StubInfo mInfo;
+    static const size_t kParamSize = sizeof(StubInfo);
 };
 
 }  // namespace android
diff --git a/media/jni/audioeffect/Visualizer.cpp b/media/jni/audioeffect/Visualizer.cpp
index f4d65d0..a74ae53 100644
--- a/media/jni/audioeffect/Visualizer.cpp
+++ b/media/jni/audioeffect/Visualizer.cpp
@@ -34,21 +34,9 @@
 
 // ---------------------------------------------------------------------------
 
-Visualizer::Visualizer (const String16& opPackageName,
-         int32_t priority,
-         effect_callback_t cbf,
-         void* user,
-         audio_session_t sessionId)
-    :   AudioEffect(SL_IID_VISUALIZATION, opPackageName, NULL, priority, cbf, user, sessionId),
-        mCaptureRate(CAPTURE_RATE_DEF),
-        mCaptureSize(CAPTURE_SIZE_DEF),
-        mSampleRate(44100000),
-        mScalingMode(VISUALIZER_SCALING_MODE_NORMALIZED),
-        mMeasurementMode(MEASUREMENT_MODE_NONE),
-        mCaptureCallBack(NULL),
-        mCaptureCbkUser(NULL)
+Visualizer::Visualizer (const String16& opPackageName)
+        :   AudioEffect(opPackageName)
 {
-    initCaptureSize();
 }
 
 Visualizer::~Visualizer()
@@ -58,6 +46,23 @@
     setCaptureCallBack(NULL, NULL, 0, 0);
 }
 
+status_t Visualizer::set(int32_t priority,
+                         effect_callback_t cbf,
+                         void* user,
+                         audio_session_t sessionId,
+                         audio_io_handle_t io,
+                         const AudioDeviceTypeAddr& device,
+                         bool probe)
+{
+    status_t status = AudioEffect::set(
+            SL_IID_VISUALIZATION, nullptr, priority, cbf, user, sessionId, io, device, probe);
+    if (status == NO_ERROR || status == ALREADY_EXISTS) {
+        initCaptureSize();
+    }
+    return status;
+}
+
+
 void Visualizer::release()
 {
     ALOGV("Visualizer::release()");
diff --git a/media/jni/audioeffect/Visualizer.h b/media/jni/audioeffect/Visualizer.h
index d4672a9..8b6a62f 100644
--- a/media/jni/audioeffect/Visualizer.h
+++ b/media/jni/audioeffect/Visualizer.h
@@ -65,14 +65,22 @@
     /* Constructor.
      * See AudioEffect constructor for details on parameters.
      */
-                        Visualizer(const String16& opPackageName,
-                                   int32_t priority = 0,
-                                   effect_callback_t cbf = NULL,
-                                   void* user = NULL,
-                                   audio_session_t sessionId = AUDIO_SESSION_OUTPUT_MIX);
+                        explicit Visualizer(const String16& opPackageName);
 
                         ~Visualizer();
 
+    /**
+     * Initialize an uninitialized Visualizer.
+     * See AudioEffect 'set' function for details on parameters.
+     */
+    status_t    set(int32_t priority = 0,
+                    effect_callback_t cbf = NULL,
+                    void* user = NULL,
+                    audio_session_t sessionId = AUDIO_SESSION_OUTPUT_MIX,
+                    audio_io_handle_t io = AUDIO_IO_HANDLE_NONE,
+                    const AudioDeviceTypeAddr& device = {},
+                    bool probe = false);
+
     // Declared 'final' because we call this in ~Visualizer().
     status_t    setEnabled(bool enabled) final;
 
@@ -163,15 +171,15 @@
     uint32_t initCaptureSize();
 
     Mutex mCaptureLock;
-    uint32_t mCaptureRate;
-    uint32_t mCaptureSize;
-    uint32_t mSampleRate;
-    uint32_t mScalingMode;
-    uint32_t mMeasurementMode;
-    capture_cbk_t mCaptureCallBack;
-    void *mCaptureCbkUser;
+    uint32_t mCaptureRate = CAPTURE_RATE_DEF;
+    uint32_t mCaptureSize = CAPTURE_SIZE_DEF;
+    uint32_t mSampleRate = 44100000;
+    uint32_t mScalingMode = VISUALIZER_SCALING_MODE_NORMALIZED;
+    uint32_t mMeasurementMode = MEASUREMENT_MODE_NONE;
+    capture_cbk_t mCaptureCallBack = nullptr;
+    void *mCaptureCbkUser = nullptr;
     sp<CaptureThread> mCaptureThread;
-    uint32_t mCaptureFlags;
+    uint32_t mCaptureFlags = 0;
 };
 
 
diff --git a/media/jni/audioeffect/android_media_AudioEffect.cpp b/media/jni/audioeffect/android_media_AudioEffect.cpp
index dbe7b4b..45c49e5 100644
--- a/media/jni/audioeffect/android_media_AudioEffect.cpp
+++ b/media/jni/audioeffect/android_media_AudioEffect.cpp
@@ -333,26 +333,25 @@
     if (deviceType != AUDIO_DEVICE_NONE) {
         device.mType = deviceType;
         ScopedUtfChars address(env, deviceAddress);
-        device.mAddress = address.c_str();
+        device.setAddress(address.c_str());
     }
 
     // create the native AudioEffect object
-    lpAudioEffect = new AudioEffect(typeStr,
-                                    String16(opPackageNameStr.c_str()),
-                                    uuidStr,
-                                    priority,
-                                    effectCallback,
-                                    &lpJniStorage->mCallbackData,
-                                    (audio_session_t) sessionId,
-                                    AUDIO_IO_HANDLE_NONE,
-                                    device,
-                                    probe);
+    lpAudioEffect = new AudioEffect(String16(opPackageNameStr.c_str()));
     if (lpAudioEffect == 0) {
         ALOGE("Error creating AudioEffect");
         goto setup_failure;
     }
 
-
+    lpAudioEffect->set(typeStr,
+                       uuidStr,
+                       priority,
+                       effectCallback,
+                       &lpJniStorage->mCallbackData,
+                       (audio_session_t) sessionId,
+                       AUDIO_IO_HANDLE_NONE,
+                       device,
+                       probe);
     lStatus = AudioEffectJni::translateNativeErrorToJava(lpAudioEffect->initCheck());
     if (lStatus != AUDIOEFFECT_SUCCESS && lStatus != AUDIOEFFECT_ERROR_ALREADY_EXISTS) {
         ALOGE("AudioEffect initCheck failed %d", lStatus);
diff --git a/media/jni/audioeffect/android_media_Visualizer.cpp b/media/jni/audioeffect/android_media_Visualizer.cpp
index f9a77f4..4c5970a 100644
--- a/media/jni/audioeffect/android_media_Visualizer.cpp
+++ b/media/jni/audioeffect/android_media_Visualizer.cpp
@@ -382,15 +382,15 @@
     }
 
     // create the native Visualizer object
-    lpVisualizer = new Visualizer(String16(opPackageNameStr.c_str()),
-                                  0,
-                                  android_media_visualizer_effect_callback,
-                                  lpJniStorage,
-                                  (audio_session_t) sessionId);
+    lpVisualizer = new Visualizer(String16(opPackageNameStr.c_str()));
     if (lpVisualizer == 0) {
         ALOGE("Error creating Visualizer");
         goto setup_failure;
     }
+    lpVisualizer->set(0,
+                      android_media_visualizer_effect_callback,
+                      lpJniStorage,
+                      (audio_session_t) sessionId);
 
     lStatus = translateError(lpVisualizer->initCheck());
     if (lStatus != VISUALIZER_SUCCESS && lStatus != VISUALIZER_ERROR_ALREADY_EXISTS) {
diff --git a/media/tests/MediaTranscodingTest/src/com/android/mediatranscodingtest/MediaTranscodeManagerTest.java b/media/tests/MediaTranscodingTest/src/com/android/mediatranscodingtest/MediaTranscodeManagerTest.java
index 3a5e692..8fe1088 100644
--- a/media/tests/MediaTranscodingTest/src/com/android/mediatranscodingtest/MediaTranscodeManagerTest.java
+++ b/media/tests/MediaTranscodingTest/src/com/android/mediatranscodingtest/MediaTranscodeManagerTest.java
@@ -33,10 +33,12 @@
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.OutputStream;
+import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.Executor;
 import java.util.concurrent.Executors;
 import java.util.concurrent.Semaphore;
 import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicInteger;
 
 /*
  * Functional tests for MediaTranscodeManager in the media framework.
@@ -230,5 +232,73 @@
                 30, TimeUnit.MILLISECONDS);
         assertTrue("Fails to cancel transcoding", finishedOnTime);
     }
+
+
+    @Test
+    public void testTranscodingProgressUpdate() throws Exception {
+        Log.d(TAG, "Starting: testMediaTranscodeManager");
+
+        Semaphore transcodeCompleteSemaphore = new Semaphore(0);
+        final CountDownLatch statusLatch = new CountDownLatch(1);
+
+        // Create a file Uri: file:///data/user/0/com.android.mediatranscodingtest/cache/temp.mp4
+        // The full path of this file is:
+        // /data/user/0/com.android.mediatranscodingtest/cache/temp.mp4
+        Uri destinationUri = Uri.parse(ContentResolver.SCHEME_FILE + "://"
+                + mContext.getCacheDir().getAbsolutePath() + "/HevcTranscode.mp4");
+
+        TranscodingRequest request =
+                new TranscodingRequest.Builder()
+                        .setSourceUri(mSourceHEVCVideoUri)
+                        .setDestinationUri(destinationUri)
+                        .setType(MediaTranscodeManager.TRANSCODING_TYPE_VIDEO)
+                        .setPriority(MediaTranscodeManager.PRIORITY_REALTIME)
+                        .setVideoTrackFormat(createMediaFormat())
+                        .build();
+        Executor listenerExecutor = Executors.newSingleThreadExecutor();
+
+        Log.i(TAG, "transcoding to " + createMediaFormat());
+
+        TranscodingJob job = mMediaTranscodeManager.enqueueRequest(request, listenerExecutor,
+                transcodingJob -> {
+                    Log.d(TAG, "Transcoding completed with result: " + transcodingJob.getResult());
+                    assertEquals(transcodingJob.getResult(), TranscodingJob.RESULT_SUCCESS);
+                    transcodeCompleteSemaphore.release();
+                });
+        assertNotNull(job);
+
+        AtomicInteger progressUpdateCount = new AtomicInteger(0);
+
+        // Set progress update executor and use the same executor as result listener.
+        job.setOnProgressUpdateListener(listenerExecutor,
+                new TranscodingJob.OnProgressUpdateListener() {
+                    int mPreviousProgress = 0;
+
+                    @Override
+                    public void onProgressUpdate(int newProgress) {
+                        assertTrue("Invalid proress update", newProgress > mPreviousProgress);
+                        assertTrue("Invalid proress update", newProgress <= 100);
+                        if (newProgress > 0) {
+                            statusLatch.countDown();
+                        }
+                        mPreviousProgress = newProgress;
+                        progressUpdateCount.getAndIncrement();
+                        Log.i(TAG, "Get progress update " + newProgress);
+                    }
+                });
+
+        try {
+            statusLatch.await();
+            // The transcoding should not be finished yet as the clip is long.
+            assertTrue("Invalid status", job.getStatus() == TranscodingJob.STATUS_RUNNING);
+        } catch (InterruptedException e) { }
+
+        Log.d(TAG, "testMediaTranscodeManager - Waiting for transcode to cancel.");
+        boolean finishedOnTime = transcodeCompleteSemaphore.tryAcquire(
+                TRANSCODE_TIMEOUT_SECONDS, TimeUnit.SECONDS);
+        assertTrue("Transcode failed to complete in time.", finishedOnTime);
+        assertTrue("Failed to receive at least 10 progress updates",
+                progressUpdateCount.get() > 10);
+    }
 }
 
diff --git a/non-updatable-api/current.txt b/non-updatable-api/current.txt
index 5aaca43..e0ebec6 100644
--- a/non-updatable-api/current.txt
+++ b/non-updatable-api/current.txt
@@ -5596,6 +5596,7 @@
     method public android.graphics.drawable.Icon getIcon();
     method public android.app.RemoteInput[] getRemoteInputs();
     method public int getSemanticAction();
+    method public boolean isAuthenticationRequired();
     method public boolean isContextual();
     method public void writeToParcel(android.os.Parcel, int);
     field @NonNull public static final android.os.Parcelable.Creator<android.app.Notification.Action> CREATOR;
@@ -5625,6 +5626,7 @@
     method @NonNull public android.app.Notification.Action.Builder extend(android.app.Notification.Action.Extender);
     method @NonNull public android.os.Bundle getExtras();
     method @NonNull public android.app.Notification.Action.Builder setAllowGeneratedReplies(boolean);
+    method @NonNull public android.app.Notification.Action.Builder setAuthenticationRequired(boolean);
     method @NonNull public android.app.Notification.Action.Builder setContextual(boolean);
     method @NonNull public android.app.Notification.Action.Builder setSemanticAction(int);
   }
@@ -11899,6 +11901,7 @@
     field public static final android.os.Parcelable.Creator<android.content.pm.PackageInstaller.SessionInfo> CREATOR;
     field public static final int INVALID_ID = -1; // 0xffffffff
     field public static final int STAGED_SESSION_ACTIVATION_FAILED = 2; // 0x2
+    field public static final int STAGED_SESSION_CONFLICT = 4; // 0x4
     field public static final int STAGED_SESSION_NO_ERROR = 0; // 0x0
     field public static final int STAGED_SESSION_UNKNOWN = 3; // 0x3
     field public static final int STAGED_SESSION_VERIFICATION_FAILED = 1; // 0x1
@@ -14247,6 +14250,10 @@
     enum_constant public static final android.graphics.BlurMaskFilter.Blur SOLID;
   }
 
+  public final class BlurShader extends android.graphics.Shader {
+    ctor public BlurShader(float, float, @Nullable android.graphics.Shader);
+  }
+
   public class Camera {
     ctor public Camera();
     method public void applyToCanvas(android.graphics.Canvas);
@@ -24046,6 +24053,8 @@
     method public boolean isSink();
     method public boolean isSource();
     field public static final int TYPE_AUX_LINE = 19; // 0x13
+    field public static final int TYPE_BLE_HEADSET = 26; // 0x1a
+    field public static final int TYPE_BLE_SPEAKER = 27; // 0x1b
     field public static final int TYPE_BLUETOOTH_A2DP = 8; // 0x8
     field public static final int TYPE_BLUETOOTH_SCO = 7; // 0x7
     field public static final int TYPE_BUILTIN_EARPIECE = 1; // 0x1
@@ -24189,6 +24198,7 @@
     method @NonNull public java.util.List<android.media.AudioPlaybackConfiguration> getActivePlaybackConfigurations();
     method @NonNull public java.util.List<android.media.AudioRecordingConfiguration> getActiveRecordingConfigurations();
     method public int getAllowedCapturePolicy();
+    method public int getAudioHwSyncForSession(int);
     method public android.media.AudioDeviceInfo[] getDevices(int);
     method public java.util.List<android.media.MicrophoneInfo> getMicrophones() throws java.io.IOException;
     method public int getMode();
@@ -35276,9 +35286,11 @@
   public final class PowerManager {
     method public void addThermalStatusListener(@NonNull android.os.PowerManager.OnThermalStatusChangedListener);
     method public void addThermalStatusListener(@NonNull java.util.concurrent.Executor, @NonNull android.os.PowerManager.OnThermalStatusChangedListener);
+    method @Nullable public java.time.Duration getBatteryDischargePrediction();
     method public int getCurrentThermalStatus();
     method public int getLocationPowerSaveMode();
     method public float getThermalHeadroom(@IntRange(from=0, to=60) int);
+    method public boolean isBatteryDischargePredictionPersonalized();
     method public boolean isDeviceIdleMode();
     method public boolean isIgnoringBatteryOptimizations(String);
     method public boolean isInteractive();
@@ -44120,7 +44132,9 @@
     method public final void addExistingConnection(android.telecom.PhoneAccountHandle, android.telecom.Connection);
     method public final void conferenceRemoteConnections(android.telecom.RemoteConnection, android.telecom.RemoteConnection);
     method public final void connectionServiceFocusReleased();
+    method @Nullable public final android.telecom.RemoteConference createRemoteIncomingConference(@Nullable android.telecom.PhoneAccountHandle, @Nullable android.telecom.ConnectionRequest);
     method public final android.telecom.RemoteConnection createRemoteIncomingConnection(android.telecom.PhoneAccountHandle, android.telecom.ConnectionRequest);
+    method @Nullable public final android.telecom.RemoteConference createRemoteOutgoingConference(@Nullable android.telecom.PhoneAccountHandle, @Nullable android.telecom.ConnectionRequest);
     method public final android.telecom.RemoteConnection createRemoteOutgoingConnection(android.telecom.PhoneAccountHandle, android.telecom.ConnectionRequest);
     method public final java.util.Collection<android.telecom.Conference> getAllConferences();
     method public final java.util.Collection<android.telecom.Connection> getAllConnections();
@@ -44351,6 +44365,7 @@
 
   public final class RemoteConnection {
     method public void abort();
+    method public void addConferenceParticipants(@NonNull java.util.List<android.net.Uri>);
     method public void answer();
     method public void disconnect();
     method public android.net.Uri getAddress();
@@ -45128,7 +45143,7 @@
     method public long getNci();
     method @IntRange(from=0, to=3279165) public int getNrarfcn();
     method @IntRange(from=0, to=1007) public int getPci();
-    method @IntRange(from=0, to=65535) public int getTac();
+    method @IntRange(from=0, to=16777215) public int getTac();
     method public void writeToParcel(android.os.Parcel, int);
     field @NonNull public static final android.os.Parcelable.Creator<android.telephony.CellIdentityNr> CREATOR;
   }
@@ -45977,6 +45992,7 @@
     method public void onDataConnectionStateChanged(int);
     method public void onDataConnectionStateChanged(int, int);
     method @RequiresPermission("android.permission.READ_PHONE_STATE") public void onDisplayInfoChanged(@NonNull android.telephony.TelephonyDisplayInfo);
+    method public void onEmergencyNumberListChanged(@NonNull java.util.Map<java.lang.Integer,java.util.List<android.telephony.emergency.EmergencyNumber>>);
     method @RequiresPermission("android.permission.READ_PRECISE_PHONE_STATE") public void onImsCallDisconnectCauseChanged(@NonNull android.telephony.ims.ImsReasonInfo);
     method public void onMessageWaitingIndicatorChanged(boolean);
     method @RequiresPermission("android.permission.MODIFY_PHONE_STATE") public void onPreciseDataConnectionStateChanged(@NonNull android.telephony.PreciseDataConnectionState);
@@ -46014,6 +46030,7 @@
     method @Nullable public android.net.LinkProperties getLinkProperties();
     method public int getNetworkType();
     method public int getState();
+    method public int getTransportType();
     method public void writeToParcel(@NonNull android.os.Parcel, int);
     field @NonNull public static final android.os.Parcelable.Creator<android.telephony.PreciseDataConnectionState> CREATOR;
   }
@@ -46492,7 +46509,9 @@
     method @Deprecated public String iccTransmitApduBasicChannel(int, int, int, int, int, String);
     method @Deprecated public String iccTransmitApduLogicalChannel(int, int, int, int, int, int, String);
     method public boolean isConcurrentVoiceAndDataSupported();
+    method @RequiresPermission(anyOf={android.Manifest.permission.ACCESS_NETWORK_STATE, android.Manifest.permission.READ_PHONE_STATE, "android.permission.READ_PRIVILEGED_PHONE_STATE"}) public boolean isDataConnectionAllowed();
     method @RequiresPermission(anyOf={android.Manifest.permission.ACCESS_NETWORK_STATE, android.Manifest.permission.MODIFY_PHONE_STATE}) public boolean isDataEnabled();
+    method @RequiresPermission(anyOf={android.Manifest.permission.ACCESS_NETWORK_STATE, android.Manifest.permission.READ_PHONE_STATE}) public boolean isDataEnabledForReason(int);
     method @RequiresPermission(anyOf={android.Manifest.permission.ACCESS_NETWORK_STATE, android.Manifest.permission.READ_PHONE_STATE}) public boolean isDataRoamingEnabled();
     method public boolean isEmergencyNumber(@NonNull String);
     method public boolean isHearingAidCompatibilitySupported();
@@ -46514,6 +46533,7 @@
     method @RequiresPermission(android.Manifest.permission.CALL_PHONE) public void sendUssdRequest(String, android.telephony.TelephonyManager.UssdResponseCallback, android.os.Handler);
     method public void sendVisualVoicemailSms(String, int, String, android.app.PendingIntent);
     method @Deprecated @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setDataEnabled(boolean);
+    method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setDataEnabledForReason(int, boolean);
     method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public int setForbiddenPlmns(@NonNull java.util.List<java.lang.String>);
     method public boolean setLine1NumberForDisplay(String, String);
     method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setNetworkSelectionModeAutomatic();
@@ -46561,6 +46581,10 @@
     field public static final int DATA_CONNECTING = 1; // 0x1
     field public static final int DATA_DISCONNECTED = 0; // 0x0
     field public static final int DATA_DISCONNECTING = 4; // 0x4
+    field public static final int DATA_ENABLED_REASON_CARRIER = 2; // 0x2
+    field public static final int DATA_ENABLED_REASON_POLICY = 1; // 0x1
+    field public static final int DATA_ENABLED_REASON_THERMAL = 3; // 0x3
+    field public static final int DATA_ENABLED_REASON_USER = 0; // 0x0
     field public static final int DATA_SUSPENDED = 3; // 0x3
     field public static final int DATA_UNKNOWN = -1; // 0xffffffff
     field public static final String EXTRA_ACTIVE_SIM_SUPPORTED_COUNT = "android.telephony.extra.ACTIVE_SIM_SUPPORTED_COUNT";
@@ -53318,7 +53342,7 @@
   }
 
   public class ViewPropertyAnimator {
-    method public android.view.ViewPropertyAnimator alpha(float);
+    method public android.view.ViewPropertyAnimator alpha(@FloatRange(from=0.0f, to=1.0f) float);
     method public android.view.ViewPropertyAnimator alphaBy(float);
     method public void cancel();
     method public long getDuration();
@@ -63860,7 +63884,7 @@
 
 package java.math {
 
-  public class BigDecimal extends java.lang.Number implements java.lang.Comparable<java.math.BigDecimal> java.io.Serializable {
+  public class BigDecimal extends java.lang.Number implements java.lang.Comparable<java.math.BigDecimal> {
     ctor public BigDecimal(char[], int, int);
     ctor public BigDecimal(char[], int, int, java.math.MathContext);
     ctor public BigDecimal(char[]);
@@ -63947,19 +63971,20 @@
     field public static final java.math.BigDecimal ZERO;
   }
 
-  public class BigInteger extends java.lang.Number implements java.lang.Comparable<java.math.BigInteger> java.io.Serializable {
+  public class BigInteger extends java.lang.Number implements java.lang.Comparable<java.math.BigInteger> {
+    ctor public BigInteger(byte[]);
+    ctor public BigInteger(int, byte[]);
+    ctor public BigInteger(@NonNull String, int);
+    ctor public BigInteger(@NonNull String);
     ctor public BigInteger(int, @NonNull java.util.Random);
     ctor public BigInteger(int, int, @NonNull java.util.Random);
-    ctor public BigInteger(@NonNull String);
-    ctor public BigInteger(@NonNull String, int);
-    ctor public BigInteger(int, byte[]);
-    ctor public BigInteger(byte[]);
     method @NonNull public java.math.BigInteger abs();
     method @NonNull public java.math.BigInteger add(@NonNull java.math.BigInteger);
     method @NonNull public java.math.BigInteger and(@NonNull java.math.BigInteger);
     method @NonNull public java.math.BigInteger andNot(@NonNull java.math.BigInteger);
     method public int bitCount();
     method public int bitLength();
+    method public byte byteValueExact();
     method @NonNull public java.math.BigInteger clearBit(int);
     method public int compareTo(@NonNull java.math.BigInteger);
     method @NonNull public java.math.BigInteger divide(@NonNull java.math.BigInteger);
@@ -63970,8 +63995,10 @@
     method @NonNull public java.math.BigInteger gcd(@NonNull java.math.BigInteger);
     method public int getLowestSetBit();
     method public int intValue();
+    method public int intValueExact();
     method public boolean isProbablePrime(int);
     method public long longValue();
+    method public long longValueExact();
     method @NonNull public java.math.BigInteger max(@NonNull java.math.BigInteger);
     method @NonNull public java.math.BigInteger min(@NonNull java.math.BigInteger);
     method @NonNull public java.math.BigInteger mod(@NonNull java.math.BigInteger);
@@ -63988,6 +64015,7 @@
     method @NonNull public java.math.BigInteger setBit(int);
     method @NonNull public java.math.BigInteger shiftLeft(int);
     method @NonNull public java.math.BigInteger shiftRight(int);
+    method public short shortValueExact();
     method public int signum();
     method @NonNull public java.math.BigInteger subtract(@NonNull java.math.BigInteger);
     method public boolean testBit(int);
diff --git a/non-updatable-api/module-lib-current.txt b/non-updatable-api/module-lib-current.txt
index 45ff6d3..8892a29 100644
--- a/non-updatable-api/module-lib-current.txt
+++ b/non-updatable-api/module-lib-current.txt
@@ -7,6 +7,7 @@
 
   public class NotificationManager {
     method public boolean hasEnabledNotificationListener(@NonNull String, @NonNull android.os.UserHandle);
+    field public static final String ACTION_NOTIFICATION_LISTENER_ENABLED_CHANGED = "android.app.action.NOTIFICATION_LISTENER_ENABLED_CHANGED";
   }
 
 }
@@ -31,6 +32,29 @@
 
 }
 
+package android.media {
+
+  public class AudioManager {
+    field public static final int FLAG_FROM_KEY = 4096; // 0x1000
+  }
+
+}
+
+package android.media.session {
+
+  public final class MediaSession {
+    field public static final int FLAG_EXCLUSIVE_GLOBAL_PRIORITY = 65536; // 0x10000
+  }
+
+  public final class MediaSessionManager {
+    method public void dispatchMediaKeyEventAsSystemService(@NonNull android.view.KeyEvent);
+    method public boolean dispatchMediaKeyEventAsSystemService(@NonNull android.media.session.MediaSession.Token, @NonNull android.view.KeyEvent);
+    method public void dispatchVolumeKeyEventAsSystemService(@NonNull android.view.KeyEvent, int);
+    method public void dispatchVolumeKeyEventAsSystemService(@NonNull android.media.session.MediaSession.Token, @NonNull android.view.KeyEvent);
+  }
+
+}
+
 package android.os {
 
   public class Binder implements android.os.IBinder {
@@ -38,6 +62,7 @@
   }
 
   public interface Parcelable {
+    method public default int getStability();
     field public static final int PARCELABLE_STABILITY_LOCAL = 0; // 0x0
     field public static final int PARCELABLE_STABILITY_VINTF = 1; // 0x1
   }
diff --git a/non-updatable-api/system-current.txt b/non-updatable-api/system-current.txt
index 222e563..d1264df 100644
--- a/non-updatable-api/system-current.txt
+++ b/non-updatable-api/system-current.txt
@@ -301,6 +301,7 @@
     field public static final int config_helpIntentNameKey = 17039390; // 0x104001e
     field public static final int config_helpPackageNameKey = 17039387; // 0x104001b
     field public static final int config_helpPackageNameValue = 17039388; // 0x104001c
+    field public static final int config_systemAutomotiveCluster = 17039400; // 0x1040028
     field public static final int config_systemGallery = 17039399; // 0x1040027
   }
 
@@ -4135,32 +4136,38 @@
 
   public class AudioManager {
     method @Deprecated public int abandonAudioFocus(android.media.AudioManager.OnAudioFocusChangeListener, android.media.AudioAttributes);
-    method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public void addOnPreferredDeviceForStrategyChangedListener(@NonNull java.util.concurrent.Executor, @NonNull android.media.AudioManager.OnPreferredDeviceForStrategyChangedListener) throws java.lang.SecurityException;
+    method @Deprecated @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public void addOnPreferredDeviceForStrategyChangedListener(@NonNull java.util.concurrent.Executor, @NonNull android.media.AudioManager.OnPreferredDeviceForStrategyChangedListener) throws java.lang.SecurityException;
+    method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public void addOnPreferredDevicesForStrategyChangedListener(@NonNull java.util.concurrent.Executor, @NonNull android.media.AudioManager.OnPreferredDevicesForStrategyChangedListener) throws java.lang.SecurityException;
     method public void clearAudioServerStateCallback();
     method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public int dispatchAudioFocusChange(@NonNull android.media.AudioFocusInfo, int, @NonNull android.media.audiopolicy.AudioPolicy);
     method @IntRange(from=0) public long getAdditionalOutputDeviceDelay(@NonNull android.media.AudioDeviceInfo);
     method @NonNull @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public static java.util.List<android.media.audiopolicy.AudioProductStrategy> getAudioProductStrategies();
     method @NonNull @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public static java.util.List<android.media.audiopolicy.AudioVolumeGroup> getAudioVolumeGroups();
+    method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public int getDeviceVolumeBehavior(@NonNull android.media.AudioDeviceAttributes);
     method @NonNull @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public java.util.List<android.media.AudioDeviceAttributes> getDevicesForAttributes(@NonNull android.media.AudioAttributes);
     method @IntRange(from=0) public long getMaxAdditionalOutputDeviceDelay(@NonNull android.media.AudioDeviceInfo);
     method @IntRange(from=0) @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public int getMaxVolumeIndexForAttributes(@NonNull android.media.AudioAttributes);
     method @IntRange(from=0) @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public int getMinVolumeIndexForAttributes(@NonNull android.media.AudioAttributes);
     method @Nullable @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public android.media.AudioDeviceAttributes getPreferredDeviceForStrategy(@NonNull android.media.audiopolicy.AudioProductStrategy);
+    method @NonNull @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public java.util.List<android.media.AudioDeviceAttributes> getPreferredDevicesForStrategy(@NonNull android.media.audiopolicy.AudioProductStrategy);
     method @NonNull @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public int[] getSupportedSystemUsages();
     method @IntRange(from=0) @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public int getVolumeIndexForAttributes(@NonNull android.media.AudioAttributes);
     method public boolean isAudioServerRunning();
     method public boolean isHdmiSystemAudioSupported();
     method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public int registerAudioPolicy(@NonNull android.media.audiopolicy.AudioPolicy);
     method public void registerVolumeGroupCallback(@NonNull java.util.concurrent.Executor, @NonNull android.media.AudioManager.VolumeGroupCallback);
-    method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public void removeOnPreferredDeviceForStrategyChangedListener(@NonNull android.media.AudioManager.OnPreferredDeviceForStrategyChangedListener);
+    method @Deprecated @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public void removeOnPreferredDeviceForStrategyChangedListener(@NonNull android.media.AudioManager.OnPreferredDeviceForStrategyChangedListener);
+    method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public void removeOnPreferredDevicesForStrategyChangedListener(@NonNull android.media.AudioManager.OnPreferredDevicesForStrategyChangedListener);
     method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public boolean removePreferredDeviceForStrategy(@NonNull android.media.audiopolicy.AudioProductStrategy);
     method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public int requestAudioFocus(android.media.AudioManager.OnAudioFocusChangeListener, @NonNull android.media.AudioAttributes, int, int) throws java.lang.IllegalArgumentException;
     method @Deprecated @RequiresPermission(anyOf={android.Manifest.permission.MODIFY_PHONE_STATE, android.Manifest.permission.MODIFY_AUDIO_ROUTING}) public int requestAudioFocus(android.media.AudioManager.OnAudioFocusChangeListener, @NonNull android.media.AudioAttributes, int, int, android.media.audiopolicy.AudioPolicy) throws java.lang.IllegalArgumentException;
     method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public int requestAudioFocus(@NonNull android.media.AudioFocusRequest, @Nullable android.media.audiopolicy.AudioPolicy);
     method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public boolean setAdditionalOutputDeviceDelay(@NonNull android.media.AudioDeviceInfo, @IntRange(from=0) long);
     method public void setAudioServerStateCallback(@NonNull java.util.concurrent.Executor, @NonNull android.media.AudioManager.AudioServerStateCallback);
+    method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public void setDeviceVolumeBehavior(@NonNull android.media.AudioDeviceAttributes, int);
     method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public void setFocusRequestResult(@NonNull android.media.AudioFocusInfo, int, @NonNull android.media.audiopolicy.AudioPolicy);
     method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public boolean setPreferredDeviceForStrategy(@NonNull android.media.audiopolicy.AudioProductStrategy, @NonNull android.media.AudioDeviceAttributes);
+    method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public boolean setPreferredDevicesForStrategy(@NonNull android.media.audiopolicy.AudioProductStrategy, @NonNull java.util.List<android.media.AudioDeviceAttributes>);
     method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public void setSupportedSystemUsages(@NonNull int[]);
     method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public void setVolumeIndexForAttributes(@NonNull android.media.AudioAttributes, int, int);
     method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public void unregisterAudioPolicy(@NonNull android.media.audiopolicy.AudioPolicy);
@@ -4169,6 +4176,11 @@
     field public static final int AUDIOFOCUS_FLAG_DELAY_OK = 1; // 0x1
     field public static final int AUDIOFOCUS_FLAG_LOCK = 4; // 0x4
     field public static final int AUDIOFOCUS_FLAG_PAUSES_ON_DUCKABLE_LOSS = 2; // 0x2
+    field public static final int DEVICE_VOLUME_BEHAVIOR_ABSOLUTE = 3; // 0x3
+    field public static final int DEVICE_VOLUME_BEHAVIOR_ABSOLUTE_MULTI_MODE = 4; // 0x4
+    field public static final int DEVICE_VOLUME_BEHAVIOR_FIXED = 2; // 0x2
+    field public static final int DEVICE_VOLUME_BEHAVIOR_FULL = 1; // 0x1
+    field public static final int DEVICE_VOLUME_BEHAVIOR_VARIABLE = 0; // 0x0
     field @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public static final int STREAM_ASSISTANT = 11; // 0xb
     field public static final int SUCCESS = 0; // 0x0
   }
@@ -4179,8 +4191,12 @@
     method public void onAudioServerUp();
   }
 
-  public static interface AudioManager.OnPreferredDeviceForStrategyChangedListener {
-    method public void onPreferredDeviceForStrategyChanged(@NonNull android.media.audiopolicy.AudioProductStrategy, @Nullable android.media.AudioDeviceAttributes);
+  @Deprecated public static interface AudioManager.OnPreferredDeviceForStrategyChangedListener {
+    method @Deprecated public void onPreferredDeviceForStrategyChanged(@NonNull android.media.audiopolicy.AudioProductStrategy, @Nullable android.media.AudioDeviceAttributes);
+  }
+
+  public static interface AudioManager.OnPreferredDevicesForStrategyChangedListener {
+    method public void onPreferredDevicesForStrategyChanged(@NonNull android.media.audiopolicy.AudioProductStrategy, @NonNull java.util.List<android.media.AudioDeviceAttributes>);
   }
 
   public abstract static class AudioManager.VolumeGroupCallback {
@@ -7282,6 +7298,7 @@
     method @RequiresPermission(android.Manifest.permission.READ_DREAM_STATE) public boolean isAmbientDisplaySuppressedForToken(@NonNull String);
     method @RequiresPermission(anyOf={android.Manifest.permission.DEVICE_POWER, android.Manifest.permission.POWER_SAVER}) public boolean setAdaptivePowerSaveEnabled(boolean);
     method @RequiresPermission(anyOf={android.Manifest.permission.DEVICE_POWER, android.Manifest.permission.POWER_SAVER}) public boolean setAdaptivePowerSavePolicy(@NonNull android.os.BatterySaverPolicyConfig);
+    method @RequiresPermission(android.Manifest.permission.DEVICE_POWER) public void setBatteryDischargePrediction(@NonNull java.time.Duration, boolean);
     method @RequiresPermission(android.Manifest.permission.POWER_SAVER) public boolean setDynamicPowerSaveHint(boolean, int);
     method @RequiresPermission(anyOf={android.Manifest.permission.DEVICE_POWER, android.Manifest.permission.POWER_SAVER}) public boolean setPowerSaveModeEnabled(boolean);
     method @RequiresPermission(android.Manifest.permission.WRITE_DREAM_STATE) public void suppressAmbientDisplay(@NonNull String, boolean);
@@ -9350,7 +9367,7 @@
     method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public int getCurrentTtyMode();
     method @Nullable @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public String getDefaultDialerPackage(@NonNull android.os.UserHandle);
     method @Deprecated public android.content.ComponentName getDefaultPhoneApp();
-    method public java.util.List<android.telecom.PhoneAccountHandle> getPhoneAccountsForPackage();
+    method @Deprecated public java.util.List<android.telecom.PhoneAccountHandle> getPhoneAccountsForPackage();
     method @RequiresPermission(anyOf={android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE, android.Manifest.permission.READ_PHONE_STATE}) public java.util.List<android.telecom.PhoneAccountHandle> getPhoneAccountsSupportingScheme(String);
     method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public boolean isInEmergencyCall();
     method @RequiresPermission(anyOf={android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE, android.Manifest.permission.READ_PHONE_STATE}) public boolean isRinging();
@@ -9666,7 +9683,8 @@
 
   public class PhoneStateListener {
     method public void onCallAttributesChanged(@NonNull android.telephony.CallAttributes);
-    method public void onOutgoingEmergencyCall(@NonNull android.telephony.emergency.EmergencyNumber);
+    method @Deprecated public void onOutgoingEmergencyCall(@NonNull android.telephony.emergency.EmergencyNumber);
+    method public void onOutgoingEmergencyCall(@NonNull android.telephony.emergency.EmergencyNumber, int);
     method public void onOutgoingEmergencySms(@NonNull android.telephony.emergency.EmergencyNumber);
     method @RequiresPermission("android.permission.READ_PRECISE_PHONE_STATE") public void onPreciseCallStateChanged(@NonNull android.telephony.PreciseCallState);
     method public void onRadioPowerStateChanged(int);
@@ -9706,6 +9724,7 @@
     method @Deprecated public int getDataConnectionApnTypeBitMask();
     method @Deprecated public int getDataConnectionFailCause();
     method @Deprecated public int getDataConnectionState();
+    method public int getId();
   }
 
   public final class PreciseDisconnectCause {
@@ -10082,10 +10101,8 @@
     method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public boolean isAnyRadioPoweredOn();
     method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public boolean isApnMetered(int);
     method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public boolean isApplicationOnUicc(int);
-    method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public boolean isDataConnectionAllowed();
     method public boolean isDataConnectivityPossible();
     method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public boolean isDataEnabledForApn(int);
-    method @RequiresPermission(anyOf={android.Manifest.permission.ACCESS_NETWORK_STATE, android.Manifest.permission.READ_PHONE_STATE}) public boolean isDataEnabledWithReason(int);
     method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public boolean isEmergencyAssistanceEnabled();
     method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) @WorkerThread public boolean isIccLockEnabled();
     method @Deprecated @RequiresPermission(anyOf={android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE, android.Manifest.permission.READ_PHONE_STATE}) public boolean isIdle();
@@ -10117,7 +10134,6 @@
     method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public int setCarrierRestrictionRules(@NonNull android.telephony.CarrierRestrictionRules);
     method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setDataActivationState(int);
     method @Deprecated @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setDataEnabled(int, boolean);
-    method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setDataEnabledWithReason(int, boolean);
     method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setDataRoamingEnabled(boolean);
     method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setMultiSimCarrierRestriction(boolean);
     method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public boolean setOpportunisticNetworkState(boolean);
@@ -10155,10 +10171,6 @@
     field public static final int CARRIER_PRIVILEGE_STATUS_HAS_ACCESS = 1; // 0x1
     field public static final int CARRIER_PRIVILEGE_STATUS_NO_ACCESS = 0; // 0x0
     field public static final int CARRIER_PRIVILEGE_STATUS_RULES_NOT_LOADED = -1; // 0xffffffff
-    field public static final int DATA_ENABLED_REASON_CARRIER = 2; // 0x2
-    field public static final int DATA_ENABLED_REASON_POLICY = 1; // 0x1
-    field public static final int DATA_ENABLED_REASON_THERMAL = 3; // 0x3
-    field public static final int DATA_ENABLED_REASON_USER = 0; // 0x0
     field public static final String EXTRA_ANOMALY_DESCRIPTION = "android.telephony.extra.ANOMALY_DESCRIPTION";
     field public static final String EXTRA_ANOMALY_ID = "android.telephony.extra.ANOMALY_ID";
     field public static final String EXTRA_PHONE_IN_ECM_STATE = "android.telephony.extra.PHONE_IN_ECM_STATE";
@@ -11020,6 +11032,7 @@
     method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) @WorkerThread public int setProvisioningStringValue(int, @NonNull String);
     method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) @WorkerThread public void setRcsProvisioningStatusForCapability(int, boolean);
     method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public void unregisterProvisioningChangedCallback(@NonNull android.telephony.ims.ProvisioningManager.Callback);
+    field public static final int KEY_VOICE_OVER_WIFI_ENTITLEMENT_ID = 67; // 0x43
     field public static final int KEY_VOICE_OVER_WIFI_MODE_OVERRIDE = 27; // 0x1b
     field public static final int KEY_VOICE_OVER_WIFI_ROAMING_ENABLED_OVERRIDE = 26; // 0x1a
     field public static final int PROVISIONING_VALUE_DISABLED = 0; // 0x0
@@ -11567,6 +11580,9 @@
   public interface PacProcessor {
     method @Nullable public String findProxyForUrl(@NonNull String);
     method @NonNull public static android.webkit.PacProcessor getInstance();
+    method @NonNull public static android.webkit.PacProcessor getInstanceForNetwork(@Nullable android.net.Network);
+    method @Nullable public default android.net.Network getNetwork();
+    method public default void releasePacProcessor();
     method public boolean setProxyScript(@NonNull String);
   }
 
@@ -11706,6 +11722,7 @@
     method public android.webkit.CookieManager getCookieManager();
     method public android.webkit.GeolocationPermissions getGeolocationPermissions();
     method @NonNull public default android.webkit.PacProcessor getPacProcessor();
+    method @NonNull public default android.webkit.PacProcessor getPacProcessorForNetwork(@Nullable android.net.Network);
     method public android.webkit.ServiceWorkerController getServiceWorkerController();
     method public android.webkit.WebViewFactoryProvider.Statics getStatics();
     method @Deprecated public android.webkit.TokenBindingService getTokenBindingService();
diff --git a/packages/CarSystemUI/Android.bp b/packages/CarSystemUI/Android.bp
index 8598f74e..08dd799 100644
--- a/packages/CarSystemUI/Android.bp
+++ b/packages/CarSystemUI/Android.bp
@@ -128,6 +128,8 @@
         "CarSystemUI-core",
     ],
 
+    export_package_resources: true,
+
     libs: [
         "android.car",
     ],
diff --git a/packages/CarSystemUI/proguard.flags b/packages/CarSystemUI/proguard.flags
index 66cbf26..f0b20c1 100644
--- a/packages/CarSystemUI/proguard.flags
+++ b/packages/CarSystemUI/proguard.flags
@@ -1,4 +1,7 @@
 -keep class com.android.systemui.CarSystemUIFactory
 -keep class com.android.car.notification.headsup.animationhelper.**
 
+-keep class com.android.systemui.DaggerCarGlobalRootComponent { *; }
+-keep class com.android.systemui.DaggerCarGlobalRootComponent$CarSysUIComponentImpl { *; }
+
 -include ../SystemUI/proguard.flags
diff --git a/packages/CarSystemUI/res/layout/car_navigation_bar.xml b/packages/CarSystemUI/res/layout/car_navigation_bar.xml
index 9317498..a49a637 100644
--- a/packages/CarSystemUI/res/layout/car_navigation_bar.xml
+++ b/packages/CarSystemUI/res/layout/car_navigation_bar.xml
@@ -25,7 +25,7 @@
     <!--The 20dp padding is the difference between the background selected icon size and the ripple
         that was chosen, thus it's a hack to make it look pretty and not an official margin value-->
     <LinearLayout
-        android:id="@id/nav_buttons"
+        android:id="@+id/nav_buttons"
         android:layout_width="match_parent"
         android:layout_height="wrap_content"
         android:layout_weight="1"
diff --git a/packages/CarSystemUI/res/values/config.xml b/packages/CarSystemUI/res/values/config.xml
index 039f2c0..d3277de 100644
--- a/packages/CarSystemUI/res/values/config.xml
+++ b/packages/CarSystemUI/res/values/config.xml
@@ -121,7 +121,6 @@
     <string-array name="config_systemUIServiceComponentsExclude" translatable="false">
         <item>com.android.systemui.recents.Recents</item>
         <item>com.android.systemui.volume.VolumeUI</item>
-        <item>com.android.systemui.stackdivider.Divider</item>
         <item>com.android.systemui.statusbar.phone.StatusBar</item>
         <item>com.android.systemui.keyboard.KeyboardUI</item>
         <item>com.android.systemui.pip.PipUI</item>
diff --git a/packages/CarSystemUI/res/values/ids.xml b/packages/CarSystemUI/res/values/ids.xml
index 27ed2e2..05194a4 100644
--- a/packages/CarSystemUI/res/values/ids.xml
+++ b/packages/CarSystemUI/res/values/ids.xml
@@ -18,5 +18,4 @@
 <resources>
     <!-- Values used for finding elements on the system ui nav bars -->
     <item type="id" name="lock_screen_nav_buttons"/>
-    <item type="id" name="nav_buttons"/>
 </resources>
\ No newline at end of file
diff --git a/packages/CarSystemUI/samples/sample1/rro/Android.bp b/packages/CarSystemUI/samples/sample1/rro/Android.bp
new file mode 100644
index 0000000..5b0347f
--- /dev/null
+++ b/packages/CarSystemUI/samples/sample1/rro/Android.bp
@@ -0,0 +1,27 @@
+//
+// 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.
+//
+
+android_app {
+    name: "CarSystemUISampleOneRRO",
+    resource_dirs: ["res"],
+    certificate: "platform",
+    platform_apis: true,
+    manifest: "AndroidManifest.xml",
+    aaptflags: [
+        "--no-resource-deduping",
+        "--no-resource-removal",
+     ]
+}
\ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/tv_circle_white_translucent.xml b/packages/CarSystemUI/samples/sample1/rro/AndroidManifest.xml
similarity index 62%
copy from packages/SystemUI/res/drawable/tv_circle_white_translucent.xml
copy to packages/CarSystemUI/samples/sample1/rro/AndroidManifest.xml
index 55d21de..5c25056 100644
--- a/packages/SystemUI/res/drawable/tv_circle_white_translucent.xml
+++ b/packages/CarSystemUI/samples/sample1/rro/AndroidManifest.xml
@@ -1,6 +1,5 @@
-<?xml version="1.0" encoding="utf-8"?>
 <!--
-  ~ Copyright (C) 2019 The Android Open Source Project
+  ~ 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.
@@ -15,10 +14,11 @@
   ~ limitations under the License.
   -->
 
-<shape xmlns:android="http://schemas.android.com/apk/res/android"
-    android:shape="oval">
-
-  <solid
-      android:color="@color/tv_audio_recording_indicator_pulse" />
-
-</shape>
\ No newline at end of file
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+          package="com.android.systemui.rro">
+    <overlay
+        android:targetPackage="com.android.systemui"
+        android:isStatic="false"
+        android:resourcesMap="@xml/car_sysui_overlays"
+    />
+</manifest>
\ No newline at end of file
diff --git a/packages/CarSystemUI/samples/sample1/rro/res/drawable/car_ic_apps.xml b/packages/CarSystemUI/samples/sample1/rro/res/drawable/car_ic_apps.xml
new file mode 100644
index 0000000..a8d8a2f
--- /dev/null
+++ b/packages/CarSystemUI/samples/sample1/rro/res/drawable/car_ic_apps.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2019 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License
+  -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+        android:viewportWidth="44"
+        android:viewportHeight="44"
+        android:width="44dp"
+        android:height="44dp">
+<path
+    android:pathData="M7.33333333 14.6666667L14.6666667 14.6666667L14.6666667 7.33333333L7.33333333 7.33333333L7.33333333 14.6666667ZM18.3333333 36.6666667L25.6666667 36.6666667L25.6666667 29.3333333L18.3333333 29.3333333L18.3333333 36.6666667ZM7.33333333 36.6666667L14.6666667 36.6666667L14.6666667 29.3333333L7.33333333 29.3333333L7.33333333 36.6666667ZM7.33333333 25.6666667L14.6666667 25.6666667L14.6666667 18.3333333L7.33333333 18.3333333L7.33333333 25.6666667ZM18.3333333 25.6666667L25.6666667 25.6666667L25.6666667 18.3333333L18.3333333 18.3333333L18.3333333 25.6666667ZM29.3333333 7.33333333L29.3333333 14.6666667L36.6666667 14.6666667L36.6666667 7.33333333L29.3333333 7.33333333ZM18.3333333 14.6666667L25.6666667 14.6666667L25.6666667 7.33333333L18.3333333 7.33333333L18.3333333 14.6666667ZM29.3333333 25.6666667L36.6666667 25.6666667L36.6666667 18.3333333L29.3333333 18.3333333L29.3333333 25.6666667ZM29.3333333 36.6666667L36.6666667 36.6666667L36.6666667 29.3333333L29.3333333 29.3333333L29.3333333 36.6666667Z"
+    android:fillColor="@color/car_nav_icon_fill_color" />
+</vector>
\ No newline at end of file
diff --git a/packages/CarSystemUI/samples/sample1/rro/res/drawable/car_ic_apps_selected.xml b/packages/CarSystemUI/samples/sample1/rro/res/drawable/car_ic_apps_selected.xml
new file mode 100644
index 0000000..2a4e91a
--- /dev/null
+++ b/packages/CarSystemUI/samples/sample1/rro/res/drawable/car_ic_apps_selected.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2019 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License
+  -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+        android:viewportWidth="44"
+        android:viewportHeight="44"
+        android:width="44dp"
+        android:height="44dp">
+    <path
+        android:pathData="M7.33333333 14.6666667L14.6666667 14.6666667L14.6666667 7.33333333L7.33333333 7.33333333L7.33333333 14.6666667ZM18.3333333 36.6666667L25.6666667 36.6666667L25.6666667 29.3333333L18.3333333 29.3333333L18.3333333 36.6666667ZM7.33333333 36.6666667L14.6666667 36.6666667L14.6666667 29.3333333L7.33333333 29.3333333L7.33333333 36.6666667ZM7.33333333 25.6666667L14.6666667 25.6666667L14.6666667 18.3333333L7.33333333 18.3333333L7.33333333 25.6666667ZM18.3333333 25.6666667L25.6666667 25.6666667L25.6666667 18.3333333L18.3333333 18.3333333L18.3333333 25.6666667ZM29.3333333 7.33333333L29.3333333 14.6666667L36.6666667 14.6666667L36.6666667 7.33333333L29.3333333 7.33333333ZM18.3333333 14.6666667L25.6666667 14.6666667L25.6666667 7.33333333L18.3333333 7.33333333L18.3333333 14.6666667ZM29.3333333 25.6666667L36.6666667 25.6666667L36.6666667 18.3333333L29.3333333 18.3333333L29.3333333 25.6666667ZM29.3333333 36.6666667L36.6666667 36.6666667L36.6666667 29.3333333L29.3333333 29.3333333L29.3333333 36.6666667Z"
+        android:fillColor="@color/car_nav_icon_fill_color_selected" />
+</vector>
\ No newline at end of file
diff --git a/packages/CarSystemUI/samples/sample1/rro/res/drawable/car_ic_music.xml b/packages/CarSystemUI/samples/sample1/rro/res/drawable/car_ic_music.xml
new file mode 100644
index 0000000..6339ebb
--- /dev/null
+++ b/packages/CarSystemUI/samples/sample1/rro/res/drawable/car_ic_music.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2019 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License
+  -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:viewportWidth="44"
+    android:viewportHeight="44"
+    android:width="44dp"
+    android:height="44dp">
+    <path
+        android:pathData="M22 5.5L22 24.8416667C20.9183333 24.2183333 19.6716667 23.8333333 18.3333333 23.8333333C14.2816667 23.8333333 11 27.115 11 31.1666667C11 35.2183333 14.2816667 38.5 18.3333333 38.5C22.385 38.5 25.6666667 35.2183333 25.6666667 31.1666667L25.6666667 12.8333333L33 12.8333333L33 5.5L22 5.5Z"
+        android:fillColor="@color/car_nav_icon_fill_color" />
+</vector>
\ No newline at end of file
diff --git a/packages/CarSystemUI/samples/sample1/rro/res/drawable/car_ic_music_selected.xml b/packages/CarSystemUI/samples/sample1/rro/res/drawable/car_ic_music_selected.xml
new file mode 100644
index 0000000..a56bcb3
--- /dev/null
+++ b/packages/CarSystemUI/samples/sample1/rro/res/drawable/car_ic_music_selected.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2019 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License
+  -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+        android:viewportWidth="44"
+        android:viewportHeight="44"
+        android:width="44dp"
+        android:height="44dp">
+    <path
+        android:pathData="M22 5.5L22 24.8416667C20.9183333 24.2183333 19.6716667 23.8333333 18.3333333 23.8333333C14.2816667 23.8333333 11 27.115 11 31.1666667C11 35.2183333 14.2816667 38.5 18.3333333 38.5C22.385 38.5 25.6666667 35.2183333 25.6666667 31.1666667L25.6666667 12.8333333L33 12.8333333L33 5.5L22 5.5Z"
+        android:fillColor="@color/car_nav_icon_fill_color_selected" />
+</vector>
\ No newline at end of file
diff --git a/packages/CarSystemUI/samples/sample1/rro/res/drawable/car_ic_navigation.xml b/packages/CarSystemUI/samples/sample1/rro/res/drawable/car_ic_navigation.xml
new file mode 100644
index 0000000..e1fabe0
--- /dev/null
+++ b/packages/CarSystemUI/samples/sample1/rro/res/drawable/car_ic_navigation.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2019 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License
+  -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:viewportWidth="44"
+    android:viewportHeight="44"
+    android:width="44dp"
+    android:height="44dp">
+    <path
+        android:pathData="M39.8016667 20.6983333L23.3016667 4.19833333C22.5866667 3.48333333 21.4316667 3.48333333 20.7166667 4.19833333L4.21666667 20.6983333C3.50166667 21.4133333 3.50166667 22.5683333 4.21666667 23.2833333L20.7166667 39.7833333C21.4316667 40.4983333 22.5866667 40.4983333 23.3016667 39.7833333L39.8016667 23.2833333C40.5166667 22.5866667 40.5166667 21.4316667 39.8016667 20.6983333ZM25.6666667 26.5833333L25.6666667 22L18.3333333 22L18.3333333 27.5L14.6666667 27.5L14.6666667 20.1666667C14.6666667 19.1583333 15.4916667 18.3333333 16.5 18.3333333L25.6666667 18.3333333L25.6666667 13.75L32.0833333 20.1666667L25.6666667 26.5833333Z"
+        android:fillColor="@color/car_nav_icon_fill_color" />
+</vector>
\ No newline at end of file
diff --git a/packages/CarSystemUI/samples/sample1/rro/res/drawable/car_ic_navigation_selected.xml b/packages/CarSystemUI/samples/sample1/rro/res/drawable/car_ic_navigation_selected.xml
new file mode 100644
index 0000000..d11cf28
--- /dev/null
+++ b/packages/CarSystemUI/samples/sample1/rro/res/drawable/car_ic_navigation_selected.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2019 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License
+  -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+        android:viewportWidth="44"
+        android:viewportHeight="44"
+        android:width="44dp"
+        android:height="44dp">
+    <path
+        android:pathData="M39.8016667 20.6983333L23.3016667 4.19833333C22.5866667 3.48333333 21.4316667 3.48333333 20.7166667 4.19833333L4.21666667 20.6983333C3.50166667 21.4133333 3.50166667 22.5683333 4.21666667 23.2833333L20.7166667 39.7833333C21.4316667 40.4983333 22.5866667 40.4983333 23.3016667 39.7833333L39.8016667 23.2833333C40.5166667 22.5866667 40.5166667 21.4316667 39.8016667 20.6983333ZM25.6666667 26.5833333L25.6666667 22L18.3333333 22L18.3333333 27.5L14.6666667 27.5L14.6666667 20.1666667C14.6666667 19.1583333 15.4916667 18.3333333 16.5 18.3333333L25.6666667 18.3333333L25.6666667 13.75L32.0833333 20.1666667L25.6666667 26.5833333Z"
+        android:fillColor="@color/car_nav_icon_fill_color_selected" />
+</vector>
\ No newline at end of file
diff --git a/packages/CarSystemUI/samples/sample1/rro/res/drawable/car_ic_overview.xml b/packages/CarSystemUI/samples/sample1/rro/res/drawable/car_ic_overview.xml
new file mode 100644
index 0000000..f185eb9
--- /dev/null
+++ b/packages/CarSystemUI/samples/sample1/rro/res/drawable/car_ic_overview.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2019 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License
+  -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:viewportWidth="44"
+    android:viewportHeight="44"
+    android:width="44dp"
+    android:height="44dp">
+    <path
+        android:pathData="M36.92857 22.39286A14.53571 14.53571 0 0 1 7.857143 22.39286A14.53571 14.53571 0 0 1 36.92857 22.39286Z"
+        android:strokeColor="@color/car_nav_icon_fill_color"
+        android:strokeWidth="4" />
+</vector>
\ No newline at end of file
diff --git a/packages/CarSystemUI/samples/sample1/rro/res/drawable/car_ic_overview_selected.xml b/packages/CarSystemUI/samples/sample1/rro/res/drawable/car_ic_overview_selected.xml
new file mode 100644
index 0000000..19b5583
--- /dev/null
+++ b/packages/CarSystemUI/samples/sample1/rro/res/drawable/car_ic_overview_selected.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2019 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License
+  -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+        android:viewportWidth="44"
+        android:viewportHeight="44"
+        android:width="44dp"
+        android:height="44dp">
+    <path
+        android:pathData="M36.92857 22.39286A14.53571 14.53571 0 0 1 7.857143 22.39286A14.53571 14.53571 0 0 1 36.92857 22.39286Z"
+        android:strokeColor="@color/car_nav_icon_fill_color_selected"
+        android:strokeWidth="4" />
+</vector>
\ No newline at end of file
diff --git a/packages/CarSystemUI/samples/sample1/rro/res/drawable/car_ic_phone.xml b/packages/CarSystemUI/samples/sample1/rro/res/drawable/car_ic_phone.xml
new file mode 100644
index 0000000..50e36b5
--- /dev/null
+++ b/packages/CarSystemUI/samples/sample1/rro/res/drawable/car_ic_phone.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2019 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License
+  -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:viewportWidth="44"
+    android:viewportHeight="44"
+    android:width="44dp"
+    android:height="44dp">
+    <path
+        android:pathData="M12.1366667 19.7816667C14.7766667 24.97 19.03 29.205 24.2183333 31.8633333L28.2516667 27.83C28.7466667 27.335 29.48 27.17 30.1216667 27.39C32.175 28.0683333 34.3933333 28.435 36.6666667 28.435C37.675 28.435 38.5 29.26 38.5 30.2683333L38.5 36.6666667C38.5 37.675 37.675 38.5 36.6666667 38.5C19.4516667 38.5 5.5 24.5483333 5.5 7.33333333C5.5 6.325 6.325 5.5 7.33333333 5.5L13.75 5.5C14.7583333 5.5 15.5833333 6.325 15.5833333 7.33333333C15.5833333 9.625 15.95 11.825 16.6283333 13.8783333C16.83 14.52 16.6833333 15.235 16.17 15.7483333L12.1366667 19.7816667Z"
+        android:fillColor="@color/car_nav_icon_fill_color" />
+</vector>
\ No newline at end of file
diff --git a/packages/CarSystemUI/samples/sample1/rro/res/drawable/car_ic_phone_selected.xml b/packages/CarSystemUI/samples/sample1/rro/res/drawable/car_ic_phone_selected.xml
new file mode 100644
index 0000000..11b1687
--- /dev/null
+++ b/packages/CarSystemUI/samples/sample1/rro/res/drawable/car_ic_phone_selected.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2019 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License
+  -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+        android:viewportWidth="44"
+        android:viewportHeight="44"
+        android:width="44dp"
+        android:height="44dp">
+    <path
+        android:pathData="M12.1366667 19.7816667C14.7766667 24.97 19.03 29.205 24.2183333 31.8633333L28.2516667 27.83C28.7466667 27.335 29.48 27.17 30.1216667 27.39C32.175 28.0683333 34.3933333 28.435 36.6666667 28.435C37.675 28.435 38.5 29.26 38.5 30.2683333L38.5 36.6666667C38.5 37.675 37.675 38.5 36.6666667 38.5C19.4516667 38.5 5.5 24.5483333 5.5 7.33333333C5.5 6.325 6.325 5.5 7.33333333 5.5L13.75 5.5C14.7583333 5.5 15.5833333 6.325 15.5833333 7.33333333C15.5833333 9.625 15.95 11.825 16.6283333 13.8783333C16.83 14.52 16.6833333 15.235 16.17 15.7483333L12.1366667 19.7816667Z"
+        android:fillColor="@color/car_nav_icon_fill_color_selected" />
+</vector>
\ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/tv_circle_white_translucent.xml b/packages/CarSystemUI/samples/sample1/rro/res/drawable/system_bar_background.xml
similarity index 60%
copy from packages/SystemUI/res/drawable/tv_circle_white_translucent.xml
copy to packages/CarSystemUI/samples/sample1/rro/res/drawable/system_bar_background.xml
index 55d21de..6161ad9 100644
--- a/packages/SystemUI/res/drawable/tv_circle_white_translucent.xml
+++ b/packages/CarSystemUI/samples/sample1/rro/res/drawable/system_bar_background.xml
@@ -1,6 +1,6 @@
 <?xml version="1.0" encoding="utf-8"?>
 <!--
-  ~ Copyright (C) 2019 The Android Open Source Project
+  ~ 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.
@@ -14,11 +14,20 @@
   ~ See the License for the specific language governing permissions and
   ~ limitations under the License.
   -->
-
-<shape xmlns:android="http://schemas.android.com/apk/res/android"
-    android:shape="oval">
-
-  <solid
-      android:color="@color/tv_audio_recording_indicator_pulse" />
-
+<shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle" >
+    <corners
+        android:topLeftRadius="0dp"
+        android:topRightRadius="10dp"
+        android:bottomLeftRadius="0dp"
+        android:bottomRightRadius="0dp"
+    />
+    <solid
+        android:color="#404040"
+    />
+    <padding
+        android:left="0dp"
+        android:top="0dp"
+        android:right="0dp"
+        android:bottom="0dp"
+    />
 </shape>
\ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/tv_circle_white_translucent.xml b/packages/CarSystemUI/samples/sample1/rro/res/drawable/system_bar_background_2.xml
similarity index 60%
copy from packages/SystemUI/res/drawable/tv_circle_white_translucent.xml
copy to packages/CarSystemUI/samples/sample1/rro/res/drawable/system_bar_background_2.xml
index 55d21de..3582142 100644
--- a/packages/SystemUI/res/drawable/tv_circle_white_translucent.xml
+++ b/packages/CarSystemUI/samples/sample1/rro/res/drawable/system_bar_background_2.xml
@@ -1,6 +1,6 @@
 <?xml version="1.0" encoding="utf-8"?>
 <!--
-  ~ Copyright (C) 2019 The Android Open Source Project
+  ~ 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.
@@ -14,11 +14,20 @@
   ~ See the License for the specific language governing permissions and
   ~ limitations under the License.
   -->
-
-<shape xmlns:android="http://schemas.android.com/apk/res/android"
-    android:shape="oval">
-
-  <solid
-      android:color="@color/tv_audio_recording_indicator_pulse" />
-
+<shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle" >
+    <corners
+        android:topLeftRadius="10dp"
+        android:topRightRadius="0dp"
+        android:bottomLeftRadius="0dp"
+        android:bottomRightRadius="0dp"
+    />
+    <solid
+        android:color="#404040"
+    />
+    <padding
+        android:left="0dp"
+        android:top="0dp"
+        android:right="0dp"
+        android:bottom="0dp"
+    />
 </shape>
\ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/tv_circle_white_translucent.xml b/packages/CarSystemUI/samples/sample1/rro/res/drawable/system_bar_background_3.xml
similarity index 60%
copy from packages/SystemUI/res/drawable/tv_circle_white_translucent.xml
copy to packages/CarSystemUI/samples/sample1/rro/res/drawable/system_bar_background_3.xml
index 55d21de..afa5b32 100644
--- a/packages/SystemUI/res/drawable/tv_circle_white_translucent.xml
+++ b/packages/CarSystemUI/samples/sample1/rro/res/drawable/system_bar_background_3.xml
@@ -1,6 +1,6 @@
 <?xml version="1.0" encoding="utf-8"?>
 <!--
-  ~ Copyright (C) 2019 The Android Open Source Project
+  ~ 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.
@@ -14,11 +14,20 @@
   ~ See the License for the specific language governing permissions and
   ~ limitations under the License.
   -->
-
-<shape xmlns:android="http://schemas.android.com/apk/res/android"
-    android:shape="oval">
-
-  <solid
-      android:color="@color/tv_audio_recording_indicator_pulse" />
-
+<shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle" >
+    <corners
+        android:topLeftRadius="0dp"
+        android:topRightRadius="0dp"
+        android:bottomLeftRadius="10dp"
+        android:bottomRightRadius="0dp"
+    />
+    <solid
+        android:color="#404040"
+    />
+    <padding
+        android:left="0dp"
+        android:top="0dp"
+        android:right="0dp"
+        android:bottom="0dp"
+    />
 </shape>
\ No newline at end of file
diff --git a/packages/CarSystemUI/samples/sample1/rro/res/layout/car_navigation_bar.xml b/packages/CarSystemUI/samples/sample1/rro/res/layout/car_navigation_bar.xml
new file mode 100644
index 0000000..4358d97
--- /dev/null
+++ b/packages/CarSystemUI/samples/sample1/rro/res/layout/car_navigation_bar.xml
@@ -0,0 +1,88 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2020 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+<com.android.systemui.car.navigationbar.CarNavigationBarView
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:systemui="http://schemas.android.com/apk/res-auto"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:background="@android:color/transparent"
+    android:orientation="horizontal">
+    <!--The 20dp padding is the difference between the background selected icon size and the ripple
+        that was chosen, thus it's a hack to make it look pretty and not an official margin value-->
+    <LinearLayout
+        android:id="@+id/nav_buttons"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:background="@drawable/system_bar_background"
+        android:gravity="center"
+        android:layoutDirection="ltr"
+        android:paddingEnd="20dp"
+        android:paddingStart="20dp">
+
+        <com.android.systemui.car.navigationbar.CarNavigationButton
+            android:id="@+id/home"
+            style="@style/NavigationBarButton"
+            systemui:componentNames="com.android.car.carlauncher/.CarLauncher"
+            systemui:icon="@drawable/car_ic_overview"
+            systemui:intent="intent:#Intent;action=android.intent.action.MAIN;category=android.intent.category.HOME;launchFlags=0x14000000;end"
+            systemui:selectedIcon="@drawable/car_ic_overview_selected"
+            systemui:highlightWhenSelected="true"
+        />
+
+        <com.android.systemui.car.navigationbar.CarNavigationButton
+            android:id="@+id/maps_nav"
+            style="@style/NavigationBarButton"
+            systemui:categories="android.intent.category.APP_MAPS"
+            systemui:icon="@drawable/car_ic_navigation"
+            systemui:intent="intent:#Intent;action=android.intent.action.MAIN;category=android.intent.category.APP_MAPS;launchFlags=0x14000000;end"
+            systemui:selectedIcon="@drawable/car_ic_navigation_selected"
+            systemui:highlightWhenSelected="true"
+        />
+
+        <com.android.systemui.car.navigationbar.CarNavigationButton
+            android:id="@+id/music_nav"
+            style="@style/NavigationBarButton"
+            systemui:categories="android.intent.category.APP_MUSIC"
+            systemui:icon="@drawable/car_ic_music"
+            systemui:intent="intent:#Intent;action=android.car.intent.action.MEDIA_TEMPLATE;launchFlags=0x10000000;end"
+            systemui:packages="com.android.car.media"
+            systemui:selectedIcon="@drawable/car_ic_music_selected"
+            systemui:highlightWhenSelected="true"
+        />
+
+        <com.android.systemui.car.navigationbar.CarNavigationButton
+            android:id="@+id/phone_nav"
+            style="@style/NavigationBarButton"
+            systemui:icon="@drawable/car_ic_phone"
+            systemui:intent="intent:#Intent;action=android.intent.action.MAIN;category=android.intent.category.LAUNCHER;package=com.android.car.dialer;launchFlags=0x10000000;end"
+            systemui:packages="com.android.car.dialer"
+            systemui:selectedIcon="@drawable/car_ic_phone_selected"
+            systemui:highlightWhenSelected="true"
+        />
+
+        <com.android.systemui.car.navigationbar.CarNavigationButton
+            android:id="@+id/grid_nav"
+            style="@style/NavigationBarButton"
+            systemui:componentNames="com.android.car.carlauncher/.AppGridActivity"
+            systemui:icon="@drawable/car_ic_apps"
+            systemui:intent="intent:#Intent;component=com.android.car.carlauncher/.AppGridActivity;launchFlags=0x24000000;end"
+            systemui:selectedIcon="@drawable/car_ic_apps_selected"
+            systemui:highlightWhenSelected="true"
+        />
+
+    </LinearLayout>
+</com.android.systemui.car.navigationbar.CarNavigationBarView>
diff --git a/packages/CarSystemUI/samples/sample1/rro/res/layout/car_right_navigation_bar.xml b/packages/CarSystemUI/samples/sample1/rro/res/layout/car_right_navigation_bar.xml
new file mode 100644
index 0000000..dc1d0d64
--- /dev/null
+++ b/packages/CarSystemUI/samples/sample1/rro/res/layout/car_right_navigation_bar.xml
@@ -0,0 +1,141 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+**
+** Copyright 2018, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+**     http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+
+<com.android.systemui.car.navigationbar.CarNavigationBarView
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:systemui="http://schemas.android.com/apk/res-auto"
+    android:layout_height="match_parent"
+    android:layout_width="match_parent"
+    android:orientation="vertical"
+    android:baselineAligned="false"
+    android:background="@android:color/transparent">
+    <RelativeLayout
+        android:layout_width="match_parent"
+        android:layout_height="110dp"
+        android:background="@drawable/system_bar_background_3">
+        <FrameLayout
+            android:id="@+id/clock_container"
+            android:layout_width="wrap_content"
+            android:layout_height="match_parent"
+            android:layout_centerInParent="true">
+            <com.android.systemui.car.navigationbar.CarNavigationButton
+                android:id="@+id/qs"
+                android:layout_width="match_parent"
+                android:layout_height="match_parent"
+                android:background="@null"
+                systemui:intent="intent:#Intent;component=com.android.car.settings/.common.CarSettingActivities$QuickSettingActivity;launchFlags=0x24000000;end"
+            />
+            <com.android.systemui.statusbar.policy.Clock
+                android:id="@+id/clock"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_gravity="center"
+                android:elevation="5dp"
+                android:singleLine="true"
+                android:textAppearance="@style/TextAppearance.StatusBar.Clock"
+            />
+        </FrameLayout>
+    </RelativeLayout>
+    <View
+        android:layout_width="match_parent"
+        android:layout_height="0dp"
+        android:layout_weight="1"
+    />
+    <LinearLayout
+        android:layout_width="match_parent"
+        android:layout_height="110dp"
+        android:layout_gravity="bottom"
+        android:orientation="horizontal"
+        android:background="@drawable/system_bar_background_2">
+
+        <FrameLayout
+            android:id="@+id/left_hvac_container"
+            android:layout_width="wrap_content"
+            android:layout_height="match_parent"
+            android:layout_alignParentStart="true">
+
+            <com.android.systemui.car.navigationbar.CarNavigationButton
+                android:id="@+id/hvacleft"
+                android:layout_width="match_parent"
+                android:layout_height="match_parent"
+                android:background="@null"
+                systemui:broadcast="true"
+                systemui:intent="intent:#Intent;action=android.car.intent.action.TOGGLE_HVAC_CONTROLS;end"
+            />
+
+            <com.android.systemui.car.hvac.AnimatedTemperatureView
+                android:id="@+id/lefttext"
+                android:layout_width="wrap_content"
+                android:layout_height="match_parent"
+                android:paddingStart="@*android:dimen/car_padding_4"
+                android:paddingEnd="16dp"
+                android:gravity="center_vertical|start"
+                android:minEms="4"
+                android:textAppearance="@style/TextAppearance.CarStatus"
+                systemui:hvacAreaId="49"
+                systemui:hvacMaxText="Max"
+                systemui:hvacMaxValue="126"
+                systemui:hvacMinText="Min"
+                systemui:hvacMinValue="0"
+                systemui:hvacPivotOffset="60dp"
+                systemui:hvacPropertyId="358614275"
+                systemui:hvacTempFormat="%.0f\u00B0"
+            />
+        </FrameLayout>
+        <View
+            android:layout_width="match_parent"
+            android:layout_height="0dp"
+            android:layout_weight="1"
+        />
+        <FrameLayout
+            android:id="@+id/right_hvac_container"
+            android:layout_width="wrap_content"
+            android:layout_height="match_parent"
+            android:layout_alignParentEnd="true">
+
+            <com.android.systemui.car.navigationbar.CarNavigationButton
+                android:id="@+id/hvacright"
+                android:layout_width="match_parent"
+                android:layout_height="match_parent"
+                android:background="@null"
+                systemui:broadcast="true"
+                systemui:intent="intent:#Intent;action=android.car.intent.action.TOGGLE_HVAC_CONTROLS;end"
+            />
+
+            <com.android.systemui.car.hvac.AnimatedTemperatureView
+                android:id="@+id/righttext"
+                android:layout_width="wrap_content"
+                android:layout_height="match_parent"
+                android:paddingStart="16dp"
+                android:paddingEnd="@*android:dimen/car_padding_4"
+                android:gravity="center_vertical|end"
+                android:minEms="4"
+                android:textAppearance="@style/TextAppearance.CarStatus"
+                systemui:hvacAreaId="68"
+                systemui:hvacMaxText="Max"
+                systemui:hvacMaxValue="126"
+                systemui:hvacMinText="Min"
+                systemui:hvacMinValue="0"
+                systemui:hvacPivotOffset="60dp"
+                systemui:hvacPropertyId="358614275"
+                systemui:hvacTempFormat="%.0f\u00B0"
+            />
+        </FrameLayout>
+    </LinearLayout>
+</com.android.systemui.car.navigationbar.CarNavigationBarView>
diff --git a/packages/CarSystemUI/samples/sample1/rro/res/layout/system_icons.xml b/packages/CarSystemUI/samples/sample1/rro/res/layout/system_icons.xml
new file mode 100644
index 0000000..d235792
--- /dev/null
+++ b/packages/CarSystemUI/samples/sample1/rro/res/layout/system_icons.xml
@@ -0,0 +1,34 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2018 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License
+  -->
+
+<LinearLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/system_icons"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:gravity="center_vertical">
+
+    <com.android.systemui.statusbar.phone.StatusIconContainer
+        android:id="@+id/statusIcons"
+        android:layout_width="0dp"
+        android:layout_height="match_parent"
+        android:layout_weight="1"
+        android:paddingEnd="4dp"
+        android:gravity="center_vertical"
+        android:orientation="horizontal"
+    />
+</LinearLayout>
\ No newline at end of file
diff --git a/packages/CarSystemUI/samples/sample1/rro/res/values/attrs.xml b/packages/CarSystemUI/samples/sample1/rro/res/values/attrs.xml
new file mode 100644
index 0000000..e02f9e6
--- /dev/null
+++ b/packages/CarSystemUI/samples/sample1/rro/res/values/attrs.xml
@@ -0,0 +1,44 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2020 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<resources>
+    <attr name="broadcast" format="boolean"/>
+    <attr name="icon" format="reference"/>
+    <attr name="selectedIcon" format="reference"/>
+    <attr name="intent" format="string"/>
+    <attr name="longIntent" format="string"/>
+    <attr name="componentNames" format="string" />
+    <attr name="highlightWhenSelected" format="boolean" />
+    <attr name="categories" format="string"/>
+    <attr name="packages" format="string" />
+
+    <!-- Custom attributes to configure hvac values -->
+    <declare-styleable name="AnimatedTemperatureView">
+        <attr name="hvacAreaId" format="integer"/>
+        <attr name="hvacPropertyId" format="integer"/>
+        <attr name="hvacTempFormat" format="string"/>
+        <!-- how far away the animations should center around -->
+        <attr name="hvacPivotOffset" format="dimension"/>
+        <attr name="hvacMinValue" format="float"/>
+        <attr name="hvacMaxValue" format="float"/>
+        <attr name="hvacMinText" format="string|reference"/>
+        <attr name="hvacMaxText" format="string|reference"/>
+        <attr name="android:gravity"/>
+        <attr name="android:minEms"/>
+        <attr name="android:textAppearance"/>
+    </declare-styleable>
+</resources>
diff --git a/packages/SystemUI/res/drawable/tv_circle_white_translucent.xml b/packages/CarSystemUI/samples/sample1/rro/res/values/colors.xml
similarity index 69%
copy from packages/SystemUI/res/drawable/tv_circle_white_translucent.xml
copy to packages/CarSystemUI/samples/sample1/rro/res/values/colors.xml
index 55d21de..c32d638 100644
--- a/packages/SystemUI/res/drawable/tv_circle_white_translucent.xml
+++ b/packages/CarSystemUI/samples/sample1/rro/res/values/colors.xml
@@ -1,6 +1,6 @@
 <?xml version="1.0" encoding="utf-8"?>
 <!--
-  ~ Copyright (C) 2019 The Android Open Source Project
+  ~ 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.
@@ -14,11 +14,7 @@
   ~ See the License for the specific language governing permissions and
   ~ limitations under the License.
   -->
-
-<shape xmlns:android="http://schemas.android.com/apk/res/android"
-    android:shape="oval">
-
-  <solid
-      android:color="@color/tv_audio_recording_indicator_pulse" />
-
-</shape>
\ No newline at end of file
+<resources xmlns:android="http://schemas.android.com/apk/res/android">
+    <color name="car_nav_icon_fill_color">#8F8F8F</color>
+    <color name="car_nav_icon_fill_color_selected">#FFFFFF</color>
+</resources>
diff --git a/packages/CarSystemUI/samples/sample1/rro/res/values/config.xml b/packages/CarSystemUI/samples/sample1/rro/res/values/config.xml
new file mode 100644
index 0000000..2ec90e9
--- /dev/null
+++ b/packages/CarSystemUI/samples/sample1/rro/res/values/config.xml
@@ -0,0 +1,58 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2020 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<resources>
+    <!-- Configure which system bars should be displayed. -->
+    <bool name="config_enableTopNavigationBar">false</bool>
+    <bool name="config_enableLeftNavigationBar">false</bool>
+    <bool name="config_enableRightNavigationBar">true</bool>
+    <bool name="config_enableBottomNavigationBar">true</bool>
+
+    <!-- Configure the type of each system bar. Each system bar must have a unique type. -->
+    <!--    STATUS_BAR = 0-->
+    <!--    NAVIGATION_BAR = 1-->
+    <!--    STATUS_BAR_EXTRA = 2-->
+    <!--    NAVIGATION_BAR_EXTRA = 3-->
+    <integer name="config_topSystemBarType">0</integer>
+    <integer name="config_leftSystemBarType">0</integer>
+    <integer name="config_rightSystemBarType">0</integer>
+    <integer name="config_bottomSystemBarType">1</integer>
+
+    <!-- Configure the relative z-order among the system bars. When two system bars overlap (e.g.
+         if both top bar and left bar are enabled, it creates an overlapping space in the upper left
+         corner), the system bar with the higher z-order takes the overlapping space and padding is
+         applied to the other bar.-->
+    <!-- NOTE: If two overlapping system bars have the same z-order, SystemBarConfigs will throw a
+         RuntimeException, since their placing order cannot be determined. Bars that do not overlap
+         are allowed to have the same z-order. -->
+    <!-- NOTE: If the z-order of a bar is 10 or above, it will also appear on top of HUN's.    -->
+    <integer name="config_topSystemBarZOrder">0</integer>
+    <integer name="config_leftSystemBarZOrder">0</integer>
+    <integer name="config_rightSystemBarZOrder">11</integer>
+    <integer name="config_bottomSystemBarZOrder">10</integer>
+
+    <!-- Whether heads-up notifications should be shown on the bottom. If false, heads-up
+         notifications will be shown pushed to the top of their parent container. If true, they will
+         be shown pushed to the bottom of their parent container. If true, then should override
+         config_headsUpNotificationAnimationHelper to use a different AnimationHelper, such as
+         com.android.car.notification.headsup.animationhelper.
+         CarHeadsUpNotificationBottomAnimationHelper. -->
+    <bool name="config_showHeadsUpNotificationOnBottom">true</bool>
+
+    <string name="config_notificationPanelViewMediator" translatable="false">
+        com.android.systemui.car.notification.BottomNotificationPanelViewMediator</string>
+</resources>
\ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/tv_circle_white_translucent.xml b/packages/CarSystemUI/samples/sample1/rro/res/values/dimens.xml
similarity index 67%
copy from packages/SystemUI/res/drawable/tv_circle_white_translucent.xml
copy to packages/CarSystemUI/samples/sample1/rro/res/values/dimens.xml
index 55d21de..cdfed27 100644
--- a/packages/SystemUI/res/drawable/tv_circle_white_translucent.xml
+++ b/packages/CarSystemUI/samples/sample1/rro/res/values/dimens.xml
@@ -1,6 +1,6 @@
 <?xml version="1.0" encoding="utf-8"?>
 <!--
-  ~ Copyright (C) 2019 The Android Open Source Project
+  ~ Copyright (C) 2018 The Android Open Source Project
   ~
   ~ Licensed under the Apache License, Version 2.0 (the "License");
   ~ you may not use this file except in compliance with the License.
@@ -12,13 +12,8 @@
   ~ distributed under the License is distributed on an "AS IS" BASIS,
   ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   ~ See the License for the specific language governing permissions and
-  ~ limitations under the License.
+  ~ limitations under the License
   -->
-
-<shape xmlns:android="http://schemas.android.com/apk/res/android"
-    android:shape="oval">
-
-  <solid
-      android:color="@color/tv_audio_recording_indicator_pulse" />
-
-</shape>
\ No newline at end of file
+<resources>
+    <dimen name="car_right_navigation_bar_width">280dp</dimen>
+</resources>
diff --git a/packages/CarSystemUI/samples/sample1/rro/res/values/styles.xml b/packages/CarSystemUI/samples/sample1/rro/res/values/styles.xml
new file mode 100644
index 0000000..136dc3b
--- /dev/null
+++ b/packages/CarSystemUI/samples/sample1/rro/res/values/styles.xml
@@ -0,0 +1,35 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2020 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+<resources xmlns:android="http://schemas.android.com/apk/res/android">
+    <style name="TextAppearance.StatusBar.Clock"
+           parent="@*android:style/TextAppearance.StatusBar.Icon">
+        <item name="android:textSize">40sp</item>
+        <item name="android:fontFamily">sans-serif-regular</item>
+        <item name="android:textColor">#FFFFFF</item>
+    </style>
+
+    <style name="NavigationBarButton">
+        <item name="android:layout_height">96dp</item>
+        <item name="android:layout_width">96dp</item>
+        <item name="android:background">?android:attr/selectableItemBackground</item>
+    </style>
+
+    <style name="TextAppearance.CarStatus" parent="@android:style/TextAppearance.DeviceDefault">
+        <item name="android:textSize">30sp</item>
+        <item name="android:textColor">#FFFFFF</item>
+    </style>
+</resources>
\ No newline at end of file
diff --git a/packages/CarSystemUI/samples/sample1/rro/res/xml/car_sysui_overlays.xml b/packages/CarSystemUI/samples/sample1/rro/res/xml/car_sysui_overlays.xml
new file mode 100644
index 0000000..20aa5f7
--- /dev/null
+++ b/packages/CarSystemUI/samples/sample1/rro/res/xml/car_sysui_overlays.xml
@@ -0,0 +1,76 @@
+
+<!--
+  ~ Copyright (C) 2020 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<overlay>
+    <item target="layout/car_navigation_bar" value="@layout/car_navigation_bar"/>
+    <item target="layout/system_icons" value="@layout/system_icons"/>
+    <item target="layout/car_right_navigation_bar" value="@layout/car_right_navigation_bar"/>
+
+    <item target="attr/icon" value="@attr/icon"/>
+    <item target="attr/selectedIcon" value="@attr/selectedIcon"/>
+    <item target="attr/intent" value="@attr/intent"/>
+    <item target="attr/longIntent" value="@attr/longIntent"/>
+    <item target="attr/componentNames" value="@attr/componentNames"/>
+    <item target="attr/highlightWhenSelected" value="@attr/highlightWhenSelected"/>
+    <item target="attr/categories" value="@attr/categories"/>
+    <item target="attr/packages" value="@attr/packages"/>
+    <item target="attr/hvacAreaId" value="@attr/hvacAreaId"/>
+    <item target="attr/hvacPropertyId" value="@attr/hvacPropertyId"/>
+    <item target="attr/hvacTempFormat" value="@attr/hvacTempFormat"/>
+    <item target="attr/hvacPivotOffset" value="@attr/hvacPivotOffset"/>
+    <item target="attr/hvacMinValue" value="@attr/hvacMinValue"/>
+    <item target="attr/hvacMaxValue" value="@attr/hvacMaxValue"/>
+    <item target="attr/hvacMinText" value="@attr/hvacMinText"/>
+    <item target="attr/hvacMaxText" value="@attr/hvacMaxText"/>
+    <!-- start the intent as a broad cast instead of an activity if true-->
+    <item target="attr/broadcast" value="@attr/broadcast"/>
+
+    <item target="drawable/car_ic_overview" value="@drawable/car_ic_overview" />
+    <item target="drawable/car_ic_overview_selected" value="@drawable/car_ic_overview_selected" />
+    <item target="drawable/car_ic_apps" value="@drawable/car_ic_apps" />
+    <item target="drawable/car_ic_apps_selected" value="@drawable/car_ic_apps_selected" />
+    <item target="drawable/car_ic_music" value="@drawable/car_ic_music" />
+    <item target="drawable/car_ic_music_selected" value="@drawable/car_ic_music_selected" />
+    <item target="drawable/car_ic_phone" value="@drawable/car_ic_phone" />
+    <item target="drawable/car_ic_phone_selected" value="@drawable/car_ic_phone_selected" />
+    <item target="drawable/car_ic_navigation" value="@drawable/car_ic_navigation" />
+    <item target="drawable/car_ic_navigation_selected" value="@drawable/car_ic_navigation_selected" />
+
+    <item target="dimen/car_right_navigation_bar_width" value="@dimen/car_right_navigation_bar_width" />
+
+    <item target="style/NavigationBarButton" value="@style/NavigationBarButton"/>
+
+    <item target="color/car_nav_icon_fill_color" value="@color/car_nav_icon_fill_color" />
+
+    <item target="bool/config_enableTopNavigationBar" value="@bool/config_enableTopNavigationBar"/>
+    <item target="bool/config_enableLeftNavigationBar" value="@bool/config_enableLeftNavigationBar"/>
+    <item target="bool/config_enableRightNavigationBar" value="@bool/config_enableRightNavigationBar"/>
+    <item target="bool/config_enableBottomNavigationBar" value="@bool/config_enableBottomNavigationBar"/>
+    <item target="bool/config_showHeadsUpNotificationOnBottom" value="@bool/config_showHeadsUpNotificationOnBottom"/>
+
+    <item target="integer/config_topSystemBarType" value="@integer/config_topSystemBarType"/>
+    <item target="integer/config_leftSystemBarType" value="@integer/config_leftSystemBarType"/>
+    <item target="integer/config_rightSystemBarType" value="@integer/config_rightSystemBarType"/>
+    <item target="integer/config_bottomSystemBarType" value="@integer/config_bottomSystemBarType"/>
+
+    <item target="integer/config_topSystemBarZOrder" value="@integer/config_topSystemBarZOrder"/>
+    <item target="integer/config_leftSystemBarZOrder" value="@integer/config_leftSystemBarZOrder"/>
+    <item target="integer/config_rightSystemBarZOrder" value="@integer/config_rightSystemBarZOrder"/>
+    <item target="integer/config_bottomSystemBarZOrder" value="@integer/config_bottomSystemBarZOrder"/>
+
+    <item target="string/config_notificationPanelViewMediator" value="@string/config_notificationPanelViewMediator"/>
+</overlay>
\ No newline at end of file
diff --git a/packages/CarSystemUI/src/com/android/systemui/CarGlobalRootComponent.java b/packages/CarSystemUI/src/com/android/systemui/CarGlobalRootComponent.java
new file mode 100644
index 0000000..552cadf
--- /dev/null
+++ b/packages/CarSystemUI/src/com/android/systemui/CarGlobalRootComponent.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui;
+
+import com.android.systemui.dagger.GlobalRootComponent;
+
+import javax.inject.Singleton;
+
+import dagger.Component;
+
+/** Car subclass for GlobalRootComponent. */
+@Singleton
+@Component(
+        modules = {
+                CarSysUIComponentModule.class
+        })
+public interface CarGlobalRootComponent extends GlobalRootComponent {
+    /**
+     * Builder for a CarGlobalRootComponent.
+     */
+    @Component.Builder
+    interface Builder extends GlobalRootComponent.Builder {
+        CarGlobalRootComponent build();
+    }
+
+    @Override
+    CarSysUIComponent.Builder getSysUIComponent();
+}
diff --git a/packages/CarSystemUI/src/com/android/systemui/CarSysUIComponent.java b/packages/CarSystemUI/src/com/android/systemui/CarSysUIComponent.java
new file mode 100644
index 0000000..24d9d09
--- /dev/null
+++ b/packages/CarSystemUI/src/com/android/systemui/CarSysUIComponent.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui;
+
+import com.android.systemui.dagger.DependencyBinder;
+import com.android.systemui.dagger.DependencyProvider;
+import com.android.systemui.dagger.SysUIComponent;
+import com.android.systemui.dagger.SysUISingleton;
+import com.android.systemui.dagger.SystemServicesModule;
+import com.android.systemui.dagger.SystemUIModule;
+import com.android.systemui.pip.phone.dagger.PipModule;
+
+import dagger.Subcomponent;
+
+/**
+ * Dagger Subcomponent for Core SysUI.
+ */
+@SysUISingleton
+@Subcomponent(modules = {
+        CarComponentBinder.class,
+        DependencyProvider.class,
+        DependencyBinder.class,
+        PipModule.class,
+        SystemServicesModule.class,
+        SystemUIModule.class,
+        CarSystemUIModule.class,
+        CarSystemUIBinder.class})
+public interface CarSysUIComponent extends SysUIComponent {
+
+    /**
+     * Builder for a CarSysUIComponent.
+     */
+    @Subcomponent.Builder
+    interface Builder extends SysUIComponent.Builder {
+        CarSysUIComponent build();
+    }
+}
diff --git a/media/java/android/media/IStrategyPreferredDeviceDispatcher.aidl b/packages/CarSystemUI/src/com/android/systemui/CarSysUIComponentModule.java
similarity index 67%
copy from media/java/android/media/IStrategyPreferredDeviceDispatcher.aidl
copy to packages/CarSystemUI/src/com/android/systemui/CarSysUIComponentModule.java
index b1f99e6..4de3166 100644
--- a/media/java/android/media/IStrategyPreferredDeviceDispatcher.aidl
+++ b/packages/CarSystemUI/src/com/android/systemui/CarSysUIComponentModule.java
@@ -14,17 +14,15 @@
  * limitations under the License.
  */
 
-package android.media;
+package com.android.systemui;
 
-import android.media.AudioDeviceAttributes;
+import dagger.Module;
 
 /**
- * AIDL for AudioService to signal audio strategy-preferred device updates.
+ * Dagger module for including the CarSysUIComponent.
  *
- * {@hide}
+ * TODO(b/162923491): Remove or otherwise refactor this module. This is a stop gap.
  */
-oneway interface IStrategyPreferredDeviceDispatcher {
-
-    void dispatchPrefDeviceChanged(int strategyId, in AudioDeviceAttributes device);
-
+@Module(subcomponents = {CarSysUIComponent.class})
+public abstract class CarSysUIComponentModule {
 }
diff --git a/packages/CarSystemUI/src/com/android/systemui/CarSystemUIBinder.java b/packages/CarSystemUI/src/com/android/systemui/CarSystemUIBinder.java
index 797a178..3971e18 100644
--- a/packages/CarSystemUI/src/com/android/systemui/CarSystemUIBinder.java
+++ b/packages/CarSystemUI/src/com/android/systemui/CarSystemUIBinder.java
@@ -34,7 +34,6 @@
 import com.android.systemui.recents.Recents;
 import com.android.systemui.recents.RecentsModule;
 import com.android.systemui.shortcut.ShortcutKeyDispatcher;
-import com.android.systemui.stackdivider.Divider;
 import com.android.systemui.statusbar.dagger.StatusBarModule;
 import com.android.systemui.statusbar.notification.InstantAppNotifier;
 import com.android.systemui.statusbar.notification.dagger.NotificationsModule;
@@ -42,6 +41,7 @@
 import com.android.systemui.theme.ThemeOverlayController;
 import com.android.systemui.toast.ToastUI;
 import com.android.systemui.util.leak.GarbageMonitor;
+import com.android.systemui.wmshell.WMShell;
 
 import dagger.Binds;
 import dagger.Module;
@@ -59,12 +59,6 @@
     @ClassKey(AuthController.class)
     public abstract SystemUI bindAuthController(AuthController sysui);
 
-    /** Inject into Divider. */
-    @Binds
-    @IntoMap
-    @ClassKey(Divider.class)
-    public abstract SystemUI bindDivider(Divider sysui);
-
     /** Inject Car Navigation Bar. */
     @Binds
     @IntoMap
@@ -192,4 +186,10 @@
     @IntoMap
     @ClassKey(SideLoadedAppController.class)
     public abstract SystemUI bindSideLoadedAppController(SideLoadedAppController sysui);
+
+    /** Inject into WMShell. */
+    @Binds
+    @IntoMap
+    @ClassKey(WMShell.class)
+    public abstract SystemUI bindWMShell(WMShell sysui);
 }
diff --git a/packages/CarSystemUI/src/com/android/systemui/CarSystemUIFactory.java b/packages/CarSystemUI/src/com/android/systemui/CarSystemUIFactory.java
index 03ea941..a65edc5 100644
--- a/packages/CarSystemUI/src/com/android/systemui/CarSystemUIFactory.java
+++ b/packages/CarSystemUI/src/com/android/systemui/CarSystemUIFactory.java
@@ -19,7 +19,7 @@
 import android.content.Context;
 import android.content.res.Resources;
 
-import com.android.systemui.dagger.SystemUIRootComponent;
+import com.android.systemui.dagger.GlobalRootComponent;
 
 import java.util.HashSet;
 import java.util.Set;
@@ -30,8 +30,8 @@
 public class CarSystemUIFactory extends SystemUIFactory {
 
     @Override
-    protected SystemUIRootComponent buildSystemUIRootComponent(Context context) {
-        return DaggerCarSystemUIRootComponent.builder()
+    protected GlobalRootComponent buildGlobalRootComponent(Context context) {
+        return DaggerCarGlobalRootComponent.builder()
                 .context(context)
                 .build();
     }
diff --git a/packages/CarSystemUI/src/com/android/systemui/CarSystemUIModule.java b/packages/CarSystemUI/src/com/android/systemui/CarSystemUIModule.java
index 995a3ec..3eea513 100644
--- a/packages/CarSystemUI/src/com/android/systemui/CarSystemUIModule.java
+++ b/packages/CarSystemUI/src/com/android/systemui/CarSystemUIModule.java
@@ -22,19 +22,20 @@
 import android.content.Context;
 import android.os.Handler;
 import android.os.PowerManager;
-import android.view.IWindowManager;
 
 import com.android.keyguard.KeyguardViewController;
 import com.android.systemui.broadcast.BroadcastDispatcher;
 import com.android.systemui.car.CarDeviceProvisionedController;
 import com.android.systemui.car.CarDeviceProvisionedControllerImpl;
 import com.android.systemui.car.keyguard.CarKeyguardViewController;
+import com.android.systemui.car.notification.NotificationShadeWindowControllerImpl;
 import com.android.systemui.car.statusbar.DozeServiceHost;
-import com.android.systemui.car.statusbar.DummyNotificationShadeWindowController;
 import com.android.systemui.car.volume.CarVolumeDialogComponent;
-import com.android.systemui.dagger.SystemUIRootComponent;
+import com.android.systemui.dagger.GlobalRootComponent;
+import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dagger.qualifiers.Background;
 import com.android.systemui.dagger.qualifiers.Main;
+import com.android.systemui.demomode.DemoModeController;
 import com.android.systemui.dock.DockManager;
 import com.android.systemui.dock.DockManagerImpl;
 import com.android.systemui.doze.DozeHost;
@@ -46,16 +47,15 @@
 import com.android.systemui.qs.tileimpl.QSFactoryImpl;
 import com.android.systemui.recents.Recents;
 import com.android.systemui.recents.RecentsImplementation;
-import com.android.systemui.stackdivider.DividerModule;
 import com.android.systemui.statusbar.CommandQueue;
 import com.android.systemui.statusbar.NotificationLockscreenUserManager;
 import com.android.systemui.statusbar.NotificationLockscreenUserManagerImpl;
+import com.android.systemui.statusbar.NotificationShadeWindowController;
 import com.android.systemui.statusbar.notification.NotificationEntryManager;
 import com.android.systemui.statusbar.phone.HeadsUpManagerPhone;
 import com.android.systemui.statusbar.phone.KeyguardBypassController;
 import com.android.systemui.statusbar.phone.KeyguardEnvironmentImpl;
 import com.android.systemui.statusbar.phone.NotificationGroupManager;
-import com.android.systemui.statusbar.phone.NotificationShadeWindowController;
 import com.android.systemui.statusbar.phone.ShadeController;
 import com.android.systemui.statusbar.phone.ShadeControllerImpl;
 import com.android.systemui.statusbar.policy.BatteryController;
@@ -64,30 +64,29 @@
 import com.android.systemui.statusbar.policy.DeviceProvisionedController;
 import com.android.systemui.statusbar.policy.HeadsUpManager;
 import com.android.systemui.volume.VolumeDialogComponent;
-import com.android.systemui.wm.DisplaySystemBarsController;
-import com.android.wm.shell.common.DisplayController;
-import com.android.wm.shell.common.DisplayImeController;
-import com.android.wm.shell.common.SystemWindows;
-import com.android.wm.shell.common.TransactionPool;
+import com.android.systemui.wmshell.CarWMShellModule;
 
 import javax.inject.Named;
-import javax.inject.Singleton;
 
 import dagger.Binds;
 import dagger.Module;
 import dagger.Provides;
 
-@Module(includes = {DividerModule.class, QSModule.class})
-public abstract class CarSystemUIModule {
+@Module(
+        includes = {
+                QSModule.class,
+                CarWMShellModule.class
+        })
+abstract class CarSystemUIModule {
 
-    @Singleton
+    @SysUISingleton
     @Provides
     @Named(ALLOW_NOTIFICATION_LONG_PRESS_NAME)
     static boolean provideAllowNotificationLongPress() {
         return false;
     }
 
-    @Singleton
+    @SysUISingleton
     @Provides
     static HeadsUpManagerPhone provideHeadsUpManagerPhone(
             Context context,
@@ -99,7 +98,7 @@
                 groupManager, configurationController);
     }
 
-    @Singleton
+    @SysUISingleton
     @Provides
     @Named(LEAK_REPORT_EMAIL_NAME)
     static String provideLeakReportEmail() {
@@ -107,41 +106,12 @@
     }
 
     @Provides
-    @Singleton
+    @SysUISingleton
     static Recents provideRecents(Context context, RecentsImplementation recentsImplementation,
             CommandQueue commandQueue) {
         return new Recents(context, recentsImplementation, commandQueue);
     }
 
-    @Singleton
-    @Provides
-    static TransactionPool provideTransactionPool() {
-        return new TransactionPool();
-    }
-
-    @Singleton
-    @Provides
-    static DisplayController providerDisplayController(Context context, @Main Handler handler,
-            IWindowManager wmService) {
-        return new DisplayController(context, handler, wmService);
-    }
-
-    @Singleton
-    @Provides
-    static SystemWindows provideSystemWindows(DisplayController displayController,
-            IWindowManager wmService) {
-        return new SystemWindows(displayController, wmService);
-    }
-
-    @Singleton
-    @Provides
-    static DisplayImeController provideDisplayImeController(Context context,
-            IWindowManager wmService, DisplayController displayController,
-            @Main Handler mainHandler, TransactionPool transactionPool) {
-        return new DisplaySystemBarsController.Builder(context, wmService, displayController,
-                mainHandler, transactionPool).build();
-    }
-
     @Binds
     abstract HeadsUpManager bindHeadsUpManagerPhone(HeadsUpManagerPhone headsUpManagerPhone);
 
@@ -153,19 +123,20 @@
             NotificationLockscreenUserManagerImpl notificationLockscreenUserManager);
 
     @Provides
-    @Singleton
+    @SysUISingleton
     static BatteryController provideBatteryController(Context context,
             EnhancedEstimates enhancedEstimates, PowerManager powerManager,
-            BroadcastDispatcher broadcastDispatcher, @Main Handler mainHandler,
+            BroadcastDispatcher broadcastDispatcher, DemoModeController demoModeController,
+            @Main Handler mainHandler,
             @Background Handler bgHandler) {
         BatteryController bC = new BatteryControllerImpl(context, enhancedEstimates, powerManager,
-                broadcastDispatcher, mainHandler, bgHandler);
+                broadcastDispatcher, demoModeController, mainHandler, bgHandler);
         bC.init();
         return bC;
     }
 
     @Binds
-    @Singleton
+    @SysUISingleton
     public abstract QSFactory bindQSFactory(QSFactoryImpl qsFactoryImpl);
 
     @Binds
@@ -179,8 +150,8 @@
     abstract ShadeController provideShadeController(ShadeControllerImpl shadeController);
 
     @Binds
-    abstract SystemUIRootComponent bindSystemUIRootComponent(
-            CarSystemUIRootComponent systemUIRootComponent);
+    abstract GlobalRootComponent bindGlobalRootComponent(
+            CarGlobalRootComponent globalRootComponent);
 
     @Binds
     abstract VolumeDialogComponent bindVolumeDialogComponent(
@@ -191,6 +162,10 @@
             CarKeyguardViewController carKeyguardViewController);
 
     @Binds
+    abstract NotificationShadeWindowController bindNotificationShadeController(
+            NotificationShadeWindowControllerImpl notificationPanelViewController);
+
+    @Binds
     abstract DeviceProvisionedController bindDeviceProvisionedController(
             CarDeviceProvisionedControllerImpl deviceProvisionedController);
 
@@ -199,9 +174,5 @@
             CarDeviceProvisionedControllerImpl deviceProvisionedController);
 
     @Binds
-    abstract NotificationShadeWindowController bindNotificationShadeWindowController(
-            DummyNotificationShadeWindowController notificationShadeWindowController);
-
-    @Binds
     abstract DozeHost bindDozeHost(DozeServiceHost dozeServiceHost);
 }
diff --git a/packages/CarSystemUI/src/com/android/systemui/CarSystemUIRootComponent.java b/packages/CarSystemUI/src/com/android/systemui/CarSystemUIRootComponent.java
deleted file mode 100644
index ece3bee..0000000
--- a/packages/CarSystemUI/src/com/android/systemui/CarSystemUIRootComponent.java
+++ /dev/null
@@ -1,52 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui;
-
-import com.android.systemui.dagger.DependencyBinder;
-import com.android.systemui.dagger.DependencyProvider;
-import com.android.systemui.dagger.SystemServicesModule;
-import com.android.systemui.dagger.SystemUIModule;
-import com.android.systemui.dagger.SystemUIRootComponent;
-import com.android.systemui.onehanded.dagger.OneHandedModule;
-import com.android.systemui.pip.phone.dagger.PipModule;
-
-import javax.inject.Singleton;
-
-import dagger.Component;
-
-@Singleton
-@Component(
-        modules = {
-                CarComponentBinder.class,
-                DependencyProvider.class,
-                DependencyBinder.class,
-                PipModule.class,
-                OneHandedModule.class,
-                SystemServicesModule.class,
-                SystemUIModule.class,
-                CarSystemUIModule.class,
-                CarSystemUIBinder.class
-        })
-public interface CarSystemUIRootComponent extends SystemUIRootComponent {
-    /**
-     * Builder for a CarSystemUIRootComponent.
-     */
-    @Component.Builder
-    interface Builder extends SystemUIRootComponent.Builder {
-        CarSystemUIRootComponent build();
-    }
-}
diff --git a/packages/CarSystemUI/src/com/android/systemui/car/CarDeviceProvisionedControllerImpl.java b/packages/CarSystemUI/src/com/android/systemui/car/CarDeviceProvisionedControllerImpl.java
index 09e62d2..a2ba880 100644
--- a/packages/CarSystemUI/src/com/android/systemui/car/CarDeviceProvisionedControllerImpl.java
+++ b/packages/CarSystemUI/src/com/android/systemui/car/CarDeviceProvisionedControllerImpl.java
@@ -27,17 +27,17 @@
 
 import com.android.systemui.Dependency;
 import com.android.systemui.broadcast.BroadcastDispatcher;
+import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.statusbar.policy.DeviceProvisionedControllerImpl;
 
 import javax.inject.Inject;
-import javax.inject.Singleton;
 
 /**
  * A controller that monitors the status of SUW progress for each user in addition to the
  * functionality provided by {@link DeviceProvisionedControllerImpl}.
  */
-@Singleton
+@SysUISingleton
 public class CarDeviceProvisionedControllerImpl extends DeviceProvisionedControllerImpl implements
         CarDeviceProvisionedController {
     private static final Uri USER_SETUP_IN_PROGRESS_URI = Settings.Secure.getUriFor(
diff --git a/packages/CarSystemUI/src/com/android/systemui/car/CarServiceProvider.java b/packages/CarSystemUI/src/com/android/systemui/car/CarServiceProvider.java
index 80ee371..5778d66 100644
--- a/packages/CarSystemUI/src/com/android/systemui/car/CarServiceProvider.java
+++ b/packages/CarSystemUI/src/com/android/systemui/car/CarServiceProvider.java
@@ -21,14 +21,15 @@
 
 import androidx.annotation.VisibleForTesting;
 
+import com.android.systemui.dagger.SysUISingleton;
+
 import java.util.ArrayList;
 import java.util.List;
 
 import javax.inject.Inject;
-import javax.inject.Singleton;
 
 /** Provides a common connection to the car service that can be shared. */
-@Singleton
+@SysUISingleton
 public class CarServiceProvider {
 
     private final Context mContext;
diff --git a/packages/CarSystemUI/src/com/android/systemui/car/hvac/HvacController.java b/packages/CarSystemUI/src/com/android/systemui/car/hvac/HvacController.java
index 236a6a4..a4b6bfc 100644
--- a/packages/CarSystemUI/src/com/android/systemui/car/hvac/HvacController.java
+++ b/packages/CarSystemUI/src/com/android/systemui/car/hvac/HvacController.java
@@ -29,6 +29,7 @@
 import android.view.ViewGroup;
 
 import com.android.systemui.car.CarServiceProvider;
+import com.android.systemui.dagger.SysUISingleton;
 
 import java.util.ArrayList;
 import java.util.HashMap;
@@ -40,13 +41,12 @@
 import java.util.Set;
 
 import javax.inject.Inject;
-import javax.inject.Singleton;
 
 /**
  * Manages the connection to the Car service and delegates value changes to the registered
  * {@link TemperatureView}s
  */
-@Singleton
+@SysUISingleton
 public class HvacController {
     public static final String TAG = "HvacController";
     private static final boolean DEBUG = true;
diff --git a/packages/CarSystemUI/src/com/android/systemui/car/keyguard/CarKeyguardViewController.java b/packages/CarSystemUI/src/com/android/systemui/car/keyguard/CarKeyguardViewController.java
index 51a7245..276ddfb 100644
--- a/packages/CarSystemUI/src/com/android/systemui/car/keyguard/CarKeyguardViewController.java
+++ b/packages/CarSystemUI/src/com/android/systemui/car/keyguard/CarKeyguardViewController.java
@@ -38,6 +38,7 @@
 import com.android.systemui.car.navigationbar.CarNavigationBarController;
 import com.android.systemui.car.window.OverlayViewController;
 import com.android.systemui.car.window.OverlayViewGlobalStateController;
+import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.keyguard.DismissCallbackRegistry;
 import com.android.systemui.plugins.FalsingManager;
@@ -49,7 +50,6 @@
 import com.android.systemui.statusbar.policy.KeyguardStateController;
 
 import javax.inject.Inject;
-import javax.inject.Singleton;
 
 import dagger.Lazy;
 
@@ -57,7 +57,7 @@
  * Automotive implementation of the {@link KeyguardViewController}. It controls the Keyguard View
  * that is mounted to the SystemUIOverlayWindow.
  */
-@Singleton
+@SysUISingleton
 public class CarKeyguardViewController extends OverlayViewController implements
         KeyguardViewController {
     private static final String TAG = "CarKeyguardViewController";
diff --git a/packages/CarSystemUI/src/com/android/systemui/car/keyguard/CarKeyguardViewMediator.java b/packages/CarSystemUI/src/com/android/systemui/car/keyguard/CarKeyguardViewMediator.java
index 5a35c48..155b73e 100644
--- a/packages/CarSystemUI/src/com/android/systemui/car/keyguard/CarKeyguardViewMediator.java
+++ b/packages/CarSystemUI/src/com/android/systemui/car/keyguard/CarKeyguardViewMediator.java
@@ -18,15 +18,15 @@
 
 import com.android.systemui.car.userswitcher.FullScreenUserSwitcherViewController;
 import com.android.systemui.car.window.OverlayViewMediator;
+import com.android.systemui.dagger.SysUISingleton;
 
 import javax.inject.Inject;
-import javax.inject.Singleton;
 
 /**
  * Manages events originating from the Keyguard service that cause Keyguard or other OverlayWindow
  * Components to appear or disappear.
  */
-@Singleton
+@SysUISingleton
 public class CarKeyguardViewMediator implements OverlayViewMediator {
 
     private final CarKeyguardViewController mCarKeyguardViewController;
diff --git a/packages/CarSystemUI/src/com/android/systemui/car/navigationbar/ButtonRoleHolderController.java b/packages/CarSystemUI/src/com/android/systemui/car/navigationbar/ButtonRoleHolderController.java
index 5c83c02..f8cd20f 100644
--- a/packages/CarSystemUI/src/com/android/systemui/car/navigationbar/ButtonRoleHolderController.java
+++ b/packages/CarSystemUI/src/com/android/systemui/car/navigationbar/ButtonRoleHolderController.java
@@ -30,13 +30,13 @@
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.systemui.car.CarDeviceProvisionedController;
+import com.android.systemui.dagger.SysUISingleton;
 
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
 
 import javax.inject.Inject;
-import javax.inject.Singleton;
 
 /**
  * Some CarNavigationButtons can be associated to a {@link RoleManager} role. When they are, it is
@@ -46,7 +46,7 @@
  * This class monitors the current role holders for each role type and updates the button icon for
  * this buttons with have this feature enabled.
  */
-@Singleton
+@SysUISingleton
 public class ButtonRoleHolderController {
     private static final String TAG = "ButtonRoleHolderController";
 
diff --git a/packages/CarSystemUI/src/com/android/systemui/car/navigationbar/ButtonSelectionStateController.java b/packages/CarSystemUI/src/com/android/systemui/car/navigationbar/ButtonSelectionStateController.java
index eedcfa5..aa6da89 100644
--- a/packages/CarSystemUI/src/com/android/systemui/car/navigationbar/ButtonSelectionStateController.java
+++ b/packages/CarSystemUI/src/com/android/systemui/car/navigationbar/ButtonSelectionStateController.java
@@ -26,13 +26,14 @@
 import android.view.View;
 import android.view.ViewGroup;
 
+import com.android.systemui.dagger.SysUISingleton;
+
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.List;
 import java.util.Set;
 
 import javax.inject.Inject;
-import javax.inject.Singleton;
 
 /**
  * CarNavigationButtons can optionally have selection state that toggles certain visual indications
@@ -42,7 +43,7 @@
  * This class controls the selection state of CarNavigationButtons that have opted in to have such
  * selection state-dependent visual indications.
  */
-@Singleton
+@SysUISingleton
 public class ButtonSelectionStateController {
 
     private final Set<CarNavigationButton> mRegisteredViews = new HashSet<>();
diff --git a/packages/CarSystemUI/src/com/android/systemui/car/navigationbar/ButtonSelectionStateListener.java b/packages/CarSystemUI/src/com/android/systemui/car/navigationbar/ButtonSelectionStateListener.java
index 1361798..d6216ba 100644
--- a/packages/CarSystemUI/src/com/android/systemui/car/navigationbar/ButtonSelectionStateListener.java
+++ b/packages/CarSystemUI/src/com/android/systemui/car/navigationbar/ButtonSelectionStateListener.java
@@ -19,16 +19,16 @@
 import android.app.ActivityTaskManager;
 import android.util.Log;
 
+import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.shared.system.TaskStackChangeListener;
 
 import javax.inject.Inject;
-import javax.inject.Singleton;
 
 /**
  * An implementation of TaskStackChangeListener, that listens for changes in the system
  * task stack and notifies the navigation bar.
  */
-@Singleton
+@SysUISingleton
 class ButtonSelectionStateListener extends TaskStackChangeListener {
     private static final String TAG = ButtonSelectionStateListener.class.getSimpleName();
 
diff --git a/packages/CarSystemUI/src/com/android/systemui/car/navigationbar/CarNavigationBarController.java b/packages/CarSystemUI/src/com/android/systemui/car/navigationbar/CarNavigationBarController.java
index fe26040..51a8838 100644
--- a/packages/CarSystemUI/src/com/android/systemui/car/navigationbar/CarNavigationBarController.java
+++ b/packages/CarSystemUI/src/com/android/systemui/car/navigationbar/CarNavigationBarController.java
@@ -23,14 +23,14 @@
 import androidx.annotation.Nullable;
 
 import com.android.systemui.car.hvac.HvacController;
+import com.android.systemui.dagger.SysUISingleton;
 
 import javax.inject.Inject;
-import javax.inject.Singleton;
 
 import dagger.Lazy;
 
 /** A single class which controls the navigation bar views. */
-@Singleton
+@SysUISingleton
 public class CarNavigationBarController {
 
     private final Context mContext;
diff --git a/packages/CarSystemUI/src/com/android/systemui/car/navigationbar/NavigationBarViewFactory.java b/packages/CarSystemUI/src/com/android/systemui/car/navigationbar/NavigationBarViewFactory.java
index adf8d4d..a473bb7 100644
--- a/packages/CarSystemUI/src/com/android/systemui/car/navigationbar/NavigationBarViewFactory.java
+++ b/packages/CarSystemUI/src/com/android/systemui/car/navigationbar/NavigationBarViewFactory.java
@@ -26,12 +26,12 @@
 
 import com.android.car.ui.FocusParkingView;
 import com.android.systemui.R;
+import com.android.systemui.dagger.SysUISingleton;
 
 import javax.inject.Inject;
-import javax.inject.Singleton;
 
 /** A factory that creates and caches views for navigation bars. */
-@Singleton
+@SysUISingleton
 public class NavigationBarViewFactory {
 
     private static final String TAG = NavigationBarViewFactory.class.getSimpleName();
diff --git a/packages/CarSystemUI/src/com/android/systemui/car/navigationbar/SystemBarConfigs.java b/packages/CarSystemUI/src/com/android/systemui/car/navigationbar/SystemBarConfigs.java
index 3527bf9..143c444f 100644
--- a/packages/CarSystemUI/src/com/android/systemui/car/navigationbar/SystemBarConfigs.java
+++ b/packages/CarSystemUI/src/com/android/systemui/car/navigationbar/SystemBarConfigs.java
@@ -29,6 +29,7 @@
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.systemui.R;
+import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dagger.qualifiers.Main;
 
 import java.lang.annotation.ElementType;
@@ -40,13 +41,12 @@
 import java.util.Set;
 
 import javax.inject.Inject;
-import javax.inject.Singleton;
 
 /**
  * Reads configs for system bars for each side (TOP, BOTTOM, LEFT, and RIGHT) and returns the
  * corresponding {@link android.view.WindowManager.LayoutParams} per the configuration.
  */
-@Singleton
+@SysUISingleton
 public class SystemBarConfigs {
 
     private static final String TAG = SystemBarConfigs.class.getSimpleName();
diff --git a/packages/CarSystemUI/src/com/android/systemui/car/notification/BottomNotificationPanelViewMediator.java b/packages/CarSystemUI/src/com/android/systemui/car/notification/BottomNotificationPanelViewMediator.java
index 7d353f5a..8468bef 100644
--- a/packages/CarSystemUI/src/com/android/systemui/car/notification/BottomNotificationPanelViewMediator.java
+++ b/packages/CarSystemUI/src/com/android/systemui/car/notification/BottomNotificationPanelViewMediator.java
@@ -20,16 +20,16 @@
 import com.android.systemui.car.CarDeviceProvisionedController;
 import com.android.systemui.car.navigationbar.CarNavigationBarController;
 import com.android.systemui.car.window.OverlayPanelViewController;
+import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.statusbar.policy.ConfigurationController;
 
 import javax.inject.Inject;
-import javax.inject.Singleton;
 
 /**
  * Implementation of NotificationPanelViewMediator that sets the notification panel to be opened
  * from the top navigation bar.
  */
-@Singleton
+@SysUISingleton
 public class BottomNotificationPanelViewMediator extends NotificationPanelViewMediator {
 
     @Inject
diff --git a/packages/CarSystemUI/src/com/android/systemui/car/notification/CarHeadsUpNotificationSystemContainer.java b/packages/CarSystemUI/src/com/android/systemui/car/notification/CarHeadsUpNotificationSystemContainer.java
index d4f72071..3b22a30 100644
--- a/packages/CarSystemUI/src/com/android/systemui/car/notification/CarHeadsUpNotificationSystemContainer.java
+++ b/packages/CarSystemUI/src/com/android/systemui/car/notification/CarHeadsUpNotificationSystemContainer.java
@@ -29,15 +29,15 @@
 import com.android.car.notification.headsup.CarHeadsUpNotificationContainer;
 import com.android.systemui.car.CarDeviceProvisionedController;
 import com.android.systemui.car.window.OverlayViewGlobalStateController;
+import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dagger.qualifiers.Main;
 
 import javax.inject.Inject;
-import javax.inject.Singleton;
 
 /**
  * A controller for SysUI's HUN display.
  */
-@Singleton
+@SysUISingleton
 public class CarHeadsUpNotificationSystemContainer implements CarHeadsUpNotificationContainer {
     private final CarDeviceProvisionedController mCarDeviceProvisionedController;
     private final OverlayViewGlobalStateController mOverlayViewGlobalStateController;
diff --git a/packages/CarSystemUI/src/com/android/systemui/car/notification/CarNotificationModule.java b/packages/CarSystemUI/src/com/android/systemui/car/notification/CarNotificationModule.java
index b7bc631..8a3bcfc 100644
--- a/packages/CarSystemUI/src/com/android/systemui/car/notification/CarNotificationModule.java
+++ b/packages/CarSystemUI/src/com/android/systemui/car/notification/CarNotificationModule.java
@@ -25,8 +25,7 @@
 import com.android.car.notification.NotificationDataManager;
 import com.android.car.notification.headsup.CarHeadsUpNotificationContainer;
 import com.android.internal.statusbar.IStatusBarService;
-
-import javax.inject.Singleton;
+import com.android.systemui.dagger.SysUISingleton;
 
 import dagger.Binds;
 import dagger.Module;
@@ -38,26 +37,26 @@
 @Module
 public abstract class CarNotificationModule {
     @Provides
-    @Singleton
+    @SysUISingleton
     static NotificationClickHandlerFactory provideNotificationClickHandlerFactory(
             IStatusBarService barService) {
         return new NotificationClickHandlerFactory(barService);
     }
 
     @Provides
-    @Singleton
+    @SysUISingleton
     static NotificationDataManager provideNotificationDataManager() {
         return new NotificationDataManager();
     }
 
     @Provides
-    @Singleton
+    @SysUISingleton
     static CarUxRestrictionManagerWrapper provideCarUxRestrictionManagerWrapper() {
         return new CarUxRestrictionManagerWrapper();
     }
 
     @Provides
-    @Singleton
+    @SysUISingleton
     static CarNotificationListener provideCarNotificationListener(Context context,
             CarUxRestrictionManagerWrapper carUxRestrictionManagerWrapper,
             CarHeadsUpNotificationManager carHeadsUpNotificationManager,
@@ -69,7 +68,7 @@
     }
 
     @Provides
-    @Singleton
+    @SysUISingleton
     static CarHeadsUpNotificationManager provideCarHeadsUpNotificationManager(Context context,
             NotificationClickHandlerFactory notificationClickHandlerFactory,
             NotificationDataManager notificationDataManager,
diff --git a/packages/CarSystemUI/src/com/android/systemui/car/notification/NotificationPanelViewController.java b/packages/CarSystemUI/src/com/android/systemui/car/notification/NotificationPanelViewController.java
index 8d58436..3b22fdb 100644
--- a/packages/CarSystemUI/src/com/android/systemui/car/notification/NotificationPanelViewController.java
+++ b/packages/CarSystemUI/src/com/android/systemui/car/notification/NotificationPanelViewController.java
@@ -49,6 +49,7 @@
 import com.android.systemui.car.CarServiceProvider;
 import com.android.systemui.car.window.OverlayPanelViewController;
 import com.android.systemui.car.window.OverlayViewGlobalStateController;
+import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.dagger.qualifiers.UiBackground;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
@@ -59,10 +60,9 @@
 import java.util.concurrent.Executor;
 
 import javax.inject.Inject;
-import javax.inject.Singleton;
 
 /** View controller for the notification panel. */
-@Singleton
+@SysUISingleton
 public class NotificationPanelViewController extends OverlayPanelViewController
         implements CommandQueue.Callbacks {
 
diff --git a/packages/CarSystemUI/src/com/android/systemui/car/notification/NotificationPanelViewMediator.java b/packages/CarSystemUI/src/com/android/systemui/car/notification/NotificationPanelViewMediator.java
index 0c185ba..17b6b74 100644
--- a/packages/CarSystemUI/src/com/android/systemui/car/notification/NotificationPanelViewMediator.java
+++ b/packages/CarSystemUI/src/com/android/systemui/car/notification/NotificationPanelViewMediator.java
@@ -31,16 +31,16 @@
 import com.android.systemui.car.CarDeviceProvisionedController;
 import com.android.systemui.car.navigationbar.CarNavigationBarController;
 import com.android.systemui.car.window.OverlayViewMediator;
+import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.statusbar.policy.ConfigurationController;
 
 import javax.inject.Inject;
-import javax.inject.Singleton;
 
 /**
  * The view mediator which attaches the view controller to other elements of the system ui. Disables
  * drag open behavior of the notification panel from any navigation bar.
  */
-@Singleton
+@SysUISingleton
 public class NotificationPanelViewMediator implements OverlayViewMediator,
         ConfigurationController.ConfigurationListener {
 
diff --git a/packages/CarSystemUI/src/com/android/systemui/car/notification/NotificationShadeWindowControllerImpl.java b/packages/CarSystemUI/src/com/android/systemui/car/notification/NotificationShadeWindowControllerImpl.java
new file mode 100644
index 0000000..1a1da89
--- /dev/null
+++ b/packages/CarSystemUI/src/com/android/systemui/car/notification/NotificationShadeWindowControllerImpl.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.car.notification;
+
+import com.android.systemui.car.window.OverlayViewGlobalStateController;
+import com.android.systemui.dagger.SysUISingleton;
+import com.android.systemui.statusbar.NotificationShadeWindowController;
+
+import javax.inject.Inject;
+
+/** The automotive version of the notification shade window controller. */
+@SysUISingleton
+public class NotificationShadeWindowControllerImpl implements
+        NotificationShadeWindowController {
+
+    private final OverlayViewGlobalStateController mController;
+
+    @Inject
+    public NotificationShadeWindowControllerImpl(OverlayViewGlobalStateController controller) {
+        mController = controller;
+    }
+
+    @Override
+    public void setForceDozeBrightness(boolean forceDozeBrightness) {
+        // No-op since dozing is not supported in Automotive devices.
+    }
+
+    @Override
+    public void setNotificationShadeFocusable(boolean focusable) {
+        mController.setWindowFocusable(focusable);
+    }
+}
diff --git a/packages/CarSystemUI/src/com/android/systemui/car/notification/NotificationVisibilityLogger.java b/packages/CarSystemUI/src/com/android/systemui/car/notification/NotificationVisibilityLogger.java
index 44c8197..b263f72 100644
--- a/packages/CarSystemUI/src/com/android/systemui/car/notification/NotificationVisibilityLogger.java
+++ b/packages/CarSystemUI/src/com/android/systemui/car/notification/NotificationVisibilityLogger.java
@@ -24,19 +24,19 @@
 import com.android.car.notification.NotificationDataManager;
 import com.android.internal.statusbar.IStatusBarService;
 import com.android.internal.statusbar.NotificationVisibility;
+import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dagger.qualifiers.UiBackground;
 
 import java.util.Set;
 import java.util.concurrent.Executor;
 
 import javax.inject.Inject;
-import javax.inject.Singleton;
 
 /**
  * Handles notification logging, in particular, logging which notifications are visible and which
  * are not.
  */
-@Singleton
+@SysUISingleton
 public class NotificationVisibilityLogger {
 
     private static final String TAG = "NotificationVisibilityLogger";
diff --git a/packages/CarSystemUI/src/com/android/systemui/car/notification/PowerManagerHelper.java b/packages/CarSystemUI/src/com/android/systemui/car/notification/PowerManagerHelper.java
index 92a11d8..da43c54 100644
--- a/packages/CarSystemUI/src/com/android/systemui/car/notification/PowerManagerHelper.java
+++ b/packages/CarSystemUI/src/com/android/systemui/car/notification/PowerManagerHelper.java
@@ -23,14 +23,14 @@
 import android.util.Log;
 
 import com.android.systemui.car.CarServiceProvider;
+import com.android.systemui.dagger.SysUISingleton;
 
 import javax.inject.Inject;
-import javax.inject.Singleton;
 
 /**
  * Helper class for connecting to the {@link CarPowerManager} and listening for power state changes.
  */
-@Singleton
+@SysUISingleton
 public class PowerManagerHelper {
     public static final String TAG = "PowerManagerHelper";
 
diff --git a/packages/CarSystemUI/src/com/android/systemui/car/notification/TopNotificationPanelViewMediator.java b/packages/CarSystemUI/src/com/android/systemui/car/notification/TopNotificationPanelViewMediator.java
index 89c9931..9bc5b74c 100644
--- a/packages/CarSystemUI/src/com/android/systemui/car/notification/TopNotificationPanelViewMediator.java
+++ b/packages/CarSystemUI/src/com/android/systemui/car/notification/TopNotificationPanelViewMediator.java
@@ -20,16 +20,16 @@
 import com.android.systemui.car.CarDeviceProvisionedController;
 import com.android.systemui.car.navigationbar.CarNavigationBarController;
 import com.android.systemui.car.window.OverlayPanelViewController;
+import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.statusbar.policy.ConfigurationController;
 
 import javax.inject.Inject;
-import javax.inject.Singleton;
 
 /**
  * Implementation of NotificationPanelViewMediator that sets the notification panel to be opened
  * from the top navigation bar.
  */
-@Singleton
+@SysUISingleton
 public class TopNotificationPanelViewMediator extends NotificationPanelViewMediator {
 
     @Inject
diff --git a/packages/CarSystemUI/src/com/android/systemui/car/sideloaded/SideLoadedAppController.java b/packages/CarSystemUI/src/com/android/systemui/car/sideloaded/SideLoadedAppController.java
index 6b41b35..b8d6964 100644
--- a/packages/CarSystemUI/src/com/android/systemui/car/sideloaded/SideLoadedAppController.java
+++ b/packages/CarSystemUI/src/com/android/systemui/car/sideloaded/SideLoadedAppController.java
@@ -22,14 +22,14 @@
 import android.util.Log;
 
 import com.android.systemui.SystemUI;
+import com.android.systemui.dagger.SysUISingleton;
 
 import javax.inject.Inject;
-import javax.inject.Singleton;
 
 /**
  * Controller responsible for detecting unsafe apps.
  */
-@Singleton
+@SysUISingleton
 public class SideLoadedAppController extends SystemUI {
     private static final String TAG = SideLoadedAppController.class.getSimpleName();
 
diff --git a/packages/CarSystemUI/src/com/android/systemui/car/sideloaded/SideLoadedAppDetector.java b/packages/CarSystemUI/src/com/android/systemui/car/sideloaded/SideLoadedAppDetector.java
index 5dcb9de..eb32edb 100644
--- a/packages/CarSystemUI/src/com/android/systemui/car/sideloaded/SideLoadedAppDetector.java
+++ b/packages/CarSystemUI/src/com/android/systemui/car/sideloaded/SideLoadedAppDetector.java
@@ -29,19 +29,19 @@
 
 import com.android.systemui.R;
 import com.android.systemui.car.CarDeviceProvisionedController;
+import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dagger.qualifiers.Main;
 
 import java.util.Arrays;
 import java.util.List;
 
 import javax.inject.Inject;
-import javax.inject.Singleton;
 
 /**
  * A class that detects unsafe apps.
- * An app is considered safe if is a system app or installed through whitelisted sources.
+ * An app is considered safe if is a system app or installed through allowed sources.
  */
-@Singleton
+@SysUISingleton
 public class SideLoadedAppDetector {
     private static final String TAG = SideLoadedAppDetector.class.getSimpleName();
 
diff --git a/packages/CarSystemUI/src/com/android/systemui/car/sideloaded/SideLoadedAppStateController.java b/packages/CarSystemUI/src/com/android/systemui/car/sideloaded/SideLoadedAppStateController.java
index 1d66dda..5b4faa1 100644
--- a/packages/CarSystemUI/src/com/android/systemui/car/sideloaded/SideLoadedAppStateController.java
+++ b/packages/CarSystemUI/src/com/android/systemui/car/sideloaded/SideLoadedAppStateController.java
@@ -19,13 +19,14 @@
 import android.util.Log;
 import android.view.Display;
 
+import com.android.systemui.dagger.SysUISingleton;
+
 import javax.inject.Inject;
-import javax.inject.Singleton;
 
 /**
  * Manager responsible for displaying proper UI when an unsafe app is detected.
  */
-@Singleton
+@SysUISingleton
 public class SideLoadedAppStateController {
     private static final String TAG = SideLoadedAppStateController.class.getSimpleName();
 
diff --git a/packages/CarSystemUI/src/com/android/systemui/car/statusbar/DozeServiceHost.java b/packages/CarSystemUI/src/com/android/systemui/car/statusbar/DozeServiceHost.java
index d23660c..3fb3cd8 100644
--- a/packages/CarSystemUI/src/com/android/systemui/car/statusbar/DozeServiceHost.java
+++ b/packages/CarSystemUI/src/com/android/systemui/car/statusbar/DozeServiceHost.java
@@ -16,13 +16,13 @@
 
 package com.android.systemui.car.statusbar;
 
+import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.doze.DozeHost;
 
 import javax.inject.Inject;
-import javax.inject.Singleton;
 
 /** No-op implementation of {@link DozeHost} for use by car sysui, which does not support dozing. */
-@Singleton
+@SysUISingleton
 public class DozeServiceHost implements DozeHost {
 
     @Inject
diff --git a/packages/CarSystemUI/src/com/android/systemui/car/statusbar/DummyNotificationShadeWindowController.java b/packages/CarSystemUI/src/com/android/systemui/car/statusbar/DummyNotificationShadeWindowController.java
deleted file mode 100644
index 13f2b7e..0000000
--- a/packages/CarSystemUI/src/com/android/systemui/car/statusbar/DummyNotificationShadeWindowController.java
+++ /dev/null
@@ -1,74 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.car.statusbar;
-
-import android.app.IActivityManager;
-import android.content.Context;
-import android.view.WindowManager;
-
-import com.android.systemui.car.window.SystemUIOverlayWindowController;
-import com.android.systemui.colorextraction.SysuiColorExtractor;
-import com.android.systemui.dump.DumpManager;
-import com.android.systemui.keyguard.KeyguardViewMediator;
-import com.android.systemui.plugins.statusbar.StatusBarStateController;
-import com.android.systemui.statusbar.phone.BiometricUnlockController;
-import com.android.systemui.statusbar.phone.DozeParameters;
-import com.android.systemui.statusbar.phone.KeyguardBypassController;
-import com.android.systemui.statusbar.phone.NotificationShadeWindowController;
-import com.android.systemui.statusbar.policy.ConfigurationController;
-
-import javax.inject.Inject;
-import javax.inject.Singleton;
-
-/**
- * A dummy implementation of {@link NotificationShadeWindowController}.
- *
- * TODO(b/155711562): This should be replaced with a longer term solution (i.e. separating
- * {@link BiometricUnlockController} from the views it depends on).
- */
-@Singleton
-public class DummyNotificationShadeWindowController extends NotificationShadeWindowController {
-    private final SystemUIOverlayWindowController mOverlayWindowController;
-
-    @Inject
-    public DummyNotificationShadeWindowController(Context context,
-            WindowManager windowManager, IActivityManager activityManager,
-            DozeParameters dozeParameters,
-            StatusBarStateController statusBarStateController,
-            ConfigurationController configurationController,
-            KeyguardViewMediator keyguardViewMediator,
-            KeyguardBypassController keyguardBypassController,
-            SysuiColorExtractor colorExtractor,
-            DumpManager dumpManager,
-            SystemUIOverlayWindowController overlayWindowController) {
-        super(context, windowManager, activityManager, dozeParameters, statusBarStateController,
-                configurationController, keyguardViewMediator, keyguardBypassController,
-                colorExtractor, dumpManager);
-        mOverlayWindowController = overlayWindowController;
-    }
-
-    @Override
-    public void setForceDozeBrightness(boolean forceDozeBrightness) {
-        // No op.
-    }
-
-    @Override
-    public void setNotificationShadeFocusable(boolean focusable) {
-        // The overlay window is the car sysui equivalent of the notification shade.
-        mOverlayWindowController.setWindowFocusable(focusable);
-    }
-}
diff --git a/packages/CarSystemUI/src/com/android/systemui/car/userswitcher/FullScreenUserSwitcherViewController.java b/packages/CarSystemUI/src/com/android/systemui/car/userswitcher/FullScreenUserSwitcherViewController.java
index 1a8f19e..66bfb2d 100644
--- a/packages/CarSystemUI/src/com/android/systemui/car/userswitcher/FullScreenUserSwitcherViewController.java
+++ b/packages/CarSystemUI/src/com/android/systemui/car/userswitcher/FullScreenUserSwitcherViewController.java
@@ -30,15 +30,15 @@
 import com.android.systemui.car.CarServiceProvider;
 import com.android.systemui.car.window.OverlayViewController;
 import com.android.systemui.car.window.OverlayViewGlobalStateController;
+import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dagger.qualifiers.Main;
 
 import javax.inject.Inject;
-import javax.inject.Singleton;
 
 /**
  * Controller for {@link R.layout#car_fullscreen_user_switcher}.
  */
-@Singleton
+@SysUISingleton
 public class FullScreenUserSwitcherViewController extends OverlayViewController {
     private final Context mContext;
     private final Resources mResources;
diff --git a/packages/CarSystemUI/src/com/android/systemui/car/userswitcher/FullscreenUserSwitcherViewMediator.java b/packages/CarSystemUI/src/com/android/systemui/car/userswitcher/FullscreenUserSwitcherViewMediator.java
index 8b399f8..165fe63 100644
--- a/packages/CarSystemUI/src/com/android/systemui/car/userswitcher/FullscreenUserSwitcherViewMediator.java
+++ b/packages/CarSystemUI/src/com/android/systemui/car/userswitcher/FullscreenUserSwitcherViewMediator.java
@@ -18,16 +18,16 @@
 
 import com.android.systemui.car.keyguard.CarKeyguardViewController;
 import com.android.systemui.car.window.OverlayViewMediator;
+import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.statusbar.StatusBarState;
 
 import javax.inject.Inject;
-import javax.inject.Singleton;
 
 /**
  * Manages the fullscreen user switcher and it's interactions with the keyguard.
  */
-@Singleton
+@SysUISingleton
 public class FullscreenUserSwitcherViewMediator implements OverlayViewMediator {
     private static final String TAG = FullscreenUserSwitcherViewMediator.class.getSimpleName();
 
diff --git a/packages/CarSystemUI/src/com/android/systemui/car/userswitcher/UserSwitchTransitionViewController.java b/packages/CarSystemUI/src/com/android/systemui/car/userswitcher/UserSwitchTransitionViewController.java
index 0d77c13..6178cbd 100644
--- a/packages/CarSystemUI/src/com/android/systemui/car/userswitcher/UserSwitchTransitionViewController.java
+++ b/packages/CarSystemUI/src/com/android/systemui/car/userswitcher/UserSwitchTransitionViewController.java
@@ -38,15 +38,15 @@
 import com.android.systemui.R;
 import com.android.systemui.car.window.OverlayViewController;
 import com.android.systemui.car.window.OverlayViewGlobalStateController;
+import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dagger.qualifiers.Main;
 
 import javax.inject.Inject;
-import javax.inject.Singleton;
 
 /**
  * Handles showing and hiding UserSwitchTransitionView that is mounted to SystemUiOverlayWindow.
  */
-@Singleton
+@SysUISingleton
 public class UserSwitchTransitionViewController extends OverlayViewController {
     private static final String TAG = "UserSwitchTransition";
     private static final String ENABLE_DEVELOPER_MESSAGE_TRUE = "true";
diff --git a/packages/CarSystemUI/src/com/android/systemui/car/volume/CarVolumeDialogComponent.java b/packages/CarSystemUI/src/com/android/systemui/car/volume/CarVolumeDialogComponent.java
index 98d24b1..4cdbfa3 100644
--- a/packages/CarSystemUI/src/com/android/systemui/car/volume/CarVolumeDialogComponent.java
+++ b/packages/CarSystemUI/src/com/android/systemui/car/volume/CarVolumeDialogComponent.java
@@ -19,18 +19,19 @@
 import android.content.Context;
 
 import com.android.systemui.car.CarServiceProvider;
+import com.android.systemui.dagger.SysUISingleton;
+import com.android.systemui.demomode.DemoModeController;
 import com.android.systemui.keyguard.KeyguardViewMediator;
 import com.android.systemui.plugins.VolumeDialog;
 import com.android.systemui.volume.VolumeDialogComponent;
 import com.android.systemui.volume.VolumeDialogControllerImpl;
 
 import javax.inject.Inject;
-import javax.inject.Singleton;
 
 /**
  * Allows for adding car specific dialog when the volume dialog is created.
  */
-@Singleton
+@SysUISingleton
 public class CarVolumeDialogComponent extends VolumeDialogComponent {
 
     private CarVolumeDialogImpl mCarVolumeDialog;
@@ -38,8 +39,9 @@
     @Inject
     public CarVolumeDialogComponent(Context context, KeyguardViewMediator keyguardViewMediator,
             VolumeDialogControllerImpl volumeDialogController,
+            DemoModeController demoModeController,
             CarServiceProvider carServiceProvider) {
-        super(context, keyguardViewMediator, volumeDialogController);
+        super(context, keyguardViewMediator, volumeDialogController, demoModeController);
         mCarVolumeDialog.setCarServiceProvider(carServiceProvider);
     }
 
diff --git a/packages/CarSystemUI/src/com/android/systemui/car/volume/VolumeUI.java b/packages/CarSystemUI/src/com/android/systemui/car/volume/VolumeUI.java
index 03b61e0..b0321ab 100644
--- a/packages/CarSystemUI/src/com/android/systemui/car/volume/VolumeUI.java
+++ b/packages/CarSystemUI/src/com/android/systemui/car/volume/VolumeUI.java
@@ -27,6 +27,7 @@
 import com.android.systemui.R;
 import com.android.systemui.SystemUI;
 import com.android.systemui.car.CarServiceProvider;
+import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.volume.VolumeDialogComponent;
 
@@ -34,12 +35,11 @@
 import java.io.PrintWriter;
 
 import javax.inject.Inject;
-import javax.inject.Singleton;
 
 import dagger.Lazy;
 
 /** The entry point for controlling the volume ui in cars. */
-@Singleton
+@SysUISingleton
 public class VolumeUI extends SystemUI {
 
     private static final String TAG = "VolumeUI";
diff --git a/packages/CarSystemUI/src/com/android/systemui/car/window/OverlayViewGlobalStateController.java b/packages/CarSystemUI/src/com/android/systemui/car/window/OverlayViewGlobalStateController.java
index 2494242..22b6455 100644
--- a/packages/CarSystemUI/src/com/android/systemui/car/window/OverlayViewGlobalStateController.java
+++ b/packages/CarSystemUI/src/com/android/systemui/car/window/OverlayViewGlobalStateController.java
@@ -27,6 +27,8 @@
 
 import androidx.annotation.VisibleForTesting;
 
+import com.android.systemui.dagger.SysUISingleton;
+
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.Map;
@@ -35,7 +37,6 @@
 import java.util.TreeMap;
 
 import javax.inject.Inject;
-import javax.inject.Singleton;
 
 /**
  * This controller is responsible for the following:
@@ -46,7 +47,7 @@
  * global state of SystemUIOverlayWindow.
  * </ul>
  */
-@Singleton
+@SysUISingleton
 public class OverlayViewGlobalStateController {
     private static final boolean DEBUG = false;
     private static final String TAG = OverlayViewGlobalStateController.class.getSimpleName();
diff --git a/packages/CarSystemUI/src/com/android/systemui/car/window/SystemUIOverlayWindowController.java b/packages/CarSystemUI/src/com/android/systemui/car/window/SystemUIOverlayWindowController.java
index 029bd37..81b1bf9 100644
--- a/packages/CarSystemUI/src/com/android/systemui/car/window/SystemUIOverlayWindowController.java
+++ b/packages/CarSystemUI/src/com/android/systemui/car/window/SystemUIOverlayWindowController.java
@@ -29,17 +29,17 @@
 import android.view.WindowManager;
 
 import com.android.systemui.R;
+import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.statusbar.policy.ConfigurationController;
 
 import javax.inject.Inject;
-import javax.inject.Singleton;
 
 /**
  * Controls the expansion state of the primary window which will contain all of the fullscreen sysui
  * behavior. This window still has a collapsed state in order to watch for swipe events to expand
  * this window for the notification panel.
  */
-@Singleton
+@SysUISingleton
 public class SystemUIOverlayWindowController implements
         ConfigurationController.ConfigurationListener {
 
diff --git a/packages/CarSystemUI/src/com/android/systemui/car/window/SystemUIOverlayWindowManager.java b/packages/CarSystemUI/src/com/android/systemui/car/window/SystemUIOverlayWindowManager.java
index 8cca0ed..6395ebf 100644
--- a/packages/CarSystemUI/src/com/android/systemui/car/window/SystemUIOverlayWindowManager.java
+++ b/packages/CarSystemUI/src/com/android/systemui/car/window/SystemUIOverlayWindowManager.java
@@ -21,6 +21,7 @@
 
 import com.android.systemui.R;
 import com.android.systemui.SystemUI;
+import com.android.systemui.dagger.SysUISingleton;
 
 import java.lang.reflect.Constructor;
 import java.lang.reflect.InvocationTargetException;
@@ -28,13 +29,12 @@
 
 import javax.inject.Inject;
 import javax.inject.Provider;
-import javax.inject.Singleton;
 
 /**
  * Registers {@link OverlayViewMediator}(s) and synchronizes their calls to hide/show {@link
  * OverlayViewController}(s) to allow for the correct visibility of system bars.
  */
-@Singleton
+@SysUISingleton
 public class SystemUIOverlayWindowManager extends SystemUI {
     private static final String TAG = "SystemUIOverlayWM";
     private final Map<Class<?>, Provider<OverlayViewMediator>>
diff --git a/packages/CarSystemUI/src/com/android/systemui/wm/BarControlPolicy.java b/packages/CarSystemUI/src/com/android/systemui/wm/BarControlPolicy.java
index 5f9665f..0452b83 100644
--- a/packages/CarSystemUI/src/com/android/systemui/wm/BarControlPolicy.java
+++ b/packages/CarSystemUI/src/com/android/systemui/wm/BarControlPolicy.java
@@ -172,32 +172,32 @@
     private static class Filter {
         private static final String ALL = "*";
 
-        private final ArraySet<String> mWhitelist;
-        private final ArraySet<String> mBlacklist;
+        private final ArraySet<String> mToInclude;
+        private final ArraySet<String> mToExclude;
 
-        private Filter(ArraySet<String> whitelist, ArraySet<String> blacklist) {
-            mWhitelist = whitelist;
-            mBlacklist = blacklist;
+        private Filter(ArraySet<String> toInclude, ArraySet<String> toExclude) {
+            mToInclude = toInclude;
+            mToExclude = toExclude;
         }
 
         boolean matches(String packageName) {
             if (packageName == null) return false;
-            if (onBlacklist(packageName)) return false;
-            return onWhitelist(packageName);
+            if (toExclude(packageName)) return false;
+            return toInclude(packageName);
         }
 
-        private boolean onBlacklist(String packageName) {
-            return mBlacklist.contains(packageName) || mBlacklist.contains(ALL);
+        private boolean toExclude(String packageName) {
+            return mToExclude.contains(packageName) || mToExclude.contains(ALL);
         }
 
-        private boolean onWhitelist(String packageName) {
-            return mWhitelist.contains(ALL) || mWhitelist.contains(packageName);
+        private boolean toInclude(String packageName) {
+            return mToInclude.contains(ALL) || mToInclude.contains(packageName);
         }
 
         void dump(PrintWriter pw) {
             pw.print("Filter[");
-            dump("whitelist", mWhitelist, pw); pw.print(',');
-            dump("blacklist", mBlacklist, pw); pw.print(']');
+            dump("toInclude", mToInclude, pw); pw.print(',');
+            dump("toExclude", mToExclude, pw); pw.print(']');
         }
 
         private void dump(String name, ArraySet<String> set, PrintWriter pw) {
@@ -221,18 +221,18 @@
         // e.g. "com.package1", or "com.android.systemui, com.android.keyguard" or "*"
         static Filter parse(String value) {
             if (value == null) return null;
-            ArraySet<String> whitelist = new ArraySet<String>();
-            ArraySet<String> blacklist = new ArraySet<String>();
+            ArraySet<String> toInclude = new ArraySet<String>();
+            ArraySet<String> toExclude = new ArraySet<String>();
             for (String token : value.split(",")) {
                 token = token.trim();
                 if (token.startsWith("-") && token.length() > 1) {
                     token = token.substring(1);
-                    blacklist.add(token);
+                    toExclude.add(token);
                 } else {
-                    whitelist.add(token);
+                    toInclude.add(token);
                 }
             }
-            return new Filter(whitelist, blacklist);
+            return new Filter(toInclude, toExclude);
         }
     }
 
diff --git a/packages/CarSystemUI/src/com/android/systemui/wm/DisplaySystemBarsController.java b/packages/CarSystemUI/src/com/android/systemui/wm/DisplaySystemBarsController.java
index e493c97..c7354ed 100644
--- a/packages/CarSystemUI/src/com/android/systemui/wm/DisplaySystemBarsController.java
+++ b/packages/CarSystemUI/src/com/android/systemui/wm/DisplaySystemBarsController.java
@@ -49,7 +49,7 @@
     private final Context mContext;
     private SparseArray<PerDisplay> mPerDisplaySparseArray;
 
-    private DisplaySystemBarsController(
+    public DisplaySystemBarsController(
             Context context,
             IWindowManager wmService,
             DisplayController displayController,
@@ -167,33 +167,4 @@
             }
         }
     }
-
-    /** Builds {@link DisplaySystemBarsController} instance. */
-    public static class Builder {
-        private Context mContext;
-        private IWindowManager mWmService;
-        private DisplayController mDisplayController;
-        private Handler mHandler;
-        private TransactionPool mTransactionPool;
-
-        public Builder(Context context, IWindowManager wmService,
-                DisplayController displayController, Handler handler,
-                TransactionPool transactionPool) {
-            mContext = context;
-            mWmService = wmService;
-            mDisplayController = displayController;
-            mHandler = handler;
-            mTransactionPool = transactionPool;
-        }
-
-        /** Builds and initializes {@link DisplaySystemBarsController} instance. */
-        public DisplaySystemBarsController build() {
-            DisplaySystemBarsController displaySystemBarsController =
-                    new DisplaySystemBarsController(
-                            mContext, mWmService, mDisplayController, mHandler, mTransactionPool);
-            // Separates startMonitorDisplays from constructor to prevent circular init issue.
-            displaySystemBarsController.startMonitorDisplays();
-            return displaySystemBarsController;
-        }
-    }
 }
diff --git a/packages/CarSystemUI/src/com/android/systemui/wmshell/CarWMShellModule.java b/packages/CarSystemUI/src/com/android/systemui/wmshell/CarWMShellModule.java
new file mode 100644
index 0000000..fd6685f
--- /dev/null
+++ b/packages/CarSystemUI/src/com/android/systemui/wmshell/CarWMShellModule.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.wmshell;
+
+import android.content.Context;
+import android.os.Handler;
+import android.view.IWindowManager;
+
+import com.android.systemui.dagger.SysUISingleton;
+import com.android.systemui.dagger.qualifiers.Main;
+import com.android.systemui.pip.phone.PipMenuActivity;
+import com.android.systemui.pip.phone.dagger.PipMenuActivityClass;
+import com.android.systemui.wm.DisplaySystemBarsController;
+import com.android.wm.shell.common.DisplayController;
+import com.android.wm.shell.common.DisplayImeController;
+import com.android.wm.shell.common.TransactionPool;
+
+import dagger.Module;
+import dagger.Provides;
+
+/** Provides dependencies from {@link com.android.wm.shell} for CarSystemUI. */
+@Module(includes = WMShellBaseModule.class)
+public class CarWMShellModule {
+    @SysUISingleton
+    @Provides
+    DisplayImeController provideDisplayImeController(Context context,
+            IWindowManager wmService, DisplayController displayController,
+            @Main Handler mainHandler, TransactionPool transactionPool) {
+        return new DisplaySystemBarsController(context, wmService, displayController,
+                mainHandler, transactionPool);
+    }
+
+    /** TODO(b/150319024): PipMenuActivity will move to a Window */
+    @SysUISingleton
+    @PipMenuActivityClass
+    @Provides
+    Class<?> providePipMenuActivityClass() {
+        return PipMenuActivity.class;
+    }
+}
diff --git a/packages/CarSystemUI/tests/src/com/android/AAAPlusPlusVerifySysuiRequiredTestPropertiesTest.java b/packages/CarSystemUI/tests/src/com/android/AAAPlusPlusVerifySysuiRequiredTestPropertiesTest.java
index d769cac..86b86d4 100644
--- a/packages/CarSystemUI/tests/src/com/android/AAAPlusPlusVerifySysuiRequiredTestPropertiesTest.java
+++ b/packages/CarSystemUI/tests/src/com/android/AAAPlusPlusVerifySysuiRequiredTestPropertiesTest.java
@@ -63,7 +63,7 @@
 
     private static final String TAG = "AAA++VerifyTest";
 
-    private static final Class[] BASE_CLS_WHITELIST = {
+    private static final Class[] BASE_CLS_TO_INCLUDE = {
             SysuiTestCase.class,
             SysuiBaseFragmentTest.class,
     };
@@ -85,7 +85,7 @@
             if (!isTestClass(cls)) continue;
 
             boolean hasParent = false;
-            for (Class<?> parent : BASE_CLS_WHITELIST) {
+            for (Class<?> parent : BASE_CLS_TO_INCLUDE) {
                 if (parent.isAssignableFrom(cls)) {
                     hasParent = true;
                     break;
@@ -129,7 +129,7 @@
     }
 
     private String getClsStr() {
-        return TextUtils.join(",", Arrays.asList(BASE_CLS_WHITELIST)
+        return TextUtils.join(",", Arrays.asList(BASE_CLS_TO_INCLUDE)
                 .stream().map(cls -> cls.getSimpleName()).toArray());
     }
 
diff --git a/packages/CarSystemUI/tests/src/com/android/systemui/car/voicerecognition/ConnectedDeviceVoiceRecognitionNotifierTest.java b/packages/CarSystemUI/tests/src/com/android/systemui/car/voicerecognition/ConnectedDeviceVoiceRecognitionNotifierTest.java
index 31f1170..f77294e 100644
--- a/packages/CarSystemUI/tests/src/com/android/systemui/car/voicerecognition/ConnectedDeviceVoiceRecognitionNotifierTest.java
+++ b/packages/CarSystemUI/tests/src/com/android/systemui/car/voicerecognition/ConnectedDeviceVoiceRecognitionNotifierTest.java
@@ -19,6 +19,8 @@
 import static com.android.systemui.car.voicerecognition.ConnectedDeviceVoiceRecognitionNotifier.INVALID_VALUE;
 import static com.android.systemui.car.voicerecognition.ConnectedDeviceVoiceRecognitionNotifier.VOICE_RECOGNITION_STARTED;
 
+import static com.google.common.truth.Truth.assertThat;
+
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.spy;
@@ -40,11 +42,13 @@
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
 
 @CarSystemUiTest
 @RunWith(AndroidTestingRunner.class)
 @TestableLooper.RunWithLooper
 @SmallTest
+// TODO(b/162866441): Refactor to use the Executor pattern instead.
 public class ConnectedDeviceVoiceRecognitionNotifierTest extends SysuiTestCase {
 
     private static final String BLUETOOTH_PERM = android.Manifest.permission.BLUETOOTH;
@@ -52,13 +56,15 @@
 
     private ConnectedDeviceVoiceRecognitionNotifier mVoiceRecognitionNotifier;
     private TestableLooper mTestableLooper;
+    private Handler mHandler;
     private Handler mTestHandler;
     private BluetoothDevice mBluetoothDevice;
 
     @Before
     public void setUp() throws Exception {
         mTestableLooper = TestableLooper.get(this);
-        mTestHandler = spy(new Handler(mTestableLooper.getLooper()));
+        mHandler = new Handler(mTestableLooper.getLooper());
+        mTestHandler = spy(mHandler);
         mBluetoothDevice = BluetoothAdapter.getDefaultAdapter().getRemoteDevice(
                 BLUETOOTH_REMOTE_ADDRESS);
         mVoiceRecognitionNotifier = new ConnectedDeviceVoiceRecognitionNotifier(
@@ -74,8 +80,14 @@
 
         mContext.sendBroadcast(intent, BLUETOOTH_PERM);
         mTestableLooper.processAllMessages();
+        waitForIdleSync();
 
-        verify(mTestHandler).post(any());
+        mHandler.post(() -> {
+            ArgumentCaptor<Runnable> argumentCaptor = ArgumentCaptor.forClass(Runnable.class);
+            verify(mTestHandler).post(argumentCaptor.capture());
+            assertThat(argumentCaptor.getValue()).isNotNull();
+            assertThat(argumentCaptor.getValue()).isNotEqualTo(this);
+        });
     }
 
     @Test
@@ -86,8 +98,11 @@
 
         mContext.sendBroadcast(intent, BLUETOOTH_PERM);
         mTestableLooper.processAllMessages();
+        waitForIdleSync();
 
-        verify(mTestHandler, never()).post(any());
+        mHandler.post(() -> {
+            verify(mTestHandler, never()).post(any());
+        });
     }
 
     @Test
@@ -97,8 +112,11 @@
 
         mContext.sendBroadcast(intent, BLUETOOTH_PERM);
         mTestableLooper.processAllMessages();
+        waitForIdleSync();
 
-        verify(mTestHandler, never()).post(any());
+        mHandler.post(() -> {
+            verify(mTestHandler, never()).post(any());
+        });
     }
 
     @Test
@@ -108,7 +126,10 @@
 
         mContext.sendBroadcast(intent, BLUETOOTH_PERM);
         mTestableLooper.processAllMessages();
+        waitForIdleSync();
 
-        verify(mTestHandler, never()).post(any());
+        mHandler.post(() -> {
+            verify(mTestHandler, never()).post(any());
+        });
     }
 }
diff --git a/packages/CarSystemUI/tests/src/com/android/systemui/wm/DisplaySystemBarsControllerTest.java b/packages/CarSystemUI/tests/src/com/android/systemui/wm/DisplaySystemBarsControllerTest.java
index b65578d..391f75e 100644
--- a/packages/CarSystemUI/tests/src/com/android/systemui/wm/DisplaySystemBarsControllerTest.java
+++ b/packages/CarSystemUI/tests/src/com/android/systemui/wm/DisplaySystemBarsControllerTest.java
@@ -64,13 +64,13 @@
     public void setUp() {
         MockitoAnnotations.initMocks(this);
 
-        mController = new DisplaySystemBarsController.Builder(
+        mController = new DisplaySystemBarsController(
                 mContext,
                 mIWindowManager,
                 mDisplayController,
                 mHandler,
                 mTransactionPool
-        ).build();
+        );
     }
 
     @Test
diff --git a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/DeviceDiscoveryService.java b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/DeviceDiscoveryService.java
index bcaee36..f108e06 100644
--- a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/DeviceDiscoveryService.java
+++ b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/DeviceDiscoveryService.java
@@ -259,18 +259,19 @@
     private void onDeviceFound(@Nullable DeviceFilterPair device) {
         if (device == null) return;
 
-        if (mDevicesFound.contains(device)) {
-            return;
-        }
-
-        if (DEBUG) Log.i(LOG_TAG, "Found device " + device);
-
         Handler.getMain().sendMessage(obtainMessage(
                 DeviceDiscoveryService::onDeviceFoundMainThread, this, device));
     }
 
     @MainThread
     void onDeviceFoundMainThread(@NonNull DeviceFilterPair device) {
+        if (mDevicesFound.contains(device)) {
+            Log.i(LOG_TAG, "Skipping device " + device + " - already among found devices");
+            return;
+        }
+
+        Log.i(LOG_TAG, "Found device " + device);
+
         if (mDevicesFound.isEmpty()) {
             onReadyToShowUI();
         }
@@ -428,10 +429,10 @@
 
         @Override
         public String toString() {
-            return "DeviceFilterPair{" +
-                    "device=" + device +
-                    ", filter=" + filter +
-                    '}';
+            return "DeviceFilterPair{"
+                    + "device=" + device + " " + getDisplayName()
+                    + ", filter=" + filter
+                    + '}';
         }
     }
 
diff --git a/packages/DynamicSystemInstallationService/src/com/android/dynsystem/DynamicSystemInstallationService.java b/packages/DynamicSystemInstallationService/src/com/android/dynsystem/DynamicSystemInstallationService.java
index f80e934..9ff8684 100644
--- a/packages/DynamicSystemInstallationService/src/com/android/dynsystem/DynamicSystemInstallationService.java
+++ b/packages/DynamicSystemInstallationService/src/com/android/dynsystem/DynamicSystemInstallationService.java
@@ -352,16 +352,18 @@
             if (powerManager != null) {
                 powerManager.reboot("dynsystem");
             }
-        } else {
-            Log.e(TAG, "Failed to enable DynamicSystem because of native runtime error.");
-            mNM.cancel(NOTIFICATION_ID);
-
-            Toast.makeText(this,
-                    getString(R.string.toast_failed_to_reboot_to_dynsystem),
-                    Toast.LENGTH_LONG).show();
-
-            mDynSystem.remove();
+            return;
         }
+
+        Log.e(TAG, "Failed to enable DynamicSystem because of native runtime error.");
+
+        Toast.makeText(this,
+                getString(R.string.toast_failed_to_reboot_to_dynsystem),
+                Toast.LENGTH_LONG).show();
+
+        postStatus(STATUS_NOT_STARTED, CAUSE_ERROR_EXCEPTION, null);
+        resetTaskAndStop();
+        mDynSystem.remove();
     }
 
     private void executeRebootToNormalCommand() {
diff --git a/packages/FakeOemFeatures/src/com/android/fakeoemfeatures/FakeView.java b/packages/FakeOemFeatures/src/com/android/fakeoemfeatures/FakeView.java
index 276d55ee..9fe7ab65 100644
--- a/packages/FakeOemFeatures/src/com/android/fakeoemfeatures/FakeView.java
+++ b/packages/FakeOemFeatures/src/com/android/fakeoemfeatures/FakeView.java
@@ -26,7 +26,7 @@
 import android.view.View;
 
 /**
- * Dummy view to emulate stuff an OEM may want to do.
+ * Fake view to emulate stuff an OEM may want to do.
  */
 public class FakeView extends View {
     static final long TICK_DELAY = 30*1000; // 30 seconds
diff --git a/packages/FusedLocation/test/src/com/android/location/fused/tests/FusedLocationServiceTest.java b/packages/FusedLocation/test/src/com/android/location/fused/tests/FusedLocationServiceTest.java
index 8efbad6..2c4545e 100644
--- a/packages/FusedLocation/test/src/com/android/location/fused/tests/FusedLocationServiceTest.java
+++ b/packages/FusedLocation/test/src/com/android/location/fused/tests/FusedLocationServiceTest.java
@@ -155,41 +155,6 @@
         assertThat(mManager.getNextLocation(TIMEOUT_MS)).isEqualTo(location);
     }
 
-    @Test
-    public void testBypassRequest() throws Exception {
-        LocationRequest request = LocationRequest.createFromDeprecatedProvider(FUSED_PROVIDER, 1000,
-                0, false).setQuality(LocationRequest.POWER_HIGH).setLocationSettingsIgnored(true);
-
-        mProvider.setRequest(
-                new ProviderRequest.Builder()
-                        .setInterval(1000)
-                        .setLocationSettingsIgnored(true)
-                        .setLocationRequests(Collections.singletonList(request))
-                        .build(),
-                new WorkSource());
-
-        boolean containsNetworkBypass = false;
-        for (LocationRequest iRequest : mLocationManager.getTestProviderCurrentRequests(
-                NETWORK_PROVIDER)) {
-            if (iRequest.isLocationSettingsIgnored()) {
-                containsNetworkBypass = true;
-                break;
-            }
-        }
-
-        boolean containsGpsBypass = false;
-        for (LocationRequest iRequest : mLocationManager.getTestProviderCurrentRequests(
-                GPS_PROVIDER)) {
-            if (iRequest.isLocationSettingsIgnored()) {
-                containsGpsBypass = true;
-                break;
-            }
-        }
-
-        assertThat(containsNetworkBypass).isTrue();
-        assertThat(containsGpsBypass).isTrue();
-    }
-
     private static class LocationProviderManagerCapture extends ILocationProviderManager.Stub {
 
         private final LinkedBlockingQueue<Location> mLocations;
diff --git a/packages/PrintSpooler/res/values-as/strings.xml b/packages/PrintSpooler/res/values-as/strings.xml
index a93fceb..b6b287f 100644
--- a/packages/PrintSpooler/res/values-as/strings.xml
+++ b/packages/PrintSpooler/res/values-as/strings.xml
@@ -47,7 +47,7 @@
     <string name="savetopdf_button" msgid="2976186791686924743">"PDFৰ জৰিয়তে ছেভ কৰক"</string>
     <string name="print_options_expanded" msgid="6944679157471691859">"প্ৰিণ্ট বিকল্পসমূহ বিস্তাৰ কৰা হ’ল"</string>
     <string name="print_options_collapsed" msgid="7455930445670414332">"প্ৰিণ্ট বিকল্পসমূহ সংকুচিত কৰা হ’ল"</string>
-    <string name="search" msgid="5421724265322228497">"সন্ধান কৰক"</string>
+    <string name="search" msgid="5421724265322228497">"Search"</string>
     <string name="all_printers_label" msgid="3178848870161526399">"সকলো প্ৰিণ্টাৰ"</string>
     <string name="add_print_service_label" msgid="5356702546188981940">"সেৱা যোগ কৰক"</string>
     <string name="print_search_box_shown_utterance" msgid="7967404953901376090">"সন্ধান বাকচটো দেখুওৱা হ’ল"</string>
diff --git a/packages/PrintSpooler/res/values-bn/strings.xml b/packages/PrintSpooler/res/values-bn/strings.xml
index 637becb..b2e1eed 100644
--- a/packages/PrintSpooler/res/values-bn/strings.xml
+++ b/packages/PrintSpooler/res/values-bn/strings.xml
@@ -47,7 +47,7 @@
     <string name="savetopdf_button" msgid="2976186791686924743">"পিডিএফ হিসাবে সেভ করুন"</string>
     <string name="print_options_expanded" msgid="6944679157471691859">"প্রিন্ট বিকল্প প্রসারিত হয়েছে"</string>
     <string name="print_options_collapsed" msgid="7455930445670414332">"প্রিন্ট বিকল্প সংকুচিত হয়েছে"</string>
-    <string name="search" msgid="5421724265322228497">"খুঁজুন"</string>
+    <string name="search" msgid="5421724265322228497">"সার্চ"</string>
     <string name="all_printers_label" msgid="3178848870161526399">"সমস্ত প্রিন্টার"</string>
     <string name="add_print_service_label" msgid="5356702546188981940">"পরিষেবা যোগ করুন"</string>
     <string name="print_search_box_shown_utterance" msgid="7967404953901376090">"সার্চ বাক্স দেখানো হচ্ছে"</string>
diff --git a/packages/PrintSpooler/res/values-kn/strings.xml b/packages/PrintSpooler/res/values-kn/strings.xml
index 150ede4..261fe4b 100644
--- a/packages/PrintSpooler/res/values-kn/strings.xml
+++ b/packages/PrintSpooler/res/values-kn/strings.xml
@@ -47,7 +47,7 @@
     <string name="savetopdf_button" msgid="2976186791686924743">"PDF ಗೆ ಉಳಿಸು"</string>
     <string name="print_options_expanded" msgid="6944679157471691859">"ಪ್ರಿಂಟ್ ಆಯ್ಕೆಗಳನ್ನು ವಿಸ್ತರಿಸಲಾಗಿದೆ"</string>
     <string name="print_options_collapsed" msgid="7455930445670414332">"ಪ್ರಿಂಟ್ ಆಯ್ಕೆಗಳನ್ನು ಮುಚ್ಚಲಾಗಿದೆ"</string>
-    <string name="search" msgid="5421724265322228497">"ಹುಡುಕಿ"</string>
+    <string name="search" msgid="5421724265322228497">"Search"</string>
     <string name="all_printers_label" msgid="3178848870161526399">"ಎಲ್ಲಾ ಪ್ರಿಂಟರ್‌ಗಳು"</string>
     <string name="add_print_service_label" msgid="5356702546188981940">"ಸೇವೆಯನ್ನು ಸೇರಿಸು"</string>
     <string name="print_search_box_shown_utterance" msgid="7967404953901376090">"ಹುಡುಕಾಟ ಪೆಟ್ಟಿಗೆಯನ್ನು ತೋರಿಸಲಾಗಿದೆ"</string>
diff --git a/packages/PrintSpooler/res/values-ml/strings.xml b/packages/PrintSpooler/res/values-ml/strings.xml
index dbcd34b..73af95d 100644
--- a/packages/PrintSpooler/res/values-ml/strings.xml
+++ b/packages/PrintSpooler/res/values-ml/strings.xml
@@ -47,7 +47,7 @@
     <string name="savetopdf_button" msgid="2976186791686924743">"PDF-ൽ സംരക്ഷിക്കുക"</string>
     <string name="print_options_expanded" msgid="6944679157471691859">"പ്രിന്റ് ചെയ്യാനുള്ള ഓപ്‌ഷനുകൾ വിപുലീകരിച്ചു"</string>
     <string name="print_options_collapsed" msgid="7455930445670414332">"പ്രിന്റ് ചെയ്യാനുള്ള ഓപ്‌ഷനുകൾ ചുരുക്കി"</string>
-    <string name="search" msgid="5421724265322228497">"തിരയൽ"</string>
+    <string name="search" msgid="5421724265322228497">"Search"</string>
     <string name="all_printers_label" msgid="3178848870161526399">"എല്ലാ പ്രിന്ററുകളും"</string>
     <string name="add_print_service_label" msgid="5356702546188981940">"സേവനം ചേർക്കുക"</string>
     <string name="print_search_box_shown_utterance" msgid="7967404953901376090">"തിരയൽ ബോക്‌സ് ദൃശ്യമാക്കിയിരിക്കുന്നു"</string>
diff --git a/packages/PrintSpooler/res/values-mr/strings.xml b/packages/PrintSpooler/res/values-mr/strings.xml
index 44456b4..4d7e919 100644
--- a/packages/PrintSpooler/res/values-mr/strings.xml
+++ b/packages/PrintSpooler/res/values-mr/strings.xml
@@ -47,7 +47,7 @@
     <string name="savetopdf_button" msgid="2976186791686924743">"पीडीएफ वर सेव्ह करा"</string>
     <string name="print_options_expanded" msgid="6944679157471691859">"प्रिंट पर्याय विस्तृत झाले"</string>
     <string name="print_options_collapsed" msgid="7455930445670414332">"प्रिंट पर्याय संक्षिप्त झाले"</string>
-    <string name="search" msgid="5421724265322228497">"शोध"</string>
+    <string name="search" msgid="5421724265322228497">"Search"</string>
     <string name="all_printers_label" msgid="3178848870161526399">"सर्व प्रिंटर"</string>
     <string name="add_print_service_label" msgid="5356702546188981940">"सेवा जोडा"</string>
     <string name="print_search_box_shown_utterance" msgid="7967404953901376090">"शोध बॉक्स दर्शविला"</string>
diff --git a/packages/PrintSpooler/res/values-or/strings.xml b/packages/PrintSpooler/res/values-or/strings.xml
index a1675fa..7000b95 100644
--- a/packages/PrintSpooler/res/values-or/strings.xml
+++ b/packages/PrintSpooler/res/values-or/strings.xml
@@ -47,7 +47,7 @@
     <string name="savetopdf_button" msgid="2976186791686924743">"PDFରେ ସେଭ୍‍ କରନ୍ତୁ"</string>
     <string name="print_options_expanded" msgid="6944679157471691859">"ପ୍ରିଣ୍ଟ ବିକଳ୍ପକୁ ବଡ଼ କରାଯାଇଛି"</string>
     <string name="print_options_collapsed" msgid="7455930445670414332">"ପ୍ରିଣ୍ଟ ବିକଳ୍ପକୁ ଛୋଟ କରାଯାଇଛି"</string>
-    <string name="search" msgid="5421724265322228497">"ସର୍ଚ୍ଚ କରନ୍ତୁ"</string>
+    <string name="search" msgid="5421724265322228497">"Search"</string>
     <string name="all_printers_label" msgid="3178848870161526399">"ସମସ୍ତ ପ୍ରିଣ୍ଟର୍‌"</string>
     <string name="add_print_service_label" msgid="5356702546188981940">"ସେବା ଯୋଗ କରନ୍ତୁ"</string>
     <string name="print_search_box_shown_utterance" msgid="7967404953901376090">"ସର୍ଚ୍ଚ ବକ୍ସ ଦେଖାଯାଇଛି"</string>
diff --git a/packages/PrintSpooler/res/values-te/strings.xml b/packages/PrintSpooler/res/values-te/strings.xml
index b01b50a..79944bb 100644
--- a/packages/PrintSpooler/res/values-te/strings.xml
+++ b/packages/PrintSpooler/res/values-te/strings.xml
@@ -47,7 +47,7 @@
     <string name="savetopdf_button" msgid="2976186791686924743">"PDF వలె సేవ్ చేయి"</string>
     <string name="print_options_expanded" msgid="6944679157471691859">"ముద్రణ ఎంపికలు విస్తరించబడ్డాయి"</string>
     <string name="print_options_collapsed" msgid="7455930445670414332">"ముద్రణ ఎంపికలు కుదించబడ్డాయి"</string>
-    <string name="search" msgid="5421724265322228497">"వెతుకు"</string>
+    <string name="search" msgid="5421724265322228497">"సెర్చ్"</string>
     <string name="all_printers_label" msgid="3178848870161526399">"అన్ని ప్రింటర్‌లు"</string>
     <string name="add_print_service_label" msgid="5356702546188981940">"సేవను జోడించు"</string>
     <string name="print_search_box_shown_utterance" msgid="7967404953901376090">"శోధన పెట్టె చూపబడింది"</string>
diff --git a/packages/SettingsLib/Tile/src/com/android/settingslib/drawer/MasterSwitchController.java b/packages/SettingsLib/Tile/src/com/android/settingslib/drawer/PrimarySwitchController.java
similarity index 87%
rename from packages/SettingsLib/Tile/src/com/android/settingslib/drawer/MasterSwitchController.java
rename to packages/SettingsLib/Tile/src/com/android/settingslib/drawer/PrimarySwitchController.java
index a12aa83..a08f566 100644
--- a/packages/SettingsLib/Tile/src/com/android/settingslib/drawer/MasterSwitchController.java
+++ b/packages/SettingsLib/Tile/src/com/android/settingslib/drawer/PrimarySwitchController.java
@@ -19,9 +19,9 @@
 import android.os.Bundle;
 
 /**
- * A controller that manages event for master switch.
+ * A controller that manages event for Primary switch.
  */
-public abstract class MasterSwitchController extends SwitchController {
+public abstract class PrimarySwitchController extends SwitchController {
 
     @Override
     protected final MetaData getMetaData() {
diff --git a/packages/SettingsLib/Tile/src/com/android/settingslib/drawer/SwitchesProvider.java b/packages/SettingsLib/Tile/src/com/android/settingslib/drawer/SwitchesProvider.java
index 73f1a90..f2b3e30 100644
--- a/packages/SettingsLib/Tile/src/com/android/settingslib/drawer/SwitchesProvider.java
+++ b/packages/SettingsLib/Tile/src/com/android/settingslib/drawer/SwitchesProvider.java
@@ -88,7 +88,7 @@
 
             controller.setAuthority(mAuthority);
             mControllerMap.put(key, controller);
-            if (!(controller instanceof MasterSwitchController)) {
+            if (!(controller instanceof PrimarySwitchController)) {
                 mSwitchDataList.add(controller.getBundle());
             }
         });
@@ -116,7 +116,7 @@
 
         switch (method) {
             case METHOD_GET_SWITCH_DATA:
-                if (!(controller instanceof MasterSwitchController)) {
+                if (!(controller instanceof PrimarySwitchController)) {
                     return controller.getBundle();
                 }
                 break;
diff --git a/packages/SettingsLib/Tile/src/com/android/settingslib/drawer/Tile.java b/packages/SettingsLib/Tile/src/com/android/settingslib/drawer/Tile.java
index 1e4c7ca..52d2b3c 100644
--- a/packages/SettingsLib/Tile/src/com/android/settingslib/drawer/Tile.java
+++ b/packages/SettingsLib/Tile/src/com/android/settingslib/drawer/Tile.java
@@ -376,8 +376,12 @@
      * Check whether tile only has primary profile.
      */
     public boolean isPrimaryProfileOnly() {
-        String profile = mMetaData != null
-                ? mMetaData.getString(META_DATA_KEY_PROFILE) : PROFILE_ALL;
+        return isPrimaryProfileOnly(mMetaData);
+    }
+
+    static boolean isPrimaryProfileOnly(Bundle metaData) {
+        String profile = metaData != null
+                ? metaData.getString(META_DATA_KEY_PROFILE) : PROFILE_ALL;
         profile = (profile != null ? profile : PROFILE_ALL);
         return TextUtils.equals(profile, PROFILE_PRIMARY);
     }
diff --git a/packages/SettingsLib/Tile/src/com/android/settingslib/drawer/TileUtils.java b/packages/SettingsLib/Tile/src/com/android/settingslib/drawer/TileUtils.java
index ace50f3..49f6bd8 100644
--- a/packages/SettingsLib/Tile/src/com/android/settingslib/drawer/TileUtils.java
+++ b/packages/SettingsLib/Tile/src/com/android/settingslib/drawer/TileUtils.java
@@ -339,6 +339,16 @@
     private static void loadTile(UserHandle user, Map<Pair<String, String>, Tile> addedCache,
             String defaultCategory, List<Tile> outTiles, Intent intent, Bundle metaData,
             ComponentInfo componentInfo) {
+        // Skip loading tile if the component is tagged primary_profile_only but not running on
+        // the current user.
+        if (user.getIdentifier() != ActivityManager.getCurrentUser()
+                && Tile.isPrimaryProfileOnly(componentInfo.metaData)) {
+            Log.w(LOG_TAG, "Found " + componentInfo.name + " for intent "
+                    + intent + " is primary profile only, skip loading tile for uid "
+                    + user.getIdentifier());
+            return;
+        }
+
         String categoryKey = defaultCategory;
         // Load category
         if ((metaData == null || !metaData.containsKey(EXTRA_CATEGORY_KEY))
diff --git a/packages/SettingsLib/res/values-af/strings.xml b/packages/SettingsLib/res/values-af/strings.xml
index 704d264..6751fa4 100644
--- a/packages/SettingsLib/res/values-af/strings.xml
+++ b/packages/SettingsLib/res/values-af/strings.xml
@@ -257,7 +257,6 @@
     <string name="bluetooth_show_devices_without_names" msgid="923584526471885819">"Wys Bluetooth-toestelle sonder name"</string>
     <string name="bluetooth_disable_absolute_volume" msgid="1452342324349203434">"Deaktiveer absolute volume"</string>
     <string name="bluetooth_enable_gabeldorsche" msgid="9131730396242883416">"Aktiveer Gabeldorsche"</string>
-    <string name="enhanced_connectivity" msgid="7201127377781666804">"Verbeterde konnektiwiteit"</string>
     <string name="bluetooth_select_avrcp_version_string" msgid="1710571610177659127">"Bluetooth AVRCP-weergawe"</string>
     <string name="bluetooth_select_avrcp_version_dialog_title" msgid="7846922290083709633">"Kies Bluetooth AVRCP-weergawe"</string>
     <string name="bluetooth_select_map_version_string" msgid="526308145174175327">"Bluetooth MAP-weergawe"</string>
diff --git a/packages/SettingsLib/res/values-am/strings.xml b/packages/SettingsLib/res/values-am/strings.xml
index 585924d..470e780 100644
--- a/packages/SettingsLib/res/values-am/strings.xml
+++ b/packages/SettingsLib/res/values-am/strings.xml
@@ -257,7 +257,6 @@
     <string name="bluetooth_show_devices_without_names" msgid="923584526471885819">"የብሉቱዝ መሣሪያዎችን ያለ ስሞች አሳይ"</string>
     <string name="bluetooth_disable_absolute_volume" msgid="1452342324349203434">"ፍጹማዊ ድምፅን አሰናክል"</string>
     <string name="bluetooth_enable_gabeldorsche" msgid="9131730396242883416">"Gabeldorscheን አንቃ"</string>
-    <string name="enhanced_connectivity" msgid="7201127377781666804">"የተሻሻለ ተገናኝነት"</string>
     <string name="bluetooth_select_avrcp_version_string" msgid="1710571610177659127">"የብሉቱዝ AVRCP ስሪት"</string>
     <string name="bluetooth_select_avrcp_version_dialog_title" msgid="7846922290083709633">"የብሉቱዝ AVRCP ስሪት ይምረጡ"</string>
     <string name="bluetooth_select_map_version_string" msgid="526308145174175327">"የብሉቱዝ MAP ስሪት"</string>
diff --git a/packages/SettingsLib/res/values-ar/strings.xml b/packages/SettingsLib/res/values-ar/strings.xml
index 39777cd..9acfa0d 100644
--- a/packages/SettingsLib/res/values-ar/strings.xml
+++ b/packages/SettingsLib/res/values-ar/strings.xml
@@ -152,7 +152,7 @@
     <string name="user_guest" msgid="6939192779649870792">"ضيف"</string>
     <string name="unknown" msgid="3544487229740637809">"غير معروف"</string>
     <string name="running_process_item_user_label" msgid="3988506293099805796">"المستخدم: <xliff:g id="USER_NAME">%1$s</xliff:g>"</string>
-    <string name="launch_defaults_some" msgid="3631650616557252926">"تم تعيين بعض الإعدادات التلقائية"</string>
+    <string name="launch_defaults_some" msgid="3631650616557252926">"تم ضبط بعض الإعدادات التلقائية"</string>
     <string name="launch_defaults_none" msgid="8049374306261262709">"لم يتم تعيين إعدادات تلقائية"</string>
     <string name="tts_settings" msgid="8130616705989351312">"إعدادات تحويل النص إلى كلام"</string>
     <string name="tts_settings_title" msgid="7602210956640483039">"تحويل النص إلى كلام"</string>
@@ -257,7 +257,6 @@
     <string name="bluetooth_show_devices_without_names" msgid="923584526471885819">"عرض أجهزة البلوتوث بدون أسماء"</string>
     <string name="bluetooth_disable_absolute_volume" msgid="1452342324349203434">"إيقاف مستوى الصوت المطلق"</string>
     <string name="bluetooth_enable_gabeldorsche" msgid="9131730396242883416">"‏تفعيل Gabeldorsche"</string>
-    <string name="enhanced_connectivity" msgid="7201127377781666804">"إمكانية اتصال محسّن"</string>
     <string name="bluetooth_select_avrcp_version_string" msgid="1710571610177659127">"‏إصدار Bluetooth AVRCP"</string>
     <string name="bluetooth_select_avrcp_version_dialog_title" msgid="7846922290083709633">"‏اختيار إصدار Bluetooth AVRCP"</string>
     <string name="bluetooth_select_map_version_string" msgid="526308145174175327">"‏إصدار Bluetooth MAP"</string>
diff --git a/packages/SettingsLib/res/values-as/strings.xml b/packages/SettingsLib/res/values-as/strings.xml
index e0455cb..f993dba 100644
--- a/packages/SettingsLib/res/values-as/strings.xml
+++ b/packages/SettingsLib/res/values-as/strings.xml
@@ -257,7 +257,6 @@
     <string name="bluetooth_show_devices_without_names" msgid="923584526471885819">"নামবিহীন ব্লুটুথ ডিভাইচসমূহ দেখুৱাওক"</string>
     <string name="bluetooth_disable_absolute_volume" msgid="1452342324349203434">"পূৰ্ণ মাত্ৰাৰ ভলিউম অক্ষম কৰক"</string>
     <string name="bluetooth_enable_gabeldorsche" msgid="9131730396242883416">"Gabeldorsche সক্ষম কৰক"</string>
-    <string name="enhanced_connectivity" msgid="7201127377781666804">"উন্নত সংযোগ"</string>
     <string name="bluetooth_select_avrcp_version_string" msgid="1710571610177659127">"ব্লুটুথ AVRCP সংস্কৰণ"</string>
     <string name="bluetooth_select_avrcp_version_dialog_title" msgid="7846922290083709633">"ব্লুটুথ AVRCP সংস্কৰণ বাছনি কৰক"</string>
     <string name="bluetooth_select_map_version_string" msgid="526308145174175327">"ব্লুটুথ MAP সংস্কৰণ"</string>
diff --git a/packages/SettingsLib/res/values-az/strings.xml b/packages/SettingsLib/res/values-az/strings.xml
index 6565d53..6a50661 100644
--- a/packages/SettingsLib/res/values-az/strings.xml
+++ b/packages/SettingsLib/res/values-az/strings.xml
@@ -257,7 +257,6 @@
     <string name="bluetooth_show_devices_without_names" msgid="923584526471885819">"Bluetooth cihazlarını adsız göstərin"</string>
     <string name="bluetooth_disable_absolute_volume" msgid="1452342324349203434">"Mütləq səs həcmi deaktiv edin"</string>
     <string name="bluetooth_enable_gabeldorsche" msgid="9131730396242883416">"Gabeldorsche\'ni aktiv edin"</string>
-    <string name="enhanced_connectivity" msgid="7201127377781666804">"Təkmilləşdirilmiş Bağlantı"</string>
     <string name="bluetooth_select_avrcp_version_string" msgid="1710571610177659127">"Bluetooth AVRCP Versiya"</string>
     <string name="bluetooth_select_avrcp_version_dialog_title" msgid="7846922290083709633">"Bluetooth AVRCP Versiyasını seçin"</string>
     <string name="bluetooth_select_map_version_string" msgid="526308145174175327">"Bluetooth MAP Versiyası"</string>
diff --git a/packages/SettingsLib/res/values-b+sr+Latn/strings.xml b/packages/SettingsLib/res/values-b+sr+Latn/strings.xml
index 3ced29b..cf988ab 100644
--- a/packages/SettingsLib/res/values-b+sr+Latn/strings.xml
+++ b/packages/SettingsLib/res/values-b+sr+Latn/strings.xml
@@ -257,7 +257,6 @@
     <string name="bluetooth_show_devices_without_names" msgid="923584526471885819">"Prikaži Bluetooth uređaje bez naziva"</string>
     <string name="bluetooth_disable_absolute_volume" msgid="1452342324349203434">"Onemogući glavno podešavanje jačine zvuka"</string>
     <string name="bluetooth_enable_gabeldorsche" msgid="9131730396242883416">"Omogući Gabeldorsche"</string>
-    <string name="enhanced_connectivity" msgid="7201127377781666804">"Poboljšano povezivanje"</string>
     <string name="bluetooth_select_avrcp_version_string" msgid="1710571610177659127">"Verzija Bluetooth AVRCP-a"</string>
     <string name="bluetooth_select_avrcp_version_dialog_title" msgid="7846922290083709633">"Izaberite verziju Bluetooth AVRCP-a"</string>
     <string name="bluetooth_select_map_version_string" msgid="526308145174175327">"Verzija Bluetooth MAP-a"</string>
diff --git a/packages/SettingsLib/res/values-be/strings.xml b/packages/SettingsLib/res/values-be/strings.xml
index 01d7682..8f71509 100644
--- a/packages/SettingsLib/res/values-be/strings.xml
+++ b/packages/SettingsLib/res/values-be/strings.xml
@@ -257,7 +257,6 @@
     <string name="bluetooth_show_devices_without_names" msgid="923584526471885819">"Паказваць прылады Bluetooth без назваў"</string>
     <string name="bluetooth_disable_absolute_volume" msgid="1452342324349203434">"Адключыць абсалютны гук"</string>
     <string name="bluetooth_enable_gabeldorsche" msgid="9131730396242883416">"Уключыць Gabeldorsche"</string>
-    <string name="enhanced_connectivity" msgid="7201127377781666804">"Палепшанае падключэнне"</string>
     <string name="bluetooth_select_avrcp_version_string" msgid="1710571610177659127">"Версія Bluetooth AVRCP"</string>
     <string name="bluetooth_select_avrcp_version_dialog_title" msgid="7846922290083709633">"Выбраць версію Bluetooth AVRCP"</string>
     <string name="bluetooth_select_map_version_string" msgid="526308145174175327">"Версія Bluetooth MAP"</string>
diff --git a/packages/SettingsLib/res/values-bg/strings.xml b/packages/SettingsLib/res/values-bg/strings.xml
index d042c0f..747cb26 100644
--- a/packages/SettingsLib/res/values-bg/strings.xml
+++ b/packages/SettingsLib/res/values-bg/strings.xml
@@ -257,7 +257,6 @@
     <string name="bluetooth_show_devices_without_names" msgid="923584526471885819">"Показване на устройствата с Bluetooth без имена"</string>
     <string name="bluetooth_disable_absolute_volume" msgid="1452342324349203434">"Деактивиране на пълната сила на звука"</string>
     <string name="bluetooth_enable_gabeldorsche" msgid="9131730396242883416">"Активиране на Gabeldorsche"</string>
-    <string name="enhanced_connectivity" msgid="7201127377781666804">"Подобрена свързаност"</string>
     <string name="bluetooth_select_avrcp_version_string" msgid="1710571610177659127">"Версия на AVRCP за Bluetooth"</string>
     <string name="bluetooth_select_avrcp_version_dialog_title" msgid="7846922290083709633">"Избиране на версия на AVRCP за Bluetooth"</string>
     <string name="bluetooth_select_map_version_string" msgid="526308145174175327">"MAP версия за Bluetooth"</string>
diff --git a/packages/SettingsLib/res/values-bn/strings.xml b/packages/SettingsLib/res/values-bn/strings.xml
index 2db23f7..87f3b7a 100644
--- a/packages/SettingsLib/res/values-bn/strings.xml
+++ b/packages/SettingsLib/res/values-bn/strings.xml
@@ -257,7 +257,6 @@
     <string name="bluetooth_show_devices_without_names" msgid="923584526471885819">"নামহীন ব্লুটুথ ডিভাইসগুলি দেখুন"</string>
     <string name="bluetooth_disable_absolute_volume" msgid="1452342324349203434">"চূড়ান্ত ভলিউম অক্ষম করুন"</string>
     <string name="bluetooth_enable_gabeldorsche" msgid="9131730396242883416">"Gabeldorsche ফিচার চালু করুন"</string>
-    <string name="enhanced_connectivity" msgid="7201127377781666804">"কানেক্টিভিটি উন্নত করা হয়েছে"</string>
     <string name="bluetooth_select_avrcp_version_string" msgid="1710571610177659127">"ব্লুটুথ AVRCP ভার্সন"</string>
     <string name="bluetooth_select_avrcp_version_dialog_title" msgid="7846922290083709633">"ব্লুটুথ AVRCP ভার্সন বেছে নিন"</string>
     <string name="bluetooth_select_map_version_string" msgid="526308145174175327">"ব্লুটুথ MAP ভার্সন"</string>
diff --git a/packages/SettingsLib/res/values-bs/strings.xml b/packages/SettingsLib/res/values-bs/strings.xml
index f26fe9d..e329c99 100644
--- a/packages/SettingsLib/res/values-bs/strings.xml
+++ b/packages/SettingsLib/res/values-bs/strings.xml
@@ -257,7 +257,6 @@
     <string name="bluetooth_show_devices_without_names" msgid="923584526471885819">"Prikaži Bluetooth uređaje bez naziva"</string>
     <string name="bluetooth_disable_absolute_volume" msgid="1452342324349203434">"Onemogući apsolutnu jačinu zvuka"</string>
     <string name="bluetooth_enable_gabeldorsche" msgid="9131730396242883416">"Omogući Gabeldorsche"</string>
-    <string name="enhanced_connectivity" msgid="7201127377781666804">"Poboljšana povezivost"</string>
     <string name="bluetooth_select_avrcp_version_string" msgid="1710571610177659127">"Bluetooth AVRCP verzija"</string>
     <string name="bluetooth_select_avrcp_version_dialog_title" msgid="7846922290083709633">"Odaberite Bluetooth AVRCP verziju"</string>
     <string name="bluetooth_select_map_version_string" msgid="526308145174175327">"Bluetooth MAP verzija"</string>
diff --git a/packages/SettingsLib/res/values-ca/strings.xml b/packages/SettingsLib/res/values-ca/strings.xml
index 3ca9d52..5ffdacd 100644
--- a/packages/SettingsLib/res/values-ca/strings.xml
+++ b/packages/SettingsLib/res/values-ca/strings.xml
@@ -257,7 +257,6 @@
     <string name="bluetooth_show_devices_without_names" msgid="923584526471885819">"Mostra els dispositius Bluetooth sense el nom"</string>
     <string name="bluetooth_disable_absolute_volume" msgid="1452342324349203434">"Desactiva el volum absolut"</string>
     <string name="bluetooth_enable_gabeldorsche" msgid="9131730396242883416">"Activa Gabeldorsche"</string>
-    <string name="enhanced_connectivity" msgid="7201127377781666804">"Connectivitat millorada"</string>
     <string name="bluetooth_select_avrcp_version_string" msgid="1710571610177659127">"Versió AVRCP de Bluetooth"</string>
     <string name="bluetooth_select_avrcp_version_dialog_title" msgid="7846922290083709633">"Selecciona la versió AVRCP de Bluetooth"</string>
     <string name="bluetooth_select_map_version_string" msgid="526308145174175327">"Versió MAP de Bluetooth"</string>
diff --git a/packages/SettingsLib/res/values-cs/strings.xml b/packages/SettingsLib/res/values-cs/strings.xml
index a5532e0..0aef99f 100644
--- a/packages/SettingsLib/res/values-cs/strings.xml
+++ b/packages/SettingsLib/res/values-cs/strings.xml
@@ -257,7 +257,6 @@
     <string name="bluetooth_show_devices_without_names" msgid="923584526471885819">"Zobrazovat zařízení Bluetooth bez názvů"</string>
     <string name="bluetooth_disable_absolute_volume" msgid="1452342324349203434">"Zakázat absolutní hlasitost"</string>
     <string name="bluetooth_enable_gabeldorsche" msgid="9131730396242883416">"Zapnout funkci Gabeldorsche"</string>
-    <string name="enhanced_connectivity" msgid="7201127377781666804">"Lepší připojování"</string>
     <string name="bluetooth_select_avrcp_version_string" msgid="1710571610177659127">"Verze profilu Bluetooth AVRCP"</string>
     <string name="bluetooth_select_avrcp_version_dialog_title" msgid="7846922290083709633">"Vyberte verzi profilu Bluetooth AVRCP"</string>
     <string name="bluetooth_select_map_version_string" msgid="526308145174175327">"Verze MAP pro Bluetooth"</string>
diff --git a/packages/SettingsLib/res/values-da/strings.xml b/packages/SettingsLib/res/values-da/strings.xml
index 8ca22d7..98068cb 100644
--- a/packages/SettingsLib/res/values-da/strings.xml
+++ b/packages/SettingsLib/res/values-da/strings.xml
@@ -257,7 +257,6 @@
     <string name="bluetooth_show_devices_without_names" msgid="923584526471885819">"Vis Bluetooth-enheder uden navne"</string>
     <string name="bluetooth_disable_absolute_volume" msgid="1452342324349203434">"Deaktiver absolut lydstyrke"</string>
     <string name="bluetooth_enable_gabeldorsche" msgid="9131730396242883416">"Aktivér Gabeldorsche"</string>
-    <string name="enhanced_connectivity" msgid="7201127377781666804">"Enhanced Connectivity"</string>
     <string name="bluetooth_select_avrcp_version_string" msgid="1710571610177659127">"AVRCP-version for Bluetooth"</string>
     <string name="bluetooth_select_avrcp_version_dialog_title" msgid="7846922290083709633">"Vælg AVRCP-version for Bluetooth"</string>
     <string name="bluetooth_select_map_version_string" msgid="526308145174175327">"MAP-version for Bluetooth"</string>
diff --git a/packages/SettingsLib/res/values-de/strings.xml b/packages/SettingsLib/res/values-de/strings.xml
index 6b0ae2e2..0837ad3 100644
--- a/packages/SettingsLib/res/values-de/strings.xml
+++ b/packages/SettingsLib/res/values-de/strings.xml
@@ -257,7 +257,6 @@
     <string name="bluetooth_show_devices_without_names" msgid="923584526471885819">"Bluetooth-Geräte ohne Namen anzeigen"</string>
     <string name="bluetooth_disable_absolute_volume" msgid="1452342324349203434">"Absolute Lautstärkeregelung deaktivieren"</string>
     <string name="bluetooth_enable_gabeldorsche" msgid="9131730396242883416">"Bluetooth-Gabeldorsche aktivieren"</string>
-    <string name="enhanced_connectivity" msgid="7201127377781666804">"Verbesserte Konnektivität"</string>
     <string name="bluetooth_select_avrcp_version_string" msgid="1710571610177659127">"Bluetooth AVRCP-Version"</string>
     <string name="bluetooth_select_avrcp_version_dialog_title" msgid="7846922290083709633">"Bluetooth AVRCP-Version auswählen"</string>
     <string name="bluetooth_select_map_version_string" msgid="526308145174175327">"Bluetooth MAP-Version"</string>
diff --git a/packages/SettingsLib/res/values-el/strings.xml b/packages/SettingsLib/res/values-el/strings.xml
index 4d7c882..1f9d977 100644
--- a/packages/SettingsLib/res/values-el/strings.xml
+++ b/packages/SettingsLib/res/values-el/strings.xml
@@ -257,7 +257,6 @@
     <string name="bluetooth_show_devices_without_names" msgid="923584526471885819">"Εμφάνιση συσκευών Bluetooth χωρίς ονόματα"</string>
     <string name="bluetooth_disable_absolute_volume" msgid="1452342324349203434">"Απενεργοποίηση απόλυτης έντασης"</string>
     <string name="bluetooth_enable_gabeldorsche" msgid="9131730396242883416">"Ενεργοποίηση Gabeldorsche"</string>
-    <string name="enhanced_connectivity" msgid="7201127377781666804">"Βελτιωμένη συνδεσιμότητα"</string>
     <string name="bluetooth_select_avrcp_version_string" msgid="1710571610177659127">"Έκδοση AVRCP Bluetooth"</string>
     <string name="bluetooth_select_avrcp_version_dialog_title" msgid="7846922290083709633">"Επιλογή έκδοσης AVRCP Bluetooth"</string>
     <string name="bluetooth_select_map_version_string" msgid="526308145174175327">"Έκδοση MAP Bluetooth"</string>
diff --git a/packages/SettingsLib/res/values-en-rAU/strings.xml b/packages/SettingsLib/res/values-en-rAU/strings.xml
index cc3b2aa..abe61a9 100644
--- a/packages/SettingsLib/res/values-en-rAU/strings.xml
+++ b/packages/SettingsLib/res/values-en-rAU/strings.xml
@@ -257,7 +257,6 @@
     <string name="bluetooth_show_devices_without_names" msgid="923584526471885819">"Show Bluetooth devices without names"</string>
     <string name="bluetooth_disable_absolute_volume" msgid="1452342324349203434">"Disable absolute volume"</string>
     <string name="bluetooth_enable_gabeldorsche" msgid="9131730396242883416">"Enable Gabeldorsche"</string>
-    <string name="enhanced_connectivity" msgid="7201127377781666804">"Enhanced connectivity"</string>
     <string name="bluetooth_select_avrcp_version_string" msgid="1710571610177659127">"Bluetooth AVRCP version"</string>
     <string name="bluetooth_select_avrcp_version_dialog_title" msgid="7846922290083709633">"Select Bluetooth AVRCP Version"</string>
     <string name="bluetooth_select_map_version_string" msgid="526308145174175327">"Bluetooth MAP version"</string>
diff --git a/packages/SettingsLib/res/values-en-rCA/strings.xml b/packages/SettingsLib/res/values-en-rCA/strings.xml
index a9f039a..959ad46 100644
--- a/packages/SettingsLib/res/values-en-rCA/strings.xml
+++ b/packages/SettingsLib/res/values-en-rCA/strings.xml
@@ -257,7 +257,6 @@
     <string name="bluetooth_show_devices_without_names" msgid="923584526471885819">"Show Bluetooth devices without names"</string>
     <string name="bluetooth_disable_absolute_volume" msgid="1452342324349203434">"Disable absolute volume"</string>
     <string name="bluetooth_enable_gabeldorsche" msgid="9131730396242883416">"Enable Gabeldorsche"</string>
-    <string name="enhanced_connectivity" msgid="7201127377781666804">"Enhanced connectivity"</string>
     <string name="bluetooth_select_avrcp_version_string" msgid="1710571610177659127">"Bluetooth AVRCP version"</string>
     <string name="bluetooth_select_avrcp_version_dialog_title" msgid="7846922290083709633">"Select Bluetooth AVRCP Version"</string>
     <string name="bluetooth_select_map_version_string" msgid="526308145174175327">"Bluetooth MAP version"</string>
diff --git a/packages/SettingsLib/res/values-en-rGB/strings.xml b/packages/SettingsLib/res/values-en-rGB/strings.xml
index cc3b2aa..abe61a9 100644
--- a/packages/SettingsLib/res/values-en-rGB/strings.xml
+++ b/packages/SettingsLib/res/values-en-rGB/strings.xml
@@ -257,7 +257,6 @@
     <string name="bluetooth_show_devices_without_names" msgid="923584526471885819">"Show Bluetooth devices without names"</string>
     <string name="bluetooth_disable_absolute_volume" msgid="1452342324349203434">"Disable absolute volume"</string>
     <string name="bluetooth_enable_gabeldorsche" msgid="9131730396242883416">"Enable Gabeldorsche"</string>
-    <string name="enhanced_connectivity" msgid="7201127377781666804">"Enhanced connectivity"</string>
     <string name="bluetooth_select_avrcp_version_string" msgid="1710571610177659127">"Bluetooth AVRCP version"</string>
     <string name="bluetooth_select_avrcp_version_dialog_title" msgid="7846922290083709633">"Select Bluetooth AVRCP Version"</string>
     <string name="bluetooth_select_map_version_string" msgid="526308145174175327">"Bluetooth MAP version"</string>
diff --git a/packages/SettingsLib/res/values-en-rIN/strings.xml b/packages/SettingsLib/res/values-en-rIN/strings.xml
index cc3b2aa..abe61a9 100644
--- a/packages/SettingsLib/res/values-en-rIN/strings.xml
+++ b/packages/SettingsLib/res/values-en-rIN/strings.xml
@@ -257,7 +257,6 @@
     <string name="bluetooth_show_devices_without_names" msgid="923584526471885819">"Show Bluetooth devices without names"</string>
     <string name="bluetooth_disable_absolute_volume" msgid="1452342324349203434">"Disable absolute volume"</string>
     <string name="bluetooth_enable_gabeldorsche" msgid="9131730396242883416">"Enable Gabeldorsche"</string>
-    <string name="enhanced_connectivity" msgid="7201127377781666804">"Enhanced connectivity"</string>
     <string name="bluetooth_select_avrcp_version_string" msgid="1710571610177659127">"Bluetooth AVRCP version"</string>
     <string name="bluetooth_select_avrcp_version_dialog_title" msgid="7846922290083709633">"Select Bluetooth AVRCP Version"</string>
     <string name="bluetooth_select_map_version_string" msgid="526308145174175327">"Bluetooth MAP version"</string>
diff --git a/packages/SettingsLib/res/values-en-rXC/strings.xml b/packages/SettingsLib/res/values-en-rXC/strings.xml
index 41c20e0..738dd2a 100644
--- a/packages/SettingsLib/res/values-en-rXC/strings.xml
+++ b/packages/SettingsLib/res/values-en-rXC/strings.xml
@@ -257,7 +257,6 @@
     <string name="bluetooth_show_devices_without_names" msgid="923584526471885819">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‎‎‏‏‎‎‏‏‎‏‎‎‎‏‎‎‏‏‏‎‏‏‎‎‏‏‏‎‏‏‏‏‎‎‎‏‎‎‎‏‏‎‎‏‏‏‎‏‏‏‎‎‏‏‏‏‏‏‏‎‏‏‎Show Bluetooth devices without names‎‏‎‎‏‎"</string>
     <string name="bluetooth_disable_absolute_volume" msgid="1452342324349203434">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‎‏‏‎‏‎‎‎‎‏‎‎‏‏‏‏‏‎‎‎‎‎‏‏‎‏‎‎‏‏‎‎‎‏‎‏‏‏‏‎‎‎‏‏‎‎‏‎‏‏‏‎‎‏‏‏‏‏‎‏‎‏‎‎Disable absolute volume‎‏‎‎‏‎"</string>
     <string name="bluetooth_enable_gabeldorsche" msgid="9131730396242883416">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‏‏‏‏‏‏‏‏‎‏‎‏‏‏‎‏‎‎‏‏‎‏‏‎‎‎‏‏‎‏‎‎‏‏‏‎‏‎‏‎‎‏‎‏‏‎‎‎‏‏‎‏‏‎‎‏‏‎‏‎‏‏‎‎‎‎Enable Gabeldorsche‎‏‎‎‏‎"</string>
-    <string name="enhanced_connectivity" msgid="7201127377781666804">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‏‏‏‏‎‎‎‏‏‏‏‏‎‏‏‏‏‏‎‎‎‏‎‏‏‎‎‏‏‎‎‏‎‎‏‏‎‎‏‏‏‎‎‎‎‎‏‎‎‎‎‏‏‎‎‏‏‏‏‏‏‎‏‎‎‎Enhanced Connectivity‎‏‎‎‏‎"</string>
     <string name="bluetooth_select_avrcp_version_string" msgid="1710571610177659127">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‎‏‏‎‏‏‏‏‎‏‏‏‏‎‏‎‎‏‎‏‎‏‏‏‏‎‏‎‎‎‏‎‎‎‎‎‎‏‏‏‎‎‎‎‎‎‏‏‏‏‎‎‎‎‎‏‏‏‏‎‏‏‏‎Bluetooth AVRCP Version‎‏‎‎‏‎"</string>
     <string name="bluetooth_select_avrcp_version_dialog_title" msgid="7846922290083709633">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‏‏‏‏‎‏‏‎‎‏‏‏‎‎‏‎‏‏‏‎‏‏‏‏‎‎‎‏‏‏‏‏‏‏‎‎‎‎‎‎‎‏‎‎‎‏‎‎‎‏‏‎‎‎‎‏‎‏‏‎‎‎‎‎‏‎Select Bluetooth AVRCP Version‎‏‎‎‏‎"</string>
     <string name="bluetooth_select_map_version_string" msgid="526308145174175327">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‎‏‏‏‏‏‎‏‎‎‏‏‎‏‏‏‎‏‎‎‏‎‎‏‏‏‏‎‎‎‎‎‏‏‎‎‏‏‏‎‎‎‎‎‎‎‎‏‎‏‏‏‏‎‎‏‎‏‏‏‏‏‎Bluetooth MAP Version‎‏‎‎‏‎"</string>
diff --git a/packages/SettingsLib/res/values-es-rUS/strings.xml b/packages/SettingsLib/res/values-es-rUS/strings.xml
index 287a1ac..d1e4fb5 100644
--- a/packages/SettingsLib/res/values-es-rUS/strings.xml
+++ b/packages/SettingsLib/res/values-es-rUS/strings.xml
@@ -257,7 +257,6 @@
     <string name="bluetooth_show_devices_without_names" msgid="923584526471885819">"Mostrar dispositivos Bluetooth sin nombre"</string>
     <string name="bluetooth_disable_absolute_volume" msgid="1452342324349203434">"Inhabilitar volumen absoluto"</string>
     <string name="bluetooth_enable_gabeldorsche" msgid="9131730396242883416">"Habilitar Gabeldorsche"</string>
-    <string name="enhanced_connectivity" msgid="7201127377781666804">"Conectividad mejorada"</string>
     <string name="bluetooth_select_avrcp_version_string" msgid="1710571610177659127">"Versión de AVRCP del Bluetooth"</string>
     <string name="bluetooth_select_avrcp_version_dialog_title" msgid="7846922290083709633">"Selecciona la versión de AVRCP del Bluetooth"</string>
     <string name="bluetooth_select_map_version_string" msgid="526308145174175327">"Versión de MAP de Bluetooth"</string>
diff --git a/packages/SettingsLib/res/values-es/strings.xml b/packages/SettingsLib/res/values-es/strings.xml
index 9d34557..9ad71e2 100644
--- a/packages/SettingsLib/res/values-es/strings.xml
+++ b/packages/SettingsLib/res/values-es/strings.xml
@@ -257,7 +257,6 @@
     <string name="bluetooth_show_devices_without_names" msgid="923584526471885819">"Mostrar dispositivos Bluetooth sin nombre"</string>
     <string name="bluetooth_disable_absolute_volume" msgid="1452342324349203434">"Inhabilitar volumen absoluto"</string>
     <string name="bluetooth_enable_gabeldorsche" msgid="9131730396242883416">"Habilitar Gabeldorsche"</string>
-    <string name="enhanced_connectivity" msgid="7201127377781666804">"Conectividad mejorada"</string>
     <string name="bluetooth_select_avrcp_version_string" msgid="1710571610177659127">"Versión AVRCP de Bluetooth"</string>
     <string name="bluetooth_select_avrcp_version_dialog_title" msgid="7846922290083709633">"Selecciona la versión AVRCP de Bluetooth"</string>
     <string name="bluetooth_select_map_version_string" msgid="526308145174175327">"Versión de MAP de Bluetooth"</string>
diff --git a/packages/SettingsLib/res/values-et/strings.xml b/packages/SettingsLib/res/values-et/strings.xml
index d003ef0..14d3b57 100644
--- a/packages/SettingsLib/res/values-et/strings.xml
+++ b/packages/SettingsLib/res/values-et/strings.xml
@@ -257,7 +257,6 @@
     <string name="bluetooth_show_devices_without_names" msgid="923584526471885819">"Kuva ilma nimedeta Bluetoothi seadmed"</string>
     <string name="bluetooth_disable_absolute_volume" msgid="1452342324349203434">"Keela absoluutne helitugevus"</string>
     <string name="bluetooth_enable_gabeldorsche" msgid="9131730396242883416">"Luba Gabeldorsche"</string>
-    <string name="enhanced_connectivity" msgid="7201127377781666804">"Täiustatud ühenduvus"</string>
     <string name="bluetooth_select_avrcp_version_string" msgid="1710571610177659127">"Bluetoothi AVRCP versioon"</string>
     <string name="bluetooth_select_avrcp_version_dialog_title" msgid="7846922290083709633">"Valige Bluetoothi AVRCP versioon"</string>
     <string name="bluetooth_select_map_version_string" msgid="526308145174175327">"Bluetoothi MAP-i versioon"</string>
diff --git a/packages/SettingsLib/res/values-eu/strings.xml b/packages/SettingsLib/res/values-eu/strings.xml
index 0042321..dcdadbd 100644
--- a/packages/SettingsLib/res/values-eu/strings.xml
+++ b/packages/SettingsLib/res/values-eu/strings.xml
@@ -257,7 +257,6 @@
     <string name="bluetooth_show_devices_without_names" msgid="923584526471885819">"Erakutsi Bluetooth bidezko gailuak izenik gabe"</string>
     <string name="bluetooth_disable_absolute_volume" msgid="1452342324349203434">"Desgaitu bolumen absolutua"</string>
     <string name="bluetooth_enable_gabeldorsche" msgid="9131730396242883416">"Gaitu Gabeldorsche"</string>
-    <string name="enhanced_connectivity" msgid="7201127377781666804">"Konexio hobeak"</string>
     <string name="bluetooth_select_avrcp_version_string" msgid="1710571610177659127">"Bluetooth AVRCP bertsioa"</string>
     <string name="bluetooth_select_avrcp_version_dialog_title" msgid="7846922290083709633">"Hautatu Bluetooth AVRCP bertsioa"</string>
     <string name="bluetooth_select_map_version_string" msgid="526308145174175327">"Bluetooth MAParen bertsioa"</string>
diff --git a/packages/SettingsLib/res/values-fa/strings.xml b/packages/SettingsLib/res/values-fa/strings.xml
index 1c08815..f3b22d3 100644
--- a/packages/SettingsLib/res/values-fa/strings.xml
+++ b/packages/SettingsLib/res/values-fa/strings.xml
@@ -257,7 +257,6 @@
     <string name="bluetooth_show_devices_without_names" msgid="923584526471885819">"نمایش دستگاه‌های بلوتوث بدون نام"</string>
     <string name="bluetooth_disable_absolute_volume" msgid="1452342324349203434">"غیرفعال کردن میزان صدای مطلق"</string>
     <string name="bluetooth_enable_gabeldorsche" msgid="9131730396242883416">"‏فعال کردن Gabeldorsche"</string>
-    <string name="enhanced_connectivity" msgid="7201127377781666804">"اتصال بهبودیافته"</string>
     <string name="bluetooth_select_avrcp_version_string" msgid="1710571610177659127">"‏نسخه AVRCP بلوتوث"</string>
     <string name="bluetooth_select_avrcp_version_dialog_title" msgid="7846922290083709633">"‏انتخاب نسخه AVRCP بلوتوث"</string>
     <string name="bluetooth_select_map_version_string" msgid="526308145174175327">"‏نسخه MAP بلوتوث"</string>
diff --git a/packages/SettingsLib/res/values-fi/strings.xml b/packages/SettingsLib/res/values-fi/strings.xml
index 3945e55..3d28f1d 100644
--- a/packages/SettingsLib/res/values-fi/strings.xml
+++ b/packages/SettingsLib/res/values-fi/strings.xml
@@ -257,7 +257,6 @@
     <string name="bluetooth_show_devices_without_names" msgid="923584526471885819">"Näytä nimettömät Bluetooth-laitteet"</string>
     <string name="bluetooth_disable_absolute_volume" msgid="1452342324349203434">"Poista yleinen äänenvoimakkuuden säätö käytöstä"</string>
     <string name="bluetooth_enable_gabeldorsche" msgid="9131730396242883416">"Ota Gabeldorsche käyttöön"</string>
-    <string name="enhanced_connectivity" msgid="7201127377781666804">"Parannetut yhteydet"</string>
     <string name="bluetooth_select_avrcp_version_string" msgid="1710571610177659127">"Bluetoothin AVRCP-versio"</string>
     <string name="bluetooth_select_avrcp_version_dialog_title" msgid="7846922290083709633">"Valitse Bluetoothin AVRCP-versio"</string>
     <string name="bluetooth_select_map_version_string" msgid="526308145174175327">"Bluetoothin MAP-versio"</string>
diff --git a/packages/SettingsLib/res/values-fr-rCA/strings.xml b/packages/SettingsLib/res/values-fr-rCA/strings.xml
index 140d4ce..87d3de1 100644
--- a/packages/SettingsLib/res/values-fr-rCA/strings.xml
+++ b/packages/SettingsLib/res/values-fr-rCA/strings.xml
@@ -257,7 +257,6 @@
     <string name="bluetooth_show_devices_without_names" msgid="923584526471885819">"Afficher les appareils Bluetooth sans nom"</string>
     <string name="bluetooth_disable_absolute_volume" msgid="1452342324349203434">"Désactiver le volume absolu"</string>
     <string name="bluetooth_enable_gabeldorsche" msgid="9131730396242883416">"Activer le Gabeldorsche"</string>
-    <string name="enhanced_connectivity" msgid="7201127377781666804">"Connectivité améliorée"</string>
     <string name="bluetooth_select_avrcp_version_string" msgid="1710571610177659127">"Version du profil Bluetooth AVRCP"</string>
     <string name="bluetooth_select_avrcp_version_dialog_title" msgid="7846922290083709633">"Sélectionner la version du profil Bluetooth AVRCP"</string>
     <string name="bluetooth_select_map_version_string" msgid="526308145174175327">"Version du profil Bluetooth MAP"</string>
diff --git a/packages/SettingsLib/res/values-fr/strings.xml b/packages/SettingsLib/res/values-fr/strings.xml
index 1b1ae8e..5f5be97 100644
--- a/packages/SettingsLib/res/values-fr/strings.xml
+++ b/packages/SettingsLib/res/values-fr/strings.xml
@@ -165,7 +165,7 @@
     <string name="tts_lang_not_selected" msgid="7927823081096056147">"Langue non sélectionnée"</string>
     <string name="tts_default_lang_summary" msgid="9042620014800063470">"Définir la langue utilisée par la synthèse vocale"</string>
     <string name="tts_play_example_title" msgid="1599468547216481684">"Écouter un échantillon"</string>
-    <string name="tts_play_example_summary" msgid="634044730710636383">"Lire une courte démonstration de la synthèse vocale"</string>
+    <string name="tts_play_example_summary" msgid="634044730710636383">"Écoutez une courte démonstration de la synthèse vocale"</string>
     <string name="tts_install_data_title" msgid="1829942496472751703">"Installer les données vocales"</string>
     <string name="tts_install_data_summary" msgid="3608874324992243851">"Installer les données nécessaires à la synthèse vocale"</string>
     <string name="tts_engine_security_warning" msgid="3372432853837988146">"Ce moteur de synthèse vocale est susceptible de collecter tout ce qui sera lu, y compris les données personnelles comme les mots de passe et les numéros de carte de paiement. Il provient du moteur <xliff:g id="TTS_PLUGIN_ENGINE_NAME">%s</xliff:g>. Voulez-vous activer son utilisation ?"</string>
@@ -189,8 +189,8 @@
     <item msgid="1158955023692670059">"Rapide"</item>
     <item msgid="5664310435707146591">"Plus rapide"</item>
     <item msgid="5491266922147715962">"Très rapide"</item>
-    <item msgid="7659240015901486196">"Rapide"</item>
-    <item msgid="7147051179282410945">"Très rapide"</item>
+    <item msgid="7659240015901486196">"Beaucoup plus rapide"</item>
+    <item msgid="7147051179282410945">"Extrêmement rapide"</item>
     <item msgid="581904787661470707">"La plus rapide"</item>
   </string-array>
     <string name="choose_profile" msgid="343803890897657450">"Sélectionner un profil"</string>
@@ -251,13 +251,12 @@
     <string name="wifi_display_certification" msgid="1805579519992520381">"Certification affichage sans fil"</string>
     <string name="wifi_verbose_logging" msgid="1785910450009679371">"Autoriser l\'enregistrement d\'infos Wi-Fi détaillées"</string>
     <string name="wifi_scan_throttling" msgid="2985624788509913617">"Limiter la recherche Wi‑Fi"</string>
-    <string name="wifi_enhanced_mac_randomization" msgid="5437378364995776979">"Chgt aléatoire d\'adresse MAC sur Wi-Fi"</string>
+    <string name="wifi_enhanced_mac_randomization" msgid="5437378364995776979">"Chgt aléatoire d\'adresse MAC en Wi-Fi"</string>
     <string name="mobile_data_always_on" msgid="8275958101875563572">"Données mobiles toujours actives"</string>
     <string name="tethering_hardware_offload" msgid="4116053719006939161">"Accélération matérielle pour le partage de connexion"</string>
     <string name="bluetooth_show_devices_without_names" msgid="923584526471885819">"Afficher les appareils Bluetooth sans nom"</string>
     <string name="bluetooth_disable_absolute_volume" msgid="1452342324349203434">"Désactiver le volume absolu"</string>
     <string name="bluetooth_enable_gabeldorsche" msgid="9131730396242883416">"Activer Gabeldorsche"</string>
-    <string name="enhanced_connectivity" msgid="7201127377781666804">"Connectivité améliorée"</string>
     <string name="bluetooth_select_avrcp_version_string" msgid="1710571610177659127">"Version Bluetooth AVRCP"</string>
     <string name="bluetooth_select_avrcp_version_dialog_title" msgid="7846922290083709633">"Sélectionner la version Bluetooth AVRCP"</string>
     <string name="bluetooth_select_map_version_string" msgid="526308145174175327">"Version Bluetooth MAP"</string>
@@ -284,7 +283,7 @@
     <string name="wifi_display_certification_summary" msgid="8111151348106907513">"Afficher les options pour la certification de l\'affichage sans fil"</string>
     <string name="wifi_verbose_logging_summary" msgid="4993823188807767892">"Détailler les infos Wi-Fi, afficher par RSSI de SSID dans l\'outil de sélection Wi-Fi"</string>
     <string name="wifi_scan_throttling_summary" msgid="2577105472017362814">"Réduit la décharge de la batterie et améliore les performances du réseau"</string>
-    <string name="wifi_enhanced_mac_randomization_summary" msgid="1210663439867489931">"Lorsque ce mode est activé, l\'adresse e-mail MAC de cet appareil peut changer lors de chaque connexion à un réseau pour lequel le changement aléatoire d\'adresse MAC est activé."</string>
+    <string name="wifi_enhanced_mac_randomization_summary" msgid="1210663439867489931">"Lorsque ce mode est activé, l\'adresse MAC de cet appareil peut changer lors de chaque connexion à un réseau Wi-Fi pour lequel le changement aléatoire d\'adresse MAC est activé"</string>
     <string name="wifi_metered_label" msgid="8737187690304098638">"Facturé à l\'usage"</string>
     <string name="wifi_unmetered_label" msgid="6174142840934095093">"Non facturé à l\'usage"</string>
     <string name="select_logd_size_title" msgid="1604578195914595173">"Tailles des tampons de l\'enregistreur"</string>
diff --git a/packages/SettingsLib/res/values-gl/strings.xml b/packages/SettingsLib/res/values-gl/strings.xml
index f9d57c4..af43099 100644
--- a/packages/SettingsLib/res/values-gl/strings.xml
+++ b/packages/SettingsLib/res/values-gl/strings.xml
@@ -257,7 +257,6 @@
     <string name="bluetooth_show_devices_without_names" msgid="923584526471885819">"Mostrar dispositivos Bluetooth sen nomes"</string>
     <string name="bluetooth_disable_absolute_volume" msgid="1452342324349203434">"Desactivar volume absoluto"</string>
     <string name="bluetooth_enable_gabeldorsche" msgid="9131730396242883416">"Activar Gabeldorsche"</string>
-    <string name="enhanced_connectivity" msgid="7201127377781666804">"Conectividade mellorada"</string>
     <string name="bluetooth_select_avrcp_version_string" msgid="1710571610177659127">"Versión de Bluetooth AVRCP"</string>
     <string name="bluetooth_select_avrcp_version_dialog_title" msgid="7846922290083709633">"Selecciona a versión de Bluetooth AVRCP"</string>
     <string name="bluetooth_select_map_version_string" msgid="526308145174175327">"Versión de MAP de Bluetooth"</string>
diff --git a/packages/SettingsLib/res/values-gu/strings.xml b/packages/SettingsLib/res/values-gu/strings.xml
index aa1f960..3261f69 100644
--- a/packages/SettingsLib/res/values-gu/strings.xml
+++ b/packages/SettingsLib/res/values-gu/strings.xml
@@ -257,7 +257,6 @@
     <string name="bluetooth_show_devices_without_names" msgid="923584526471885819">"નામ વિનાના બ્લૂટૂથ ઉપકરણો બતાવો"</string>
     <string name="bluetooth_disable_absolute_volume" msgid="1452342324349203434">"ચોક્કસ વૉલ્યૂમને અક્ષમ કરો"</string>
     <string name="bluetooth_enable_gabeldorsche" msgid="9131730396242883416">"Gabeldorsche ચાલુ કરો"</string>
-    <string name="enhanced_connectivity" msgid="7201127377781666804">"વિસ્તૃત કનેક્ટિવિટી"</string>
     <string name="bluetooth_select_avrcp_version_string" msgid="1710571610177659127">"બ્લૂટૂથ AVRCP સંસ્કરણ"</string>
     <string name="bluetooth_select_avrcp_version_dialog_title" msgid="7846922290083709633">"બ્લૂટૂથ AVRCP સંસ્કરણ પસંદ કરો"</string>
     <string name="bluetooth_select_map_version_string" msgid="526308145174175327">"બ્લૂટૂથ MAP વર્ઝન"</string>
diff --git a/packages/SettingsLib/res/values-hi/strings.xml b/packages/SettingsLib/res/values-hi/strings.xml
index 9b6a27a..904a70e 100644
--- a/packages/SettingsLib/res/values-hi/strings.xml
+++ b/packages/SettingsLib/res/values-hi/strings.xml
@@ -257,7 +257,6 @@
     <string name="bluetooth_show_devices_without_names" msgid="923584526471885819">"बिना नाम वाले ब्लूटूथ डिवाइस दिखाएं"</string>
     <string name="bluetooth_disable_absolute_volume" msgid="1452342324349203434">"ब्लूटूथ से आवाज़ के नियंत्रण की सुविधा रोकें"</string>
     <string name="bluetooth_enable_gabeldorsche" msgid="9131730396242883416">"Gabeldorsche चालू करें"</string>
-    <string name="enhanced_connectivity" msgid="7201127377781666804">"कनेक्टिविटी बेहतर बनाएं"</string>
     <string name="bluetooth_select_avrcp_version_string" msgid="1710571610177659127">"ब्लूटूथ एवीआरसीपी वर्शन"</string>
     <string name="bluetooth_select_avrcp_version_dialog_title" msgid="7846922290083709633">"ब्लूटूथ AVRCP वर्शन चुनें"</string>
     <string name="bluetooth_select_map_version_string" msgid="526308145174175327">"ब्लूटूथ का MAP वर्शन"</string>
diff --git a/packages/SettingsLib/res/values-hr/strings.xml b/packages/SettingsLib/res/values-hr/strings.xml
index 14e3330..3edc452 100644
--- a/packages/SettingsLib/res/values-hr/strings.xml
+++ b/packages/SettingsLib/res/values-hr/strings.xml
@@ -257,7 +257,6 @@
     <string name="bluetooth_show_devices_without_names" msgid="923584526471885819">"Prikaži Bluetooth uređaje bez naziva"</string>
     <string name="bluetooth_disable_absolute_volume" msgid="1452342324349203434">"Onemogući apsolutnu glasnoću"</string>
     <string name="bluetooth_enable_gabeldorsche" msgid="9131730396242883416">"Omogući Gabeldorsche"</string>
-    <string name="enhanced_connectivity" msgid="7201127377781666804">"Poboljšana povezivost"</string>
     <string name="bluetooth_select_avrcp_version_string" msgid="1710571610177659127">"Verzija AVRCP-a za Bluetooth"</string>
     <string name="bluetooth_select_avrcp_version_dialog_title" msgid="7846922290083709633">"Odaberite verziju AVRCP-a za Bluetooth"</string>
     <string name="bluetooth_select_map_version_string" msgid="526308145174175327">"Verzija MAP-a za Bluetooth"</string>
diff --git a/packages/SettingsLib/res/values-hu/strings.xml b/packages/SettingsLib/res/values-hu/strings.xml
index d16ff03..fec2dd6 100644
--- a/packages/SettingsLib/res/values-hu/strings.xml
+++ b/packages/SettingsLib/res/values-hu/strings.xml
@@ -257,7 +257,6 @@
     <string name="bluetooth_show_devices_without_names" msgid="923584526471885819">"Név nélküli Bluetooth-eszközök megjelenítése"</string>
     <string name="bluetooth_disable_absolute_volume" msgid="1452342324349203434">"Abszolút hangerő funkció letiltása"</string>
     <string name="bluetooth_enable_gabeldorsche" msgid="9131730396242883416">"A Gabeldorsche engedélyezése"</string>
-    <string name="enhanced_connectivity" msgid="7201127377781666804">"Enhanced Connectivity"</string>
     <string name="bluetooth_select_avrcp_version_string" msgid="1710571610177659127">"A Bluetooth AVRCP-verziója"</string>
     <string name="bluetooth_select_avrcp_version_dialog_title" msgid="7846922290083709633">"A Bluetooth AVRCP-verziójának kiválasztása"</string>
     <string name="bluetooth_select_map_version_string" msgid="526308145174175327">"A Bluetooth MAP-verziója"</string>
diff --git a/packages/SettingsLib/res/values-hy/strings.xml b/packages/SettingsLib/res/values-hy/strings.xml
index b010b50..8b92640 100644
--- a/packages/SettingsLib/res/values-hy/strings.xml
+++ b/packages/SettingsLib/res/values-hy/strings.xml
@@ -37,7 +37,7 @@
     <string name="wifi_no_internet" msgid="1774198889176926299">"Ինտերնետ կապ չկա"</string>
     <string name="saved_network" msgid="7143698034077223645">"Ով է պահել՝ <xliff:g id="NAME">%1$s</xliff:g>"</string>
     <string name="connected_via_network_scorer" msgid="7665725527352893558">"Ավտոմատ կերպով կապակցվել է %1$s-ի միջոցով"</string>
-    <string name="connected_via_network_scorer_default" msgid="7973529709744526285">"Ավտոմատ կերպով միացել է ցանցի վարկանիշի ծառայության մատակարարի միջոցով"</string>
+    <string name="connected_via_network_scorer_default" msgid="7973529709744526285">"Ավտոմատ միացել է ցանցերի վարկանիշի մատակարարի միջոցով"</string>
     <string name="connected_via_passpoint" msgid="7735442932429075684">"Միացված է %1$s-ի միջոցով"</string>
     <string name="connected_via_app" msgid="3532267661404276584">"Միացված է <xliff:g id="NAME">%1$s</xliff:g>-ի միջոցով"</string>
     <string name="available_via_passpoint" msgid="1716000261192603682">"Հասանելի է %1$s-ի միջոցով"</string>
@@ -257,7 +257,6 @@
     <string name="bluetooth_show_devices_without_names" msgid="923584526471885819">"Ցուցադրել Bluetooth սարքերն առանց անունների"</string>
     <string name="bluetooth_disable_absolute_volume" msgid="1452342324349203434">"Անջատել ձայնի բացարձակ ուժգնությունը"</string>
     <string name="bluetooth_enable_gabeldorsche" msgid="9131730396242883416">"Միացնել Gabeldorsche-ը"</string>
-    <string name="enhanced_connectivity" msgid="7201127377781666804">"Տվյալների լավացված փոխանակում"</string>
     <string name="bluetooth_select_avrcp_version_string" msgid="1710571610177659127">"Bluetooth AVRCP տարբերակը"</string>
     <string name="bluetooth_select_avrcp_version_dialog_title" msgid="7846922290083709633">"Ընտրել Bluetooth AVRCP տարբերակը"</string>
     <string name="bluetooth_select_map_version_string" msgid="526308145174175327">"Bluetooth MAP-ի տարբերակ"</string>
diff --git a/packages/SettingsLib/res/values-in/arrays.xml b/packages/SettingsLib/res/values-in/arrays.xml
index 37cf189f..3ab50cc 100644
--- a/packages/SettingsLib/res/values-in/arrays.xml
+++ b/packages/SettingsLib/res/values-in/arrays.xml
@@ -31,7 +31,7 @@
     <item msgid="7852381437933824454">"Memutus sambungan..."</item>
     <item msgid="5046795712175415059">"Sambungan terputus"</item>
     <item msgid="2473654476624070462">"Gagal"</item>
-    <item msgid="9146847076036105115">"Dicekal"</item>
+    <item msgid="9146847076036105115">"Diblokir"</item>
     <item msgid="4543924085816294893">"Menghindari sambungan buruk untuk sementara"</item>
   </string-array>
   <string-array name="wifi_status_with_ssid">
diff --git a/packages/SettingsLib/res/values-in/strings.xml b/packages/SettingsLib/res/values-in/strings.xml
index 42ccd53..a6f8846 100644
--- a/packages/SettingsLib/res/values-in/strings.xml
+++ b/packages/SettingsLib/res/values-in/strings.xml
@@ -180,8 +180,8 @@
     <string name="tts_engine_settings_button" msgid="477155276199968948">"Luncurkan setelan mesin"</string>
     <string name="tts_engine_preference_section_title" msgid="3861562305498624904">"Mesin yang dipilih"</string>
     <string name="tts_general_section_title" msgid="8919671529502364567">"Umum"</string>
-    <string name="tts_reset_speech_pitch_title" msgid="7149398585468413246">"Setel ulang tinggi nada ucapan"</string>
-    <string name="tts_reset_speech_pitch_summary" msgid="6822904157021406449">"Setel ulang tinggi nada diucapkannya teks menjadi default."</string>
+    <string name="tts_reset_speech_pitch_title" msgid="7149398585468413246">"Reset tinggi nada ucapan"</string>
+    <string name="tts_reset_speech_pitch_summary" msgid="6822904157021406449">"Reset tinggi nada diucapkannya teks menjadi default."</string>
   <string-array name="tts_rate_entries">
     <item msgid="9004239613505400644">"Sangat lambat"</item>
     <item msgid="1815382991399815061">"Lambat"</item>
@@ -257,7 +257,6 @@
     <string name="bluetooth_show_devices_without_names" msgid="923584526471885819">"Tampilkan perangkat Bluetooth tanpa nama"</string>
     <string name="bluetooth_disable_absolute_volume" msgid="1452342324349203434">"Nonaktifkan volume absolut"</string>
     <string name="bluetooth_enable_gabeldorsche" msgid="9131730396242883416">"Aktifkan Gabeldorsche"</string>
-    <string name="enhanced_connectivity" msgid="7201127377781666804">"Konektivitas Yang Disempurnakan"</string>
     <string name="bluetooth_select_avrcp_version_string" msgid="1710571610177659127">"Versi AVRCP Bluetooth"</string>
     <string name="bluetooth_select_avrcp_version_dialog_title" msgid="7846922290083709633">"Pilih Versi AVRCP Bluetooth"</string>
     <string name="bluetooth_select_map_version_string" msgid="526308145174175327">"Versi MAP Bluetooth"</string>
diff --git a/packages/SettingsLib/res/values-is/strings.xml b/packages/SettingsLib/res/values-is/strings.xml
index 0ebc341..caf2323 100644
--- a/packages/SettingsLib/res/values-is/strings.xml
+++ b/packages/SettingsLib/res/values-is/strings.xml
@@ -257,7 +257,6 @@
     <string name="bluetooth_show_devices_without_names" msgid="923584526471885819">"Sýna Bluetooth-tæki án heita"</string>
     <string name="bluetooth_disable_absolute_volume" msgid="1452342324349203434">"Slökkva á samstillingu hljóðstyrks"</string>
     <string name="bluetooth_enable_gabeldorsche" msgid="9131730396242883416">"Virkja Gabeldorsche"</string>
-    <string name="enhanced_connectivity" msgid="7201127377781666804">"Aukin tengigeta"</string>
     <string name="bluetooth_select_avrcp_version_string" msgid="1710571610177659127">"Bluetooth AVRCP-útgáfa"</string>
     <string name="bluetooth_select_avrcp_version_dialog_title" msgid="7846922290083709633">"Velja Bluetooth AVRCP-útgáfu"</string>
     <string name="bluetooth_select_map_version_string" msgid="526308145174175327">"Bluetooth MAP-útgáfa"</string>
diff --git a/packages/SettingsLib/res/values-it/strings.xml b/packages/SettingsLib/res/values-it/strings.xml
index 50fdfc9..8d18727 100644
--- a/packages/SettingsLib/res/values-it/strings.xml
+++ b/packages/SettingsLib/res/values-it/strings.xml
@@ -257,7 +257,6 @@
     <string name="bluetooth_show_devices_without_names" msgid="923584526471885819">"Mostra dispositivi Bluetooth senza nome"</string>
     <string name="bluetooth_disable_absolute_volume" msgid="1452342324349203434">"Disattiva volume assoluto"</string>
     <string name="bluetooth_enable_gabeldorsche" msgid="9131730396242883416">"Attiva Gabeldorsche"</string>
-    <string name="enhanced_connectivity" msgid="7201127377781666804">"Connettività migliorata"</string>
     <string name="bluetooth_select_avrcp_version_string" msgid="1710571610177659127">"Versione Bluetooth AVRCP"</string>
     <string name="bluetooth_select_avrcp_version_dialog_title" msgid="7846922290083709633">"Seleziona versione Bluetooth AVRCP"</string>
     <string name="bluetooth_select_map_version_string" msgid="526308145174175327">"Versione Bluetooth MAP"</string>
diff --git a/packages/SettingsLib/res/values-iw/strings.xml b/packages/SettingsLib/res/values-iw/strings.xml
index fb7d00f..fff881c 100644
--- a/packages/SettingsLib/res/values-iw/strings.xml
+++ b/packages/SettingsLib/res/values-iw/strings.xml
@@ -257,7 +257,6 @@
     <string name="bluetooth_show_devices_without_names" msgid="923584526471885819">"‏הצגת מכשירי Bluetooth ללא שמות"</string>
     <string name="bluetooth_disable_absolute_volume" msgid="1452342324349203434">"השבת עוצמת קול מוחלטת"</string>
     <string name="bluetooth_enable_gabeldorsche" msgid="9131730396242883416">"‏הפעלת Gabeldorsche"</string>
-    <string name="enhanced_connectivity" msgid="7201127377781666804">"קישוריות משופרת"</string>
     <string name="bluetooth_select_avrcp_version_string" msgid="1710571610177659127">"‏Bluetooth גרסה AVRCP"</string>
     <string name="bluetooth_select_avrcp_version_dialog_title" msgid="7846922290083709633">"‏בחר Bluetooth גרסה AVRCP"</string>
     <string name="bluetooth_select_map_version_string" msgid="526308145174175327">"‏גרסת Bluetooth MAP"</string>
diff --git a/packages/SettingsLib/res/values-ja/strings.xml b/packages/SettingsLib/res/values-ja/strings.xml
index 3537cea..5e579b7 100644
--- a/packages/SettingsLib/res/values-ja/strings.xml
+++ b/packages/SettingsLib/res/values-ja/strings.xml
@@ -257,7 +257,6 @@
     <string name="bluetooth_show_devices_without_names" msgid="923584526471885819">"Bluetooth デバイスを名前なしで表示"</string>
     <string name="bluetooth_disable_absolute_volume" msgid="1452342324349203434">"絶対音量を無効にする"</string>
     <string name="bluetooth_enable_gabeldorsche" msgid="9131730396242883416">"Gabeldorsche を有効にする"</string>
-    <string name="enhanced_connectivity" msgid="7201127377781666804">"接続強化"</string>
     <string name="bluetooth_select_avrcp_version_string" msgid="1710571610177659127">"Bluetooth AVRCP バージョン"</string>
     <string name="bluetooth_select_avrcp_version_dialog_title" msgid="7846922290083709633">"Bluetooth AVRCP バージョンを選択する"</string>
     <string name="bluetooth_select_map_version_string" msgid="526308145174175327">"Bluetooth MAP バージョン"</string>
diff --git a/packages/SettingsLib/res/values-ka/strings.xml b/packages/SettingsLib/res/values-ka/strings.xml
index 9b671a8..1b5fae9 100644
--- a/packages/SettingsLib/res/values-ka/strings.xml
+++ b/packages/SettingsLib/res/values-ka/strings.xml
@@ -257,7 +257,6 @@
     <string name="bluetooth_show_devices_without_names" msgid="923584526471885819">"Bluetooth-მოწყობილობების ჩვენება სახელების გარეშე"</string>
     <string name="bluetooth_disable_absolute_volume" msgid="1452342324349203434">"ხმის აბსოლუტური სიძლიერის გათიშვა"</string>
     <string name="bluetooth_enable_gabeldorsche" msgid="9131730396242883416">"Gabeldorsche-ის ჩართვა"</string>
-    <string name="enhanced_connectivity" msgid="7201127377781666804">"კავშირის გაძლიერებული შესაძლებლობა"</string>
     <string name="bluetooth_select_avrcp_version_string" msgid="1710571610177659127">"Bluetooth-ის AVRCP-ის ვერსია"</string>
     <string name="bluetooth_select_avrcp_version_dialog_title" msgid="7846922290083709633">"აირჩიეთ Bluetooth-ის AVRCP-ის ვერსია"</string>
     <string name="bluetooth_select_map_version_string" msgid="526308145174175327">"Bluetooth MAP-ის ვერსია"</string>
diff --git a/packages/SettingsLib/res/values-kk/strings.xml b/packages/SettingsLib/res/values-kk/strings.xml
index 279aca0..9c290e9 100644
--- a/packages/SettingsLib/res/values-kk/strings.xml
+++ b/packages/SettingsLib/res/values-kk/strings.xml
@@ -257,7 +257,6 @@
     <string name="bluetooth_show_devices_without_names" msgid="923584526471885819">"Bluetooth құрылғыларын атаусыз көрсету"</string>
     <string name="bluetooth_disable_absolute_volume" msgid="1452342324349203434">"Абсолютті дыбыс деңгейін өшіру"</string>
     <string name="bluetooth_enable_gabeldorsche" msgid="9131730396242883416">"Gabeldorsche функциясын іске қосу"</string>
-    <string name="enhanced_connectivity" msgid="7201127377781666804">"Жетілдірілген байланыс"</string>
     <string name="bluetooth_select_avrcp_version_string" msgid="1710571610177659127">"Bluetooth AVRCP нұсқасы"</string>
     <string name="bluetooth_select_avrcp_version_dialog_title" msgid="7846922290083709633">"Bluetooth AVRCP нұсқасын таңдау"</string>
     <string name="bluetooth_select_map_version_string" msgid="526308145174175327">"Bluetooth MAP нұсқасы"</string>
diff --git a/packages/SettingsLib/res/values-km/strings.xml b/packages/SettingsLib/res/values-km/strings.xml
index 03065e8..2878db1 100644
--- a/packages/SettingsLib/res/values-km/strings.xml
+++ b/packages/SettingsLib/res/values-km/strings.xml
@@ -257,7 +257,6 @@
     <string name="bluetooth_show_devices_without_names" msgid="923584526471885819">"បង្ហាញ​ឧបករណ៍​ប្ល៊ូធូស​គ្មានឈ្មោះ"</string>
     <string name="bluetooth_disable_absolute_volume" msgid="1452342324349203434">"បិទកម្រិតសំឡេងលឺខ្លាំង"</string>
     <string name="bluetooth_enable_gabeldorsche" msgid="9131730396242883416">"បើក Gabeldorsche"</string>
-    <string name="enhanced_connectivity" msgid="7201127377781666804">"ការតភ្ជាប់​ដែលបានធ្វើឱ្យប្រសើរឡើង"</string>
     <string name="bluetooth_select_avrcp_version_string" msgid="1710571610177659127">"កំណែប្ល៊ូធូស AVRCP"</string>
     <string name="bluetooth_select_avrcp_version_dialog_title" msgid="7846922290083709633">"ជ្រើសរើសកំណែប្ល៊ូធូស AVRCP"</string>
     <string name="bluetooth_select_map_version_string" msgid="526308145174175327">"កំណែ​ប៊្លូធូស MAP"</string>
diff --git a/packages/SettingsLib/res/values-kn/strings.xml b/packages/SettingsLib/res/values-kn/strings.xml
index 71c5e49..7b01056 100644
--- a/packages/SettingsLib/res/values-kn/strings.xml
+++ b/packages/SettingsLib/res/values-kn/strings.xml
@@ -257,7 +257,6 @@
     <string name="bluetooth_show_devices_without_names" msgid="923584526471885819">"ಹೆಸರುಗಳಿಲ್ಲದ ಬ್ಲೂಟೂತ್ ಸಾಧನಗಳನ್ನು ತೋರಿಸಿ"</string>
     <string name="bluetooth_disable_absolute_volume" msgid="1452342324349203434">"ಸಂಪೂರ್ಣ ವಾಲ್ಯೂಮ್‌ ನಿಷ್ಕ್ರಿಯಗೊಳಿಸಿ"</string>
     <string name="bluetooth_enable_gabeldorsche" msgid="9131730396242883416">"Gabeldorsche ಅನ್ನು ಸಕ್ರಿಯಗೊಳಿಸಿ"</string>
-    <string name="enhanced_connectivity" msgid="7201127377781666804">"ವರ್ಧಿತ ಸಂಪರ್ಕ"</string>
     <string name="bluetooth_select_avrcp_version_string" msgid="1710571610177659127">"ಬ್ಲೂಟೂತ್ AVRCP ಆವೃತ್ತಿ"</string>
     <string name="bluetooth_select_avrcp_version_dialog_title" msgid="7846922290083709633">"ಬ್ಲೂಟೂತ್ AVRCP ಆವೃತ್ತಿಯನ್ನು ಆಯ್ಕೆ ಮಾಡಿ"</string>
     <string name="bluetooth_select_map_version_string" msgid="526308145174175327">"ಬ್ಲೂಟೂತ್ MAP ಆವೃತ್ತಿ"</string>
diff --git a/packages/SettingsLib/res/values-ko/strings.xml b/packages/SettingsLib/res/values-ko/strings.xml
index 5d82eae..696ed29 100644
--- a/packages/SettingsLib/res/values-ko/strings.xml
+++ b/packages/SettingsLib/res/values-ko/strings.xml
@@ -257,7 +257,6 @@
     <string name="bluetooth_show_devices_without_names" msgid="923584526471885819">"이름이 없는 블루투스 기기 표시"</string>
     <string name="bluetooth_disable_absolute_volume" msgid="1452342324349203434">"절대 볼륨 사용 안함"</string>
     <string name="bluetooth_enable_gabeldorsche" msgid="9131730396242883416">"Gabeldorsche 사용 설정"</string>
-    <string name="enhanced_connectivity" msgid="7201127377781666804">"향상된 연결"</string>
     <string name="bluetooth_select_avrcp_version_string" msgid="1710571610177659127">"블루투스 AVRCP 버전"</string>
     <string name="bluetooth_select_avrcp_version_dialog_title" msgid="7846922290083709633">"블루투스 AVRCP 버전 선택"</string>
     <string name="bluetooth_select_map_version_string" msgid="526308145174175327">"블루투스 MAP 버전"</string>
diff --git a/packages/SettingsLib/res/values-ky/strings.xml b/packages/SettingsLib/res/values-ky/strings.xml
index 2702392..c4b5f7e 100644
--- a/packages/SettingsLib/res/values-ky/strings.xml
+++ b/packages/SettingsLib/res/values-ky/strings.xml
@@ -257,7 +257,6 @@
     <string name="bluetooth_show_devices_without_names" msgid="923584526471885819">"Аталышсыз Bluetooth түзмөктөрү көрсөтүлсүн"</string>
     <string name="bluetooth_disable_absolute_volume" msgid="1452342324349203434">"Үндүн абсолюттук деңгээли өчүрүлсүн"</string>
     <string name="bluetooth_enable_gabeldorsche" msgid="9131730396242883416">"Gabeldorsche функциясын иштетүү"</string>
-    <string name="enhanced_connectivity" msgid="7201127377781666804">"Жакшыртылган туташуу"</string>
     <string name="bluetooth_select_avrcp_version_string" msgid="1710571610177659127">"Bluetooth AVRCP версиясы"</string>
     <string name="bluetooth_select_avrcp_version_dialog_title" msgid="7846922290083709633">"Bluetooth AVRCP версиясын тандоо"</string>
     <string name="bluetooth_select_map_version_string" msgid="526308145174175327">"Bluetooth MAP версиясы"</string>
diff --git a/packages/SettingsLib/res/values-lo/strings.xml b/packages/SettingsLib/res/values-lo/strings.xml
index 7a2c338..a72861e 100644
--- a/packages/SettingsLib/res/values-lo/strings.xml
+++ b/packages/SettingsLib/res/values-lo/strings.xml
@@ -257,7 +257,6 @@
     <string name="bluetooth_show_devices_without_names" msgid="923584526471885819">"ສະແດງອຸປະກອນ Bluetooth ທີ່ບໍ່ມີຊື່"</string>
     <string name="bluetooth_disable_absolute_volume" msgid="1452342324349203434">"ປິດໃຊ້ລະດັບສຽງສົມບູນ"</string>
     <string name="bluetooth_enable_gabeldorsche" msgid="9131730396242883416">"ເປີດໃຊ້ Gabeldorsche"</string>
-    <string name="enhanced_connectivity" msgid="7201127377781666804">"ການເຊື່ອມຕໍ່ທີ່ເສີມແຕ່ງແລ້ວ"</string>
     <string name="bluetooth_select_avrcp_version_string" msgid="1710571610177659127">"ເວີຊັນ Bluetooth AVRCP"</string>
     <string name="bluetooth_select_avrcp_version_dialog_title" msgid="7846922290083709633">"ເລືອກເວີຊັນ Bluetooth AVRCP"</string>
     <string name="bluetooth_select_map_version_string" msgid="526308145174175327">"ເວີຊັນ Bluetooth MAP"</string>
diff --git a/packages/SettingsLib/res/values-lt/strings.xml b/packages/SettingsLib/res/values-lt/strings.xml
index b73aa66..c72bf21 100644
--- a/packages/SettingsLib/res/values-lt/strings.xml
+++ b/packages/SettingsLib/res/values-lt/strings.xml
@@ -257,7 +257,6 @@
     <string name="bluetooth_show_devices_without_names" msgid="923584526471885819">"Rodyti „Bluetooth“ įrenginius be pavadinimų"</string>
     <string name="bluetooth_disable_absolute_volume" msgid="1452342324349203434">"Išjungti didžiausią garsą"</string>
     <string name="bluetooth_enable_gabeldorsche" msgid="9131730396242883416">"Įgalinti „Gabeldorsche“"</string>
-    <string name="enhanced_connectivity" msgid="7201127377781666804">"Patobulintas ryšys"</string>
     <string name="bluetooth_select_avrcp_version_string" msgid="1710571610177659127">"„Bluetooth“ AVRCP versija"</string>
     <string name="bluetooth_select_avrcp_version_dialog_title" msgid="7846922290083709633">"Pasirinkite „Bluetooth“ AVRCP versiją"</string>
     <string name="bluetooth_select_map_version_string" msgid="526308145174175327">"„Bluetooth“ MRK versija"</string>
diff --git a/packages/SettingsLib/res/values-lv/strings.xml b/packages/SettingsLib/res/values-lv/strings.xml
index 8e5d24c..d95e57d 100644
--- a/packages/SettingsLib/res/values-lv/strings.xml
+++ b/packages/SettingsLib/res/values-lv/strings.xml
@@ -257,7 +257,6 @@
     <string name="bluetooth_show_devices_without_names" msgid="923584526471885819">"Rādīt Bluetooth ierīces bez nosaukumiem"</string>
     <string name="bluetooth_disable_absolute_volume" msgid="1452342324349203434">"Atspējot absolūto skaļumu"</string>
     <string name="bluetooth_enable_gabeldorsche" msgid="9131730396242883416">"Iespējot Gabeldorsche"</string>
-    <string name="enhanced_connectivity" msgid="7201127377781666804">"Uzlabota savienojamība"</string>
     <string name="bluetooth_select_avrcp_version_string" msgid="1710571610177659127">"Bluetooth AVRCP versija"</string>
     <string name="bluetooth_select_avrcp_version_dialog_title" msgid="7846922290083709633">"Atlasiet Bluetooth AVRCP versiju"</string>
     <string name="bluetooth_select_map_version_string" msgid="526308145174175327">"Bluetooth MAP versija"</string>
diff --git a/packages/SettingsLib/res/values-mk/strings.xml b/packages/SettingsLib/res/values-mk/strings.xml
index 34299d8..fb7b634 100644
--- a/packages/SettingsLib/res/values-mk/strings.xml
+++ b/packages/SettingsLib/res/values-mk/strings.xml
@@ -257,7 +257,6 @@
     <string name="bluetooth_show_devices_without_names" msgid="923584526471885819">"Прикажувај уреди со Bluetooth без имиња"</string>
     <string name="bluetooth_disable_absolute_volume" msgid="1452342324349203434">"Оневозможете апсолутна јачина на звук"</string>
     <string name="bluetooth_enable_gabeldorsche" msgid="9131730396242883416">"Овозможи Gabeldorsche"</string>
-    <string name="enhanced_connectivity" msgid="7201127377781666804">"Подобрена поврзливост"</string>
     <string name="bluetooth_select_avrcp_version_string" msgid="1710571610177659127">"Верзија Bluetooth AVRCP"</string>
     <string name="bluetooth_select_avrcp_version_dialog_title" msgid="7846922290083709633">"Изберете верзија Bluetooth AVRCP"</string>
     <string name="bluetooth_select_map_version_string" msgid="526308145174175327">"Верзија на Bluetooth MAP"</string>
diff --git a/packages/SettingsLib/res/values-ml/strings.xml b/packages/SettingsLib/res/values-ml/strings.xml
index c95f8bf..3c281c8 100644
--- a/packages/SettingsLib/res/values-ml/strings.xml
+++ b/packages/SettingsLib/res/values-ml/strings.xml
@@ -257,7 +257,6 @@
     <string name="bluetooth_show_devices_without_names" msgid="923584526471885819">"പേരില്ലാത്ത Bluetooth ഉപകരണങ്ങൾ കാണിക്കുക"</string>
     <string name="bluetooth_disable_absolute_volume" msgid="1452342324349203434">"അബ്‌സൊല്യൂട്ട് വോളിയം പ്രവർത്തനരഹിതമാക്കുക"</string>
     <string name="bluetooth_enable_gabeldorsche" msgid="9131730396242883416">"Gabeldorsche പ്രവർത്തനക്ഷമമാക്കുക"</string>
-    <string name="enhanced_connectivity" msgid="7201127377781666804">"മെച്ചപ്പെടുത്തിയ കണക്റ്റിവിറ്റി"</string>
     <string name="bluetooth_select_avrcp_version_string" msgid="1710571610177659127">"Bluetooth AVRCP പതിപ്പ്"</string>
     <string name="bluetooth_select_avrcp_version_dialog_title" msgid="7846922290083709633">"Bluetooth AVRCP പതിപ്പ് തിരഞ്ഞെടുക്കുക"</string>
     <string name="bluetooth_select_map_version_string" msgid="526308145174175327">"Bluetooth MAP പതിപ്പ്"</string>
diff --git a/packages/SettingsLib/res/values-mn/strings.xml b/packages/SettingsLib/res/values-mn/strings.xml
index 8407db6..37fc5b4 100644
--- a/packages/SettingsLib/res/values-mn/strings.xml
+++ b/packages/SettingsLib/res/values-mn/strings.xml
@@ -257,7 +257,6 @@
     <string name="bluetooth_show_devices_without_names" msgid="923584526471885819">"Нэргүй Bluetooth төхөөрөмжийг харуулах"</string>
     <string name="bluetooth_disable_absolute_volume" msgid="1452342324349203434">"Үнэмлэхүй дууны түвшинг идэвхгүй болгох"</string>
     <string name="bluetooth_enable_gabeldorsche" msgid="9131730396242883416">"Gabeldorsche-г идэвхжүүлэх"</string>
-    <string name="enhanced_connectivity" msgid="7201127377781666804">"Сайжруулсан холболт"</string>
     <string name="bluetooth_select_avrcp_version_string" msgid="1710571610177659127">"Bluetooth AVRCP хувилбар"</string>
     <string name="bluetooth_select_avrcp_version_dialog_title" msgid="7846922290083709633">"Bluetooth AVRCP хувилбарыг сонгох"</string>
     <string name="bluetooth_select_map_version_string" msgid="526308145174175327">"Bluetooth MAP хувилбар"</string>
diff --git a/packages/SettingsLib/res/values-mr/strings.xml b/packages/SettingsLib/res/values-mr/strings.xml
index c50f365..360f158 100644
--- a/packages/SettingsLib/res/values-mr/strings.xml
+++ b/packages/SettingsLib/res/values-mr/strings.xml
@@ -257,7 +257,6 @@
     <string name="bluetooth_show_devices_without_names" msgid="923584526471885819">"नावांशिवाय ब्‍लूटूथ डिव्‍हाइस दाखवा"</string>
     <string name="bluetooth_disable_absolute_volume" msgid="1452342324349203434">"संपूर्ण आवाज बंद करा"</string>
     <string name="bluetooth_enable_gabeldorsche" msgid="9131730396242883416">"गाबलडॉर्ष सुरू करा"</string>
-    <string name="enhanced_connectivity" msgid="7201127377781666804">"वर्धित कनेक्टिव्हिटी"</string>
     <string name="bluetooth_select_avrcp_version_string" msgid="1710571610177659127">"ब्लूटूथ AVRCP आवृत्ती"</string>
     <string name="bluetooth_select_avrcp_version_dialog_title" msgid="7846922290083709633">"ब्लूटूथ AVRCP आवृत्ती निवडा"</string>
     <string name="bluetooth_select_map_version_string" msgid="526308145174175327">"ब्लूटूथ MAP आवृत्ती"</string>
diff --git a/packages/SettingsLib/res/values-ms/strings.xml b/packages/SettingsLib/res/values-ms/strings.xml
index a0a434f..68356df2 100644
--- a/packages/SettingsLib/res/values-ms/strings.xml
+++ b/packages/SettingsLib/res/values-ms/strings.xml
@@ -257,7 +257,6 @@
     <string name="bluetooth_show_devices_without_names" msgid="923584526471885819">"Tunjukkan peranti Bluetooth tanpa nama"</string>
     <string name="bluetooth_disable_absolute_volume" msgid="1452342324349203434">"Lumpuhkan kelantangan mutlak"</string>
     <string name="bluetooth_enable_gabeldorsche" msgid="9131730396242883416">"Dayakan Gabeldorsche"</string>
-    <string name="enhanced_connectivity" msgid="7201127377781666804">"Kesambungan Dipertingkat"</string>
     <string name="bluetooth_select_avrcp_version_string" msgid="1710571610177659127">"Versi AVRCP Bluetooth"</string>
     <string name="bluetooth_select_avrcp_version_dialog_title" msgid="7846922290083709633">"Pilih Versi AVRCP Bluetooth"</string>
     <string name="bluetooth_select_map_version_string" msgid="526308145174175327">"Versi MAP Bluetooth"</string>
diff --git a/packages/SettingsLib/res/values-my/strings.xml b/packages/SettingsLib/res/values-my/strings.xml
index fa49929..3729a83 100644
--- a/packages/SettingsLib/res/values-my/strings.xml
+++ b/packages/SettingsLib/res/values-my/strings.xml
@@ -257,7 +257,6 @@
     <string name="bluetooth_show_devices_without_names" msgid="923584526471885819">"အမည်မရှိသော ဘလူးတုသ်စက်ပစ္စည်းများကို ပြသရန်"</string>
     <string name="bluetooth_disable_absolute_volume" msgid="1452342324349203434">"ပကတိ အသံနှုန်း သတ်မှတ်ချက် ပိတ်ရန်"</string>
     <string name="bluetooth_enable_gabeldorsche" msgid="9131730396242883416">"Gabeldorsche ကို ဖွင့်ရန်"</string>
-    <string name="enhanced_connectivity" msgid="7201127377781666804">"အရည်အသွေးမြှင့်တင်ထားသော ချိတ်ဆက်နိုင်မှု"</string>
     <string name="bluetooth_select_avrcp_version_string" msgid="1710571610177659127">"ဘလူးတုသ် AVRCP ဗားရှင်း"</string>
     <string name="bluetooth_select_avrcp_version_dialog_title" msgid="7846922290083709633">"ဘလူးတုသ် AVRCP ဗားရှင်းကို ရွေးပါ"</string>
     <string name="bluetooth_select_map_version_string" msgid="526308145174175327">"ဘလူးတုသ် MAP ဗားရှင်း"</string>
diff --git a/packages/SettingsLib/res/values-nb/strings.xml b/packages/SettingsLib/res/values-nb/strings.xml
index aeaba31..0e0e761 100644
--- a/packages/SettingsLib/res/values-nb/strings.xml
+++ b/packages/SettingsLib/res/values-nb/strings.xml
@@ -257,7 +257,6 @@
     <string name="bluetooth_show_devices_without_names" msgid="923584526471885819">"Vis Bluetooth-enheter uten navn"</string>
     <string name="bluetooth_disable_absolute_volume" msgid="1452342324349203434">"Slå av funksjonen for absolutt volum"</string>
     <string name="bluetooth_enable_gabeldorsche" msgid="9131730396242883416">"Aktiver Gabeldorsche"</string>
-    <string name="enhanced_connectivity" msgid="7201127377781666804">"Forbedret tilkobling"</string>
     <string name="bluetooth_select_avrcp_version_string" msgid="1710571610177659127">"Bluetooth AVRCP-versjon"</string>
     <string name="bluetooth_select_avrcp_version_dialog_title" msgid="7846922290083709633">"Velg Bluetooth AVRCP-versjon"</string>
     <string name="bluetooth_select_map_version_string" msgid="526308145174175327">"Bluetooth MAP-versjon"</string>
diff --git a/packages/SettingsLib/res/values-ne/strings.xml b/packages/SettingsLib/res/values-ne/strings.xml
index 4a2c171..762d830 100644
--- a/packages/SettingsLib/res/values-ne/strings.xml
+++ b/packages/SettingsLib/res/values-ne/strings.xml
@@ -257,7 +257,6 @@
     <string name="bluetooth_show_devices_without_names" msgid="923584526471885819">"नामकरण नगरिएका ब्लुटुथ यन्त्रहरू देखाउनुहोस्"</string>
     <string name="bluetooth_disable_absolute_volume" msgid="1452342324349203434">"निरपेक्ष आवाज असक्षम गर्नुहोस्"</string>
     <string name="bluetooth_enable_gabeldorsche" msgid="9131730396242883416">"Gabeldorsche सक्षम पार्नुहोस्"</string>
-    <string name="enhanced_connectivity" msgid="7201127377781666804">"परिष्कृत जडान"</string>
     <string name="bluetooth_select_avrcp_version_string" msgid="1710571610177659127">"ब्लुटुथको AVRCP संस्करण"</string>
     <string name="bluetooth_select_avrcp_version_dialog_title" msgid="7846922290083709633">"ब्लुटुथको AVRCP संस्करण चयन गर्नुहोस्"</string>
     <string name="bluetooth_select_map_version_string" msgid="526308145174175327">"ब्लुटुथको MAP संस्करण"</string>
diff --git a/packages/SettingsLib/res/values-nl/strings.xml b/packages/SettingsLib/res/values-nl/strings.xml
index 32cc39e..27f5dc9 100644
--- a/packages/SettingsLib/res/values-nl/strings.xml
+++ b/packages/SettingsLib/res/values-nl/strings.xml
@@ -93,8 +93,8 @@
     <string name="bluetooth_profile_sap" msgid="8304170950447934386">"Sim-toegang"</string>
     <string name="bluetooth_profile_a2dp_high_quality" msgid="4739440941324792775">"HD-audio: <xliff:g id="CODEC_NAME">%1$s</xliff:g>"</string>
     <string name="bluetooth_profile_a2dp_high_quality_unknown_codec" msgid="2477639096903834374">"HD-audio"</string>
-    <string name="bluetooth_profile_hearing_aid" msgid="58154575573984914">"Gehoorapparaten"</string>
-    <string name="bluetooth_hearing_aid_profile_summary_connected" msgid="8191273236809964030">"Verbonden met gehoorapparaten"</string>
+    <string name="bluetooth_profile_hearing_aid" msgid="58154575573984914">"Hoortoestellen"</string>
+    <string name="bluetooth_hearing_aid_profile_summary_connected" msgid="8191273236809964030">"Verbonden met hoortoestellen"</string>
     <string name="bluetooth_a2dp_profile_summary_connected" msgid="7422607970115444153">"Verbonden met audio van medium"</string>
     <string name="bluetooth_headset_profile_summary_connected" msgid="2420981566026949688">"Verbonden met audio van telefoon"</string>
     <string name="bluetooth_opp_profile_summary_connected" msgid="2393521801478157362">"Verbonden met server voor bestandsoverdracht"</string>
@@ -111,7 +111,7 @@
     <string name="bluetooth_headset_profile_summary_use_for" msgid="808970643123744170">"Gebruiken voor audio van telefoon"</string>
     <string name="bluetooth_opp_profile_summary_use_for" msgid="461981154387015457">"Gebruiken voor bestandsoverdracht"</string>
     <string name="bluetooth_hid_profile_summary_use_for" msgid="4289460627406490952">"Gebruiken voor invoer"</string>
-    <string name="bluetooth_hearing_aid_profile_summary_use_for" msgid="7689393730163320483">"Gebruiken voor gehoorapparaten"</string>
+    <string name="bluetooth_hearing_aid_profile_summary_use_for" msgid="7689393730163320483">"Gebruiken voor hoortoestellen"</string>
     <string name="bluetooth_pairing_accept" msgid="2054232610815498004">"Koppelen"</string>
     <string name="bluetooth_pairing_accept_all_caps" msgid="2734383073450506220">"KOPPELEN"</string>
     <string name="bluetooth_pairing_decline" msgid="6483118841204885890">"Annuleren"</string>
@@ -127,8 +127,8 @@
     <string name="bluetooth_talkback_headphone" msgid="8613073829180337091">"Hoofdtelefoon"</string>
     <string name="bluetooth_talkback_input_peripheral" msgid="5133944817800149942">"Randapparaat voor invoer"</string>
     <string name="bluetooth_talkback_bluetooth" msgid="1143241359781999989">"Bluetooth"</string>
-    <string name="bluetooth_hearingaid_left_pairing_message" msgid="8561855779703533591">"Linker gehoorapparaat koppelen…"</string>
-    <string name="bluetooth_hearingaid_right_pairing_message" msgid="2655347721696331048">"Rechter gehoorapparaat koppelen…"</string>
+    <string name="bluetooth_hearingaid_left_pairing_message" msgid="8561855779703533591">"Linker hoortoestel koppelen…"</string>
+    <string name="bluetooth_hearingaid_right_pairing_message" msgid="2655347721696331048">"Rechter hoortoestel koppelen…"</string>
     <string name="bluetooth_hearingaid_left_battery_level" msgid="7375621694748104876">"Links: batterijniveau <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g>"</string>
     <string name="bluetooth_hearingaid_right_battery_level" msgid="1850094448499089312">"Rechts: batterijniveau <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g>"</string>
     <string name="accessibility_wifi_off" msgid="1195445715254137155">"Wifi: uitgeschakeld."</string>
@@ -257,7 +257,6 @@
     <string name="bluetooth_show_devices_without_names" msgid="923584526471885819">"Bluetooth-apparaten zonder namen weergeven"</string>
     <string name="bluetooth_disable_absolute_volume" msgid="1452342324349203434">"Absoluut volume uitschakelen"</string>
     <string name="bluetooth_enable_gabeldorsche" msgid="9131730396242883416">"Gabeldorsche inschakelen"</string>
-    <string name="enhanced_connectivity" msgid="7201127377781666804">"Verbeterde connectiviteit"</string>
     <string name="bluetooth_select_avrcp_version_string" msgid="1710571610177659127">"Bluetooth-AVRCP-versie"</string>
     <string name="bluetooth_select_avrcp_version_dialog_title" msgid="7846922290083709633">"Bluetooth-AVRCP-versie selecteren"</string>
     <string name="bluetooth_select_map_version_string" msgid="526308145174175327">"MAP-versie voor bluetooth"</string>
diff --git a/packages/SettingsLib/res/values-or/strings.xml b/packages/SettingsLib/res/values-or/strings.xml
index 8e5bf25..d200f50 100644
--- a/packages/SettingsLib/res/values-or/strings.xml
+++ b/packages/SettingsLib/res/values-or/strings.xml
@@ -257,7 +257,6 @@
     <string name="bluetooth_show_devices_without_names" msgid="923584526471885819">"ବ୍ଲୁଟୂଥ୍‍‌ ଡିଭାଇସ୍‌ଗୁଡ଼ିକୁ ନାମ ବିନା ଦେଖନ୍ତୁ"</string>
     <string name="bluetooth_disable_absolute_volume" msgid="1452342324349203434">"ପୂର୍ଣ୍ଣ ଭଲ୍ୟୁମ୍‌ ଅକ୍ଷମ କରନ୍ତୁ"</string>
     <string name="bluetooth_enable_gabeldorsche" msgid="9131730396242883416">"ଗାବେଲ୍‌ଡୋର୍ସ ସକ୍ରିୟ କରନ୍ତୁ"</string>
-    <string name="enhanced_connectivity" msgid="7201127377781666804">"ଏନହାନ୍ସଡ୍ କନେକ୍ଟିଭିଟି"</string>
     <string name="bluetooth_select_avrcp_version_string" msgid="1710571610177659127">"ବ୍ଲୁଟୂଥ୍‌ AVRCP ଭର୍ସନ୍"</string>
     <string name="bluetooth_select_avrcp_version_dialog_title" msgid="7846922290083709633">"ବ୍ଲୁଟୂଥ୍‍‌ AVRCP ଭର୍ସନ୍‌"</string>
     <string name="bluetooth_select_map_version_string" msgid="526308145174175327">"ବ୍ଲୁଟୁଥ୍ MAP ସଂସ୍କରଣ"</string>
diff --git a/packages/SettingsLib/res/values-pa/strings.xml b/packages/SettingsLib/res/values-pa/strings.xml
index 1570013..354ee12 100644
--- a/packages/SettingsLib/res/values-pa/strings.xml
+++ b/packages/SettingsLib/res/values-pa/strings.xml
@@ -257,7 +257,6 @@
     <string name="bluetooth_show_devices_without_names" msgid="923584526471885819">"ਅਨਾਮ ਬਲੂਟੁੱਥ ਡੀਵਾਈਸਾਂ ਦਿਖਾਓ"</string>
     <string name="bluetooth_disable_absolute_volume" msgid="1452342324349203434">"ਪੂਰਨ ਅਵਾਜ਼ ਨੂੰ ਬੰਦ ਕਰੋ"</string>
     <string name="bluetooth_enable_gabeldorsche" msgid="9131730396242883416">"Gabeldorsche ਨੂੰ ਚਾਲੂ ਕਰੋ"</string>
-    <string name="enhanced_connectivity" msgid="7201127377781666804">"ਵਿਸਤ੍ਰਿਤ ਕਨੈਕਟੀਵਿਟੀ"</string>
     <string name="bluetooth_select_avrcp_version_string" msgid="1710571610177659127">"ਬਲੂਟੁੱਥ AVRCP ਵਰਜਨ"</string>
     <string name="bluetooth_select_avrcp_version_dialog_title" msgid="7846922290083709633">"ਬਲੂਟੁੱਥ AVRCP ਵਰਜਨ ਚੁਣੋ"</string>
     <string name="bluetooth_select_map_version_string" msgid="526308145174175327">"Bluetooth MAP ਵਰਜਨ"</string>
diff --git a/packages/SettingsLib/res/values-pl/strings.xml b/packages/SettingsLib/res/values-pl/strings.xml
index 1120c50..095412c 100644
--- a/packages/SettingsLib/res/values-pl/strings.xml
+++ b/packages/SettingsLib/res/values-pl/strings.xml
@@ -257,7 +257,6 @@
     <string name="bluetooth_show_devices_without_names" msgid="923584526471885819">"Pokaż urządzenia Bluetooth bez nazw"</string>
     <string name="bluetooth_disable_absolute_volume" msgid="1452342324349203434">"Wyłącz głośność bezwzględną"</string>
     <string name="bluetooth_enable_gabeldorsche" msgid="9131730396242883416">"Włącz Gabeldorsche"</string>
-    <string name="enhanced_connectivity" msgid="7201127377781666804">"Lepsza obsługa połączeń"</string>
     <string name="bluetooth_select_avrcp_version_string" msgid="1710571610177659127">"Wersja AVRCP Bluetooth"</string>
     <string name="bluetooth_select_avrcp_version_dialog_title" msgid="7846922290083709633">"Wybierz wersję AVRCP Bluetooth"</string>
     <string name="bluetooth_select_map_version_string" msgid="526308145174175327">"Wersja MAP Bluetooth"</string>
diff --git a/packages/SettingsLib/res/values-pt-rBR/strings.xml b/packages/SettingsLib/res/values-pt-rBR/strings.xml
index 4214a27..895a987 100644
--- a/packages/SettingsLib/res/values-pt-rBR/strings.xml
+++ b/packages/SettingsLib/res/values-pt-rBR/strings.xml
@@ -257,7 +257,6 @@
     <string name="bluetooth_show_devices_without_names" msgid="923584526471885819">"Mostrar dispositivos Bluetooth sem nomes"</string>
     <string name="bluetooth_disable_absolute_volume" msgid="1452342324349203434">"Desativar volume absoluto"</string>
     <string name="bluetooth_enable_gabeldorsche" msgid="9131730396242883416">"Ativar Gabeldorsche"</string>
-    <string name="enhanced_connectivity" msgid="7201127377781666804">"Conectividade melhorada"</string>
     <string name="bluetooth_select_avrcp_version_string" msgid="1710571610177659127">"Versão do Bluetooth AVRCP"</string>
     <string name="bluetooth_select_avrcp_version_dialog_title" msgid="7846922290083709633">"Selecionar versão do Bluetooth AVRCP"</string>
     <string name="bluetooth_select_map_version_string" msgid="526308145174175327">"Versão MAP do Bluetooth"</string>
diff --git a/packages/SettingsLib/res/values-pt-rPT/strings.xml b/packages/SettingsLib/res/values-pt-rPT/strings.xml
index 508cbfc..2d9f037 100644
--- a/packages/SettingsLib/res/values-pt-rPT/strings.xml
+++ b/packages/SettingsLib/res/values-pt-rPT/strings.xml
@@ -257,7 +257,6 @@
     <string name="bluetooth_show_devices_without_names" msgid="923584526471885819">"Mostrar dispositivos Bluetooth sem nomes"</string>
     <string name="bluetooth_disable_absolute_volume" msgid="1452342324349203434">"Desativar volume absoluto"</string>
     <string name="bluetooth_enable_gabeldorsche" msgid="9131730396242883416">"Ativar o Gabeldorsche"</string>
-    <string name="enhanced_connectivity" msgid="7201127377781666804">"Conetividade melhorada"</string>
     <string name="bluetooth_select_avrcp_version_string" msgid="1710571610177659127">"Versão de Bluetooth AVRCP"</string>
     <string name="bluetooth_select_avrcp_version_dialog_title" msgid="7846922290083709633">"Selecionar versão de Bluetooth AVRCP"</string>
     <string name="bluetooth_select_map_version_string" msgid="526308145174175327">"Versão do MAP do Bluetooth"</string>
diff --git a/packages/SettingsLib/res/values-pt/strings.xml b/packages/SettingsLib/res/values-pt/strings.xml
index 4214a27..895a987 100644
--- a/packages/SettingsLib/res/values-pt/strings.xml
+++ b/packages/SettingsLib/res/values-pt/strings.xml
@@ -257,7 +257,6 @@
     <string name="bluetooth_show_devices_without_names" msgid="923584526471885819">"Mostrar dispositivos Bluetooth sem nomes"</string>
     <string name="bluetooth_disable_absolute_volume" msgid="1452342324349203434">"Desativar volume absoluto"</string>
     <string name="bluetooth_enable_gabeldorsche" msgid="9131730396242883416">"Ativar Gabeldorsche"</string>
-    <string name="enhanced_connectivity" msgid="7201127377781666804">"Conectividade melhorada"</string>
     <string name="bluetooth_select_avrcp_version_string" msgid="1710571610177659127">"Versão do Bluetooth AVRCP"</string>
     <string name="bluetooth_select_avrcp_version_dialog_title" msgid="7846922290083709633">"Selecionar versão do Bluetooth AVRCP"</string>
     <string name="bluetooth_select_map_version_string" msgid="526308145174175327">"Versão MAP do Bluetooth"</string>
diff --git a/packages/SettingsLib/res/values-ro/strings.xml b/packages/SettingsLib/res/values-ro/strings.xml
index 663d3f7..728db17 100644
--- a/packages/SettingsLib/res/values-ro/strings.xml
+++ b/packages/SettingsLib/res/values-ro/strings.xml
@@ -257,7 +257,6 @@
     <string name="bluetooth_show_devices_without_names" msgid="923584526471885819">"Afișați dispozitivele Bluetooth fără nume"</string>
     <string name="bluetooth_disable_absolute_volume" msgid="1452342324349203434">"Dezactivați volumul absolut"</string>
     <string name="bluetooth_enable_gabeldorsche" msgid="9131730396242883416">"Activați Gabeldorsche"</string>
-    <string name="enhanced_connectivity" msgid="7201127377781666804">"Conectivitate îmbunătățită"</string>
     <string name="bluetooth_select_avrcp_version_string" msgid="1710571610177659127">"Versiunea AVRCP pentru Bluetooth"</string>
     <string name="bluetooth_select_avrcp_version_dialog_title" msgid="7846922290083709633">"Selectați versiunea AVRCP pentru Bluetooth"</string>
     <string name="bluetooth_select_map_version_string" msgid="526308145174175327">"Versiunea MAP pentru Bluetooth"</string>
diff --git a/packages/SettingsLib/res/values-ru/strings.xml b/packages/SettingsLib/res/values-ru/strings.xml
index 9c0a2f5..ff2115e 100644
--- a/packages/SettingsLib/res/values-ru/strings.xml
+++ b/packages/SettingsLib/res/values-ru/strings.xml
@@ -257,7 +257,6 @@
     <string name="bluetooth_show_devices_without_names" msgid="923584526471885819">"Показывать Bluetooth-устройства без названий"</string>
     <string name="bluetooth_disable_absolute_volume" msgid="1452342324349203434">"Отключить абсолютный уровень громкости"</string>
     <string name="bluetooth_enable_gabeldorsche" msgid="9131730396242883416">"Включить Gabeldorsche"</string>
-    <string name="enhanced_connectivity" msgid="7201127377781666804">"Улучшенный обмен данными"</string>
     <string name="bluetooth_select_avrcp_version_string" msgid="1710571610177659127">"Версия Bluetooth AVRCP"</string>
     <string name="bluetooth_select_avrcp_version_dialog_title" msgid="7846922290083709633">"Выберите версию Bluetooth AVRCP"</string>
     <string name="bluetooth_select_map_version_string" msgid="526308145174175327">"Версия Bluetooth MAP"</string>
diff --git a/packages/SettingsLib/res/values-si/strings.xml b/packages/SettingsLib/res/values-si/strings.xml
index 8d0e93e..a883cc6 100644
--- a/packages/SettingsLib/res/values-si/strings.xml
+++ b/packages/SettingsLib/res/values-si/strings.xml
@@ -257,7 +257,6 @@
     <string name="bluetooth_show_devices_without_names" msgid="923584526471885819">"නම් නොමැති බ්ලූටූත් උපාංග පෙන්වන්න"</string>
     <string name="bluetooth_disable_absolute_volume" msgid="1452342324349203434">"නිරපේක්ෂ හඩ පරිමාව අබල කරන්න"</string>
     <string name="bluetooth_enable_gabeldorsche" msgid="9131730396242883416">"Gabeldorsche සබල කරන්න"</string>
-    <string name="enhanced_connectivity" msgid="7201127377781666804">"වැඩිදියුණු කළ සබැඳුම් හැකියාව"</string>
     <string name="bluetooth_select_avrcp_version_string" msgid="1710571610177659127">"බ්ලූටූත් AVRCP අනුවාදය"</string>
     <string name="bluetooth_select_avrcp_version_dialog_title" msgid="7846922290083709633">"බ්ලූටූත් AVRCP අනුවාදය තෝරන්න"</string>
     <string name="bluetooth_select_map_version_string" msgid="526308145174175327">"Bluetooth MAP අනුවාදය"</string>
diff --git a/packages/SettingsLib/res/values-sk/strings.xml b/packages/SettingsLib/res/values-sk/strings.xml
index f39a741..05c6379 100644
--- a/packages/SettingsLib/res/values-sk/strings.xml
+++ b/packages/SettingsLib/res/values-sk/strings.xml
@@ -257,7 +257,6 @@
     <string name="bluetooth_show_devices_without_names" msgid="923584526471885819">"Zobrazovať zariadenia Bluetooth bez názvov"</string>
     <string name="bluetooth_disable_absolute_volume" msgid="1452342324349203434">"Zakázať absolútnu hlasitosť"</string>
     <string name="bluetooth_enable_gabeldorsche" msgid="9131730396242883416">"Povoliť Gabeldorsche"</string>
-    <string name="enhanced_connectivity" msgid="7201127377781666804">"Zlepšené možnosti pripojenia"</string>
     <string name="bluetooth_select_avrcp_version_string" msgid="1710571610177659127">"Verzia rozhrania Bluetooth AVRCP"</string>
     <string name="bluetooth_select_avrcp_version_dialog_title" msgid="7846922290083709633">"Zvoľte verziu rozhrania Bluetooth AVRCP"</string>
     <string name="bluetooth_select_map_version_string" msgid="526308145174175327">"Verzia profilu Bluetooth MAP"</string>
diff --git a/packages/SettingsLib/res/values-sl/strings.xml b/packages/SettingsLib/res/values-sl/strings.xml
index 233c8e4..fd216e8 100644
--- a/packages/SettingsLib/res/values-sl/strings.xml
+++ b/packages/SettingsLib/res/values-sl/strings.xml
@@ -257,7 +257,6 @@
     <string name="bluetooth_show_devices_without_names" msgid="923584526471885819">"Prikaži naprave Bluetooth brez imen"</string>
     <string name="bluetooth_disable_absolute_volume" msgid="1452342324349203434">"Onemogočanje absolutne glasnosti"</string>
     <string name="bluetooth_enable_gabeldorsche" msgid="9131730396242883416">"Omogoči Gabeldorsche"</string>
-    <string name="enhanced_connectivity" msgid="7201127377781666804">"Izboljšana povezljivost"</string>
     <string name="bluetooth_select_avrcp_version_string" msgid="1710571610177659127">"Različica profila AVRCP za Bluetooth"</string>
     <string name="bluetooth_select_avrcp_version_dialog_title" msgid="7846922290083709633">"Izberite različico profila AVRCP za Bluetooth"</string>
     <string name="bluetooth_select_map_version_string" msgid="526308145174175327">"Različica profila MAP za Bluetooth"</string>
diff --git a/packages/SettingsLib/res/values-sq/strings.xml b/packages/SettingsLib/res/values-sq/strings.xml
index 6af1062..002c7fc 100644
--- a/packages/SettingsLib/res/values-sq/strings.xml
+++ b/packages/SettingsLib/res/values-sq/strings.xml
@@ -257,7 +257,6 @@
     <string name="bluetooth_show_devices_without_names" msgid="923584526471885819">"Shfaq pajisjet me Bluetooth pa emra"</string>
     <string name="bluetooth_disable_absolute_volume" msgid="1452342324349203434">"Çaktivizo volumin absolut"</string>
     <string name="bluetooth_enable_gabeldorsche" msgid="9131730396242883416">"Aktivizo Gabeldorsche"</string>
-    <string name="enhanced_connectivity" msgid="7201127377781666804">"Lidhshmëria e përmirësuar"</string>
     <string name="bluetooth_select_avrcp_version_string" msgid="1710571610177659127">"Versioni AVRCP i Bluetooth-it"</string>
     <string name="bluetooth_select_avrcp_version_dialog_title" msgid="7846922290083709633">"Zgjidh versionin AVRCP të Bluetooth-it"</string>
     <string name="bluetooth_select_map_version_string" msgid="526308145174175327">"Versioni MAP i Bluetooth-it"</string>
diff --git a/packages/SettingsLib/res/values-sr/strings.xml b/packages/SettingsLib/res/values-sr/strings.xml
index 74c2aec..25a1beb7 100644
--- a/packages/SettingsLib/res/values-sr/strings.xml
+++ b/packages/SettingsLib/res/values-sr/strings.xml
@@ -257,7 +257,6 @@
     <string name="bluetooth_show_devices_without_names" msgid="923584526471885819">"Прикажи Bluetooth уређаје без назива"</string>
     <string name="bluetooth_disable_absolute_volume" msgid="1452342324349203434">"Онемогући главно подешавање јачине звука"</string>
     <string name="bluetooth_enable_gabeldorsche" msgid="9131730396242883416">"Омогући Gabeldorsche"</string>
-    <string name="enhanced_connectivity" msgid="7201127377781666804">"Побољшано повезивање"</string>
     <string name="bluetooth_select_avrcp_version_string" msgid="1710571610177659127">"Верзија Bluetooth AVRCP-а"</string>
     <string name="bluetooth_select_avrcp_version_dialog_title" msgid="7846922290083709633">"Изаберите верзију Bluetooth AVRCP-а"</string>
     <string name="bluetooth_select_map_version_string" msgid="526308145174175327">"Верзија Bluetooth MAP-а"</string>
diff --git a/packages/SettingsLib/res/values-sv/strings.xml b/packages/SettingsLib/res/values-sv/strings.xml
index fe1b0a8..352cb0a 100644
--- a/packages/SettingsLib/res/values-sv/strings.xml
+++ b/packages/SettingsLib/res/values-sv/strings.xml
@@ -257,7 +257,6 @@
     <string name="bluetooth_show_devices_without_names" msgid="923584526471885819">"Visa namnlösa Bluetooth-enheter"</string>
     <string name="bluetooth_disable_absolute_volume" msgid="1452342324349203434">"Inaktivera Absolute volume"</string>
     <string name="bluetooth_enable_gabeldorsche" msgid="9131730396242883416">"Aktivera Gabeldorsche"</string>
-    <string name="enhanced_connectivity" msgid="7201127377781666804">"Förbättrad anslutning"</string>
     <string name="bluetooth_select_avrcp_version_string" msgid="1710571610177659127">"AVRCP-version för Bluetooth"</string>
     <string name="bluetooth_select_avrcp_version_dialog_title" msgid="7846922290083709633">"Välj AVRCP-version för Bluetooth"</string>
     <string name="bluetooth_select_map_version_string" msgid="526308145174175327">"MAP-version för Bluetooth"</string>
diff --git a/packages/SettingsLib/res/values-sw/strings.xml b/packages/SettingsLib/res/values-sw/strings.xml
index 5c80627..d2891a0 100644
--- a/packages/SettingsLib/res/values-sw/strings.xml
+++ b/packages/SettingsLib/res/values-sw/strings.xml
@@ -257,7 +257,6 @@
     <string name="bluetooth_show_devices_without_names" msgid="923584526471885819">"Onyesha vifaa vya Bluetooth visivyo na majina"</string>
     <string name="bluetooth_disable_absolute_volume" msgid="1452342324349203434">"Zima sauti kamili"</string>
     <string name="bluetooth_enable_gabeldorsche" msgid="9131730396242883416">"Washa Gabeldorsche"</string>
-    <string name="enhanced_connectivity" msgid="7201127377781666804">"Muunganisho Ulioboreshwa"</string>
     <string name="bluetooth_select_avrcp_version_string" msgid="1710571610177659127">"Toleo la Bluetooth AVRCP"</string>
     <string name="bluetooth_select_avrcp_version_dialog_title" msgid="7846922290083709633">"Chagua Toleo la Bluetooth AVRCP"</string>
     <string name="bluetooth_select_map_version_string" msgid="526308145174175327">"Toleo la Ramani ya Bluetooth"</string>
diff --git a/packages/SettingsLib/res/values-ta/strings.xml b/packages/SettingsLib/res/values-ta/strings.xml
index 241644f..7837dd8 100644
--- a/packages/SettingsLib/res/values-ta/strings.xml
+++ b/packages/SettingsLib/res/values-ta/strings.xml
@@ -257,7 +257,6 @@
     <string name="bluetooth_show_devices_without_names" msgid="923584526471885819">"பெயர்கள் இல்லாத புளூடூத் சாதனங்களைக் காட்டு"</string>
     <string name="bluetooth_disable_absolute_volume" msgid="1452342324349203434">"அப்சல்யூட் ஒலியளவு அம்சத்தை முடக்கு"</string>
     <string name="bluetooth_enable_gabeldorsche" msgid="9131730396242883416">"Gabeldorscheவை இயக்கு"</string>
-    <string name="enhanced_connectivity" msgid="7201127377781666804">"மேம்படுத்தப்பட்ட இணைப்புநிலை"</string>
     <string name="bluetooth_select_avrcp_version_string" msgid="1710571610177659127">"புளூடூத் AVRCP பதிப்பு"</string>
     <string name="bluetooth_select_avrcp_version_dialog_title" msgid="7846922290083709633">"புளூடூத் AVRCP பதிப்பைத் தேர்ந்தெடு"</string>
     <string name="bluetooth_select_map_version_string" msgid="526308145174175327">"புளூடூத்தின் MAP பதிப்பு"</string>
diff --git a/packages/SettingsLib/res/values-te/strings.xml b/packages/SettingsLib/res/values-te/strings.xml
index a9ec2ea9..60001a0 100644
--- a/packages/SettingsLib/res/values-te/strings.xml
+++ b/packages/SettingsLib/res/values-te/strings.xml
@@ -257,7 +257,6 @@
     <string name="bluetooth_show_devices_without_names" msgid="923584526471885819">"పేర్లు లేని బ్లూటూత్ పరికరాలు  చూపించు"</string>
     <string name="bluetooth_disable_absolute_volume" msgid="1452342324349203434">"సంపూర్ణ వాల్యూమ్‌‍ను నిలిపివేయి"</string>
     <string name="bluetooth_enable_gabeldorsche" msgid="9131730396242883416">"Gabeldorscheను ఎనేబుల్ చేయి"</string>
-    <string name="enhanced_connectivity" msgid="7201127377781666804">"మెరుగైన కనెక్టివిటీ"</string>
     <string name="bluetooth_select_avrcp_version_string" msgid="1710571610177659127">"బ్లూటూత్ AVRCP వెర్షన్"</string>
     <string name="bluetooth_select_avrcp_version_dialog_title" msgid="7846922290083709633">"బ్లూటూత్ AVRCP సంస్కరణను ఎంచుకోండి"</string>
     <string name="bluetooth_select_map_version_string" msgid="526308145174175327">"బ్లూటూత్ MAP వెర్షన్‌"</string>
diff --git a/packages/SettingsLib/res/values-th/strings.xml b/packages/SettingsLib/res/values-th/strings.xml
index b8343c6..defc33e 100644
--- a/packages/SettingsLib/res/values-th/strings.xml
+++ b/packages/SettingsLib/res/values-th/strings.xml
@@ -257,7 +257,6 @@
     <string name="bluetooth_show_devices_without_names" msgid="923584526471885819">"แสดงอุปกรณ์บลูทูธที่ไม่มีชื่อ"</string>
     <string name="bluetooth_disable_absolute_volume" msgid="1452342324349203434">"ปิดใช้การควบคุมระดับเสียงของอุปกรณ์อื่น"</string>
     <string name="bluetooth_enable_gabeldorsche" msgid="9131730396242883416">"เปิดใช้ Gabeldorsche"</string>
-    <string name="enhanced_connectivity" msgid="7201127377781666804">"การเชื่อมต่อที่ปรับปรุงแล้ว"</string>
     <string name="bluetooth_select_avrcp_version_string" msgid="1710571610177659127">"เวอร์ชันของบลูทูธ AVRCP"</string>
     <string name="bluetooth_select_avrcp_version_dialog_title" msgid="7846922290083709633">"เลือกเวอร์ชันของบลูทูธ AVRCP"</string>
     <string name="bluetooth_select_map_version_string" msgid="526308145174175327">"เวอร์ชัน MAP ของบลูทูธ"</string>
diff --git a/packages/SettingsLib/res/values-tl/strings.xml b/packages/SettingsLib/res/values-tl/strings.xml
index 8aeb392..5d4e975 100644
--- a/packages/SettingsLib/res/values-tl/strings.xml
+++ b/packages/SettingsLib/res/values-tl/strings.xml
@@ -257,7 +257,6 @@
     <string name="bluetooth_show_devices_without_names" msgid="923584526471885819">"Ipakita ang mga Bluetooth device na walang pangalan"</string>
     <string name="bluetooth_disable_absolute_volume" msgid="1452342324349203434">"I-disable ang absolute volume"</string>
     <string name="bluetooth_enable_gabeldorsche" msgid="9131730396242883416">"I-enable ang Gabeldorsche"</string>
-    <string name="enhanced_connectivity" msgid="7201127377781666804">"Pinagandang Pagkakonekta"</string>
     <string name="bluetooth_select_avrcp_version_string" msgid="1710571610177659127">"Bersyon ng AVRCP ng Bluetooth"</string>
     <string name="bluetooth_select_avrcp_version_dialog_title" msgid="7846922290083709633">"Pumili ng Bersyon ng AVRCP ng Bluetooth"</string>
     <string name="bluetooth_select_map_version_string" msgid="526308145174175327">"Bersyon ng MAP ng Bluetooth"</string>
diff --git a/packages/SettingsLib/res/values-tr/strings.xml b/packages/SettingsLib/res/values-tr/strings.xml
index e6d9380..f01f3fa 100644
--- a/packages/SettingsLib/res/values-tr/strings.xml
+++ b/packages/SettingsLib/res/values-tr/strings.xml
@@ -257,7 +257,6 @@
     <string name="bluetooth_show_devices_without_names" msgid="923584526471885819">"Adsız Bluetooth cihazlarını göster"</string>
     <string name="bluetooth_disable_absolute_volume" msgid="1452342324349203434">"Mutlak sesi iptal et"</string>
     <string name="bluetooth_enable_gabeldorsche" msgid="9131730396242883416">"Gabeldorsche\'yi etkileştir"</string>
-    <string name="enhanced_connectivity" msgid="7201127377781666804">"Gelişmiş Bağlantı"</string>
     <string name="bluetooth_select_avrcp_version_string" msgid="1710571610177659127">"Bluetooth AVRCP Sürümü"</string>
     <string name="bluetooth_select_avrcp_version_dialog_title" msgid="7846922290083709633">"Bluetooth AVRCP Sürümünü seçin"</string>
     <string name="bluetooth_select_map_version_string" msgid="526308145174175327">"Bluetooth MAP Sürümü"</string>
diff --git a/packages/SettingsLib/res/values-uk/strings.xml b/packages/SettingsLib/res/values-uk/strings.xml
index cf1fafd..9ca2f06 100644
--- a/packages/SettingsLib/res/values-uk/strings.xml
+++ b/packages/SettingsLib/res/values-uk/strings.xml
@@ -257,7 +257,6 @@
     <string name="bluetooth_show_devices_without_names" msgid="923584526471885819">"Показувати пристрої Bluetooth без назв"</string>
     <string name="bluetooth_disable_absolute_volume" msgid="1452342324349203434">"Вимкнути абсолютну гучність"</string>
     <string name="bluetooth_enable_gabeldorsche" msgid="9131730396242883416">"Увімкнути Gabeldorsche"</string>
-    <string name="enhanced_connectivity" msgid="7201127377781666804">"Покращене з\'єднання"</string>
     <string name="bluetooth_select_avrcp_version_string" msgid="1710571610177659127">"Версія Bluetooth AVRCP"</string>
     <string name="bluetooth_select_avrcp_version_dialog_title" msgid="7846922290083709633">"Виберіть версію Bluetooth AVRCP"</string>
     <string name="bluetooth_select_map_version_string" msgid="526308145174175327">"Версія Bluetooth MAP"</string>
diff --git a/packages/SettingsLib/res/values-ur/strings.xml b/packages/SettingsLib/res/values-ur/strings.xml
index b7fbe6f..8953f50 100644
--- a/packages/SettingsLib/res/values-ur/strings.xml
+++ b/packages/SettingsLib/res/values-ur/strings.xml
@@ -257,7 +257,6 @@
     <string name="bluetooth_show_devices_without_names" msgid="923584526471885819">"بغیر نام والے بلوٹوتھ آلات دکھائیں"</string>
     <string name="bluetooth_disable_absolute_volume" msgid="1452342324349203434">"مطلق والیوم کو غیر فعال کریں"</string>
     <string name="bluetooth_enable_gabeldorsche" msgid="9131730396242883416">"‏Gabeldorsche فعال کریں"</string>
-    <string name="enhanced_connectivity" msgid="7201127377781666804">"بہتر کردہ کنیکٹوٹی"</string>
     <string name="bluetooth_select_avrcp_version_string" msgid="1710571610177659127">"‏بلوٹوتھ AVRCP ورژن"</string>
     <string name="bluetooth_select_avrcp_version_dialog_title" msgid="7846922290083709633">"‏بلوٹوتھ AVRCP ورژن منتخب کریں"</string>
     <string name="bluetooth_select_map_version_string" msgid="526308145174175327">"‏بلوٹوتھ MAP ورژن"</string>
diff --git a/packages/SettingsLib/res/values-uz/strings.xml b/packages/SettingsLib/res/values-uz/strings.xml
index f81731a..f25b3ac 100644
--- a/packages/SettingsLib/res/values-uz/strings.xml
+++ b/packages/SettingsLib/res/values-uz/strings.xml
@@ -257,7 +257,6 @@
     <string name="bluetooth_show_devices_without_names" msgid="923584526471885819">"Bluetooth qurilmalarini nomlarisiz ko‘rsatish"</string>
     <string name="bluetooth_disable_absolute_volume" msgid="1452342324349203434">"Tovush balandligining mutlaq darajasini faolsizlantirish"</string>
     <string name="bluetooth_enable_gabeldorsche" msgid="9131730396242883416">"Gabeldorsche funksiyasini yoqish"</string>
-    <string name="enhanced_connectivity" msgid="7201127377781666804">"Kuchaytirilgan aloqa"</string>
     <string name="bluetooth_select_avrcp_version_string" msgid="1710571610177659127">"Bluetooth AVRCP versiyasi"</string>
     <string name="bluetooth_select_avrcp_version_dialog_title" msgid="7846922290083709633">"Bluetooth AVRCP versiyasini tanlang"</string>
     <string name="bluetooth_select_map_version_string" msgid="526308145174175327">"Bluetooth MAP versiyasi"</string>
diff --git a/packages/SettingsLib/res/values-vi/strings.xml b/packages/SettingsLib/res/values-vi/strings.xml
index b7ccf8d..b5798f3 100644
--- a/packages/SettingsLib/res/values-vi/strings.xml
+++ b/packages/SettingsLib/res/values-vi/strings.xml
@@ -257,7 +257,6 @@
     <string name="bluetooth_show_devices_without_names" msgid="923584526471885819">"Hiển thị các thiết bị Bluetooth không có tên"</string>
     <string name="bluetooth_disable_absolute_volume" msgid="1452342324349203434">"Vô hiệu hóa âm lượng tuyệt đối"</string>
     <string name="bluetooth_enable_gabeldorsche" msgid="9131730396242883416">"Bật tính năng Gabeldorsche"</string>
-    <string name="enhanced_connectivity" msgid="7201127377781666804">"Kết nối nâng cao"</string>
     <string name="bluetooth_select_avrcp_version_string" msgid="1710571610177659127">"Phiên bản Bluetooth AVRCP"</string>
     <string name="bluetooth_select_avrcp_version_dialog_title" msgid="7846922290083709633">"Chọn phiên bản Bluetooth AVRCP"</string>
     <string name="bluetooth_select_map_version_string" msgid="526308145174175327">"Phiên bản Bluetooth MAP"</string>
diff --git a/packages/SettingsLib/res/values-zh-rCN/strings.xml b/packages/SettingsLib/res/values-zh-rCN/strings.xml
index 75c1333..c4dcfff 100644
--- a/packages/SettingsLib/res/values-zh-rCN/strings.xml
+++ b/packages/SettingsLib/res/values-zh-rCN/strings.xml
@@ -257,7 +257,6 @@
     <string name="bluetooth_show_devices_without_names" msgid="923584526471885819">"显示没有名称的蓝牙设备"</string>
     <string name="bluetooth_disable_absolute_volume" msgid="1452342324349203434">"停用绝对音量功能"</string>
     <string name="bluetooth_enable_gabeldorsche" msgid="9131730396242883416">"启用“Gabeldorsche”"</string>
-    <string name="enhanced_connectivity" msgid="7201127377781666804">"增强连接性"</string>
     <string name="bluetooth_select_avrcp_version_string" msgid="1710571610177659127">"蓝牙 AVRCP 版本"</string>
     <string name="bluetooth_select_avrcp_version_dialog_title" msgid="7846922290083709633">"选择蓝牙 AVRCP 版本"</string>
     <string name="bluetooth_select_map_version_string" msgid="526308145174175327">"蓝牙 MAP 版本"</string>
diff --git a/packages/SettingsLib/res/values-zh-rHK/strings.xml b/packages/SettingsLib/res/values-zh-rHK/strings.xml
index 26ddfb1..e04651c 100644
--- a/packages/SettingsLib/res/values-zh-rHK/strings.xml
+++ b/packages/SettingsLib/res/values-zh-rHK/strings.xml
@@ -257,7 +257,6 @@
     <string name="bluetooth_show_devices_without_names" msgid="923584526471885819">"顯示沒有名稱的藍牙裝置"</string>
     <string name="bluetooth_disable_absolute_volume" msgid="1452342324349203434">"停用絕對音量功能"</string>
     <string name="bluetooth_enable_gabeldorsche" msgid="9131730396242883416">"啟用 Gabeldorsche"</string>
-    <string name="enhanced_connectivity" msgid="7201127377781666804">"強化連線功能"</string>
     <string name="bluetooth_select_avrcp_version_string" msgid="1710571610177659127">"藍牙 AVRCP 版本"</string>
     <string name="bluetooth_select_avrcp_version_dialog_title" msgid="7846922290083709633">"選擇藍牙 AVRCP 版本"</string>
     <string name="bluetooth_select_map_version_string" msgid="526308145174175327">"藍牙 MAP 版本"</string>
diff --git a/packages/SettingsLib/res/values-zh-rTW/strings.xml b/packages/SettingsLib/res/values-zh-rTW/strings.xml
index 72ea043..a1ae6b6 100644
--- a/packages/SettingsLib/res/values-zh-rTW/strings.xml
+++ b/packages/SettingsLib/res/values-zh-rTW/strings.xml
@@ -257,7 +257,6 @@
     <string name="bluetooth_show_devices_without_names" msgid="923584526471885819">"顯示沒有名稱的藍牙裝置"</string>
     <string name="bluetooth_disable_absolute_volume" msgid="1452342324349203434">"停用絕對音量功能"</string>
     <string name="bluetooth_enable_gabeldorsche" msgid="9131730396242883416">"啟用 Gabeldorsche"</string>
-    <string name="enhanced_connectivity" msgid="7201127377781666804">"加強型連線"</string>
     <string name="bluetooth_select_avrcp_version_string" msgid="1710571610177659127">"藍牙 AVRCP 版本"</string>
     <string name="bluetooth_select_avrcp_version_dialog_title" msgid="7846922290083709633">"選取藍牙 AVRCP 版本"</string>
     <string name="bluetooth_select_map_version_string" msgid="526308145174175327">"藍牙 MAP 版本"</string>
diff --git a/packages/SettingsLib/res/values-zu/strings.xml b/packages/SettingsLib/res/values-zu/strings.xml
index 6b8739f..2dafad8 100644
--- a/packages/SettingsLib/res/values-zu/strings.xml
+++ b/packages/SettingsLib/res/values-zu/strings.xml
@@ -257,7 +257,6 @@
     <string name="bluetooth_show_devices_without_names" msgid="923584526471885819">"Bonisa amadivayisi e-Bluetooth ngaphandle kwamagama"</string>
     <string name="bluetooth_disable_absolute_volume" msgid="1452342324349203434">"Khubaza ivolumu ngokuphelele"</string>
     <string name="bluetooth_enable_gabeldorsche" msgid="9131730396242883416">"Nika amandla i-Gabeldorsche"</string>
-    <string name="enhanced_connectivity" msgid="7201127377781666804">"Ukuxhumeka Okuthuthukisiwe"</string>
     <string name="bluetooth_select_avrcp_version_string" msgid="1710571610177659127">"Inguqulo ye-Bluetooth ye-AVRCP"</string>
     <string name="bluetooth_select_avrcp_version_dialog_title" msgid="7846922290083709633">"Khetha inguqulo ye-Bluetooth AVRCP"</string>
     <string name="bluetooth_select_map_version_string" msgid="526308145174175327">"Inguqulo ye-Bluetooth MAP"</string>
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothEventManager.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothEventManager.java
index 4b4861a..59d8acb 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothEventManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothEventManager.java
@@ -298,18 +298,12 @@
             CachedBluetoothDevice cachedDevice = mDeviceManager.findDevice(device);
             if (cachedDevice == null) {
                 cachedDevice = mDeviceManager.addDevice(device);
-                Log.d(TAG, "DeviceFoundHandler created new CachedBluetoothDevice: "
-                        + cachedDevice);
+                Log.d(TAG, "DeviceFoundHandler created new CachedBluetoothDevice");
             } else if (cachedDevice.getBondState() == BluetoothDevice.BOND_BONDED
                     && !cachedDevice.getDevice().isConnected()) {
                 // Dispatch device add callback to show bonded but
                 // not connected devices in discovery mode
                 dispatchDeviceAdded(cachedDevice);
-                Log.d(TAG, "DeviceFoundHandler found bonded and not connected device:"
-                        + cachedDevice);
-            } else {
-                Log.d(TAG, "DeviceFoundHandler found existing CachedBluetoothDevice:"
-                        + cachedDevice);
             }
             cachedDevice.setRssi(rssi);
             cachedDevice.setJustDiscovered(true);
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java
index 287f804..4c80b91 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java
@@ -151,8 +151,8 @@
 
     void onProfileStateChanged(LocalBluetoothProfile profile, int newProfileState) {
         if (BluetoothUtils.D) {
-            Log.d(TAG, "onProfileStateChanged: profile " + profile + ", device=" + mDevice
-                    + ", newProfileState " + newProfileState);
+            Log.d(TAG, "onProfileStateChanged: profile " + profile + ", device "
+                    + mDevice.getAlias() + ", newProfileState " + newProfileState);
         }
         if (mLocalAdapter.getState() == BluetoothAdapter.STATE_TURNING_OFF)
         {
@@ -290,9 +290,6 @@
     }
 
     public void setHiSyncId(long id) {
-        if (BluetoothUtils.D) {
-            Log.d(TAG, "setHiSyncId: mDevice " + mDevice + ", id " + id);
-        }
         mHiSyncId = id;
     }
 
@@ -638,7 +635,7 @@
         }
 
         if (BluetoothUtils.D) {
-            Log.e(TAG, "updating profiles for " + mDevice.getAlias() + ", " + mDevice);
+            Log.d(TAG, "updating profiles for " + mDevice.getAlias());
             BluetoothClass bluetoothClass = mDevice.getBluetoothClass();
 
             if (bluetoothClass != null) Log.v(TAG, "Class: " + bluetoothClass.toString());
diff --git a/packages/SettingsLib/src/com/android/settingslib/development/DeveloperOptionsPreferenceController.java b/packages/SettingsLib/src/com/android/settingslib/development/DeveloperOptionsPreferenceController.java
index f757aa4..b29595e 100644
--- a/packages/SettingsLib/src/com/android/settingslib/development/DeveloperOptionsPreferenceController.java
+++ b/packages/SettingsLib/src/com/android/settingslib/development/DeveloperOptionsPreferenceController.java
@@ -24,7 +24,7 @@
 import com.android.settingslib.core.AbstractPreferenceController;
 
 /**
- * This controller is used handle changes for the master switch in the developer options page.
+ * This controller is used handle changes for the primary switch in the developer options page.
  *
  * All Preference Controllers that are a part of the developer options page should inherit this
  * class.
diff --git a/packages/SettingsLib/src/com/android/settingslib/fuelgauge/PowerWhitelistBackend.java b/packages/SettingsLib/src/com/android/settingslib/fuelgauge/PowerAllowlistBackend.java
similarity index 69%
rename from packages/SettingsLib/src/com/android/settingslib/fuelgauge/PowerWhitelistBackend.java
rename to packages/SettingsLib/src/com/android/settingslib/fuelgauge/PowerAllowlistBackend.java
index 3c647a7..c501b3a 100644
--- a/packages/SettingsLib/src/com/android/settingslib/fuelgauge/PowerWhitelistBackend.java
+++ b/packages/SettingsLib/src/com/android/settingslib/fuelgauge/PowerAllowlistBackend.java
@@ -34,44 +34,50 @@
 import com.android.internal.util.ArrayUtils;
 
 /**
- * Handles getting/changing the whitelist for the exceptions to battery saving features.
+ * Handles getting/changing the allowlist for the exceptions to battery saving features.
  */
-public class PowerWhitelistBackend {
+public class PowerAllowlistBackend {
 
-    private static final String TAG = "PowerWhitelistBackend";
+    private static final String TAG = "PowerAllowlistBackend";
 
     private static final String DEVICE_IDLE_SERVICE = "deviceidle";
 
-    private static PowerWhitelistBackend sInstance;
+    private static PowerAllowlistBackend sInstance;
 
     private final Context mAppContext;
     private final IDeviceIdleController mDeviceIdleService;
-    private final ArraySet<String> mWhitelistedApps = new ArraySet<>();
-    private final ArraySet<String> mSysWhitelistedApps = new ArraySet<>();
+    private final ArraySet<String> mAllowlistedApps = new ArraySet<>();
+    private final ArraySet<String> mSysAllowlistedApps = new ArraySet<>();
     private final ArraySet<String> mDefaultActiveApps = new ArraySet<>();
 
-    public PowerWhitelistBackend(Context context) {
+    public PowerAllowlistBackend(Context context) {
         this(context, IDeviceIdleController.Stub.asInterface(
                 ServiceManager.getService(DEVICE_IDLE_SERVICE)));
     }
 
     @VisibleForTesting
-    PowerWhitelistBackend(Context context, IDeviceIdleController deviceIdleService) {
+    PowerAllowlistBackend(Context context, IDeviceIdleController deviceIdleService) {
         mAppContext = context.getApplicationContext();
         mDeviceIdleService = deviceIdleService;
         refreshList();
     }
 
-    public int getWhitelistSize() {
-        return mWhitelistedApps.size();
+    public int getAllowlistSize() {
+        return mAllowlistedApps.size();
     }
 
-    public boolean isSysWhitelisted(String pkg) {
-        return mSysWhitelistedApps.contains(pkg);
+    /**
+    * Check if target package is in System allow list
+    */
+    public boolean isSysAllowlisted(String pkg) {
+        return mSysAllowlistedApps.contains(pkg);
     }
 
-    public boolean isWhitelisted(String pkg) {
-        if (mWhitelistedApps.contains(pkg)) {
+    /**
+     * Check if target package is in allow list
+     */
+    public boolean isAllowlisted(String pkg) {
+        if (mAllowlistedApps.contains(pkg)) {
             return true;
         }
 
@@ -87,7 +93,7 @@
      */
     public boolean isDefaultActiveApp(String pkg) {
         // Additionally, check if pkg is default dialer/sms. They are considered essential apps and
-        // should be automatically whitelisted (otherwise user may be able to set restriction on
+        // should be automatically allowlisted (otherwise user may be able to set restriction on
         // them, leading to bad device behavior.)
 
         if (mDefaultActiveApps.contains(pkg)) {
@@ -103,12 +109,17 @@
         return false;
     }
 
-    public boolean isWhitelisted(String[] pkgs) {
+    /**
+     *
+     * @param pkgs a list of packageName
+     * @return true when one of package is in allow list
+     */
+    public boolean isAllowlisted(String[] pkgs) {
         if (ArrayUtils.isEmpty(pkgs)) {
             return false;
         }
         for (String pkg : pkgs) {
-            if (isWhitelisted(pkg)) {
+            if (isAllowlisted(pkg)) {
                 return true;
             }
         }
@@ -116,40 +127,51 @@
         return false;
     }
 
+    /**
+     * Add app into power save allow list.
+     * @param pkg packageName
+     */
     public void addApp(String pkg) {
         try {
             mDeviceIdleService.addPowerSaveWhitelistApp(pkg);
-            mWhitelistedApps.add(pkg);
+            mAllowlistedApps.add(pkg);
         } catch (RemoteException e) {
             Log.w(TAG, "Unable to reach IDeviceIdleController", e);
         }
     }
 
+    /**
+     * Remove package from power save allow list.
+     * @param pkg
+     */
     public void removeApp(String pkg) {
         try {
             mDeviceIdleService.removePowerSaveWhitelistApp(pkg);
-            mWhitelistedApps.remove(pkg);
+            mAllowlistedApps.remove(pkg);
         } catch (RemoteException e) {
             Log.w(TAG, "Unable to reach IDeviceIdleController", e);
         }
     }
 
+    /**
+     * Refresh all of lists
+     */
     @VisibleForTesting
     public void refreshList() {
-        mSysWhitelistedApps.clear();
-        mWhitelistedApps.clear();
+        mSysAllowlistedApps.clear();
+        mAllowlistedApps.clear();
         mDefaultActiveApps.clear();
         if (mDeviceIdleService == null) {
             return;
         }
         try {
-            final String[] whitelistedApps = mDeviceIdleService.getFullPowerWhitelist();
-            for (String app : whitelistedApps) {
-                mWhitelistedApps.add(app);
+            final String[] allowlistedApps = mDeviceIdleService.getFullPowerWhitelist();
+            for (String app : allowlistedApps) {
+                mAllowlistedApps.add(app);
             }
-            final String[] sysWhitelistedApps = mDeviceIdleService.getSystemPowerWhitelist();
-            for (String app : sysWhitelistedApps) {
-                mSysWhitelistedApps.add(app);
+            final String[] sysAllowlistedApps = mDeviceIdleService.getSystemPowerWhitelist();
+            for (String app : sysAllowlistedApps) {
+                mSysAllowlistedApps.add(app);
             }
             final boolean hasTelephony = mAppContext.getPackageManager().hasSystemFeature(
                     PackageManager.FEATURE_TELEPHONY);
@@ -171,9 +193,13 @@
         }
     }
 
-    public static PowerWhitelistBackend getInstance(Context context) {
+    /**
+     * @param context
+     * @return a PowerAllowlistBackend object
+     */
+    public static PowerAllowlistBackend getInstance(Context context) {
         if (sInstance == null) {
-            sInstance = new PowerWhitelistBackend(context);
+            sInstance = new PowerAllowlistBackend(context);
         }
         return sInstance;
     }
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/LocalMediaManager.java b/packages/SettingsLib/src/com/android/settingslib/media/LocalMediaManager.java
index 9d06c84..72a6074 100644
--- a/packages/SettingsLib/src/com/android/settingslib/media/LocalMediaManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/media/LocalMediaManager.java
@@ -465,7 +465,16 @@
             synchronized (mMediaDevicesLock) {
                 mMediaDevices.clear();
                 mMediaDevices.addAll(devices);
-                mMediaDevices.addAll(buildDisconnectedBluetoothDevice());
+                // Add disconnected bluetooth devices only when phone output device is available.
+                for (MediaDevice device : devices) {
+                    final int type = device.getDeviceType();
+                    if (type == MediaDevice.MediaDeviceType.TYPE_USB_C_AUDIO_DEVICE
+                            || type == MediaDevice.MediaDeviceType.TYPE_3POINT5_MM_AUDIO_DEVICE
+                            || type == MediaDevice.MediaDeviceType.TYPE_PHONE_DEVICE) {
+                        mMediaDevices.addAll(buildDisconnectedBluetoothDevice());
+                        break;
+                    }
+                }
             }
 
             final MediaDevice infoMediaDevice = mInfoMediaManager.getCurrentConnectedDevice();
diff --git a/packages/SettingsLib/src/com/android/settingslib/users/AppRestrictionsHelper.java b/packages/SettingsLib/src/com/android/settingslib/users/AppRestrictionsHelper.java
index 4941f7e..8ac4349 100644
--- a/packages/SettingsLib/src/com/android/settingslib/users/AppRestrictionsHelper.java
+++ b/packages/SettingsLib/src/com/android/settingslib/users/AppRestrictionsHelper.java
@@ -135,7 +135,7 @@
                 // Ignore
             }
         } else {
-            // Blacklist all other apps, system or downloaded
+            // Denylist all other apps, system or downloaded
             try {
                 ApplicationInfo info = mIPm.getApplicationInfo(packageName, 0, userId);
                 if (info != null) {
@@ -258,11 +258,11 @@
             }
         }
 
-        // Establish master/slave relationship for entries that share a package name
+        // Establish primary/secondary relationship for entries that share a package name
         HashMap<String,SelectableAppInfo> packageMap = new HashMap<String,SelectableAppInfo>();
         for (SelectableAppInfo info : mVisibleApps) {
             if (packageMap.containsKey(info.packageName)) {
-                info.masterEntry = packageMap.get(info.packageName);
+                info.primaryEntry = packageMap.get(info.packageName);
             } else {
                 packageMap.put(info.packageName, info);
             }
@@ -366,12 +366,12 @@
         public CharSequence appName;
         public CharSequence activityName;
         public Drawable icon;
-        public SelectableAppInfo masterEntry;
+        public SelectableAppInfo primaryEntry;
 
         @Override
         public String toString() {
             return packageName + ": appName=" + appName + "; activityName=" + activityName
-                    + "; icon=" + icon + "; masterEntry=" + masterEntry;
+                    + "; icon=" + icon + "; primaryEntry=" + primaryEntry;
         }
     }
 
diff --git a/packages/SettingsLib/src/com/android/settingslib/wifi/TestAccessPointBuilder.java b/packages/SettingsLib/src/com/android/settingslib/wifi/TestAccessPointBuilder.java
index 2fb2481..df98b17 100644
--- a/packages/SettingsLib/src/com/android/settingslib/wifi/TestAccessPointBuilder.java
+++ b/packages/SettingsLib/src/com/android/settingslib/wifi/TestAccessPointBuilder.java
@@ -108,8 +108,8 @@
     public TestAccessPointBuilder setActive(boolean active) {
         if (active) {
             mNetworkInfo = new NetworkInfo(
-                ConnectivityManager.TYPE_DUMMY,
-                ConnectivityManager.TYPE_DUMMY,
+                ConnectivityManager.TYPE_WIFI,
+                ConnectivityManager.TYPE_WIFI,
                 "TestNetwork",
                 "TestNetwork");
         } else {
diff --git a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiStatusTracker.java b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiStatusTracker.java
index b7ae3dc..bc58bfc 100644
--- a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiStatusTracker.java
+++ b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiStatusTracker.java
@@ -189,10 +189,12 @@
                 }
             }
             updateStatusLabel();
+            mCallback.run();
         } else if (action.equals(WifiManager.RSSI_CHANGED_ACTION)) {
             // Default to -200 as its below WifiManager.MIN_RSSI.
             updateRssi(intent.getIntExtra(WifiManager.EXTRA_NEW_RSSI, -200));
             updateStatusLabel();
+            mCallback.run();
         }
     }
 
@@ -218,13 +220,15 @@
             return;
         }
         NetworkCapabilities networkCapabilities;
-        final Network currentWifiNetwork = mWifiManager.getCurrentNetwork();
-        if (currentWifiNetwork != null && currentWifiNetwork.equals(mDefaultNetwork)) {
+        isDefaultNetwork = false;
+        if (mDefaultNetworkCapabilities != null) {
+            isDefaultNetwork = mDefaultNetworkCapabilities.hasTransport(
+                    NetworkCapabilities.TRANSPORT_WIFI);
+        }
+        if (isDefaultNetwork) {
             // Wifi is connected and the default network.
-            isDefaultNetwork = true;
             networkCapabilities = mDefaultNetworkCapabilities;
         } else {
-            isDefaultNetwork = false;
             networkCapabilities = mConnectivityManager.getNetworkCapabilities(
                     mWifiManager.getCurrentNetwork());
         }
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/RestrictedLockUtilsTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/RestrictedLockUtilsTest.java
index 11c799e..94e28f2 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/RestrictedLockUtilsTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/RestrictedLockUtilsTest.java
@@ -290,12 +290,12 @@
     @Test
     public void sendShowAdminSupportDetailsIntent_extraRestrictionProvided() {
         EnforcedAdmin enforcedAdmin = new EnforcedAdmin();
-        enforcedAdmin.enforcedRestriction = "Dummy";
+        enforcedAdmin.enforcedRestriction = "Fake";
         RestrictedLockUtils.sendShowAdminSupportDetailsIntent(mContext, enforcedAdmin);
 
         ArgumentCaptor<Intent> intentCaptor = ArgumentCaptor.forClass(Intent.class);
         verify(mContext).startActivityAsUser(intentCaptor.capture(), any());
-        assertThat(intentCaptor.getValue().getExtra(EXTRA_RESTRICTION)).isEqualTo("Dummy");
+        assertThat(intentCaptor.getValue().getExtra(EXTRA_RESTRICTION)).isEqualTo("Fake");
     }
 
     @Test
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/drawer/MasterSwitchControllerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/drawer/PrimarySwitchControllerTest.java
similarity index 86%
rename from packages/SettingsLib/tests/robotests/src/com/android/settingslib/drawer/MasterSwitchControllerTest.java
rename to packages/SettingsLib/tests/robotests/src/com/android/settingslib/drawer/PrimarySwitchControllerTest.java
index 69d0f2e..9e4cde8 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/drawer/MasterSwitchControllerTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/drawer/PrimarySwitchControllerTest.java
@@ -23,16 +23,16 @@
 import org.robolectric.RobolectricTestRunner;
 
 @RunWith(RobolectricTestRunner.class)
-public class MasterSwitchControllerTest {
+public class PrimarySwitchControllerTest {
 
     @Rule
     public final ExpectedException thrown = ExpectedException.none();
 
-    private MasterSwitchController mController;
+    private PrimarySwitchController mController;
 
     @Before
     public void setUp() {
-        mController = new TestMasterSwitchController("123");
+        mController = new TestPrimarySwitchController("123");
     }
 
     @Test
@@ -49,11 +49,11 @@
         mController.getBundle();
     }
 
-    static class TestMasterSwitchController extends MasterSwitchController {
+    static class TestPrimarySwitchController extends PrimarySwitchController {
 
         private String mKey;
 
-        TestMasterSwitchController(String key) {
+        TestPrimarySwitchController(String key) {
             mKey = key;
         }
 
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/drawer/SwitchesProviderTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/drawer/SwitchesProviderTest.java
index a740e68..bd0100b 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/drawer/SwitchesProviderTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/drawer/SwitchesProviderTest.java
@@ -35,7 +35,7 @@
 import android.content.pm.ProviderInfo;
 import android.os.Bundle;
 
-import com.android.settingslib.drawer.MasterSwitchControllerTest.TestMasterSwitchController;
+import com.android.settingslib.drawer.PrimarySwitchControllerTest.TestPrimarySwitchController;
 import com.android.settingslib.drawer.SwitchController.MetaData;
 
 import org.junit.Before;
@@ -124,8 +124,8 @@
     }
 
     @Test
-    public void getSwitchData_shouldNotReturnMasterSwitchData() {
-        final SwitchController controller = new TestMasterSwitchController("123");
+    public void getSwitchData_shouldNotReturnPrimarySwitchData() {
+        final SwitchController controller = new TestPrimarySwitchController("123");
         mSwitchesProvider.addSwitchController(controller);
         mSwitchesProvider.attachInfo(mContext, mProviderInfo);
 
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/drawer/TileUtilsTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/drawer/TileUtilsTest.java
index 9b4b97e..1769053 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/drawer/TileUtilsTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/drawer/TileUtilsTest.java
@@ -17,11 +17,14 @@
 package com.android.settingslib.drawer;
 
 import static com.android.settingslib.drawer.TileUtils.IA_SETTINGS_ACTION;
+import static com.android.settingslib.drawer.TileUtils.META_DATA_KEY_PROFILE;
 import static com.android.settingslib.drawer.TileUtils.META_DATA_PREFERENCE_ICON;
 import static com.android.settingslib.drawer.TileUtils.META_DATA_PREFERENCE_ICON_URI;
 import static com.android.settingslib.drawer.TileUtils.META_DATA_PREFERENCE_KEYHINT;
 import static com.android.settingslib.drawer.TileUtils.META_DATA_PREFERENCE_SUMMARY;
 import static com.android.settingslib.drawer.TileUtils.META_DATA_PREFERENCE_SUMMARY_URI;
+import static com.android.settingslib.drawer.TileUtils.PROFILE_ALL;
+import static com.android.settingslib.drawer.TileUtils.PROFILE_PRIMARY;
 
 import static com.google.common.truth.Truth.assertThat;
 
@@ -189,7 +192,7 @@
         List<Tile> outTiles = new ArrayList<>();
         List<ResolveInfo> info = new ArrayList<>();
         ResolveInfo resolveInfo = newInfo(true, null /* category */, null, URI_GET_ICON,
-                URI_GET_SUMMARY, "my title", 0);
+                URI_GET_SUMMARY, "my title", 0, PROFILE_ALL);
         info.add(resolveInfo);
 
         when(mPackageManager.queryIntentActivitiesAsUser(any(Intent.class), anyInt(), anyInt()))
@@ -211,7 +214,7 @@
         List<Tile> outTiles = new ArrayList<>();
         List<ResolveInfo> info = new ArrayList<>();
         ResolveInfo resolveInfo = newInfo(true, null /* category */, null, URI_GET_ICON,
-                URI_GET_SUMMARY, null, 123);
+                URI_GET_SUMMARY, null, 123, PROFILE_ALL);
         info.add(resolveInfo);
 
         when(mPackageManager.queryIntentActivitiesAsUser(any(Intent.class), anyInt(), anyInt()))
@@ -235,7 +238,7 @@
         List<Tile> outTiles = new ArrayList<>();
         List<ResolveInfo> info = new ArrayList<>();
         ResolveInfo resolveInfo = newInfo(true, null /* category */, null, URI_GET_ICON,
-                URI_GET_SUMMARY, null, 123);
+                URI_GET_SUMMARY, null, 123, PROFILE_ALL);
         resolveInfo.activityInfo.packageName = "com.android.settings";
         resolveInfo.activityInfo.applicationInfo.packageName = "com.android.settings";
         info.add(resolveInfo);
@@ -258,7 +261,7 @@
         final List<Tile> outTiles = new ArrayList<>();
         final List<ResolveInfo> info = new ArrayList<>();
         final ResolveInfo resolveInfo = newInfo(true, null /* category */, null, URI_GET_ICON,
-                URI_GET_SUMMARY, null, 123);
+                URI_GET_SUMMARY, null, 123, PROFILE_ALL);
         resolveInfo.activityInfo.packageName = "com.android.settings";
         resolveInfo.activityInfo.applicationInfo.packageName = "com.android.settings";
         info.add(resolveInfo);
@@ -290,7 +293,7 @@
         List<Tile> outTiles = new ArrayList<>();
         List<ResolveInfo> info = new ArrayList<>();
         ResolveInfo resolveInfo = newInfo(true, null /* category */, null, URI_GET_ICON,
-                URI_GET_SUMMARY, null, 123);
+                URI_GET_SUMMARY, null, 123, PROFILE_ALL);
         resolveInfo.activityInfo.metaData
                 .putBoolean(TileUtils.META_DATA_PREFERENCE_ICON_TINTABLE, true);
         info.add(resolveInfo);
@@ -327,6 +330,26 @@
         assertThat(outTiles).hasSize(2);
     }
 
+    @Test
+    public void loadTilesForAction_isPrimaryProfileOnly_shouldSkipNonPrimaryUserTiles() {
+        Map<Pair<String, String>, Tile> addedCache = new ArrayMap<>();
+        List<Tile> outTiles = new ArrayList<>();
+        List<ResolveInfo> info = new ArrayList<>();
+        ResolveInfo resolveInfo = newInfo(true, null /* category */, null, URI_GET_ICON,
+                URI_GET_SUMMARY, null, 123, PROFILE_PRIMARY);
+        info.add(resolveInfo);
+
+        when(mPackageManager.queryIntentActivitiesAsUser(any(Intent.class), anyInt(), anyInt()))
+                .thenReturn(info);
+        when(mPackageManager.queryIntentContentProvidersAsUser(any(Intent.class), anyInt(),
+                anyInt())).thenReturn(info);
+
+        TileUtils.loadTilesForAction(mContext, new UserHandle(10), IA_SETTINGS_ACTION,
+                addedCache, null /* defaultCategory */, outTiles, false /* requiresSettings */);
+
+        assertThat(outTiles).isEmpty();
+    }
+
     public static ResolveInfo newInfo(boolean systemApp, String category) {
         return newInfo(systemApp, category, null);
     }
@@ -337,14 +360,14 @@
 
     private static ResolveInfo newInfo(boolean systemApp, String category, String keyHint,
             String iconUri, String summaryUri) {
-        return newInfo(systemApp, category, keyHint, iconUri, summaryUri, null, 0);
+        return newInfo(systemApp, category, keyHint, iconUri, summaryUri, null, 0, PROFILE_ALL);
     }
 
     private static ResolveInfo newInfo(boolean systemApp, String category, String keyHint,
-            String iconUri, String summaryUri, String title, int titleResId) {
+            String iconUri, String summaryUri, String title, int titleResId, String profile) {
 
         final Bundle metaData = newMetaData(category, keyHint, iconUri, summaryUri, title,
-                titleResId);
+                titleResId, profile);
         final ResolveInfo info = new ResolveInfo();
         info.system = systemApp;
 
@@ -358,6 +381,7 @@
         info.providerInfo.packageName = "abc";
         info.providerInfo.name = "456";
         info.providerInfo.authority = "auth";
+        info.providerInfo.metaData = metaData;
         ShadowTileUtils.setMetaData(metaData);
         info.providerInfo.applicationInfo = new ApplicationInfo();
 
@@ -369,7 +393,7 @@
     }
 
     private static Bundle newMetaData(String category, String keyHint, String iconUri,
-            String summaryUri, String title, int titleResId) {
+            String summaryUri, String title, int titleResId, String profile) {
         final Bundle metaData = new Bundle();
         metaData.putString("com.android.settings.category", category);
         metaData.putInt(META_DATA_PREFERENCE_ICON, 314159);
@@ -388,6 +412,9 @@
         } else if (title != null) {
             metaData.putString(TileUtils.META_DATA_PREFERENCE_TITLE, title);
         }
+        if (profile != null) {
+            metaData.putString(META_DATA_KEY_PROFILE, profile);
+        }
         return metaData;
     }
 
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/fuelgauge/PowerWhitelistBackendTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/fuelgauge/PowerAllowlistBackendTest.java
similarity index 67%
rename from packages/SettingsLib/tests/robotests/src/com/android/settingslib/fuelgauge/PowerWhitelistBackendTest.java
rename to packages/SettingsLib/tests/robotests/src/com/android/settingslib/fuelgauge/PowerAllowlistBackendTest.java
index 2090892..4f11fb1 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/fuelgauge/PowerWhitelistBackendTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/fuelgauge/PowerAllowlistBackendTest.java
@@ -47,7 +47,7 @@
 
 @RunWith(RobolectricTestRunner.class)
 @Config(shadows = {ShadowDefaultDialerManager.class, ShadowSmsApplication.class})
-public class PowerWhitelistBackendTest {
+public class PowerAllowlistBackendTest {
 
     private static final String PACKAGE_ONE = "com.example.packageone";
     private static final String PACKAGE_TWO = "com.example.packagetwo";
@@ -56,7 +56,7 @@
     private IDeviceIdleController mDeviceIdleService;
     @Mock
     private DevicePolicyManager mDevicePolicyManager;
-    private PowerWhitelistBackend mPowerWhitelistBackend;
+    private PowerAllowlistBackend mPowerAllowlistBackend;
     private ShadowPackageManager mPackageManager;
     private Context mContext;
 
@@ -74,81 +74,81 @@
         mPackageManager.setSystemFeature(PackageManager.FEATURE_TELEPHONY, true);
         doReturn(mDevicePolicyManager).when(mContext).getSystemService(DevicePolicyManager.class);
 
-        mPowerWhitelistBackend = new PowerWhitelistBackend(mContext, mDeviceIdleService);
+        mPowerAllowlistBackend = new PowerAllowlistBackend(mContext, mDeviceIdleService);
     }
 
     @Test
-    public void testIsWhitelisted() throws Exception {
+    public void testIsAllowlisted() throws Exception {
         doReturn(new String[] {PACKAGE_ONE}).when(mDeviceIdleService).getFullPowerWhitelist();
-        mPowerWhitelistBackend.refreshList();
+        mPowerAllowlistBackend.refreshList();
 
-        assertThat(mPowerWhitelistBackend.isWhitelisted(PACKAGE_ONE)).isTrue();
-        assertThat(mPowerWhitelistBackend.isWhitelisted(PACKAGE_TWO)).isFalse();
-        assertThat(mPowerWhitelistBackend.isWhitelisted(new String[] {PACKAGE_ONE})).isTrue();
-        assertThat(mPowerWhitelistBackend.isWhitelisted(new String[] {PACKAGE_TWO})).isFalse();
+        assertThat(mPowerAllowlistBackend.isAllowlisted(PACKAGE_ONE)).isTrue();
+        assertThat(mPowerAllowlistBackend.isAllowlisted(PACKAGE_TWO)).isFalse();
+        assertThat(mPowerAllowlistBackend.isAllowlisted(new String[] {PACKAGE_ONE})).isTrue();
+        assertThat(mPowerAllowlistBackend.isAllowlisted(new String[] {PACKAGE_TWO})).isFalse();
 
-        mPowerWhitelistBackend.addApp(PACKAGE_TWO);
+        mPowerAllowlistBackend.addApp(PACKAGE_TWO);
 
         verify(mDeviceIdleService, atLeastOnce()).addPowerSaveWhitelistApp(PACKAGE_TWO);
-        assertThat(mPowerWhitelistBackend.isWhitelisted(PACKAGE_ONE)).isTrue();
-        assertThat(mPowerWhitelistBackend.isWhitelisted(PACKAGE_TWO)).isTrue();
-        assertThat(mPowerWhitelistBackend.isWhitelisted(
+        assertThat(mPowerAllowlistBackend.isAllowlisted(PACKAGE_ONE)).isTrue();
+        assertThat(mPowerAllowlistBackend.isAllowlisted(PACKAGE_TWO)).isTrue();
+        assertThat(mPowerAllowlistBackend.isAllowlisted(
                 new String[] {PACKAGE_ONE, PACKAGE_TWO})).isTrue();
 
-        mPowerWhitelistBackend.removeApp(PACKAGE_TWO);
+        mPowerAllowlistBackend.removeApp(PACKAGE_TWO);
 
         verify(mDeviceIdleService, atLeastOnce()).removePowerSaveWhitelistApp(PACKAGE_TWO);
-        assertThat(mPowerWhitelistBackend.isWhitelisted(PACKAGE_ONE)).isTrue();
-        assertThat(mPowerWhitelistBackend.isWhitelisted(PACKAGE_TWO)).isFalse();
-        assertThat(mPowerWhitelistBackend.isWhitelisted(new String[] {PACKAGE_ONE})).isTrue();
-        assertThat(mPowerWhitelistBackend.isWhitelisted(new String[] {PACKAGE_TWO})).isFalse();
+        assertThat(mPowerAllowlistBackend.isAllowlisted(PACKAGE_ONE)).isTrue();
+        assertThat(mPowerAllowlistBackend.isAllowlisted(PACKAGE_TWO)).isFalse();
+        assertThat(mPowerAllowlistBackend.isAllowlisted(new String[] {PACKAGE_ONE})).isTrue();
+        assertThat(mPowerAllowlistBackend.isAllowlisted(new String[] {PACKAGE_TWO})).isFalse();
 
-        mPowerWhitelistBackend.removeApp(PACKAGE_ONE);
+        mPowerAllowlistBackend.removeApp(PACKAGE_ONE);
 
         verify(mDeviceIdleService, atLeastOnce()).removePowerSaveWhitelistApp(PACKAGE_ONE);
-        assertThat(mPowerWhitelistBackend.isWhitelisted(PACKAGE_ONE)).isFalse();
-        assertThat(mPowerWhitelistBackend.isWhitelisted(PACKAGE_TWO)).isFalse();
-        assertThat(mPowerWhitelistBackend.isWhitelisted(
+        assertThat(mPowerAllowlistBackend.isAllowlisted(PACKAGE_ONE)).isFalse();
+        assertThat(mPowerAllowlistBackend.isAllowlisted(PACKAGE_TWO)).isFalse();
+        assertThat(mPowerAllowlistBackend.isAllowlisted(
                 new String[] {PACKAGE_ONE, PACKAGE_TWO})).isFalse();
     }
 
     @Test
-    public void isWhitelisted_shouldWhitelistDefaultSms() {
+    public void isAllowlisted_shouldAllowlistDefaultSms() {
         final String testSms = "com.android.test.defaultsms";
         ShadowSmsApplication.setDefaultSmsApplication(new ComponentName(testSms, "receiver"));
 
-        mPowerWhitelistBackend.refreshList();
+        mPowerAllowlistBackend.refreshList();
 
-        assertThat(mPowerWhitelistBackend.isWhitelisted(testSms)).isTrue();
-        assertThat(mPowerWhitelistBackend.isDefaultActiveApp(testSms)).isTrue();
+        assertThat(mPowerAllowlistBackend.isAllowlisted(testSms)).isTrue();
+        assertThat(mPowerAllowlistBackend.isDefaultActiveApp(testSms)).isTrue();
     }
 
     @Test
-    public void isWhitelisted_shouldWhitelistDefaultDialer() {
+    public void isAllowlisted_shouldAllowlistDefaultDialer() {
         final String testDialer = "com.android.test.defaultdialer";
         ShadowDefaultDialerManager.setDefaultDialerApplication(testDialer);
 
-        mPowerWhitelistBackend.refreshList();
+        mPowerAllowlistBackend.refreshList();
 
-        assertThat(mPowerWhitelistBackend.isWhitelisted(testDialer)).isTrue();
-        assertThat(mPowerWhitelistBackend.isDefaultActiveApp(testDialer)).isTrue();
+        assertThat(mPowerAllowlistBackend.isAllowlisted(testDialer)).isTrue();
+        assertThat(mPowerAllowlistBackend.isDefaultActiveApp(testDialer)).isTrue();
     }
 
     @Test
-    public void isWhitelisted_shouldWhitelistActiveDeviceAdminApp() {
+    public void isAllowlisted_shouldAllowlistActiveDeviceAdminApp() {
         doReturn(true).when(mDevicePolicyManager).packageHasActiveAdmins(PACKAGE_ONE);
 
-        assertThat(mPowerWhitelistBackend.isWhitelisted(PACKAGE_ONE)).isTrue();
-        assertThat(mPowerWhitelistBackend.isDefaultActiveApp(PACKAGE_ONE)).isTrue();
+        assertThat(mPowerAllowlistBackend.isAllowlisted(PACKAGE_ONE)).isTrue();
+        assertThat(mPowerAllowlistBackend.isDefaultActiveApp(PACKAGE_ONE)).isTrue();
     }
 
     @Test
-    public void testIsSystemWhitelisted() throws Exception {
+    public void testIsSystemAllowlisted() throws Exception {
         doReturn(new String[] {PACKAGE_ONE}).when(mDeviceIdleService).getSystemPowerWhitelist();
-        mPowerWhitelistBackend.refreshList();
+        mPowerAllowlistBackend.refreshList();
 
-        assertThat(mPowerWhitelistBackend.isSysWhitelisted(PACKAGE_ONE)).isTrue();
-        assertThat(mPowerWhitelistBackend.isSysWhitelisted(PACKAGE_TWO)).isFalse();
-        assertThat(mPowerWhitelistBackend.isWhitelisted(PACKAGE_ONE)).isFalse();
+        assertThat(mPowerAllowlistBackend.isSysAllowlisted(PACKAGE_ONE)).isTrue();
+        assertThat(mPowerAllowlistBackend.isSysAllowlisted(PACKAGE_TWO)).isFalse();
+        assertThat(mPowerAllowlistBackend.isAllowlisted(PACKAGE_ONE)).isFalse();
     }
 }
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/inputmethod/InputMethodAndSubtypeUtilCompatTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/inputmethod/InputMethodAndSubtypeUtilCompatTest.java
index b930aa6..84d722a 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/inputmethod/InputMethodAndSubtypeUtilCompatTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/inputmethod/InputMethodAndSubtypeUtilCompatTest.java
@@ -197,61 +197,61 @@
     public void isValidSystemNonAuxAsciiCapableIme() {
         // System IME w/ no subtype
         assertThat(InputMethodAndSubtypeUtilCompat.isValidSystemNonAuxAsciiCapableIme(
-                createDummyIme(true, false)))
+                createFakeIme(true, false)))
                 .isFalse();
 
         // System IME w/ non-Aux and non-ASCII-capable "keyboard" subtype
         assertThat(InputMethodAndSubtypeUtilCompat.isValidSystemNonAuxAsciiCapableIme(
-                createDummyIme(true, false, createDummySubtype("keyboard", false, false))))
+                createFakeIme(true, false, createFakeSubtype("keyboard", false, false))))
                 .isFalse();
 
         // System IME w/ non-Aux and ASCII-capable "keyboard" subtype
         assertThat(InputMethodAndSubtypeUtilCompat.isValidSystemNonAuxAsciiCapableIme(
-                createDummyIme(true, false, createDummySubtype("keyboard", false, true))))
+                createFakeIme(true, false, createFakeSubtype("keyboard", false, true))))
                 .isTrue();
 
         // System IME w/ Aux and ASCII-capable "keyboard" subtype
         assertThat(InputMethodAndSubtypeUtilCompat.isValidSystemNonAuxAsciiCapableIme(
-                createDummyIme(true, true, createDummySubtype("keyboard", true, true))))
+                createFakeIme(true, true, createFakeSubtype("keyboard", true, true))))
                 .isFalse();
 
         // System IME w/ non-Aux and ASCII-capable "voice" subtype
         assertThat(InputMethodAndSubtypeUtilCompat.isValidSystemNonAuxAsciiCapableIme(
-                createDummyIme(true, false, createDummySubtype("voice", false, true))))
+                createFakeIme(true, false, createFakeSubtype("voice", false, true))))
                 .isFalse();
 
         // System IME w/ non-Aux and non-ASCII-capable subtype + Non-Aux and ASCII-capable subtype
         assertThat(InputMethodAndSubtypeUtilCompat.isValidSystemNonAuxAsciiCapableIme(
-                createDummyIme(true, false,
-                        createDummySubtype("keyboard", false, true),
-                        createDummySubtype("keyboard", false, false))))
+                createFakeIme(true, false,
+                        createFakeSubtype("keyboard", false, true),
+                        createFakeSubtype("keyboard", false, false))))
                 .isTrue();
 
         // Non-system IME w/ non-Aux and ASCII-capable "keyboard" subtype
         assertThat(InputMethodAndSubtypeUtilCompat.isValidSystemNonAuxAsciiCapableIme(
-                createDummyIme(false, false, createDummySubtype("keyboard", false, true))))
+                createFakeIme(false, false, createFakeSubtype("keyboard", false, true))))
                 .isFalse();
     }
 
-    private static InputMethodInfo createDummyIme(boolean isSystem, boolean isAuxIme,
+    private static InputMethodInfo createFakeIme(boolean isSystem, boolean isAuxIme,
             InputMethodSubtype... subtypes) {
         final ResolveInfo ri = new ResolveInfo();
         final ServiceInfo si = new ServiceInfo();
         final ApplicationInfo ai = new ApplicationInfo();
-        ai.packageName = "com.example.android.dummyime";
+        ai.packageName = "com.example.android.fakeime";
         ai.enabled = true;
         ai.flags |= (isSystem ? ApplicationInfo.FLAG_SYSTEM : 0);
         si.applicationInfo = ai;
         si.enabled = true;
-        si.packageName = "com.example.android.dummyime";
-        si.name = "Dummy IME";
+        si.packageName = "com.example.android.fakeime";
+        si.name = "Fake IME";
         si.exported = true;
-        si.nonLocalizedLabel = "Dummy IME";
+        si.nonLocalizedLabel = "Fake IME";
         ri.serviceInfo = si;
         return new InputMethodInfo(ri, isAuxIme, "", Arrays.asList(subtypes), 1, false);
     }
 
-    private static InputMethodSubtype createDummySubtype(
+    private static InputMethodSubtype createFakeSubtype(
             String mode, boolean isAuxiliary, boolean isAsciiCapable) {
         return new InputMethodSubtypeBuilder()
                 .setSubtypeNameResId(0)
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/inputmethod/InputMethodAndSubtypeUtilTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/inputmethod/InputMethodAndSubtypeUtilTest.java
index 5171dda..97d8705 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/inputmethod/InputMethodAndSubtypeUtilTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/inputmethod/InputMethodAndSubtypeUtilTest.java
@@ -195,55 +195,55 @@
     public void isValidNonAuxAsciiCapableIme() {
         // IME w/ no subtype
         assertThat(InputMethodAndSubtypeUtil.isValidNonAuxAsciiCapableIme(
-                createDummyIme(false)))
+                createFakeIme(false)))
                 .isFalse();
 
         // IME w/ non-Aux and non-ASCII-capable "keyboard" subtype
         assertThat(InputMethodAndSubtypeUtil.isValidNonAuxAsciiCapableIme(
-                createDummyIme(false, createDummySubtype("keyboard", false, false))))
+                createFakeIme(false, createFakeSubtype("keyboard", false, false))))
                 .isFalse();
 
         // IME w/ non-Aux and ASCII-capable "keyboard" subtype
         assertThat(InputMethodAndSubtypeUtil.isValidNonAuxAsciiCapableIme(
-                createDummyIme(false, createDummySubtype("keyboard", false, true))))
+                createFakeIme(false, createFakeSubtype("keyboard", false, true))))
                 .isTrue();
 
         // IME w/ Aux and ASCII-capable "keyboard" subtype
         assertThat(InputMethodAndSubtypeUtil.isValidNonAuxAsciiCapableIme(
-                createDummyIme(true, createDummySubtype("keyboard", true, true))))
+                createFakeIme(true, createFakeSubtype("keyboard", true, true))))
                 .isFalse();
 
         // IME w/ non-Aux and ASCII-capable "voice" subtype
         assertThat(InputMethodAndSubtypeUtil.isValidNonAuxAsciiCapableIme(
-                createDummyIme(false, createDummySubtype("voice", false, true))))
+                createFakeIme(false, createFakeSubtype("voice", false, true))))
                 .isFalse();
 
         // IME w/ non-Aux and non-ASCII-capable subtype + Non-Aux and ASCII-capable subtype
         assertThat(InputMethodAndSubtypeUtil.isValidNonAuxAsciiCapableIme(
-                createDummyIme(false,
-                        createDummySubtype("keyboard", false, true),
-                        createDummySubtype("keyboard", false, false))))
+                createFakeIme(false,
+                        createFakeSubtype("keyboard", false, true),
+                        createFakeSubtype("keyboard", false, false))))
                 .isTrue();
    }
 
-    private static InputMethodInfo createDummyIme(boolean isAuxIme,
+    private static InputMethodInfo createFakeIme(boolean isAuxIme,
             InputMethodSubtype... subtypes) {
         final ResolveInfo ri = new ResolveInfo();
         final ServiceInfo si = new ServiceInfo();
         final ApplicationInfo ai = new ApplicationInfo();
-        ai.packageName = "com.example.android.dummyime";
+        ai.packageName = "com.example.android.fakeime";
         ai.enabled = true;
         si.applicationInfo = ai;
         si.enabled = true;
-        si.packageName = "com.example.android.dummyime";
-        si.name = "Dummy IME";
+        si.packageName = "com.example.android.fakeime";
+        si.name = "Fake IME";
         si.exported = true;
-        si.nonLocalizedLabel = "Dummy IME";
+        si.nonLocalizedLabel = "Fake IME";
         ri.serviceInfo = si;
         return new InputMethodInfo(ri, isAuxIme, "",  Arrays.asList(subtypes), 1, false);
     }
 
-    private static InputMethodSubtype createDummySubtype(
+    private static InputMethodSubtype createFakeSubtype(
             String mode, boolean isAuxiliary, boolean isAsciiCapable) {
         return new InputMethodSubtypeBuilder()
                 .setSubtypeNameResId(0)
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/LocalMediaManagerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/LocalMediaManagerTest.java
index a654fd4..8e850b2 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/LocalMediaManagerTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/LocalMediaManagerTest.java
@@ -585,6 +585,7 @@
         when(device1.getId()).thenReturn(TEST_DEVICE_ID_1);
         when(device2.getId()).thenReturn(TEST_DEVICE_ID_2);
         when(device3.getId()).thenReturn(TEST_DEVICE_ID_3);
+        when(device1.getDeviceType()).thenReturn(MediaDevice.MediaDeviceType.TYPE_PHONE_DEVICE);
         when(mLocalMediaManager.mPhoneDevice.getId()).thenReturn("test_phone_id");
 
         assertThat(mLocalMediaManager.mMediaDevices).hasSize(2);
@@ -683,6 +684,7 @@
         when(device1.getId()).thenReturn(TEST_DEVICE_ID_1);
         when(device2.getId()).thenReturn(TEST_DEVICE_ID_2);
         when(device3.getId()).thenReturn(TEST_DEVICE_ID_3);
+        when(device1.getDeviceType()).thenReturn(MediaDevice.MediaDeviceType.TYPE_PHONE_DEVICE);
         when(mLocalMediaManager.mPhoneDevice.getId()).thenReturn("test_phone_id");
 
         assertThat(mLocalMediaManager.mMediaDevices).hasSize(2);
diff --git a/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java b/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java
index 18c2957..bcd2ff7 100644
--- a/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java
+++ b/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java
@@ -112,7 +112,6 @@
         Settings.Secure.FACE_UNLOCK_ALWAYS_REQUIRE_CONFIRMATION,
         Settings.Secure.VR_DISPLAY_MODE,
         Settings.Secure.NOTIFICATION_BADGING,
-        Settings.Secure.NOTIFICATION_FEEDBACK_ENABLED,
         Settings.Secure.NOTIFICATION_DISMISS_RTL,
         Settings.Secure.QS_AUTO_ADDED_TILES,
         Settings.Secure.SCREENSAVER_ENABLED,
@@ -166,6 +165,7 @@
         Settings.Secure.AWARE_TAP_PAUSE_TOUCH_COUNT,
         Settings.Secure.PEOPLE_STRIP,
         Settings.Secure.MEDIA_CONTROLS_RESUME,
+        Settings.Secure.MEDIA_CONTROLS_RESUME_BLOCKED,
         Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE,
         Settings.Secure.ACCESSIBILITY_BUTTON_TARGETS,
         Settings.Secure.ACCESSIBILITY_MAGNIFICATION_CAPABILITY,
@@ -174,5 +174,7 @@
         Settings.Secure.TAPS_APP_TO_EXIT,
         Settings.Secure.SWIPE_BOTTOM_TO_NOTIFICATION_ENABLED,
         Settings.Secure.PANIC_GESTURE_ENABLED,
+        Settings.Secure.PANIC_SOUND_ENABLED,
+        Settings.Secure.ADAPTIVE_CONNECTIVITY_ENABLED
     };
 }
diff --git a/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java b/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java
index dd94d2e..a02d67f 100644
--- a/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java
+++ b/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java
@@ -149,5 +149,6 @@
         VALIDATORS.put(Global.CUSTOM_BUGREPORT_HANDLER_APP, ANY_STRING_VALIDATOR);
         VALIDATORS.put(Global.CUSTOM_BUGREPORT_HANDLER_USER, ANY_INTEGER_VALIDATOR);
         VALIDATORS.put(Global.DEVELOPMENT_SETTINGS_ENABLED, BOOLEAN_VALIDATOR);
+        VALIDATORS.put(Global.NOTIFICATION_FEEDBACK_ENABLED, BOOLEAN_VALIDATOR);
     }
 }
diff --git a/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java b/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
index 91f3f4a..3630f25 100644
--- a/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
+++ b/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
@@ -189,7 +189,6 @@
         VALIDATORS.put(Secure.LOCK_SCREEN_SHOW_SILENT_NOTIFICATIONS, BOOLEAN_VALIDATOR);
         VALIDATORS.put(Secure.SHOW_NOTIFICATION_SNOOZE, BOOLEAN_VALIDATOR);
         VALIDATORS.put(Secure.NOTIFICATION_HISTORY_ENABLED, BOOLEAN_VALIDATOR);
-        VALIDATORS.put(Secure.NOTIFICATION_FEEDBACK_ENABLED, BOOLEAN_VALIDATOR);
         VALIDATORS.put(Secure.ZEN_DURATION, ANY_INTEGER_VALIDATOR);
         VALIDATORS.put(Secure.SHOW_ZEN_UPGRADE_NOTIFICATION, BOOLEAN_VALIDATOR);
         VALIDATORS.put(Secure.SHOW_ZEN_SETTINGS_SUGGESTION, BOOLEAN_VALIDATOR);
@@ -245,6 +244,8 @@
         VALIDATORS.put(Secure.TAP_GESTURE, BOOLEAN_VALIDATOR);
         VALIDATORS.put(Secure.PEOPLE_STRIP, BOOLEAN_VALIDATOR);
         VALIDATORS.put(Secure.MEDIA_CONTROLS_RESUME, BOOLEAN_VALIDATOR);
+        VALIDATORS.put(Secure.MEDIA_CONTROLS_RESUME_BLOCKED,
+                COLON_SEPARATED_PACKAGE_LIST_VALIDATOR);
         VALIDATORS.put(Secure.ACCESSIBILITY_MAGNIFICATION_MODE,
                 new InclusiveIntegerRangeValidator(
                         Secure.ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN,
@@ -261,5 +262,7 @@
         VALIDATORS.put(Secure.TAPS_APP_TO_EXIT, BOOLEAN_VALIDATOR);
         VALIDATORS.put(Secure.SWIPE_BOTTOM_TO_NOTIFICATION_ENABLED, BOOLEAN_VALIDATOR);
         VALIDATORS.put(Secure.PANIC_GESTURE_ENABLED, BOOLEAN_VALIDATOR);
+        VALIDATORS.put(Secure.PANIC_SOUND_ENABLED, BOOLEAN_VALIDATOR);
+        VALIDATORS.put(Secure.ADAPTIVE_CONNECTIVITY_ENABLED, BOOLEAN_VALIDATOR);
     }
 }
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsHelper.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsHelper.java
index c1543fd..bfd5b1cc 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsHelper.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsHelper.java
@@ -81,6 +81,7 @@
         sBroadcastOnRestore.add(Settings.Secure.DARK_THEME_CUSTOM_START_TIME);
         sBroadcastOnRestore.add(Settings.Secure.DARK_THEME_CUSTOM_END_TIME);
         sBroadcastOnRestore.add(Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_NAVBAR_ENABLED);
+        sBroadcastOnRestore.add(Settings.Secure.ACCESSIBILITY_BUTTON_TARGETS);
     }
 
     private interface SettingsLookup {
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
index fa06e14..a293148 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
@@ -773,29 +773,29 @@
                 Settings.Global.GPU_DEBUG_LAYERS_GLES,
                 GlobalSettingsProto.Gpu.DEBUG_LAYERS_GLES);
         dumpSetting(s, p,
-                Settings.Global.GAME_DRIVER_ALL_APPS,
-                GlobalSettingsProto.Gpu.GAME_DRIVER_ALL_APPS);
+                Settings.Global.UPDATABLE_DRIVER_ALL_APPS,
+                GlobalSettingsProto.Gpu.UPDATABLE_DRIVER_ALL_APPS);
         dumpSetting(s, p,
-                Settings.Global.GAME_DRIVER_OPT_IN_APPS,
-                GlobalSettingsProto.Gpu.GAME_DRIVER_OPT_IN_APPS);
+                Settings.Global.UPDATABLE_DRIVER_PRODUCTION_OPT_IN_APPS,
+                GlobalSettingsProto.Gpu.UPDATABLE_DRIVER_PRODUCTION_OPT_IN_APPS);
         dumpSetting(s, p,
-                Settings.Global.GAME_DRIVER_PRERELEASE_OPT_IN_APPS,
-                GlobalSettingsProto.Gpu.GAME_DRIVER_PRERELEASE_OPT_IN_APPS);
+                Settings.Global.UPDATABLE_DRIVER_PRERELEASE_OPT_IN_APPS,
+                GlobalSettingsProto.Gpu.UPDATABLE_DRIVER_PRERELEASE_OPT_IN_APPS);
         dumpSetting(s, p,
-                Settings.Global.GAME_DRIVER_OPT_OUT_APPS,
-                GlobalSettingsProto.Gpu.GAME_DRIVER_OPT_OUT_APPS);
+                Settings.Global.UPDATABLE_DRIVER_PRODUCTION_OPT_OUT_APPS,
+                GlobalSettingsProto.Gpu.UPDATABLE_DRIVER_PRODUCTION_OPT_OUT_APPS);
         dumpSetting(s, p,
-                Settings.Global.GAME_DRIVER_DENYLIST,
-                GlobalSettingsProto.Gpu.GAME_DRIVER_DENYLIST);
+                Settings.Global.UPDATABLE_DRIVER_PRODUCTION_DENYLIST,
+                GlobalSettingsProto.Gpu.UPDATABLE_DRIVER_PRODUCTION_DENYLIST);
         dumpSetting(s, p,
-                Settings.Global.GAME_DRIVER_ALLOWLIST,
-                GlobalSettingsProto.Gpu.GAME_DRIVER_ALLOWLIST);
+                Settings.Global.UPDATABLE_DRIVER_PRODUCTION_ALLOWLIST,
+                GlobalSettingsProto.Gpu.UPDATABLE_DRIVER_PRODUCTION_ALLOWLIST);
         dumpSetting(s, p,
-                Settings.Global.GAME_DRIVER_DENYLISTS,
-                GlobalSettingsProto.Gpu.GAME_DRIVER_DENYLISTS);
+                Settings.Global.UPDATABLE_DRIVER_PRODUCTION_DENYLISTS,
+                GlobalSettingsProto.Gpu.UPDATABLE_DRIVER_PRODUCTION_DENYLISTS);
         dumpSetting(s, p,
-                Settings.Global.GAME_DRIVER_SPHAL_LIBRARIES,
-                GlobalSettingsProto.Gpu.GAME_DRIVER_SPHAL_LIBRARIES);
+                Settings.Global.UPDATABLE_DRIVER_SPHAL_LIBRARIES,
+                GlobalSettingsProto.Gpu.UPDATABLE_DRIVER_SPHAL_LIBRARIES);
         p.end(gpuToken);
 
         final long hdmiToken = p.start(GlobalSettingsProto.HDMI);
@@ -860,9 +860,6 @@
         p.end(intentFirewallToken);
 
         dumpSetting(s, p,
-                Settings.Global.JOB_SCHEDULER_CONSTANTS,
-                GlobalSettingsProto.JOB_SCHEDULER_CONSTANTS);
-        dumpSetting(s, p,
                 Settings.Global.JOB_SCHEDULER_QUOTA_CONTROLLER_CONSTANTS,
                 GlobalSettingsProto.JOB_SCHEDULER_QUOTA_CONTROLLER_CONSTANTS);
         dumpSetting(s, p,
@@ -1976,6 +1973,9 @@
         dumpSetting(s, p,
                 Settings.Secure.CONNECTIVITY_RELEASE_PENDING_INTENT_DELAY_MS,
                 SecureSettingsProto.CONNECTIVITY_RELEASE_PENDING_INTENT_DELAY_MS);
+        dumpSetting(s, p,
+                Settings.Secure.ADAPTIVE_CONNECTIVITY_ENABLED,
+                SecureSettingsProto.ADAPTIVE_CONNECTIVITY_ENABLED);
 
         final long controlsToken = p.start(SecureSettingsProto.CONTROLS);
         dumpSetting(s, p,
@@ -2028,6 +2028,9 @@
         dumpSetting(s, p,
                 Settings.Secure.PANIC_GESTURE_ENABLED,
                 SecureSettingsProto.EmergencyResponse.PANIC_GESTURE_ENABLED);
+        dumpSetting(s, p,
+                Settings.Secure.PANIC_SOUND_ENABLED,
+                SecureSettingsProto.EmergencyResponse.PANIC_SOUND_ENABLED);
         p.end(emergencyResponseToken);
 
         dumpSetting(s, p,
diff --git a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
index 24f8104..f219aec 100644
--- a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
+++ b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
@@ -309,7 +309,6 @@
                     Settings.Global.INSTANT_APP_DEXOPT_ENABLED,
                     Settings.Global.INTENT_FIREWALL_UPDATE_CONTENT_URL,
                     Settings.Global.INTENT_FIREWALL_UPDATE_METADATA_URL,
-                    Settings.Global.JOB_SCHEDULER_CONSTANTS,
                     Settings.Global.JOB_SCHEDULER_QUOTA_CONTROLLER_CONSTANTS,
                     Settings.Global.KEEP_PROFILE_IN_BACKGROUND,
                     Settings.Global.KERNEL_CPU_THREAD_READER,
@@ -389,6 +388,7 @@
                     Settings.Global.NITZ_UPDATE_DIFF,
                     Settings.Global.NITZ_UPDATE_SPACING,
                     Settings.Global.NOTIFICATION_SNOOZE_OPTIONS,
+                    Settings.Global.NOTIFICATION_FEEDBACK_ENABLED,
                     Settings.Global.NR_NSA_TRACKING_SCREEN_OFF_MODE,
                     Settings.Global.NSD_ON,
                     Settings.Global.NTP_SERVER,
@@ -503,14 +503,14 @@
                     Settings.Global.GLOBAL_SETTINGS_ANGLE_GL_DRIVER_SELECTION_PKGS,
                     Settings.Global.GLOBAL_SETTINGS_ANGLE_GL_DRIVER_SELECTION_VALUES,
                     Settings.Global.GLOBAL_SETTINGS_ANGLE_ALLOWLIST,
-                    Settings.Global.GAME_DRIVER_ALL_APPS,
-                    Settings.Global.GAME_DRIVER_OPT_IN_APPS,
-                    Settings.Global.GAME_DRIVER_PRERELEASE_OPT_IN_APPS,
-                    Settings.Global.GAME_DRIVER_OPT_OUT_APPS,
-                    Settings.Global.GAME_DRIVER_DENYLISTS,
-                    Settings.Global.GAME_DRIVER_DENYLIST,
-                    Settings.Global.GAME_DRIVER_ALLOWLIST,
-                    Settings.Global.GAME_DRIVER_SPHAL_LIBRARIES,
+                    Settings.Global.UPDATABLE_DRIVER_ALL_APPS,
+                    Settings.Global.UPDATABLE_DRIVER_PRODUCTION_OPT_IN_APPS,
+                    Settings.Global.UPDATABLE_DRIVER_PRERELEASE_OPT_IN_APPS,
+                    Settings.Global.UPDATABLE_DRIVER_PRODUCTION_OPT_OUT_APPS,
+                    Settings.Global.UPDATABLE_DRIVER_PRODUCTION_DENYLISTS,
+                    Settings.Global.UPDATABLE_DRIVER_PRODUCTION_DENYLIST,
+                    Settings.Global.UPDATABLE_DRIVER_PRODUCTION_ALLOWLIST,
+                    Settings.Global.UPDATABLE_DRIVER_SPHAL_LIBRARIES,
                     Settings.Global.GLOBAL_SETTINGS_SHOW_ANGLE_IN_USE_DIALOG_BOX,
                     Settings.Global.GPU_DEBUG_LAYER_APP,
                     Settings.Global.ENABLE_GNSS_RAW_MEAS_FULL_TRACKING,
diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml
index aa96087..319b44c 100644
--- a/packages/Shell/AndroidManifest.xml
+++ b/packages/Shell/AndroidManifest.xml
@@ -320,19 +320,6 @@
     <!-- Permissions required for CTS test - AdbManagerTest -->
     <uses-permission android:name="android.permission.MANAGE_DEBUGGING" />
 
-    <!-- Permissions required for ATS tests - AtsCarHostTestCases, AtsCarDeviceApp -->
-    <uses-permission android:name="android.car.permission.CAR_DRIVING_STATE" />
-    <!-- Permissions required for ATS tests - AtsDeviceInfo, AtsAudioDeviceTestCases -->
-    <uses-permission android:name="android.car.permission.CAR_CONTROL_AUDIO_VOLUME" />
-    <!-- Permissions required for ATS tests - AtsDeviceInfo -->
-    <uses-permission android:name="android.car.permission.CAR_DIAGNOSTICS" />
-    <!-- Permissions required for ATS tests - AtsDeviceInfo -->
-    <uses-permission android:name="android.car.permission.CLEAR_CAR_DIAGNOSTICS" />
-    <!-- Permissions required for ATS tests - AtsCarHostTestCases -->
-    <uses-permission android:name="android.car.permission.CONTROL_APP_BLOCKING" />
-    <!-- Permissions required for ATS tests - AtsCarHostTestCases -->
-    <uses-permission android:name="android.car.permission.CAR_UX_RESTRICTIONS_CONFIGURATION" />
-
     <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 8e01619..85f2552 100644
--- a/packages/Shell/src/com/android/shell/Screenshooter.java
+++ b/packages/Shell/src/com/android/shell/Screenshooter.java
@@ -17,11 +17,8 @@
 package com.android.shell;
 
 import android.graphics.Bitmap;
-import android.graphics.Point;
-import android.graphics.Rect;
-import android.hardware.display.DisplayManagerGlobal;
+import android.os.IBinder;
 import android.util.Log;
-import android.view.Display;
 import android.view.SurfaceControl;
 
 /**
@@ -40,22 +37,17 @@
      * @return The screenshot bitmap on success, null otherwise.
      */
     static Bitmap takeScreenshot() {
-        Display display = DisplayManagerGlobal.getInstance()
-                .getRealDisplay(Display.DEFAULT_DISPLAY);
-        Point displaySize = new Point();
-        display.getRealSize(displaySize);
-        final int displayWidth = displaySize.x;
-        final int displayHeight = displaySize.y;
-
-        int rotation = display.getRotation();
-        Rect crop = new Rect(0, 0, displayWidth, displayHeight);
-        Log.d(TAG, "Taking screenshot of dimensions " + displayWidth + " x " + displayHeight);
+        Log.d(TAG, "Taking fullscreen screenshot");
         // Take the screenshot
-        Bitmap screenShot =
-                SurfaceControl.screenshot(crop, displayWidth, displayHeight, rotation);
+        final IBinder displayToken = SurfaceControl.getInternalDisplayToken();
+        final SurfaceControl.DisplayCaptureArgs captureArgs =
+                new SurfaceControl.DisplayCaptureArgs.Builder(displayToken)
+                        .build();
+        final SurfaceControl.ScreenshotHardwareBuffer screenshotBuffer =
+                SurfaceControl.captureDisplay(captureArgs);
+        final Bitmap screenShot = screenshotBuffer == null ? null : screenshotBuffer.asBitmap();
         if (screenShot == null) {
-            Log.e(TAG, "Failed to take screenshot of dimensions " + displayWidth + " x "
-                    + displayHeight);
+            Log.e(TAG, "Failed to take fullscreen screenshot");
             return null;
         }
 
diff --git a/packages/SimAppDialog/Android.bp b/packages/SimAppDialog/Android.bp
index 176035f..1c680bb 100644
--- a/packages/SimAppDialog/Android.bp
+++ b/packages/SimAppDialog/Android.bp
@@ -7,7 +7,8 @@
 
     static_libs: [
         "androidx.legacy_legacy-support-v4",
-        "setup-wizard-lib",
+        "setupcompat",
+        "setupdesign",
     ],
 
     resource_dirs: ["res"],
diff --git a/packages/SimAppDialog/AndroidManifest.xml b/packages/SimAppDialog/AndroidManifest.xml
index 873f6c5..e7368f3 100644
--- a/packages/SimAppDialog/AndroidManifest.xml
+++ b/packages/SimAppDialog/AndroidManifest.xml
@@ -23,7 +23,7 @@
             android:name=".InstallCarrierAppActivity"
             android:exported="true"
             android:permission="android.permission.NETWORK_SETTINGS"
-            android:theme="@style/SuwThemeGlif.Light">
+            android:theme="@style/SudThemeGlif.Light">
         </activity>
     </application>
 </manifest>
diff --git a/packages/SimAppDialog/res/layout/install_carrier_app_activity.xml b/packages/SimAppDialog/res/layout/install_carrier_app_activity.xml
index 12f9bb6..68113db 100644
--- a/packages/SimAppDialog/res/layout/install_carrier_app_activity.xml
+++ b/packages/SimAppDialog/res/layout/install_carrier_app_activity.xml
@@ -14,18 +14,17 @@
     See the License for the specific language governing permissions and
     limitations under the License.
 -->
-<com.android.setupwizardlib.GlifLayout
+<com.google.android.setupdesign.GlifLayout
     xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:app="http://schemas.android.com/apk/res-auto"
     android:id="@+id/setup_wizard_layout"
     android:layout_width="match_parent"
     android:layout_height="match_parent"
     android:icon="@drawable/ic_signal_cellular_alt_rounded"
-    app:suwHeaderText="@string/install_carrier_app_title"
-    app:suwFooter="@layout/install_carrier_app_footer">
+    app:sucHeaderText="@string/install_carrier_app_title">
 
     <LinearLayout
-        style="@style/SuwContentFrame"
+        style="@style/SudContentFrame"
         android:id="@+id/content_frame"
         android:layout_width="match_parent"
         android:layout_height="wrap_content"
@@ -33,12 +32,12 @@
 
         <TextView
             android:id="@+id/install_carrier_app_description"
-            style="@style/SuwDescription.Glif"
+            style="@style/SudDescription.Glif"
             android:text="@string/install_carrier_app_description_default"
             android:layout_width="match_parent"
             android:layout_height="wrap_content"/>
 
-        <com.android.setupwizardlib.view.FillContentLayout
+        <com.google.android.setupdesign.view.FillContentLayout
             android:id="@+id/illo_container"
             android:layout_width="match_parent"
             android:layout_height="wrap_content"
@@ -47,12 +46,12 @@
 
             <ImageView
                 android:src="@drawable/illo_sim_app_dialog"
-                style="@style/SuwContentIllustration"
+                style="@style/SudContentIllustration"
                 android:contentDescription="@string/install_carrier_app_image_content_description"
                 android:layout_width="match_parent"
                 android:layout_height="match_parent"/>
 
-        </com.android.setupwizardlib.view.FillContentLayout>
-</LinearLayout>
+        </com.google.android.setupdesign.view.FillContentLayout>
+    </LinearLayout>
 
-</com.android.setupwizardlib.GlifLayout>
+</com.google.android.setupdesign.GlifLayout>
diff --git a/packages/SimAppDialog/res/layout/install_carrier_app_footer.xml b/packages/SimAppDialog/res/layout/install_carrier_app_footer.xml
deleted file mode 100644
index 10dcb77..0000000
--- a/packages/SimAppDialog/res/layout/install_carrier_app_footer.xml
+++ /dev/null
@@ -1,43 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-    Copyright (C) 2018 The Android Open Source Project
-
-    Licensed under the Apache License, Version 2.0 (the "License");
-    you may not use this file except in compliance with the License.
-    You may obtain a copy of the License at
-
-        http://www.apache.org/licenses/LICENSE-2.0
-
-    Unless required by applicable law or agreed to in writing, software
-    distributed under the License is distributed on an "AS IS" BASIS,
-    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-    See the License for the specific language governing permissions and
-    limitations under the License.
--->
-
-<com.android.setupwizardlib.view.ButtonBarLayout
-    xmlns:android="http://schemas.android.com/apk/res/android"
-    android:id="@+id/footer"
-    style="@style/SuwGlifButtonBar.Stackable"
-    android:layout_width="match_parent"
-    android:layout_height="wrap_content">
-
-    <Button
-        android:id="@+id/skip_button"
-        style="@style/SuwGlifButton.Secondary"
-        android:text="@string/install_carrier_app_defer_action"
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"/>
-
-    <Space
-        android:layout_width="0dp"
-        android:layout_height="0dp"
-        android:layout_weight="1"/>
-
-    <Button
-        android:id="@+id/download_button"
-        style="@style/SuwGlifButton.Primary"
-        android:text="@string/install_carrier_app_download_action"
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"/>
-</com.android.setupwizardlib.view.ButtonBarLayout>
diff --git a/packages/SimAppDialog/res/values/styles.xml b/packages/SimAppDialog/res/values/styles.xml
new file mode 100644
index 0000000..824e380
--- /dev/null
+++ b/packages/SimAppDialog/res/values/styles.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+    Copyright (C) 2020 The Android Open Source Project
+
+    Licensed under the Apache License, Version 2.0 (the "License");
+    you may not use this file except in compliance with the License.
+    You may obtain a copy of the License at
+
+        http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+    See the License for the specific language governing permissions and
+    limitations under the License.
+-->
+<resources>
+
+  <style name="SetupWizardPartnerResource">
+    <!-- Disable to use partner overlay theme for outside setupwizard flow. -->
+    <item name="sucUsePartnerResource">false</item>
+    <!-- Enable heavy theme style inside setupwizard flow. -->
+    <item name="sudUsePartnerHeavyTheme">true</item>
+  </style>
+
+</resources>
diff --git a/packages/SimAppDialog/src/com/android/simappdialog/InstallCarrierAppActivity.java b/packages/SimAppDialog/src/com/android/simappdialog/InstallCarrierAppActivity.java
index abe82a8..0b6f9bb 100644
--- a/packages/SimAppDialog/src/com/android/simappdialog/InstallCarrierAppActivity.java
+++ b/packages/SimAppDialog/src/com/android/simappdialog/InstallCarrierAppActivity.java
@@ -17,14 +17,17 @@
 
 import android.app.Activity;
 import android.content.Intent;
+import android.content.res.Resources;
 import android.os.Bundle;
 import android.sysprop.SetupWizardProperties;
 import android.text.TextUtils;
 import android.view.View;
-import android.widget.Button;
 import android.widget.TextView;
 
-import com.android.setupwizardlib.util.WizardManagerHelper;
+import com.google.android.setupcompat.template.FooterBarMixin;
+import com.google.android.setupcompat.template.FooterButton;
+import com.google.android.setupdesign.GlifLayout;
+import com.google.android.setupdesign.util.ThemeResolver;
 
 /**
  * Activity that gives a user the choice to download the SIM app or defer until a later time
@@ -35,7 +38,7 @@
  * Can display the carrier app name if its passed into the intent with key
  * {@link #BUNDLE_KEY_CARRIER_NAME}
  */
-public class InstallCarrierAppActivity extends Activity implements View.OnClickListener {
+public class InstallCarrierAppActivity extends Activity {
     /**
      * Key for the carrier app name that will be displayed as the app to download.  If unset, a
      * default description will be used
@@ -50,20 +53,33 @@
     protected void onCreate(Bundle icicle) {
         // Setup theme for aosp/pixel
         setTheme(
-                WizardManagerHelper.getThemeRes(
-                        SetupWizardProperties.theme().orElse(""),
-                        R.style.SuwThemeGlif_Light
-                )
-        );
+                new ThemeResolver.Builder()
+                        .setDefaultTheme(R.style.SudThemeGlifV3_Light)
+                        .build()
+                        .resolve(SetupWizardProperties.theme().orElse(""),
+                                /* suppressDayNight= */ false));
 
         super.onCreate(icicle);
         setContentView(R.layout.install_carrier_app_activity);
 
-        Button notNowButton = findViewById(R.id.skip_button);
-        notNowButton.setOnClickListener(this);
+        GlifLayout layout = findViewById(R.id.setup_wizard_layout);
+        FooterBarMixin mixin = layout.getMixin(FooterBarMixin.class);
+        mixin.setSecondaryButton(
+                new FooterButton.Builder(this)
+                        .setText(R.string.install_carrier_app_defer_action)
+                        .setListener(this::onSkipButtonClick)
+                        .setButtonType(FooterButton.ButtonType.SKIP)
+                        .setTheme(R.style.SudGlifButton_Secondary)
+                        .build());
 
-        Button downloadButton = findViewById(R.id.download_button);
-        downloadButton.setOnClickListener(this);
+        mixin.setPrimaryButton(
+                new FooterButton.Builder(this)
+                        .setText(R.string.install_carrier_app_download_action)
+                        .setListener(this::onDownloadButtonClick)
+                        .setButtonType(FooterButton.ButtonType.OTHER)
+                        .setTheme(R.style.SudGlifButton_Primary)
+                        .build());
+
 
         // Show/hide illo depending on whether one was provided in a resource overlay
         boolean showIllo = getResources().getBoolean(R.bool.show_sim_app_dialog_illo);
@@ -82,15 +98,17 @@
     }
 
     @Override
-    public void onClick(View v) {
-        switch (v.getId()) {
-            case R.id.skip_button:
-                finish(DEFER_RESULT);
-                break;
-            case R.id.download_button:
-                finish(DOWNLOAD_RESULT);
-                break;
-        }
+    protected void onApplyThemeResource(Resources.Theme theme, int resid, boolean first) {
+        theme.applyStyle(R.style.SetupWizardPartnerResource, true);
+        super.onApplyThemeResource(theme, resid, first);
+    }
+
+    protected void onSkipButtonClick(View view) {
+        finish(DEFER_RESULT);
+    }
+
+    protected void onDownloadButtonClick(View view) {
+        finish(DOWNLOAD_RESULT);
     }
 
     private void finish(int resultCode) {
diff --git a/packages/SoundPicker/res/values-ar/strings.xml b/packages/SoundPicker/res/values-ar/strings.xml
index a917955..f8844e9 100644
--- a/packages/SoundPicker/res/values-ar/strings.xml
+++ b/packages/SoundPicker/res/values-ar/strings.xml
@@ -25,5 +25,5 @@
     <string name="delete_ringtone_text" msgid="201443984070732499">"حذف"</string>
     <string name="unable_to_add_ringtone" msgid="4583511263449467326">"يتعذر إضافة نغمة رنين مخصصة"</string>
     <string name="unable_to_delete_ringtone" msgid="6792301380142859496">"يتعذر حذف نغمة الرنين المخصصة"</string>
-    <string name="app_label" msgid="3091611356093417332">"Sounds"</string>
+    <string name="app_label" msgid="3091611356093417332">"الأصوات"</string>
 </resources>
diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml
index 98d3553..af008b9 100644
--- a/packages/SystemUI/AndroidManifest.xml
+++ b/packages/SystemUI/AndroidManifest.xml
@@ -113,6 +113,7 @@
     <uses-permission android:name="android.permission.SET_ORIENTATION" />
     <uses-permission android:name="android.permission.DISABLE_KEYGUARD" />
     <uses-permission android:name="android.permission.MONITOR_INPUT" />
+    <uses-permission android:name="android.permission.INPUT_CONSUMER" />
 
     <!-- DreamManager -->
     <uses-permission android:name="android.permission.READ_DREAM_STATE" />
@@ -240,6 +241,8 @@
     <!-- Listen app op changes -->
     <uses-permission android:name="android.permission.WATCH_APPOPS" />
     <uses-permission android:name="android.permission.OBSERVE_GRANT_REVOKE_PERMISSIONS" />
+    <!-- For handling silent audio recordings -->
+    <uses-permission android:name="android.permission.MODIFY_AUDIO_ROUTING" />
 
     <!-- to read and change hvac values in a car -->
     <uses-permission android:name="android.car.permission.CONTROL_CAR_CLIMATE" />
@@ -267,6 +270,7 @@
     <!-- Permission to make accessibility service access Bubbles -->
     <uses-permission android:name="android.permission.ADD_TRUSTED_DISPLAY" />
 
+
     <protected-broadcast android:name="com.android.settingslib.action.REGISTER_SLICE_RECEIVER" />
     <protected-broadcast android:name="com.android.settingslib.action.UNREGISTER_SLICE_RECEIVER" />
     <protected-broadcast android:name="com.android.settings.flashlight.action.FLASHLIGHT_CHANGED" />
diff --git a/packages/SystemUI/README.md b/packages/SystemUI/README.md
index 68b9553..148fabb 100644
--- a/packages/SystemUI/README.md
+++ b/packages/SystemUI/README.md
@@ -88,11 +88,6 @@
 Registers all the callbacks/listeners required to show the Volume dialog when
 it should be shown.
 
-### [com.android.systemui.stackdivider.Divider](/packages/SystemUI/src/com/android/systemui/stackdivider/Divider.java)
-
-Shows the drag handle for the divider between two apps when in split screen
-mode.
-
 ### [com.android.systemui.status.phone.StatusBar](/packages/SystemUI/src/com/android/systemui/status/phone/StatusBar.java)
 
 This shows the UI for the status bar and the notification shade it contains.
@@ -153,6 +148,10 @@
 
 Biometric UI.
 
+### [com.android.systemui.wmshell.WMShell](/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java)
+
+Delegates SysUI events to WM Shell controllers.
+
 ---
 
  * [Plugins](/packages/SystemUI/docs/plugins.md)
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/statusbar/NotificationMenuRowPlugin.java b/packages/SystemUI/plugin/src/com/android/systemui/plugins/statusbar/NotificationMenuRowPlugin.java
index d2112a0..883f4de 100644
--- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/statusbar/NotificationMenuRowPlugin.java
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/statusbar/NotificationMenuRowPlugin.java
@@ -75,11 +75,6 @@
     public MenuItem getLongpressMenuItem(Context context);
 
     /**
-     * @return the {@link MenuItem} to display when app ops icons are pressed.
-     */
-    public MenuItem getAppOpsMenuItem(Context context);
-
-    /**
      * @return the {@link MenuItem} to display when feedback icon is pressed.
      */
     public MenuItem getFeedbackMenuItem(Context context);
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/statusbar/StatusBarStateController.java b/packages/SystemUI/plugin/src/com/android/systemui/plugins/statusbar/StatusBarStateController.java
index 0d960f0..6c5c4ef 100644
--- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/statusbar/StatusBarStateController.java
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/statusbar/StatusBarStateController.java
@@ -39,6 +39,10 @@
      */
     boolean isDozing();
 
+    /**
+     * Is the status bar panel expanded.
+     */
+    boolean isExpanded();
 
     /**
      * Is device pulsing.
@@ -113,5 +117,10 @@
          * Callback to be notified when the pulsing state changes
          */
         default void onPulsingChanged(boolean pulsing) {}
+
+        /**
+         * Callback to be notified when the expanded state of the status bar changes
+         */
+        default void onExpandedChanged(boolean isExpanded) {}
     }
 }
diff --git a/packages/SystemUI/proguard.flags b/packages/SystemUI/proguard.flags
index 14097b1..df66bf5 100644
--- a/packages/SystemUI/proguard.flags
+++ b/packages/SystemUI/proguard.flags
@@ -1,9 +1,9 @@
--keep class com.android.systemui.statusbar.policy.KeyButtonView {
+-keep class com.android.systemui.navigationbar.buttons.KeyButtonView {
   public float getDrawingAlpha();
   public void setDrawingAlpha(float);
 }
 
--keep class com.android.systemui.statusbar.policy.KeyButtonRipple {
+-keep class com.android.systemui.navigationbar.buttons.KeyButtonRipple {
   public float getGlowAlpha();
   public float getGlowScale();
   public void setGlowAlpha(float);
@@ -41,4 +41,9 @@
     public <init>(android.content.Context);
 }
 
--keep class com.android.wm.shell.*
\ No newline at end of file
+-keep class com.android.wm.shell.*
+
+-keep class com.android.systemui.dagger.GlobalRootComponent { *; }
+-keep class com.android.systemui.dagger.GlobalRootComponent$SysUIComponentImpl { *; }
+-keep class com.android.systemui.dagger.Dagger** { *; }
+-keep class com.android.systemui.tv.Dagger** { *; }
\ No newline at end of file
diff --git a/packages/SystemUI/res-keyguard/drawable/face_auth_wallpaper.png b/packages/SystemUI/res-keyguard/drawable/face_auth_wallpaper.png
new file mode 100644
index 0000000..b907f4e
--- /dev/null
+++ b/packages/SystemUI/res-keyguard/drawable/face_auth_wallpaper.png
Binary files differ
diff --git a/packages/SystemUI/res-keyguard/values/styles.xml b/packages/SystemUI/res-keyguard/values/styles.xml
index 53eb234..401f3e3 100644
--- a/packages/SystemUI/res-keyguard/values/styles.xml
+++ b/packages/SystemUI/res-keyguard/values/styles.xml
@@ -27,6 +27,7 @@
         <item name="android:textColor">?attr/wallpaperTextColorSecondary</item>
         <item name="android:textSize">14dp</item>
         <item name="android:background">@drawable/kg_emergency_button_background</item>
+        <item name="android:fontFamily">@*android:string/config_headlineFontFamily</item>
         <item name="android:paddingLeft">12dp</item>
         <item name="android:paddingRight">12dp</item>
     </style>
diff --git a/packages/SystemUI/res-product/values-in/strings.xml b/packages/SystemUI/res-product/values-in/strings.xml
index 2e0580f..1451e2c 100644
--- a/packages/SystemUI/res-product/values-in/strings.xml
+++ b/packages/SystemUI/res-product/values-in/strings.xml
@@ -26,10 +26,10 @@
     <string name="keyguard_missing_sim_message" product="tablet" msgid="5018086454277963787">"Tidak ada kartu SIM dalam tablet."</string>
     <string name="keyguard_missing_sim_message" product="default" msgid="7053347843877341391">"Tidak ada kartu SIM dalam ponsel."</string>
     <string name="kg_invalid_confirm_pin_hint" product="default" msgid="6278551068943958651">"Kode PIN tidak cocok"</string>
-    <string name="kg_failed_attempts_almost_at_wipe" product="tablet" msgid="302165994845009232">"Anda telah <xliff:g id="NUMBER_0">%1$d</xliff:g> kali berupaya membuka kunci tablet dengan tidak benar. Setelah <xliff:g id="NUMBER_1">%2$d</xliff:g> lagi upaya yang tidak berhasil, tablet ini akan disetel ulang, sehingga semua datanya akan dihapus."</string>
-    <string name="kg_failed_attempts_almost_at_wipe" product="default" msgid="2594813176164266847">"Anda telah <xliff:g id="NUMBER_0">%1$d</xliff:g> kali berupaya membuka kunci ponsel dengan tidak benar. Setelah <xliff:g id="NUMBER_1">%2$d</xliff:g> lagi upaya yang tidak berhasil, ponsel ini akan disetel ulang, sehingga semua datanya akan dihapus."</string>
-    <string name="kg_failed_attempts_now_wiping" product="tablet" msgid="8710104080409538587">"Anda telah <xliff:g id="NUMBER">%d</xliff:g> kali berupaya membuka kunci tablet dengan tidak benar. Tablet ini akan disetel ulang, sehingga semua datanya akan dihapus."</string>
-    <string name="kg_failed_attempts_now_wiping" product="default" msgid="6381835450014881813">"Anda telah <xliff:g id="NUMBER">%d</xliff:g> kali berupaya membuka kunci ponsel dengan tidak benar. Ponsel ini akan disetel ulang, sehingga semua datanya akan dihapus."</string>
+    <string name="kg_failed_attempts_almost_at_wipe" product="tablet" msgid="302165994845009232">"Anda telah <xliff:g id="NUMBER_0">%1$d</xliff:g> kali berupaya membuka kunci tablet dengan tidak benar. Setelah <xliff:g id="NUMBER_1">%2$d</xliff:g> lagi upaya yang tidak berhasil, tablet ini akan direset, sehingga semua datanya akan dihapus."</string>
+    <string name="kg_failed_attempts_almost_at_wipe" product="default" msgid="2594813176164266847">"Anda telah <xliff:g id="NUMBER_0">%1$d</xliff:g> kali berupaya membuka kunci ponsel dengan tidak benar. Setelah <xliff:g id="NUMBER_1">%2$d</xliff:g> lagi upaya yang tidak berhasil, ponsel ini akan direset, sehingga semua datanya akan dihapus."</string>
+    <string name="kg_failed_attempts_now_wiping" product="tablet" msgid="8710104080409538587">"Anda telah <xliff:g id="NUMBER">%d</xliff:g> kali berupaya membuka kunci tablet dengan tidak benar. Tablet ini akan direset, sehingga semua datanya akan dihapus."</string>
+    <string name="kg_failed_attempts_now_wiping" product="default" msgid="6381835450014881813">"Anda telah <xliff:g id="NUMBER">%d</xliff:g> kali berupaya membuka kunci ponsel dengan tidak benar. Ponsel ini akan direset, sehingga semua datanya akan dihapus."</string>
     <string name="kg_failed_attempts_almost_at_erase_user" product="tablet" msgid="7325071812832605911">"Anda telah <xliff:g id="NUMBER_0">%1$d</xliff:g> kali berupaya membuka kunci tablet dengan tidak benar. Setelah <xliff:g id="NUMBER_1">%2$d</xliff:g> lagi upaya yang tidak berhasil, pengguna ini akan dihapus, sehingga semua data pengguna akan dihapus."</string>
     <string name="kg_failed_attempts_almost_at_erase_user" product="default" msgid="8110939900089863103">"Anda telah <xliff:g id="NUMBER_0">%1$d</xliff:g> kali berupaya membuka kunci ponsel dengan tidak benar. Setelah <xliff:g id="NUMBER_1">%2$d</xliff:g> lagi upaya yang tidak berhasil, pengguna ini akan dihapus, sehingga semua data pengguna akan dihapus."</string>
     <string name="kg_failed_attempts_now_erasing_user" product="tablet" msgid="8509811676952707883">"Anda telah <xliff:g id="NUMBER">%d</xliff:g> kali berupaya membuka kunci tablet dengan tidak benar. Pengguna ini akan dihapus, sehingga semua data pengguna akan dihapus."</string>
diff --git a/packages/SystemUI/res/drawable-television/ic_volume_media.xml b/packages/SystemUI/res/drawable-television/ic_volume_media.xml
new file mode 100644
index 0000000..e43c4b4
--- /dev/null
+++ b/packages/SystemUI/res/drawable-television/ic_volume_media.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  Copyright (C) 2020 The Android Open Source Project
+
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License
+  -->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:viewportWidth="24.0"
+    android:viewportHeight="24.0">
+    <path
+        android:fillColor="@color/tv_volume_dialog_accent"
+        android:pathData="M3,9v6h4l5,5L12,4L7,9L3,9zM16.5,12c0,-1.77 -1.02,-3.29 -2.5,-4.03v8.05c1.48,-0.73 2.5,-2.25 2.5,-4.02zM14,3.23v2.06c2.89,0.86 5,3.54 5,6.71s-2.11,5.85 -5,6.71v2.06c4.01,-0.91 7,-4.49 7,-8.77s-2.99,-7.86 -7,-8.77z"/>
+</vector>
diff --git a/packages/SystemUI/res/drawable-television/ic_volume_media_low.xml b/packages/SystemUI/res/drawable-television/ic_volume_media_low.xml
new file mode 100644
index 0000000..0f6dc95
--- /dev/null
+++ b/packages/SystemUI/res/drawable-television/ic_volume_media_low.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  Copyright (C) 2020 The Android Open Source Project
+
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License
+  -->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:viewportWidth="24.0"
+    android:viewportHeight="24.0">
+    <path
+        android:fillColor="@color/tv_volume_dialog_accent"
+        android:pathData="M3,15V9H7L12,4V20L7,15H3ZM14,7.97C15.48,8.71 16.5,10.23 16.5,12C16.5,13.77 15.48,15.29 14,16.02V7.97Z"/>
+</vector>
diff --git a/packages/SystemUI/res/drawable-television/ic_volume_media_mute.xml b/packages/SystemUI/res/drawable-television/ic_volume_media_mute.xml
new file mode 100644
index 0000000..4b59e13
--- /dev/null
+++ b/packages/SystemUI/res/drawable-television/ic_volume_media_mute.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  Copyright (C) 2020 The Android Open Source Project
+
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License
+  -->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:viewportWidth="24.0"
+    android:viewportHeight="24.0">
+    <path
+        android:fillColor="@color/tv_volume_dialog_accent"
+        android:pathData="M16.5,12c0,-1.77 -1.02,-3.29 -2.5,-4.03v2.21l2.45,2.45c0.03,-0.2 0.05,-0.41 0.05,-0.63zM19,12c0,0.94 -0.2,1.82 -0.54,2.64l1.51,1.51C20.63,14.91 21,13.5 21,12c0,-4.28 -2.99,-7.86 -7,-8.77v2.06c2.89,0.86 5,3.54 5,6.71zM4.27,3L3,4.27 7.73,9L3,9v6h4l5,5v-6.73l4.25,4.25c-0.67,0.52 -1.42,0.93 -2.25,1.18v2.06c1.38,-0.31 2.63,-0.95 3.69,-1.81L19.73,21 21,19.73l-9,-9L4.27,3zM12,4L9.91,6.09 12,8.18L12,4z"/>
+</vector>
+
diff --git a/packages/SystemUI/res/drawable/ic_volume_media_low.xml b/packages/SystemUI/res/drawable/ic_volume_media_low.xml
new file mode 100644
index 0000000..87591de
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_volume_media_low.xml
@@ -0,0 +1,18 @@
+<!--
+     Copyright (C) 2020 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:drawable="@drawable/ic_volume_media" />
+</selector>
diff --git a/packages/SystemUI/res/drawable/tv_circle_dark.xml b/packages/SystemUI/res/drawable/tv_circle_dark.xml
deleted file mode 100644
index d1ba8e7..0000000
--- a/packages/SystemUI/res/drawable/tv_circle_dark.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-  ~ Copyright (C) 2019 The Android Open Source Project
-  ~
-  ~ Licensed under the Apache License, Version 2.0 (the "License");
-  ~ you may not use this file except in compliance with the License.
-  ~ You may obtain a copy of the License at
-  ~
-  ~      http://www.apache.org/licenses/LICENSE-2.0
-  ~
-  ~ Unless required by applicable law or agreed to in writing, software
-  ~ distributed under the License is distributed on an "AS IS" BASIS,
-  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-  ~ See the License for the specific language governing permissions and
-  ~ limitations under the License.
-  -->
-
-<shape xmlns:android="http://schemas.android.com/apk/res/android"
-    android:shape="oval">
-
-  <solid
-      android:color="@color/tv_audio_recording_indicator_background" />
-
-</shape>
\ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/tv_circle_white_translucent.xml b/packages/SystemUI/res/drawable/tv_rect_shadow_rounded.xml
similarity index 75%
copy from packages/SystemUI/res/drawable/tv_circle_white_translucent.xml
copy to packages/SystemUI/res/drawable/tv_rect_shadow_rounded.xml
index 55d21de..93f8724 100644
--- a/packages/SystemUI/res/drawable/tv_circle_white_translucent.xml
+++ b/packages/SystemUI/res/drawable/tv_rect_shadow_rounded.xml
@@ -16,9 +16,10 @@
   -->
 
 <shape xmlns:android="http://schemas.android.com/apk/res/android"
-    android:shape="oval">
+       android:shape="rectangle">
 
-  <solid
-      android:color="@color/tv_audio_recording_indicator_pulse" />
+    <corners android:radius="20dp"/>
+    <solid android:color="@color/tv_audio_recording_indicator_icon_background"/>
+    <stroke android:width="1dp" android:color="@color/tv_audio_recording_indicator_stroke"/>
 
 </shape>
\ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/tv_ring_white.xml b/packages/SystemUI/res/drawable/tv_ring_white.xml
deleted file mode 100644
index 0f7cc10..0000000
--- a/packages/SystemUI/res/drawable/tv_ring_white.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-  ~ Copyright (C) 2019 The Android Open Source Project
-  ~
-  ~ Licensed under the Apache License, Version 2.0 (the "License");
-  ~ you may not use this file except in compliance with the License.
-  ~ You may obtain a copy of the License at
-  ~
-  ~      http://www.apache.org/licenses/LICENSE-2.0
-  ~
-  ~ Unless required by applicable law or agreed to in writing, software
-  ~ distributed under the License is distributed on an "AS IS" BASIS,
-  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-  ~ See the License for the specific language governing permissions and
-  ~ limitations under the License.
-  -->
-
-<shape xmlns:android="http://schemas.android.com/apk/res/android"
-    android:shape="oval">
-
-  <stroke
-      android:width="1dp"
-      android:color="@android:color/white" />
-
-</shape>
\ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/tv_circle_white_translucent.xml b/packages/SystemUI/res/drawable/tv_volume_dialog_background.xml
similarity index 75%
copy from packages/SystemUI/res/drawable/tv_circle_white_translucent.xml
copy to packages/SystemUI/res/drawable/tv_volume_dialog_background.xml
index 55d21de..fee6e57 100644
--- a/packages/SystemUI/res/drawable/tv_circle_white_translucent.xml
+++ b/packages/SystemUI/res/drawable/tv_volume_dialog_background.xml
@@ -1,6 +1,6 @@
 <?xml version="1.0" encoding="utf-8"?>
 <!--
-  ~ Copyright (C) 2019 The Android Open Source Project
+  ~ 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.
@@ -16,9 +16,9 @@
   -->
 
 <shape xmlns:android="http://schemas.android.com/apk/res/android"
-    android:shape="oval">
+    android:shape="rectangle">
 
-  <solid
-      android:color="@color/tv_audio_recording_indicator_pulse" />
+  <solid android:color="@color/tv_volume_dialog_background" />
+  <corners android:radius="@dimen/tv_volume_dialog_corner_radius"/>
 
-</shape>
\ No newline at end of file
+</shape>
diff --git a/packages/SystemUI/res/drawable/tv_circle_white_translucent.xml b/packages/SystemUI/res/drawable/tv_volume_dialog_circle.xml
similarity index 84%
rename from packages/SystemUI/res/drawable/tv_circle_white_translucent.xml
rename to packages/SystemUI/res/drawable/tv_volume_dialog_circle.xml
index 55d21de..3c4fc05 100644
--- a/packages/SystemUI/res/drawable/tv_circle_white_translucent.xml
+++ b/packages/SystemUI/res/drawable/tv_volume_dialog_circle.xml
@@ -1,6 +1,6 @@
 <?xml version="1.0" encoding="utf-8"?>
 <!--
-  ~ Copyright (C) 2019 The Android Open Source Project
+  ~ 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.
@@ -17,8 +17,6 @@
 
 <shape xmlns:android="http://schemas.android.com/apk/res/android"
     android:shape="oval">
+  <solid android:color="@color/tv_volume_dialog_circle" />
 
-  <solid
-      android:color="@color/tv_audio_recording_indicator_pulse" />
-
-</shape>
\ No newline at end of file
+</shape>
diff --git a/packages/SystemUI/res/layout-land-television/volume_dialog.xml b/packages/SystemUI/res/layout-land-television/volume_dialog.xml
index e0d158d..56d847c 100644
--- a/packages/SystemUI/res/layout-land-television/volume_dialog.xml
+++ b/packages/SystemUI/res/layout-land-television/volume_dialog.xml
@@ -20,7 +20,7 @@
     android:layout_width="wrap_content"
     android:layout_height="wrap_content"
     android:background="@android:color/transparent"
-    android:theme="@style/qs_theme">
+    android:theme="@style/volume_dialog_theme">
 
     <FrameLayout
         android:id="@+id/volume_dialog"
@@ -46,7 +46,7 @@
             android:translationZ="@dimen/volume_dialog_elevation"
             android:clipChildren="false"
             android:clipToPadding="false"
-            android:background="@drawable/rounded_bg_full">
+            android:background="@drawable/tv_volume_dialog_background">
 
             <LinearLayout
                 android:id="@+id/volume_dialog_rows"
@@ -54,9 +54,7 @@
                 android:layout_height="wrap_content"
                 android:minWidth="@dimen/volume_dialog_panel_width"
                 android:gravity="center"
-                android:orientation="horizontal"
-                android:paddingRight="@dimen/volume_dialog_stream_padding"
-                android:paddingLeft="@dimen/volume_dialog_stream_padding">
+                android:orientation="horizontal">
                 <!-- volume rows added and removed here! :-) -->
             </LinearLayout>
 
@@ -98,4 +96,4 @@
 
     </FrameLayout>
 
-</FrameLayout>
\ No newline at end of file
+</FrameLayout>
diff --git a/packages/SystemUI/res/layout-land-television/volume_dialog_row.xml b/packages/SystemUI/res/layout-land-television/volume_dialog_row.xml
index 08209ab..c0f0aa8 100644
--- a/packages/SystemUI/res/layout-land-television/volume_dialog_row.xml
+++ b/packages/SystemUI/res/layout-land-television/volume_dialog_row.xml
@@ -21,11 +21,12 @@
     android:background="@android:color/transparent"
     android:clipChildren="false"
     android:clipToPadding="false"
-    android:theme="@style/qs_theme">
+    android:theme="@style/volume_dialog_theme">
 
     <LinearLayout
         android:layout_width="wrap_content"
         android:layout_height="match_parent"
+        android:padding="@dimen/tv_volume_dialog_row_padding"
         android:background="@android:color/transparent"
         android:gravity="center"
         android:layout_gravity="center"
@@ -33,9 +34,9 @@
         <com.android.keyguard.AlphaOptimizedImageButton
             android:id="@+id/volume_row_icon"
             style="@style/VolumeButtons"
-            android:layout_width="@dimen/volume_dialog_tap_target_size"
-            android:layout_height="@dimen/volume_dialog_tap_target_size"
-            android:background="@drawable/ripple_drawable_20dp"
+            android:layout_width="@dimen/tv_volume_dialog_bubble_size"
+            android:layout_height="@dimen/tv_volume_dialog_bubble_size"
+            android:background="@drawable/tv_volume_dialog_circle"
             android:tint="@color/accent_tint_color_selector"
             android:soundEffectsEnabled="false" />
         <TextView
@@ -62,6 +63,15 @@
                 android:layout_gravity="center"
                 android:rotation="0" />
         </FrameLayout>
+        <TextView
+            android:id="@+id/volume_number"
+            android:layout_width="@dimen/tv_volume_dialog_bubble_size"
+            android:layout_height="@dimen/tv_volume_dialog_bubble_size"
+            android:maxLength="2"
+            android:gravity="center"
+            android:background="@drawable/tv_volume_dialog_circle"
+            android:textSize="@dimen/tv_volume_number_text_size"
+            android:textColor="@color/accent_tint_color_selector"/>
     </LinearLayout>
 
     <include layout="@layout/volume_dnd_icon"/>
diff --git a/packages/SystemUI/res/layout-land/volume_dialog.xml b/packages/SystemUI/res/layout-land/volume_dialog.xml
index 5da7819..c420117 100644
--- a/packages/SystemUI/res/layout-land/volume_dialog.xml
+++ b/packages/SystemUI/res/layout-land/volume_dialog.xml
@@ -22,7 +22,7 @@
     android:gravity="right"
     android:layout_gravity="right"
     android:background="@android:color/transparent"
-    android:theme="@style/qs_theme">
+    android:theme="@style/volume_dialog_theme">
 
     <FrameLayout
         android:id="@+id/volume_dialog"
diff --git a/packages/SystemUI/res/layout/back.xml b/packages/SystemUI/res/layout/back.xml
index 4e8726b..046aecd 100644
--- a/packages/SystemUI/res/layout/back.xml
+++ b/packages/SystemUI/res/layout/back.xml
@@ -14,7 +14,7 @@
      limitations under the License.
 -->
 
-<com.android.systemui.statusbar.policy.KeyButtonView
+<com.android.systemui.navigationbar.buttons.KeyButtonView
     xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:systemui="http://schemas.android.com/apk/res-auto"
     android:id="@+id/back"
diff --git a/packages/SystemUI/res/layout/bubble_stack_user_education.xml b/packages/SystemUI/res/layout/bubble_stack_user_education.xml
index 6164032..fe1ed4b 100644
--- a/packages/SystemUI/res/layout/bubble_stack_user_education.xml
+++ b/packages/SystemUI/res/layout/bubble_stack_user_education.xml
@@ -15,8 +15,8 @@
   ~ limitations under the License.
   -->
 <LinearLayout
+    android:id="@+id/stack_education_layout"
     xmlns:android="http://schemas.android.com/apk/res/android"
-    android:id="@+id/user_education_view"
     android:layout_height="wrap_content"
     android:layout_width="wrap_content"
     android:paddingTop="48dp"
@@ -25,26 +25,29 @@
     android:paddingEnd="16dp"
     android:layout_marginEnd="24dp"
     android:orientation="vertical"
-    android:background="@drawable/bubble_stack_user_education_bg">
-
+    android:background="@drawable/bubble_stack_user_education_bg"
+    >
     <TextView
-        android:id="@+id/user_education_title"
+        android:id="@+id/stack_education_title"
         android:layout_width="wrap_content"
         android:layout_height="wrap_content"
         android:paddingBottom="16dp"
         android:fontFamily="@*android:string/config_bodyFontFamilyMedium"
         android:maxLines="2"
         android:ellipsize="end"
+        android:gravity="start"
+        android:textAlignment="viewStart"
         android:text="@string/bubbles_user_education_title"
         android:textAppearance="@*android:style/TextAppearance.DeviceDefault.Headline"/>
 
     <TextView
-        android:id="@+id/user_education_description"
+        android:id="@+id/stack_education_description"
         android:layout_width="wrap_content"
         android:layout_height="wrap_content"
         android:ellipsize="end"
+        android:gravity="start"
+        android:textAlignment="viewStart"
         android:text="@string/bubbles_user_education_description"
         android:fontFamily="@*android:string/config_bodyFontFamily"
         android:textAppearance="@*android:style/TextAppearance.DeviceDefault.Body2"/>
-
 </LinearLayout>
diff --git a/packages/SystemUI/res/layout/bubbles_manage_button_education.xml b/packages/SystemUI/res/layout/bubbles_manage_button_education.xml
index 213bb92..b51dc93 100644
--- a/packages/SystemUI/res/layout/bubbles_manage_button_education.xml
+++ b/packages/SystemUI/res/layout/bubbles_manage_button_education.xml
@@ -14,77 +14,77 @@
   ~ See the License for the specific language governing permissions and
   ~ limitations under the License.
   -->
-<com.android.systemui.bubbles.ManageEducationView
+
+<LinearLayout
     xmlns:android="http://schemas.android.com/apk/res/android"
-    android:layout_width="match_parent"
-    android:layout_height="match_parent"
+    android:id="@+id/manage_education_view"
+    android:layout_width="wrap_content"
+    android:layout_height="wrap_content"
+    android:clickable="true"
+    android:paddingTop="28dp"
+    android:paddingBottom="16dp"
+    android:paddingStart="@dimen/bubble_expanded_view_padding"
+    android:paddingEnd="48dp"
+    android:layout_marginEnd="24dp"
+    android:orientation="vertical"
+    android:background="@drawable/bubble_stack_user_education_bg"
     >
-    <LinearLayout
+
+    <TextView
+        android:id="@+id/user_education_title"
         android:layout_width="wrap_content"
         android:layout_height="wrap_content"
-        android:id="@+id/manage_education_view"
-        android:clickable="true"
-        android:paddingTop="28dp"
+        android:paddingStart="16dp"
         android:paddingBottom="16dp"
-        android:paddingStart="@dimen/bubble_expanded_view_padding"
-        android:paddingEnd="48dp"
-        android:layout_marginEnd="24dp"
-        android:orientation="vertical"
-        android:background="@drawable/bubble_stack_user_education_bg"
-        >
+        android:fontFamily="@*android:string/config_bodyFontFamilyMedium"
+        android:maxLines="2"
+        android:ellipsize="end"
+        android:gravity="start"
+        android:textAlignment="viewStart"
+        android:text="@string/bubbles_user_education_manage_title"
+        android:textAppearance="@*android:style/TextAppearance.DeviceDefault.Headline"/>
 
-        <TextView
-            android:id="@+id/user_education_title"
+    <TextView
+        android:id="@+id/user_education_description"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:paddingStart="16dp"
+        android:paddingBottom="24dp"
+        android:text="@string/bubbles_user_education_manage"
+        android:maxLines="2"
+        android:ellipsize="end"
+        android:gravity="start"
+        android:textAlignment="viewStart"
+        android:fontFamily="@*android:string/config_bodyFontFamily"
+        android:textAppearance="@*android:style/TextAppearance.DeviceDefault.Body2"/>
+
+    <LinearLayout
+        android:layout_height="wrap_content"
+        android:layout_width="wrap_content"
+        android:id="@+id/button_layout"
+        android:orientation="horizontal" >
+
+        <com.android.systemui.statusbar.AlphaOptimizedButton
+            style="@android:style/Widget.Material.Button.Borderless"
+            android:id="@+id/manage"
+            android:layout_gravity="start"
             android:layout_width="wrap_content"
             android:layout_height="wrap_content"
-            android:paddingStart="16dp"
-            android:paddingBottom="16dp"
-            android:fontFamily="@*android:string/config_bodyFontFamilyMedium"
-            android:maxLines="2"
-            android:ellipsize="end"
-            android:text="@string/bubbles_user_education_manage_title"
-            android:textAppearance="@*android:style/TextAppearance.DeviceDefault.Headline"/>
+            android:focusable="true"
+            android:clickable="false"
+            android:text="@string/manage_bubbles_text"
+            android:textColor="?attr/wallpaperTextColor"
+            />
 
-        <TextView
-            android:id="@+id/user_education_description"
+        <com.android.systemui.statusbar.AlphaOptimizedButton
+            style="@android:style/Widget.Material.Button.Borderless"
+            android:id="@+id/got_it"
+            android:layout_gravity="start"
             android:layout_width="wrap_content"
             android:layout_height="wrap_content"
-            android:paddingStart="16dp"
-            android:paddingBottom="24dp"
-            android:text="@string/bubbles_user_education_manage"
-            android:maxLines="2"
-            android:ellipsize="end"
-            android:fontFamily="@*android:string/config_bodyFontFamily"
-            android:textAppearance="@*android:style/TextAppearance.DeviceDefault.Body2"/>
-
-        <LinearLayout
-            android:layout_height="wrap_content"
-            android:layout_width="wrap_content"
-            android:id="@+id/button_layout"
-            android:orientation="horizontal" >
-
-            <com.android.systemui.statusbar.AlphaOptimizedButton
-                style="@android:style/Widget.Material.Button.Borderless"
-                android:id="@+id/manage"
-                android:layout_gravity="start"
-                android:layout_width="wrap_content"
-                android:layout_height="wrap_content"
-                android:focusable="true"
-                android:clickable="false"
-                android:text="@string/manage_bubbles_text"
-                android:textColor="?attr/wallpaperTextColor"
-                />
-
-            <com.android.systemui.statusbar.AlphaOptimizedButton
-                style="@android:style/Widget.Material.Button.Borderless"
-                android:id="@+id/got_it"
-                android:layout_gravity="start"
-                android:layout_width="wrap_content"
-                android:layout_height="wrap_content"
-                android:focusable="true"
-                android:text="@string/bubbles_user_education_got_it"
-                android:textColor="?attr/wallpaperTextColor"
-                />
-        </LinearLayout>
+            android:focusable="true"
+            android:text="@string/bubbles_user_education_got_it"
+            android:textColor="?attr/wallpaperTextColor"
+            />
     </LinearLayout>
-</com.android.systemui.bubbles.ManageEducationView>
+</LinearLayout>
diff --git a/packages/SystemUI/res/layout/contextual.xml b/packages/SystemUI/res/layout/contextual.xml
index 90a7768..2cd7926 100644
--- a/packages/SystemUI/res/layout/contextual.xml
+++ b/packages/SystemUI/res/layout/contextual.xml
@@ -24,7 +24,7 @@
              android:clipChildren="false"
              android:clipToPadding="false"
              >
-    <com.android.systemui.statusbar.policy.KeyButtonView
+    <com.android.systemui.navigationbar.buttons.KeyButtonView
         android:id="@+id/menu"
         android:layout_height="match_parent"
         android:layout_width="match_parent"
@@ -47,7 +47,7 @@
              android:layout_height="match_parent"
              android:visibility="invisible"
     />
-    <com.android.systemui.statusbar.policy.KeyButtonView
+    <com.android.systemui.navigationbar.buttons.KeyButtonView
         android:id="@+id/accessibility_button"
         android:layout_width="match_parent"
         android:layout_height="match_parent"
diff --git a/packages/SystemUI/res/layout/custom_key.xml b/packages/SystemUI/res/layout/custom_key.xml
index 0b5cb72..dc65777 100644
--- a/packages/SystemUI/res/layout/custom_key.xml
+++ b/packages/SystemUI/res/layout/custom_key.xml
@@ -13,7 +13,7 @@
      See the License for the specific language governing permissions and
      limitations under the License.
 -->
-<com.android.systemui.statusbar.policy.KeyButtonView
+<com.android.systemui.navigationbar.buttons.KeyButtonView
     xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:systemui="http://schemas.android.com/apk/res-auto"
     android:layout_width="@dimen/navigation_side_padding"
diff --git a/packages/SystemUI/res/layout/global_screenshot_action_chip.xml b/packages/SystemUI/res/layout/global_screenshot_action_chip.xml
index 46396e3..4b3534b 100644
--- a/packages/SystemUI/res/layout/global_screenshot_action_chip.xml
+++ b/packages/SystemUI/res/layout/global_screenshot_action_chip.xml
@@ -32,6 +32,7 @@
         android:gravity="center">
         <ImageView
             android:id="@+id/screenshot_action_chip_icon"
+            android:tint="@*android:color/accent_device_default"
             android:layout_width="@dimen/screenshot_action_chip_icon_size"
             android:layout_height="@dimen/screenshot_action_chip_icon_size"
             android:layout_marginStart="@dimen/screenshot_action_chip_padding_start"
diff --git a/packages/SystemUI/res/layout/home.xml b/packages/SystemUI/res/layout/home.xml
index 9586327..84eed6a 100644
--- a/packages/SystemUI/res/layout/home.xml
+++ b/packages/SystemUI/res/layout/home.xml
@@ -13,7 +13,7 @@
      See the License for the specific language governing permissions and
      limitations under the License.
 -->
-<com.android.systemui.statusbar.policy.KeyButtonView
+<com.android.systemui.navigationbar.buttons.KeyButtonView
     xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:systemui="http://schemas.android.com/apk/res-auto"
     android:id="@+id/home"
diff --git a/packages/SystemUI/res/layout/home_handle.xml b/packages/SystemUI/res/layout/home_handle.xml
index 54a0b9f..c9d3f98 100644
--- a/packages/SystemUI/res/layout/home_handle.xml
+++ b/packages/SystemUI/res/layout/home_handle.xml
@@ -15,7 +15,7 @@
   ~ limitations under the License.
   -->
 
-<com.android.systemui.statusbar.phone.NavigationHandle
+<com.android.systemui.navigationbar.gestural.NavigationHandle
     xmlns:android="http://schemas.android.com/apk/res/android"
     android:id="@+id/home_handle"
     android:layout_width="@dimen/navigation_home_handle_width"
diff --git a/packages/SystemUI/res/layout/ime_switcher.xml b/packages/SystemUI/res/layout/ime_switcher.xml
index 7710b25..a2c8308 100644
--- a/packages/SystemUI/res/layout/ime_switcher.xml
+++ b/packages/SystemUI/res/layout/ime_switcher.xml
@@ -15,7 +15,7 @@
   ~ limitations under the License
   -->
 
-<com.android.systemui.statusbar.policy.KeyButtonView
+<com.android.systemui.navigationbar.buttons.KeyButtonView
     xmlns:android="http://schemas.android.com/apk/res/android"
     android:id="@+id/ime_switcher"
     android:layout_width="@dimen/navigation_key_width"
diff --git a/packages/SystemUI/res/layout/media_view.xml b/packages/SystemUI/res/layout/media_view.xml
index 3c641af..ed870f8 100644
--- a/packages/SystemUI/res/layout/media_view.xml
+++ b/packages/SystemUI/res/layout/media_view.xml
@@ -210,8 +210,98 @@
         android:layout_width="@dimen/qs_media_icon_size"
         android:layout_height="@dimen/qs_media_icon_size" />
 
-    <!-- Buttons to remove this view when no longer needed -->
-    <include
-        layout="@layout/qs_media_panel_options"
-        android:visibility="gone" />
+    <!-- Constraints are set here as they are the same regardless of host -->
+    <TextView
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_marginTop="@dimen/qs_media_panel_outer_padding"
+        android:layout_marginStart="@dimen/qs_media_panel_outer_padding"
+        android:layout_marginEnd="@dimen/qs_media_panel_outer_padding"
+        android:id="@+id/media_text"
+        android:fontFamily="@*android:string/config_headlineFontFamilyMedium"
+        android:textColor="@color/media_primary_text"
+        android:text="@string/controls_media_title"
+        app:layout_constraintTop_toTopOf="parent"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintBottom_toTopOf="@id/remove_text"
+        app:layout_constraintVertical_chainStyle="spread_inside"/>
+
+    <TextView
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_marginStart="@dimen/qs_media_panel_outer_padding"
+        android:layout_marginEnd="@dimen/qs_media_panel_outer_padding"
+        android:id="@+id/remove_text"
+        android:fontFamily="@*android:string/config_headlineFontFamily"
+        android:singleLine="true"
+        android:textColor="@color/media_primary_text"
+        android:text="@string/controls_media_close_session"
+        app:layout_constraintTop_toBottomOf="@id/media_text"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintBottom_toTopOf="@id/settings"/>
+
+    <FrameLayout
+        android:id="@+id/settings"
+        android:background="@drawable/qs_media_light_source"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_marginStart="@dimen/qs_media_panel_outer_padding"
+        android:paddingBottom="@dimen/qs_media_panel_outer_padding"
+        android:minWidth="48dp"
+        android:minHeight="48dp"
+        app:layout_constraintBottom_toBottomOf="parent"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toBottomOf="@id/remove_text">
+
+        <TextView
+            android:layout_gravity="bottom"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:fontFamily="@*android:string/config_headlineFontFamilyMedium"
+            android:textColor="@android:color/white"
+            android:text="@string/controls_media_settings_button" />
+    </FrameLayout>
+
+    <FrameLayout
+        android:id="@+id/cancel"
+        android:background="@drawable/qs_media_light_source"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_marginEnd="@dimen/qs_media_panel_outer_padding"
+        android:paddingBottom="@dimen/qs_media_panel_outer_padding"
+        android:minWidth="48dp"
+        android:minHeight="48dp"
+        app:layout_constraintBottom_toBottomOf="parent"
+        app:layout_constraintEnd_toStartOf="@id/dismiss" >
+
+        <TextView
+            android:layout_gravity="bottom"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:fontFamily="@*android:string/config_headlineFontFamilyMedium"
+            android:textColor="@android:color/white"
+            android:text="@string/cancel" />
+    </FrameLayout>
+
+    <FrameLayout
+        android:id="@+id/dismiss"
+        android:background="@drawable/qs_media_light_source"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_marginEnd="@dimen/qs_media_panel_outer_padding"
+        android:paddingBottom="@dimen/qs_media_panel_outer_padding"
+        android:minWidth="48dp"
+        android:minHeight="48dp"
+        app:layout_constraintBottom_toBottomOf="parent"
+        app:layout_constraintEnd_toEndOf="parent">
+
+        <TextView
+            android:layout_gravity="bottom"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:fontFamily="@*android:string/config_headlineFontFamilyMedium"
+            android:textColor="@android:color/white"
+            android:text="@string/controls_media_dismiss_button"
+        />
+    </FrameLayout>
 </com.android.systemui.util.animation.TransitionLayout>
diff --git a/packages/SystemUI/res/layout/menu_ime.xml b/packages/SystemUI/res/layout/menu_ime.xml
index df717f6..0bb622b 100644
--- a/packages/SystemUI/res/layout/menu_ime.xml
+++ b/packages/SystemUI/res/layout/menu_ime.xml
@@ -25,7 +25,7 @@
     are placed inside a view that has a size controlled by weight. Ensure weight is large enough to
     support icon size. Use layout_width=navigation_side_padding like other navbar buttons. -->
 
-    <com.android.systemui.statusbar.policy.KeyButtonView
+    <com.android.systemui.navigationbar.buttons.KeyButtonView
         android:id="@+id/menu"
         android:layout_width="match_parent"
         android:layout_height="match_parent"
@@ -43,7 +43,7 @@
         android:paddingStart="0dp"
         android:paddingEnd="0dp"
         />
-    <com.android.systemui.statusbar.policy.KeyButtonView
+    <com.android.systemui.navigationbar.buttons.KeyButtonView
         android:id="@+id/rotate_suggestion"
         android:layout_width="match_parent"
         android:layout_height="match_parent"
@@ -51,7 +51,7 @@
         android:scaleType="centerInside"
         android:contentDescription="@string/accessibility_rotate_button"
         />
-    <com.android.systemui.statusbar.policy.KeyButtonView
+    <com.android.systemui.navigationbar.buttons.KeyButtonView
         android:id="@+id/accessibility_button"
         android:layout_width="match_parent"
         android:layout_height="match_parent"
diff --git a/packages/SystemUI/res/layout/navigation_bar.xml b/packages/SystemUI/res/layout/navigation_bar.xml
index ba6b695..23f36a9 100644
--- a/packages/SystemUI/res/layout/navigation_bar.xml
+++ b/packages/SystemUI/res/layout/navigation_bar.xml
@@ -17,9 +17,10 @@
 */
 -->
 
-<com.android.systemui.statusbar.phone.NavigationBarView
+<com.android.systemui.navigationbar.NavigationBarView
     xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:systemui="http://schemas.android.com/apk/res-auto"
+    android:id="@+id/navigation_bar_view"
     android:layout_height="match_parent"
     android:layout_width="match_parent"
     android:background="@drawable/system_bar_background">
@@ -39,9 +40,9 @@
         android:rotation="180"
         android:visibility="gone"/>
 
-    <com.android.systemui.statusbar.phone.NavigationBarInflaterView
+    <com.android.systemui.navigationbar.NavigationBarInflaterView
         android:id="@+id/navigation_inflater"
         android:layout_width="match_parent"
         android:layout_height="match_parent" />
 
-</com.android.systemui.statusbar.phone.NavigationBarView>
+</com.android.systemui.navigationbar.NavigationBarView>
diff --git a/packages/SystemUI/res/layout/navigation_bar_window.xml b/packages/SystemUI/res/layout/navigation_bar_window.xml
index f98cbd8..b2473cd 100644
--- a/packages/SystemUI/res/layout/navigation_bar_window.xml
+++ b/packages/SystemUI/res/layout/navigation_bar_window.xml
@@ -16,7 +16,7 @@
 ** limitations under the License.
 */
 -->
-<com.android.systemui.statusbar.phone.NavigationBarFrame
+<com.android.systemui.navigationbar.NavigationBarFrame
     xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:systemui="http://schemas.android.com/apk/res-auto"
     android:id="@+id/navigation_bar_frame"
@@ -24,4 +24,4 @@
     android:layout_height="match_parent"
     android:layout_width="match_parent">
 
-</com.android.systemui.statusbar.phone.NavigationBarFrame>
+</com.android.systemui.navigationbar.NavigationBarFrame>
diff --git a/packages/SystemUI/res/layout/navigation_layout.xml b/packages/SystemUI/res/layout/navigation_layout.xml
index db1c79d..0e576fb 100644
--- a/packages/SystemUI/res/layout/navigation_layout.xml
+++ b/packages/SystemUI/res/layout/navigation_layout.xml
@@ -25,7 +25,7 @@
     android:paddingEnd="@dimen/nav_content_padding"
     android:id="@+id/horizontal">
 
-    <com.android.systemui.statusbar.phone.NearestTouchFrame
+    <com.android.systemui.navigationbar.buttons.NearestTouchFrame
         android:id="@+id/nav_buttons"
         android:layout_width="match_parent"
         android:layout_height="match_parent"
@@ -50,6 +50,6 @@
             android:clipToPadding="false"
             android:clipChildren="false" />
 
-    </com.android.systemui.statusbar.phone.NearestTouchFrame>
+    </com.android.systemui.navigationbar.buttons.NearestTouchFrame>
 
 </FrameLayout>
diff --git a/packages/SystemUI/res/layout/navigation_layout_vertical.xml b/packages/SystemUI/res/layout/navigation_layout_vertical.xml
index 285c5c4..4b67700 100644
--- a/packages/SystemUI/res/layout/navigation_layout_vertical.xml
+++ b/packages/SystemUI/res/layout/navigation_layout_vertical.xml
@@ -25,14 +25,14 @@
     android:paddingBottom="@dimen/nav_content_padding"
     android:id="@+id/vertical">
 
-    <com.android.systemui.statusbar.phone.NearestTouchFrame
+    <com.android.systemui.navigationbar.buttons.NearestTouchFrame
         android:id="@+id/nav_buttons"
         android:layout_width="match_parent"
         android:layout_height="match_parent"
         android:clipChildren="false"
         android:clipToPadding="false">
 
-        <com.android.systemui.statusbar.phone.ReverseLinearLayout
+        <com.android.systemui.navigationbar.buttons.ReverseLinearLayout
             android:id="@+id/ends_group"
             android:layout_width="match_parent"
             android:layout_height="match_parent"
@@ -40,7 +40,7 @@
             android:clipToPadding="false"
             android:clipChildren="false" />
 
-        <com.android.systemui.statusbar.phone.ReverseLinearLayout
+        <com.android.systemui.navigationbar.buttons.ReverseLinearLayout
             android:id="@+id/center_group"
             android:layout_width="match_parent"
             android:layout_height="match_parent"
@@ -49,6 +49,6 @@
             android:clipToPadding="false"
             android:clipChildren="false" />
 
-    </com.android.systemui.statusbar.phone.NearestTouchFrame>
+    </com.android.systemui.navigationbar.buttons.NearestTouchFrame>
 
 </FrameLayout>
diff --git a/packages/SystemUI/res/layout/notification_conversation_info.xml b/packages/SystemUI/res/layout/notification_conversation_info.xml
index 2c4b937..fd89c0b 100644
--- a/packages/SystemUI/res/layout/notification_conversation_info.xml
+++ b/packages/SystemUI/res/layout/notification_conversation_info.xml
@@ -210,7 +210,7 @@
                     android:clickable="false"
                     android:focusable="false"
                     android:ellipsize="end"
-                    android:maxLines="3"
+                    android:maxLines="4"
                     android:textAppearance="@style/TextAppearance.NotificationImportanceDetail"/>
             </com.android.systemui.statusbar.notification.row.ButtonLinearLayout>
 
diff --git a/packages/SystemUI/res/layout/partial_conversation_info.xml b/packages/SystemUI/res/layout/partial_conversation_info.xml
index 0795170..c353d08 100644
--- a/packages/SystemUI/res/layout/partial_conversation_info.xml
+++ b/packages/SystemUI/res/layout/partial_conversation_info.xml
@@ -128,6 +128,7 @@
                     android:layout_width="match_parent"
                     android:layout_height="wrap_content"
                     android:layout_centerVertical="true"
+                    android:textDirection="locale"
                     style="@style/TextAppearance.NotificationImportanceChannelGroup" />
             </LinearLayout>
         </com.android.systemui.statusbar.notification.row.ButtonLinearLayout>
diff --git a/packages/SystemUI/res/layout/qs_media_panel_options.xml b/packages/SystemUI/res/layout/qs_media_panel_options.xml
deleted file mode 100644
index e72c0e8..0000000
--- a/packages/SystemUI/res/layout/qs_media_panel_options.xml
+++ /dev/null
@@ -1,61 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-  ~ Copyright (C) 2019 The Android Open Source Project
-  ~
-  ~ Licensed under the Apache License, Version 2.0 (the "License");
-  ~ you may not use this file except in compliance with the License.
-  ~ You may obtain a copy of the License at
-  ~
-  ~      http://www.apache.org/licenses/LICENSE-2.0
-  ~
-  ~ Unless required by applicable law or agreed to in writing, software
-  ~ distributed under the License is distributed on an "AS IS" BASIS,
-  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-  ~ See the License for the specific language governing permissions and
-  ~ limitations under the License
-  -->
-<LinearLayout
-    xmlns:android="http://schemas.android.com/apk/res/android"
-    android:id="@+id/qs_media_controls_options"
-    android:layout_width="match_parent"
-    android:layout_height="match_parent"
-    android:gravity="center"
-    android:padding="16dp"
-    android:orientation="vertical">
-    <LinearLayout
-        android:layout_width="wrap_content"
-        android:layout_height="48dp"
-        android:layout_weight="1"
-        android:minWidth="48dp"
-        android:layout_gravity="start|bottom"
-        android:gravity="bottom"
-        android:id="@+id/remove"
-        android:orientation="horizontal">
-        <ImageView
-            android:layout_width="18dp"
-            android:layout_height="18dp"
-            android:id="@+id/remove_icon"
-            android:layout_marginEnd="16dp"
-            android:tint="@color/media_primary_text"
-            android:src="@drawable/ic_clear"/>
-        <TextView
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            android:id="@+id/remove_text"
-            android:fontFamily="@*android:string/config_headlineFontFamilyMedium"
-            android:singleLine="true"
-            android:textColor="@color/media_primary_text"
-            android:text="@string/controls_media_close_session" />
-    </LinearLayout>
-    <TextView
-        android:id="@+id/cancel"
-        android:layout_width="wrap_content"
-        android:layout_height="48dp"
-        android:layout_weight="1"
-        android:minWidth="48dp"
-        android:layout_gravity="end|bottom"
-        android:gravity="bottom"
-        android:fontFamily="@*android:string/config_headlineFontFamilyMedium"
-        android:textColor="@android:color/white"
-        android:text="@string/cancel" />
-</LinearLayout>
diff --git a/packages/SystemUI/res/layout/recent_apps.xml b/packages/SystemUI/res/layout/recent_apps.xml
index 870bcf7..e2b1374 100644
--- a/packages/SystemUI/res/layout/recent_apps.xml
+++ b/packages/SystemUI/res/layout/recent_apps.xml
@@ -14,7 +14,7 @@
      limitations under the License.
 -->
 
-<com.android.systemui.statusbar.policy.KeyButtonView
+<com.android.systemui.navigationbar.buttons.KeyButtonView
     xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:systemui="http://schemas.android.com/apk/res-auto"
     android:id="@+id/recent_apps"
diff --git a/packages/SystemUI/res/layout/rotate_suggestion.xml b/packages/SystemUI/res/layout/rotate_suggestion.xml
index d7f67db..194d2e0 100644
--- a/packages/SystemUI/res/layout/rotate_suggestion.xml
+++ b/packages/SystemUI/res/layout/rotate_suggestion.xml
@@ -15,7 +15,7 @@
   ~ limitations under the License
   -->
 
-<com.android.systemui.statusbar.policy.KeyButtonView
+<com.android.systemui.navigationbar.buttons.KeyButtonView
     xmlns:android="http://schemas.android.com/apk/res/android"
     android:id="@+id/rotate_suggestion"
     android:layout_width="match_parent"
diff --git a/packages/SystemUI/res/layout/tv_audio_recording_indicator.xml b/packages/SystemUI/res/layout/tv_audio_recording_indicator.xml
index 1d2340d..f9336a54 100644
--- a/packages/SystemUI/res/layout/tv_audio_recording_indicator.xml
+++ b/packages/SystemUI/res/layout/tv_audio_recording_indicator.xml
@@ -19,7 +19,7 @@
               android:layout_width="wrap_content"
               android:layout_height="wrap_content"
               android:orientation="horizontal"
-              android:padding="32dp">
+              android:padding="12dp">
 
     <FrameLayout
         android:layout_width="wrap_content"
@@ -32,45 +32,25 @@
             android:orientation="horizontal">
 
             <FrameLayout
-                android:layout_width="45dp"
-                android:layout_height="47dp">
+                android:layout_width="wrap_content"
+                android:layout_height="match_parent">
 
                 <View
                     android:id="@+id/icon_container_bg"
-                    android:layout_width="match_parent"
+                    android:layout_width="50dp"
                     android:layout_height="match_parent"
                     android:background="@drawable/tv_rect_dark_left_rounded"/>
 
                 <FrameLayout
                     android:id="@+id/icon_mic"
-                    android:layout_width="35dp"
-                    android:layout_height="35dp"
-                    android:layout_marginStart="6dp"
-                    android:layout_marginTop="6dp"
-                    android:layout_marginBottom="6dp">
-
-                    <View
-                        android:layout_width="27dp"
-                        android:layout_height="27dp"
-                        android:layout_gravity="center"
-                        android:background="@drawable/tv_circle_dark"/>
+                    android:layout_width="34dp"
+                    android:layout_height="24dp"
+                    android:layout_gravity="center"
+                    android:background="@drawable/tv_rect_shadow_rounded">
 
                     <ImageView
-                        android:id="@+id/pulsating_circle"
-                        android:layout_width="27dp"
-                        android:layout_height="27dp"
-                        android:layout_gravity="center"
-                        android:background="@drawable/tv_circle_white_translucent"/>
-
-                    <ImageView
-                        android:layout_width="27dp"
-                        android:layout_height="27dp"
-                        android:layout_gravity="center"
-                        android:src="@drawable/tv_ring_white"/>
-
-                    <ImageView
-                        android:layout_width="16dp"
-                        android:layout_height="16dp"
+                        android:layout_width="13dp"
+                        android:layout_height="13dp"
                         android:layout_gravity="center"
                         android:background="@drawable/tv_ic_mic_white"/>
                 </FrameLayout>
diff --git a/packages/SystemUI/res/layout/udfps_view.xml b/packages/SystemUI/res/layout/udfps_view.xml
index 732758a..31a33fb 100644
--- a/packages/SystemUI/res/layout/udfps_view.xml
+++ b/packages/SystemUI/res/layout/udfps_view.xml
@@ -5,6 +5,6 @@
     android:id="@+id/udfps_view"
     android:layout_width="match_parent"
     android:layout_height="match_parent"
-    systemui:sensorRadius="140px"
-    systemui:sensorMarginBottom="630px"
+    systemui:sensorRadius="130px"
+    systemui:sensorCenterY="1636px"
     systemui:sensorTouchAreaCoefficient="0.5"/>
diff --git a/packages/SystemUI/res/layout/volume_dialog.xml b/packages/SystemUI/res/layout/volume_dialog.xml
index 7d6547b..a90b1eb 100644
--- a/packages/SystemUI/res/layout/volume_dialog.xml
+++ b/packages/SystemUI/res/layout/volume_dialog.xml
@@ -22,7 +22,7 @@
     android:gravity="right"
     android:layout_gravity="right"
     android:background="@android:color/transparent"
-    android:theme="@style/qs_theme">
+    android:theme="@style/volume_dialog_theme">
 
     <!-- right-aligned to be physically near volume button -->
     <LinearLayout
diff --git a/packages/SystemUI/res/layout/volume_dialog_row.xml b/packages/SystemUI/res/layout/volume_dialog_row.xml
index 6128da8..b9efc5b 100644
--- a/packages/SystemUI/res/layout/volume_dialog_row.xml
+++ b/packages/SystemUI/res/layout/volume_dialog_row.xml
@@ -20,7 +20,7 @@
     android:layout_width="@dimen/volume_dialog_panel_width"
     android:clipChildren="false"
     android:clipToPadding="false"
-    android:theme="@style/qs_theme">
+    android:theme="@style/volume_dialog_theme">
 
     <LinearLayout
         android:layout_height="wrap_content"
diff --git a/packages/SystemUI/res/values-af/strings.xml b/packages/SystemUI/res/values-af/strings.xml
index 308fd88..bac915d 100644
--- a/packages/SystemUI/res/values-af/strings.xml
+++ b/packages/SystemUI/res/values-af/strings.xml
@@ -1069,7 +1069,7 @@
     <string name="controls_seeding_in_progress" msgid="3033855341410264148">"Laai tans aanbevelings"</string>
     <string name="controls_media_title" msgid="1746947284862928133">"Media"</string>
     <string name="controls_media_close_session" msgid="3957093425905475065">"Versteek die huidige sessie."</string>
-    <string name="controls_media_dismiss_button" msgid="4485675693008031646">"Versteek"</string>
+    <string name="controls_media_dismiss_button" msgid="9081375542265132213">"Maak toe"</string>
     <string name="controls_media_resume" msgid="1933520684481586053">"Hervat"</string>
     <string name="controls_media_settings_button" msgid="5815790345117172504">"Instellings"</string>
     <string name="controls_error_timeout" msgid="794197289772728958">"Onaktief, gaan program na"</string>
@@ -1084,4 +1084,6 @@
     <string name="controls_added_tooltip" msgid="4842812921719153085">"Hou aan/af-skakelaar in om nuwe kontroles te sien"</string>
     <string name="controls_menu_add" msgid="4447246119229920050">"Voeg kontroles by"</string>
     <string name="controls_menu_edit" msgid="890623986951347062">"Wysig kontroles"</string>
+    <string name="one_handed_tutorial_title" msgid="6312198435090726656">"Gebruik eenhandmodus"</string>
+    <string name="one_handed_tutorial_description" msgid="7674850272340517174">"Swiep van die onderkant van die skerm af op of tik enige plek bo die program om uit te gaan"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-am/strings.xml b/packages/SystemUI/res/values-am/strings.xml
index f2bf577..43ab1d2 100644
--- a/packages/SystemUI/res/values-am/strings.xml
+++ b/packages/SystemUI/res/values-am/strings.xml
@@ -73,7 +73,7 @@
     <string name="usb_contaminant_message" msgid="7730476585174719805">"መሣሪያዎን ከፈሳሽ ወይም ፍርስራሽ ለመጠበቅ ሲባል የዩኤስቢ ወደቡ ተሰናክሏል፣ እና ማናቸውም ተቀጥላዎችን አያገኝም።\n\nየዩኤስቢ ወደቡን እንደገና መጠቀም ችግር በማይኖረው ጊዜ ማሳወቂያ ይደርሰዎታል።"</string>
     <string name="usb_port_enabled" msgid="531823867664717018">"ኃይል መሙያዎችን እና ተጨማሪ መሣሪያዎችን ፈልጎ ለማግኘት የነቃ የዩኤስቢ ወደብ"</string>
     <string name="usb_disable_contaminant_detection" msgid="3827082183595978641">"ዩኤስቢ አንቃ"</string>
-    <string name="learn_more" msgid="4690632085667273811">"የበለጠ መረዳት"</string>
+    <string name="learn_more" msgid="4690632085667273811">"የበለጠ ለመረዳት"</string>
     <string name="compat_mode_on" msgid="4963711187149440884">"ማያ እንዲሞላ አጉላ"</string>
     <string name="compat_mode_off" msgid="7682459748279487945">"ማያ ለመሙለት ሳብ"</string>
     <string name="global_action_screenshot" msgid="2760267567509131654">"ቅጽበታዊ ገጽ እይታ"</string>
@@ -1069,7 +1069,7 @@
     <string name="controls_seeding_in_progress" msgid="3033855341410264148">"ምክሮችን በመጫን ላይ"</string>
     <string name="controls_media_title" msgid="1746947284862928133">"ሚዲያ"</string>
     <string name="controls_media_close_session" msgid="3957093425905475065">"የአሁኑን ክፍለ-ጊዜ ደብቅ።"</string>
-    <string name="controls_media_dismiss_button" msgid="4485675693008031646">"ደብቅ"</string>
+    <string name="controls_media_dismiss_button" msgid="9081375542265132213">"አሰናብት"</string>
     <string name="controls_media_resume" msgid="1933520684481586053">"ከቆመበት ቀጥል"</string>
     <string name="controls_media_settings_button" msgid="5815790345117172504">"ቅንብሮች"</string>
     <string name="controls_error_timeout" msgid="794197289772728958">"ንቁ ያልኾነ፣ መተግበሪያን ይፈትሹ"</string>
@@ -1084,4 +1084,6 @@
     <string name="controls_added_tooltip" msgid="4842812921719153085">"አዲስ መቆጣጠሪያዎችን ለማየት የኃይል አዝራር ይያዙ"</string>
     <string name="controls_menu_add" msgid="4447246119229920050">"መቆጣጠሪያዎችን አክል"</string>
     <string name="controls_menu_edit" msgid="890623986951347062">"መቆጣጠሪያዎችን ያርትዑ"</string>
+    <string name="one_handed_tutorial_title" msgid="6312198435090726656">"ባለአንድ እጅ ሁነታን በመጠቀም ላይ"</string>
+    <string name="one_handed_tutorial_description" msgid="7674850272340517174">"ለመውጣት ከማያው ግርጌ ወደ ላይ ይጥረጉ ወይም ከመተግበሪያው በላይ ማንኛውም ቦታ ላይ መታ ያድርጉ"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-ar/strings.xml b/packages/SystemUI/res/values-ar/strings.xml
index 36280e7..c68d088 100644
--- a/packages/SystemUI/res/values-ar/strings.xml
+++ b/packages/SystemUI/res/values-ar/strings.xml
@@ -1093,7 +1093,7 @@
     <string name="controls_seeding_in_progress" msgid="3033855341410264148">"جارٍ تحميل الاقتراحات"</string>
     <string name="controls_media_title" msgid="1746947284862928133">"الوسائط"</string>
     <string name="controls_media_close_session" msgid="3957093425905475065">"إخفاء الجلسة الحالية"</string>
-    <string name="controls_media_dismiss_button" msgid="4485675693008031646">"إخفاء"</string>
+    <string name="controls_media_dismiss_button" msgid="9081375542265132213">"إغلاق"</string>
     <string name="controls_media_resume" msgid="1933520684481586053">"استئناف التشغيل"</string>
     <string name="controls_media_settings_button" msgid="5815790345117172504">"الإعدادات"</string>
     <string name="controls_error_timeout" msgid="794197289772728958">"غير نشط، تحقّق من التطبيق."</string>
@@ -1108,4 +1108,6 @@
     <string name="controls_added_tooltip" msgid="4842812921719153085">"اضغط مع الاستمرار على زر التشغيل لعرض عناصر التحكّم الجديدة."</string>
     <string name="controls_menu_add" msgid="4447246119229920050">"إضافة عناصر تحكّم"</string>
     <string name="controls_menu_edit" msgid="890623986951347062">"تعديل عناصر التحكّم"</string>
+    <string name="one_handed_tutorial_title" msgid="6312198435090726656">"استخدام وضع \"التصفح بيد واحدة\""</string>
+    <string name="one_handed_tutorial_description" msgid="7674850272340517174">"للخروج، مرِّر سريعًا من أسفل الشاشة إلى أعلاها أو انقر في أي مكان فوق التطبيق."</string>
 </resources>
diff --git a/packages/SystemUI/res/values-as/strings.xml b/packages/SystemUI/res/values-as/strings.xml
index a4cc17b..d604fd4 100644
--- a/packages/SystemUI/res/values-as/strings.xml
+++ b/packages/SystemUI/res/values-as/strings.xml
@@ -125,7 +125,7 @@
     <string name="accessibility_accessibility_button" msgid="4089042473497107709">"দিব্যাংগসকলৰ বাবে থকা সুবিধাসমূহ"</string>
     <string name="accessibility_rotate_button" msgid="1238584767612362586">"স্ক্ৰীণ ঘূৰাওক"</string>
     <string name="accessibility_recent" msgid="901641734769533575">"অৱলোকন"</string>
-    <string name="accessibility_search_light" msgid="524741790416076988">"সন্ধান কৰক"</string>
+    <string name="accessibility_search_light" msgid="524741790416076988">"Search"</string>
     <string name="accessibility_camera_button" msgid="2938898391716647247">"কেমেৰা"</string>
     <string name="accessibility_phone_button" msgid="4256353121703100427">"ফ\'ন"</string>
     <string name="accessibility_voice_assist_button" msgid="6497706615649754510">"কণ্ঠধ্বনিৰে সহায়"</string>
@@ -441,7 +441,7 @@
     <string name="expanded_header_battery_charging_with_time" msgid="757991461445765011">"বেটাৰিৰ চ্চাৰ্জ সম্পূর্ণ হ\'বলৈ <xliff:g id="CHARGING_TIME">%s</xliff:g> বাকী"</string>
     <string name="expanded_header_battery_not_charging" msgid="809409140358955848">"চ্চার্জ কৰি থকা নাই"</string>
     <string name="ssl_ca_cert_warning" msgid="8373011375250324005">"নেটৱৰ্ক \nনিৰীক্ষণ কৰা হ\'ব পাৰে"</string>
-    <string name="description_target_search" msgid="3875069993128855865">"অনুসন্ধান কৰক"</string>
+    <string name="description_target_search" msgid="3875069993128855865">"Search"</string>
     <string name="description_direction_up" msgid="3632251507574121434">"<xliff:g id="TARGET_DESCRIPTION">%s</xliff:g>ৰ বাবে ওপৰলৈ শ্লাইড কৰক।"</string>
     <string name="description_direction_left" msgid="4762708739096907741">"<xliff:g id="TARGET_DESCRIPTION">%s</xliff:g>ৰ বাবে বাওঁফাললৈ শ্লাইড কৰক।"</string>
     <string name="zen_priority_introduction" msgid="3159291973383796646">"আপুনি নিৰ্দিষ্ট কৰা এলাৰ্ম, ৰিমাইণ্ডাৰ, ইভেন্ট আৰু কল কৰোঁতাৰ বাহিৰে আন কোনো শব্দৰ পৰা আপুনি অসুবিধা নাপাব। কিন্তু, সংগীত, ভিডিঅ\' আৰু খেলসমূহকে ধৰি আপুনি প্লে কৰিব খোজা যিকোনো বস্তু তথাপি শুনিব পাৰিব।"</string>
@@ -502,7 +502,7 @@
     <string name="battery_saver_notification_title" msgid="8419266546034372562">"বেটাৰি সঞ্চয়কাৰী অন হৈ আছে"</string>
     <string name="battery_saver_notification_text" msgid="2617841636449016951">"কাৰ্যদক্ষতা আৰু নেপথ্য ডেটা হ্ৰাস কৰে"</string>
     <string name="battery_saver_notification_action_text" msgid="6022091913807026887">"বেটাৰি সঞ্চয়কাৰী অফ কৰক"</string>
-    <string name="media_projection_dialog_text" msgid="1755705274910034772">"<xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g>এ আপোনাৰ স্ক্ৰীনত দৃশ্যমান হোৱা অথবা ৰেকর্ডিং অথবা কাষ্টিংৰ সময়ত আপোনাৰ ডিভাইচত প্লে\' কৰা সকলো তথ্যলৈ এক্সেছ পাব। এইটোত পাছৱর্ড, পৰিশোধৰ সবিশেষ, ফট\', বার্তাসমূহ আৰু আপুনি প্লে\' কৰা অডিঅ\'ৰ দৰে তথ্য অন্তর্ভুক্ত হয়।"</string>
+    <string name="media_projection_dialog_text" msgid="1755705274910034772">"<xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g>এ আপোনাৰ স্ক্ৰীনত দৃশ্যমান হোৱা অথবা ৰেকর্ডিং অথবা কাষ্টিঙৰ সময়ত আপোনাৰ ডিভাইচত প্লে\' কৰা সকলো তথ্যলৈ এক্সেছ পাব। এইটোত পাছৱর্ড, পৰিশোধৰ সবিশেষ, ফট\', বার্তাসমূহ আৰু আপুনি প্লে\' কৰা অডিঅ\'ৰ দৰে তথ্য অন্তর্ভুক্ত হয়।"</string>
     <string name="media_projection_dialog_service_text" msgid="958000992162214611">"এই সুবিধাটো প্ৰদান কৰা সেৱাটোৱে আপোনাৰ স্ক্ৰীনত দৃশ্যমান হোৱা অথবা ৰেকর্ডিং অথবা কাষ্টিংৰ সময়ত আপোনাৰ ডিভাইচত প্লে\' কৰা সকলো তথ্যলৈ এক্সেছ পাব। এইটোত পাছৱর্ড, পৰিশোধৰ সবিশেষ, ফট\', বার্তাসমূহ আৰু আপুনি প্লে\' কৰা অডিঅ\'ৰ দৰে তথ্য অন্তর্ভুক্ত হয়।"</string>
     <string name="media_projection_dialog_service_title" msgid="2888507074107884040">"ৰেকর্ডিং অথবা কাষ্টিং আৰম্ভ কৰিবনে?"</string>
     <string name="media_projection_dialog_title" msgid="3316063622495360646">"<xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g>ৰ জৰিয়তে ৰেকর্ডিং অথবা কাষ্টিং আৰম্ভ কৰিবনে ?"</string>
@@ -1069,7 +1069,7 @@
     <string name="controls_seeding_in_progress" msgid="3033855341410264148">"চুপাৰিছসমূহ ল’ড কৰি থকা হৈছে"</string>
     <string name="controls_media_title" msgid="1746947284862928133">"মিডিয়া"</string>
     <string name="controls_media_close_session" msgid="3957093425905475065">"বৰ্তমানৰ ছেশ্বনটো লুকুৱাওক।"</string>
-    <string name="controls_media_dismiss_button" msgid="4485675693008031646">"লুকুৱাওক"</string>
+    <string name="controls_media_dismiss_button" msgid="9081375542265132213">"অগ্ৰাহ্য কৰক"</string>
     <string name="controls_media_resume" msgid="1933520684481586053">"পুনৰ আৰম্ভ কৰক"</string>
     <string name="controls_media_settings_button" msgid="5815790345117172504">"ছেটিংসমূহ"</string>
     <string name="controls_error_timeout" msgid="794197289772728958">"সক্ৰিয় নহয়, এপ্‌টো পৰীক্ষা কৰক"</string>
@@ -1084,4 +1084,6 @@
     <string name="controls_added_tooltip" msgid="4842812921719153085">"নতুন নিয়ন্ত্ৰণসমূহ চাবলৈ পাৱাৰৰ বুটামটো ধৰি ৰাখক"</string>
     <string name="controls_menu_add" msgid="4447246119229920050">"নিয়ন্ত্ৰণসমূহ যোগ দিয়ক"</string>
     <string name="controls_menu_edit" msgid="890623986951347062">"নিয়ন্ত্ৰণসমূহ সম্পাদনা কৰক"</string>
+    <string name="one_handed_tutorial_title" msgid="6312198435090726656">"এখন হাতেৰে ব্যৱহাৰ কৰা ম’ড ব্যৱহাৰ কৰিবলৈ"</string>
+    <string name="one_handed_tutorial_description" msgid="7674850272340517174">"বাহিৰ হ’বলৈ স্ক্রীনখনৰ একেবাৰে তলৰ পৰা ওপৰলৈ ছোৱাইপ কৰক অথবা এপ্‌টোৰ ওপৰত যিকোনো ঠাইত টিপক"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-az/strings.xml b/packages/SystemUI/res/values-az/strings.xml
index 9443ba8..b1df252 100644
--- a/packages/SystemUI/res/values-az/strings.xml
+++ b/packages/SystemUI/res/values-az/strings.xml
@@ -1069,7 +1069,7 @@
     <string name="controls_seeding_in_progress" msgid="3033855341410264148">"Tövsiyələr yüklənir"</string>
     <string name="controls_media_title" msgid="1746947284862928133">"Media"</string>
     <string name="controls_media_close_session" msgid="3957093425905475065">"Cari sessiyanı gizlədin."</string>
-    <string name="controls_media_dismiss_button" msgid="4485675693008031646">"Gizlədin"</string>
+    <string name="controls_media_dismiss_button" msgid="9081375542265132213">"İmtina edin"</string>
     <string name="controls_media_resume" msgid="1933520684481586053">"Davam edin"</string>
     <string name="controls_media_settings_button" msgid="5815790345117172504">"Ayarlar"</string>
     <string name="controls_error_timeout" msgid="794197289772728958">"Aktiv deyil, tətbiqi yoxlayın"</string>
@@ -1084,4 +1084,6 @@
     <string name="controls_added_tooltip" msgid="4842812921719153085">"Yeni nizamlayıcıları görmək üçün yandırıb-söndürmə düyməsinə basıb saxlayın"</string>
     <string name="controls_menu_add" msgid="4447246119229920050">"Nizamlayıcılar əlavə edin"</string>
     <string name="controls_menu_edit" msgid="890623986951347062">"Nizamlayıcıları redaktə edin"</string>
+    <string name="one_handed_tutorial_title" msgid="6312198435090726656">"Bir əlli rejimdən istifadə edilir"</string>
+    <string name="one_handed_tutorial_description" msgid="7674850272340517174">"Çıxmaq üçün ekranın aşağısından yuxarıya doğru sürüşdürün və ya tətbiqin yuxarısında istənilən yerə toxunun"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-b+sr+Latn/strings.xml b/packages/SystemUI/res/values-b+sr+Latn/strings.xml
index 5b8c445..f3c4c6b 100644
--- a/packages/SystemUI/res/values-b+sr+Latn/strings.xml
+++ b/packages/SystemUI/res/values-b+sr+Latn/strings.xml
@@ -1075,7 +1075,7 @@
     <string name="controls_seeding_in_progress" msgid="3033855341410264148">"Učitavaju se preporuke"</string>
     <string name="controls_media_title" msgid="1746947284862928133">"Mediji"</string>
     <string name="controls_media_close_session" msgid="3957093425905475065">"Sakrijte aktuelnu sesiju."</string>
-    <string name="controls_media_dismiss_button" msgid="4485675693008031646">"Sakrij"</string>
+    <string name="controls_media_dismiss_button" msgid="9081375542265132213">"Odbaci"</string>
     <string name="controls_media_resume" msgid="1933520684481586053">"Nastavi"</string>
     <string name="controls_media_settings_button" msgid="5815790345117172504">"Podešavanja"</string>
     <string name="controls_error_timeout" msgid="794197289772728958">"Neaktivno. Vidite aplikaciju"</string>
@@ -1090,4 +1090,6 @@
     <string name="controls_added_tooltip" msgid="4842812921719153085">"Zadržite dugme za uključivanje da biste videli nove kontrole"</string>
     <string name="controls_menu_add" msgid="4447246119229920050">"Dodaj kontrole"</string>
     <string name="controls_menu_edit" msgid="890623986951347062">"Izmeni kontrole"</string>
+    <string name="one_handed_tutorial_title" msgid="6312198435090726656">"Korišćenje režima jednom rukom"</string>
+    <string name="one_handed_tutorial_description" msgid="7674850272340517174">"Da biste izašli, prevucite nagore od dna ekrana ili dodirnite bilo gde iznad aplikacije"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-be/strings.xml b/packages/SystemUI/res/values-be/strings.xml
index 6ee51682..9c0eeb2 100644
--- a/packages/SystemUI/res/values-be/strings.xml
+++ b/packages/SystemUI/res/values-be/strings.xml
@@ -1081,7 +1081,7 @@
     <string name="controls_seeding_in_progress" msgid="3033855341410264148">"Загружаюцца рэкамендацыі"</string>
     <string name="controls_media_title" msgid="1746947284862928133">"Мультымедыя"</string>
     <string name="controls_media_close_session" msgid="3957093425905475065">"Схаваць цяперашні сеанс."</string>
-    <string name="controls_media_dismiss_button" msgid="4485675693008031646">"Схаваць"</string>
+    <string name="controls_media_dismiss_button" msgid="9081375542265132213">"Адхіліць"</string>
     <string name="controls_media_resume" msgid="1933520684481586053">"Узнавіць"</string>
     <string name="controls_media_settings_button" msgid="5815790345117172504">"Налады"</string>
     <string name="controls_error_timeout" msgid="794197289772728958">"Неактыўна, праверце праграму"</string>
@@ -1096,4 +1096,6 @@
     <string name="controls_added_tooltip" msgid="4842812921719153085">"Каб убачыць новыя элементы кіравання, утрымлівайце кнопку сілкавання націснутай"</string>
     <string name="controls_menu_add" msgid="4447246119229920050">"Дадаць элементы кіравання"</string>
     <string name="controls_menu_edit" msgid="890623986951347062">"Змяніць элементы кіравання"</string>
+    <string name="one_handed_tutorial_title" msgid="6312198435090726656">"Выкарыстоўваецца рэжым кіравання адной рукой"</string>
+    <string name="one_handed_tutorial_description" msgid="7674850272340517174">"Каб выйсці, правядзіце па экране пальцам знізу ўверх або націсніце ў любым месцы над праграмай"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-bg/strings.xml b/packages/SystemUI/res/values-bg/strings.xml
index c7bb3d0..3bf12ad 100644
--- a/packages/SystemUI/res/values-bg/strings.xml
+++ b/packages/SystemUI/res/values-bg/strings.xml
@@ -1069,7 +1069,7 @@
     <string name="controls_seeding_in_progress" msgid="3033855341410264148">"Препоръките се зареждат"</string>
     <string name="controls_media_title" msgid="1746947284862928133">"Мултимедия"</string>
     <string name="controls_media_close_session" msgid="3957093425905475065">"Скриване на текущата сесия."</string>
-    <string name="controls_media_dismiss_button" msgid="4485675693008031646">"Скриване"</string>
+    <string name="controls_media_dismiss_button" msgid="9081375542265132213">"Отхвърляне"</string>
     <string name="controls_media_resume" msgid="1933520684481586053">"Възобновяване"</string>
     <string name="controls_media_settings_button" msgid="5815790345117172504">"Настройки"</string>
     <string name="controls_error_timeout" msgid="794197289772728958">"Неактивно, проверете прилож."</string>
@@ -1084,4 +1084,6 @@
     <string name="controls_added_tooltip" msgid="4842812921719153085">"Задръжте бутона за захранване, за да видите новите контроли"</string>
     <string name="controls_menu_add" msgid="4447246119229920050">"Добавяне на контроли"</string>
     <string name="controls_menu_edit" msgid="890623986951347062">"Редактиране на контролите"</string>
+    <string name="one_handed_tutorial_title" msgid="6312198435090726656">"Използване на режима за работа с една ръка"</string>
+    <string name="one_handed_tutorial_description" msgid="7674850272340517174">"За изход прекарайте пръст нагоре от долната част на екрана или докоснете произволно място над приложението"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-bn/strings.xml b/packages/SystemUI/res/values-bn/strings.xml
index 42e7523a..5b7c953 100644
--- a/packages/SystemUI/res/values-bn/strings.xml
+++ b/packages/SystemUI/res/values-bn/strings.xml
@@ -125,7 +125,7 @@
     <string name="accessibility_accessibility_button" msgid="4089042473497107709">"অ্যাক্সেসযোগ্যতা"</string>
     <string name="accessibility_rotate_button" msgid="1238584767612362586">"স্ক্রিন ঘোরান"</string>
     <string name="accessibility_recent" msgid="901641734769533575">"এক নজরে"</string>
-    <string name="accessibility_search_light" msgid="524741790416076988">"খুঁজুন"</string>
+    <string name="accessibility_search_light" msgid="524741790416076988">"সার্চ"</string>
     <string name="accessibility_camera_button" msgid="2938898391716647247">"ক্যামেরা"</string>
     <string name="accessibility_phone_button" msgid="4256353121703100427">"ফোন"</string>
     <string name="accessibility_voice_assist_button" msgid="6497706615649754510">"ভয়েস সহায়তা"</string>
@@ -441,7 +441,7 @@
     <string name="expanded_header_battery_charging_with_time" msgid="757991461445765011">"পূর্ণ হতে <xliff:g id="CHARGING_TIME">%s</xliff:g> সময় লাগবে"</string>
     <string name="expanded_header_battery_not_charging" msgid="809409140358955848">"চার্জ হচ্ছে না"</string>
     <string name="ssl_ca_cert_warning" msgid="8373011375250324005">"নেটওয়ার্ক নিরীক্ষণ\nকরা হতে পারে"</string>
-    <string name="description_target_search" msgid="3875069993128855865">"খুঁজুন"</string>
+    <string name="description_target_search" msgid="3875069993128855865">"সার্চ"</string>
     <string name="description_direction_up" msgid="3632251507574121434">"<xliff:g id="TARGET_DESCRIPTION">%s</xliff:g> এর জন্য উপরের দিকে স্লাইড করুন৷"</string>
     <string name="description_direction_left" msgid="4762708739096907741">"<xliff:g id="TARGET_DESCRIPTION">%s</xliff:g> এর জন্য বাঁ দিকে স্লাইড করুন৷"</string>
     <string name="zen_priority_introduction" msgid="3159291973383796646">"অ্যালার্ম, রিমাইন্ডার, ইভেন্ট, এবং আপনার নির্দিষ্ট করে দেওয়া ব্যক্তিদের কল ছাড়া অন্য কোনও আওয়াজ বা ভাইব্রেশন হবে না। তবে সঙ্গীত, ভিডিও, এবং গেম সহ আপনি যা কিছু চালাবেন তার আওয়াজ শুনতে পাবেন।"</string>
@@ -1069,7 +1069,7 @@
     <string name="controls_seeding_in_progress" msgid="3033855341410264148">"সাজেশন লোড করা হচ্ছে"</string>
     <string name="controls_media_title" msgid="1746947284862928133">"মিডিয়া"</string>
     <string name="controls_media_close_session" msgid="3957093425905475065">"বর্তমান সেশন লুকান।"</string>
-    <string name="controls_media_dismiss_button" msgid="4485675693008031646">"লুকান"</string>
+    <string name="controls_media_dismiss_button" msgid="9081375542265132213">"খারিজ করুন"</string>
     <string name="controls_media_resume" msgid="1933520684481586053">"আবার চালু করুন"</string>
     <string name="controls_media_settings_button" msgid="5815790345117172504">"সেটিংস"</string>
     <string name="controls_error_timeout" msgid="794197289772728958">"বন্ধ আছে, অ্যাপ চেক করুন"</string>
@@ -1084,4 +1084,6 @@
     <string name="controls_added_tooltip" msgid="4842812921719153085">"নতুন কন্ট্রোল দেখতে পাওয়ার বোতাম টিপে ধরে থাকুন"</string>
     <string name="controls_menu_add" msgid="4447246119229920050">"কন্ট্রোল যোগ করুন"</string>
     <string name="controls_menu_edit" msgid="890623986951347062">"কন্ট্রোল এডিট করুন"</string>
+    <string name="one_handed_tutorial_title" msgid="6312198435090726656">"\'এক হাতে ব্যবহার করার মোড\'-এর ব্যবহার"</string>
+    <string name="one_handed_tutorial_description" msgid="7674850272340517174">"বেরিয়ে আসার জন্য, স্ক্রিনের নিচ থেকে উপরের দিকে সোয়াইপ করুন অথবা অ্যাপের আইকনের উপরে যেকোনও জায়গায় ট্যাপ করুন"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-bs/strings.xml b/packages/SystemUI/res/values-bs/strings.xml
index 16a1aa6..31f88c8f 100644
--- a/packages/SystemUI/res/values-bs/strings.xml
+++ b/packages/SystemUI/res/values-bs/strings.xml
@@ -1075,7 +1075,7 @@
     <string name="controls_seeding_in_progress" msgid="3033855341410264148">"Učitavanje preporuka"</string>
     <string name="controls_media_title" msgid="1746947284862928133">"Mediji"</string>
     <string name="controls_media_close_session" msgid="3957093425905475065">"Sakrijte trenutnu sesiju."</string>
-    <string name="controls_media_dismiss_button" msgid="4485675693008031646">"Sakrij"</string>
+    <string name="controls_media_dismiss_button" msgid="9081375542265132213">"Odbaci"</string>
     <string name="controls_media_resume" msgid="1933520684481586053">"Nastavi"</string>
     <string name="controls_media_settings_button" msgid="5815790345117172504">"Postavke"</string>
     <string name="controls_error_timeout" msgid="794197289772728958">"Neaktivno, vidite aplikaciju"</string>
@@ -1090,4 +1090,6 @@
     <string name="controls_added_tooltip" msgid="4842812921719153085">"Zadržite dugme za uključivanje da vidite nove kontrole"</string>
     <string name="controls_menu_add" msgid="4447246119229920050">"Dodaj kontrole"</string>
     <string name="controls_menu_edit" msgid="890623986951347062">"Uredi kontrole"</string>
+    <string name="one_handed_tutorial_title" msgid="6312198435090726656">"Korištenje načina rada jednom rukom"</string>
+    <string name="one_handed_tutorial_description" msgid="7674850272340517174">"Da izađete, prevucite s dna ekrana prema gore ili dodirnite bilo gdje iznad aplikacije"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-ca/strings.xml b/packages/SystemUI/res/values-ca/strings.xml
index fdcec98..753e25f 100644
--- a/packages/SystemUI/res/values-ca/strings.xml
+++ b/packages/SystemUI/res/values-ca/strings.xml
@@ -1069,7 +1069,7 @@
     <string name="controls_seeding_in_progress" msgid="3033855341410264148">"Carregant les recomanacions"</string>
     <string name="controls_media_title" msgid="1746947284862928133">"Multimèdia"</string>
     <string name="controls_media_close_session" msgid="3957093425905475065">"Amaga la sessió actual."</string>
-    <string name="controls_media_dismiss_button" msgid="4485675693008031646">"Amaga"</string>
+    <string name="controls_media_dismiss_button" msgid="9081375542265132213">"Ignora"</string>
     <string name="controls_media_resume" msgid="1933520684481586053">"Reprèn"</string>
     <string name="controls_media_settings_button" msgid="5815790345117172504">"Configuració"</string>
     <string name="controls_error_timeout" msgid="794197289772728958">"Inactiu; comprova l\'aplicació"</string>
@@ -1084,4 +1084,6 @@
     <string name="controls_added_tooltip" msgid="4842812921719153085">"Mantén premut el botó d\'engegada per veure controls nous"</string>
     <string name="controls_menu_add" msgid="4447246119229920050">"Afegeix controls"</string>
     <string name="controls_menu_edit" msgid="890623986951347062">"Edita els controls"</string>
+    <string name="one_handed_tutorial_title" msgid="6312198435090726656">"S\'està utilitzant el mode d\'una mà"</string>
+    <string name="one_handed_tutorial_description" msgid="7674850272340517174">"Per sortir, llisca cap amunt des de la part inferior de la pantalla o toca qualsevol lloc a sobre de l\'aplicació"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-cs/strings.xml b/packages/SystemUI/res/values-cs/strings.xml
index ab3b4fb..039500d 100644
--- a/packages/SystemUI/res/values-cs/strings.xml
+++ b/packages/SystemUI/res/values-cs/strings.xml
@@ -1081,7 +1081,7 @@
     <string name="controls_seeding_in_progress" msgid="3033855341410264148">"Načítání doporučení"</string>
     <string name="controls_media_title" msgid="1746947284862928133">"Média"</string>
     <string name="controls_media_close_session" msgid="3957093425905475065">"Skrýt aktuální relaci."</string>
-    <string name="controls_media_dismiss_button" msgid="4485675693008031646">"Skrýt"</string>
+    <string name="controls_media_dismiss_button" msgid="9081375542265132213">"Zavřít"</string>
     <string name="controls_media_resume" msgid="1933520684481586053">"Pokračovat"</string>
     <string name="controls_media_settings_button" msgid="5815790345117172504">"Nastavení"</string>
     <string name="controls_error_timeout" msgid="794197289772728958">"Neaktivní, zkontrolujte aplikaci"</string>
@@ -1096,4 +1096,6 @@
     <string name="controls_added_tooltip" msgid="4842812921719153085">"Nové ovládací prvky zobrazíte podržením vypínače"</string>
     <string name="controls_menu_add" msgid="4447246119229920050">"Přidat ovládací prvky"</string>
     <string name="controls_menu_edit" msgid="890623986951347062">"Upravit ovládací prvky"</string>
+    <string name="one_handed_tutorial_title" msgid="6312198435090726656">"Používáte režim jedné ruky"</string>
+    <string name="one_handed_tutorial_description" msgid="7674850272340517174">"Režim ukončíte, když přejedete prstem z dolní části obrazovky nahoru nebo klepnete kamkoli nad aplikaci"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-da/strings.xml b/packages/SystemUI/res/values-da/strings.xml
index 0fc9b94..0c8a5b7 100644
--- a/packages/SystemUI/res/values-da/strings.xml
+++ b/packages/SystemUI/res/values-da/strings.xml
@@ -1069,7 +1069,7 @@
     <string name="controls_seeding_in_progress" msgid="3033855341410264148">"Indlæser anbefalinger"</string>
     <string name="controls_media_title" msgid="1746947284862928133">"Medie"</string>
     <string name="controls_media_close_session" msgid="3957093425905475065">"Skjul den aktuelle session."</string>
-    <string name="controls_media_dismiss_button" msgid="4485675693008031646">"Skjul"</string>
+    <string name="controls_media_dismiss_button" msgid="9081375542265132213">"Luk"</string>
     <string name="controls_media_resume" msgid="1933520684481586053">"Genoptag"</string>
     <string name="controls_media_settings_button" msgid="5815790345117172504">"Indstillinger"</string>
     <string name="controls_error_timeout" msgid="794197289772728958">"Inaktiv. Tjek appen"</string>
@@ -1084,4 +1084,6 @@
     <string name="controls_added_tooltip" msgid="4842812921719153085">"Hold afbryderknappen nede for at se nye betjeningselementer"</string>
     <string name="controls_menu_add" msgid="4447246119229920050">"Tilføj styring"</string>
     <string name="controls_menu_edit" msgid="890623986951347062">"Rediger styring"</string>
+    <string name="one_handed_tutorial_title" msgid="6312198435090726656">"Brug af enhåndstilstand"</string>
+    <string name="one_handed_tutorial_description" msgid="7674850272340517174">"Du kan afslutte ved at stryge opad fra bunden af skærmen eller trykke et vilkårligt sted over appen"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-de/strings.xml b/packages/SystemUI/res/values-de/strings.xml
index a6b137a..1c4c7ab 100644
--- a/packages/SystemUI/res/values-de/strings.xml
+++ b/packages/SystemUI/res/values-de/strings.xml
@@ -502,7 +502,7 @@
     <string name="battery_saver_notification_title" msgid="8419266546034372562">"Energiesparmodus ist aktiviert"</string>
     <string name="battery_saver_notification_text" msgid="2617841636449016951">"Reduzierung der Leistung und Hintergrunddaten"</string>
     <string name="battery_saver_notification_action_text" msgid="6022091913807026887">"Energiesparmodus deaktivieren"</string>
-    <string name="media_projection_dialog_text" msgid="1755705274910034772">"Die App \"<xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g>\" erhält Zugriff auf alle Informationen, die auf deinem Bildschirm sichtbar sind oder von deinem Gerät wiedergegeben werden, während du aufnimmst oder streamst. Dazu gehören beispielsweise Passwörter, Zahlungsdetails, Fotos, Nachrichten und Audioinhalte."</string>
+    <string name="media_projection_dialog_text" msgid="1755705274910034772">"Die App \"<xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g>\" erhält Zugriff auf alle Informationen, die auf deinem Bildschirm sichtbar sind oder von deinem Gerät wiedergegeben werden, während du aufnimmst oder streamst. Dazu gehören beispielsweise angezeigte Passwörter und Zahlungsdetails, Fotos, Nachrichten und Audioinhalte."</string>
     <string name="media_projection_dialog_service_text" msgid="958000992162214611">"Der Anbieter dieser App erhält Zugriff auf alle Informationen, die auf deinem Bildschirm sichtbar sind oder von deinem Gerät wiedergegeben werden, während du aufnimmst oder streamst. Dazu gehören beispielsweise Passwörter, Zahlungsdetails, Fotos, Nachrichten und Audioinhalte."</string>
     <string name="media_projection_dialog_service_title" msgid="2888507074107884040">"Aufnahme oder Stream starten?"</string>
     <string name="media_projection_dialog_title" msgid="3316063622495360646">"Aufnehmen oder Streamen mit der App \"<xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g>\" starten?"</string>
@@ -1069,7 +1069,7 @@
     <string name="controls_seeding_in_progress" msgid="3033855341410264148">"Empfehlungen werden geladen"</string>
     <string name="controls_media_title" msgid="1746947284862928133">"Medien"</string>
     <string name="controls_media_close_session" msgid="3957093425905475065">"Du kannst die aktuelle Sitzung ausblenden."</string>
-    <string name="controls_media_dismiss_button" msgid="4485675693008031646">"Ausblenden"</string>
+    <string name="controls_media_dismiss_button" msgid="9081375542265132213">"Ablehnen"</string>
     <string name="controls_media_resume" msgid="1933520684481586053">"Fortsetzen"</string>
     <string name="controls_media_settings_button" msgid="5815790345117172504">"Einstellungen"</string>
     <string name="controls_error_timeout" msgid="794197289772728958">"Inaktiv – sieh in der App nach"</string>
@@ -1084,4 +1084,6 @@
     <string name="controls_added_tooltip" msgid="4842812921719153085">"Zum Anzeigen der Karten für neue Geräte Ein-/Aus-Taste gedrückt halten"</string>
     <string name="controls_menu_add" msgid="4447246119229920050">"Steuerelemente hinzufügen"</string>
     <string name="controls_menu_edit" msgid="890623986951347062">"Steuerelemente bearbeiten"</string>
+    <string name="one_handed_tutorial_title" msgid="6312198435090726656">"Einhandmodus verwenden"</string>
+    <string name="one_handed_tutorial_description" msgid="7674850272340517174">"Wenn du die App schließen möchtest, wische vom unteren Rand des Displays nach oben oder tippe auf eine beliebige Stelle oberhalb der App"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-el/strings.xml b/packages/SystemUI/res/values-el/strings.xml
index bdd19a1..07ff807 100644
--- a/packages/SystemUI/res/values-el/strings.xml
+++ b/packages/SystemUI/res/values-el/strings.xml
@@ -1069,7 +1069,7 @@
     <string name="controls_seeding_in_progress" msgid="3033855341410264148">"Φόρτωση προτάσεων"</string>
     <string name="controls_media_title" msgid="1746947284862928133">"Μέσα"</string>
     <string name="controls_media_close_session" msgid="3957093425905475065">"Απόκρυψη της τρέχουσας περιόδου λειτουργίας."</string>
-    <string name="controls_media_dismiss_button" msgid="4485675693008031646">"Απόκρυψη"</string>
+    <string name="controls_media_dismiss_button" msgid="9081375542265132213">"Παράβλεψη"</string>
     <string name="controls_media_resume" msgid="1933520684481586053">"Συνέχιση"</string>
     <string name="controls_media_settings_button" msgid="5815790345117172504">"Ρυθμίσεις"</string>
     <string name="controls_error_timeout" msgid="794197289772728958">"Ανενεργό, έλεγχος εφαρμογής"</string>
@@ -1084,4 +1084,6 @@
     <string name="controls_added_tooltip" msgid="4842812921719153085">"Πατήστε το κουμπί λειτουργίας για να δείτε νέα στοιχεία ελέγχου."</string>
     <string name="controls_menu_add" msgid="4447246119229920050">"Προσθήκη στοιχείων ελέγχου"</string>
     <string name="controls_menu_edit" msgid="890623986951347062">"Επεξεργασία στοιχείων ελέγχου"</string>
+    <string name="one_handed_tutorial_title" msgid="6312198435090726656">"Χρήση λειτουργίας ενός χεριού"</string>
+    <string name="one_handed_tutorial_description" msgid="7674850272340517174">"Για έξοδο, σύρετε προς τα πάνω από το κάτω μέρος της οθόνης ή πατήστε οπουδήποτε πάνω από την εφαρμογή."</string>
 </resources>
diff --git a/packages/SystemUI/res/values-en-rAU/strings.xml b/packages/SystemUI/res/values-en-rAU/strings.xml
index 68a8d30..0e22b58 100644
--- a/packages/SystemUI/res/values-en-rAU/strings.xml
+++ b/packages/SystemUI/res/values-en-rAU/strings.xml
@@ -502,7 +502,7 @@
     <string name="battery_saver_notification_title" msgid="8419266546034372562">"Battery Saver is on"</string>
     <string name="battery_saver_notification_text" msgid="2617841636449016951">"Reduces performance and background data"</string>
     <string name="battery_saver_notification_action_text" msgid="6022091913807026887">"Turn off Battery Saver"</string>
-    <string name="media_projection_dialog_text" msgid="1755705274910034772">"<xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> will have access to all of the information that is visible on your screen or played from your device while recording or casting. This includes information, such as passwords, payment details, photos, messages and audio that you play."</string>
+    <string name="media_projection_dialog_text" msgid="1755705274910034772">"<xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> will have access to all of the information that is visible on your screen or played from your device while recording or casting. This includes information such as passwords, payment details, photos, messages and audio that you play."</string>
     <string name="media_projection_dialog_service_text" msgid="958000992162214611">"The service providing this function will have access to all of the information that is visible on your screen or played from your device while recording or casting. This includes information such as passwords, payment details, photos, messages and audio that you play."</string>
     <string name="media_projection_dialog_service_title" msgid="2888507074107884040">"Start recording or casting?"</string>
     <string name="media_projection_dialog_title" msgid="3316063622495360646">"Start recording or casting with <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g>?"</string>
@@ -1069,7 +1069,7 @@
     <string name="controls_seeding_in_progress" msgid="3033855341410264148">"Loading recommendations"</string>
     <string name="controls_media_title" msgid="1746947284862928133">"Media"</string>
     <string name="controls_media_close_session" msgid="3957093425905475065">"Hide the current session."</string>
-    <string name="controls_media_dismiss_button" msgid="4485675693008031646">"Hide"</string>
+    <string name="controls_media_dismiss_button" msgid="9081375542265132213">"Dismiss"</string>
     <string name="controls_media_resume" msgid="1933520684481586053">"Resume"</string>
     <string name="controls_media_settings_button" msgid="5815790345117172504">"Settings"</string>
     <string name="controls_error_timeout" msgid="794197289772728958">"Inactive, check app"</string>
@@ -1084,4 +1084,6 @@
     <string name="controls_added_tooltip" msgid="4842812921719153085">"Hold Power button to see new controls"</string>
     <string name="controls_menu_add" msgid="4447246119229920050">"Add controls"</string>
     <string name="controls_menu_edit" msgid="890623986951347062">"Edit controls"</string>
+    <string name="one_handed_tutorial_title" msgid="6312198435090726656">"Using one-handed mode"</string>
+    <string name="one_handed_tutorial_description" msgid="7674850272340517174">"To exit, swipe up from the bottom of the screen or tap anywhere above the app"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-en-rCA/strings.xml b/packages/SystemUI/res/values-en-rCA/strings.xml
index e9856af..acf087d 100644
--- a/packages/SystemUI/res/values-en-rCA/strings.xml
+++ b/packages/SystemUI/res/values-en-rCA/strings.xml
@@ -502,7 +502,7 @@
     <string name="battery_saver_notification_title" msgid="8419266546034372562">"Battery Saver is on"</string>
     <string name="battery_saver_notification_text" msgid="2617841636449016951">"Reduces performance and background data"</string>
     <string name="battery_saver_notification_action_text" msgid="6022091913807026887">"Turn off Battery Saver"</string>
-    <string name="media_projection_dialog_text" msgid="1755705274910034772">"<xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> will have access to all of the information that is visible on your screen or played from your device while recording or casting. This includes information, such as passwords, payment details, photos, messages and audio that you play."</string>
+    <string name="media_projection_dialog_text" msgid="1755705274910034772">"<xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> will have access to all of the information that is visible on your screen or played from your device while recording or casting. This includes information such as passwords, payment details, photos, messages and audio that you play."</string>
     <string name="media_projection_dialog_service_text" msgid="958000992162214611">"The service providing this function will have access to all of the information that is visible on your screen or played from your device while recording or casting. This includes information such as passwords, payment details, photos, messages and audio that you play."</string>
     <string name="media_projection_dialog_service_title" msgid="2888507074107884040">"Start recording or casting?"</string>
     <string name="media_projection_dialog_title" msgid="3316063622495360646">"Start recording or casting with <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g>?"</string>
@@ -1069,7 +1069,7 @@
     <string name="controls_seeding_in_progress" msgid="3033855341410264148">"Loading recommendations"</string>
     <string name="controls_media_title" msgid="1746947284862928133">"Media"</string>
     <string name="controls_media_close_session" msgid="3957093425905475065">"Hide the current session."</string>
-    <string name="controls_media_dismiss_button" msgid="4485675693008031646">"Hide"</string>
+    <string name="controls_media_dismiss_button" msgid="9081375542265132213">"Dismiss"</string>
     <string name="controls_media_resume" msgid="1933520684481586053">"Resume"</string>
     <string name="controls_media_settings_button" msgid="5815790345117172504">"Settings"</string>
     <string name="controls_error_timeout" msgid="794197289772728958">"Inactive, check app"</string>
@@ -1084,4 +1084,6 @@
     <string name="controls_added_tooltip" msgid="4842812921719153085">"Hold Power button to see new controls"</string>
     <string name="controls_menu_add" msgid="4447246119229920050">"Add controls"</string>
     <string name="controls_menu_edit" msgid="890623986951347062">"Edit controls"</string>
+    <string name="one_handed_tutorial_title" msgid="6312198435090726656">"Using one-handed mode"</string>
+    <string name="one_handed_tutorial_description" msgid="7674850272340517174">"To exit, swipe up from the bottom of the screen or tap anywhere above the app"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-en-rGB/strings.xml b/packages/SystemUI/res/values-en-rGB/strings.xml
index 68a8d30..0e22b58 100644
--- a/packages/SystemUI/res/values-en-rGB/strings.xml
+++ b/packages/SystemUI/res/values-en-rGB/strings.xml
@@ -502,7 +502,7 @@
     <string name="battery_saver_notification_title" msgid="8419266546034372562">"Battery Saver is on"</string>
     <string name="battery_saver_notification_text" msgid="2617841636449016951">"Reduces performance and background data"</string>
     <string name="battery_saver_notification_action_text" msgid="6022091913807026887">"Turn off Battery Saver"</string>
-    <string name="media_projection_dialog_text" msgid="1755705274910034772">"<xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> will have access to all of the information that is visible on your screen or played from your device while recording or casting. This includes information, such as passwords, payment details, photos, messages and audio that you play."</string>
+    <string name="media_projection_dialog_text" msgid="1755705274910034772">"<xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> will have access to all of the information that is visible on your screen or played from your device while recording or casting. This includes information such as passwords, payment details, photos, messages and audio that you play."</string>
     <string name="media_projection_dialog_service_text" msgid="958000992162214611">"The service providing this function will have access to all of the information that is visible on your screen or played from your device while recording or casting. This includes information such as passwords, payment details, photos, messages and audio that you play."</string>
     <string name="media_projection_dialog_service_title" msgid="2888507074107884040">"Start recording or casting?"</string>
     <string name="media_projection_dialog_title" msgid="3316063622495360646">"Start recording or casting with <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g>?"</string>
@@ -1069,7 +1069,7 @@
     <string name="controls_seeding_in_progress" msgid="3033855341410264148">"Loading recommendations"</string>
     <string name="controls_media_title" msgid="1746947284862928133">"Media"</string>
     <string name="controls_media_close_session" msgid="3957093425905475065">"Hide the current session."</string>
-    <string name="controls_media_dismiss_button" msgid="4485675693008031646">"Hide"</string>
+    <string name="controls_media_dismiss_button" msgid="9081375542265132213">"Dismiss"</string>
     <string name="controls_media_resume" msgid="1933520684481586053">"Resume"</string>
     <string name="controls_media_settings_button" msgid="5815790345117172504">"Settings"</string>
     <string name="controls_error_timeout" msgid="794197289772728958">"Inactive, check app"</string>
@@ -1084,4 +1084,6 @@
     <string name="controls_added_tooltip" msgid="4842812921719153085">"Hold Power button to see new controls"</string>
     <string name="controls_menu_add" msgid="4447246119229920050">"Add controls"</string>
     <string name="controls_menu_edit" msgid="890623986951347062">"Edit controls"</string>
+    <string name="one_handed_tutorial_title" msgid="6312198435090726656">"Using one-handed mode"</string>
+    <string name="one_handed_tutorial_description" msgid="7674850272340517174">"To exit, swipe up from the bottom of the screen or tap anywhere above the app"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-en-rIN/strings.xml b/packages/SystemUI/res/values-en-rIN/strings.xml
index 68a8d30..0e22b58 100644
--- a/packages/SystemUI/res/values-en-rIN/strings.xml
+++ b/packages/SystemUI/res/values-en-rIN/strings.xml
@@ -502,7 +502,7 @@
     <string name="battery_saver_notification_title" msgid="8419266546034372562">"Battery Saver is on"</string>
     <string name="battery_saver_notification_text" msgid="2617841636449016951">"Reduces performance and background data"</string>
     <string name="battery_saver_notification_action_text" msgid="6022091913807026887">"Turn off Battery Saver"</string>
-    <string name="media_projection_dialog_text" msgid="1755705274910034772">"<xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> will have access to all of the information that is visible on your screen or played from your device while recording or casting. This includes information, such as passwords, payment details, photos, messages and audio that you play."</string>
+    <string name="media_projection_dialog_text" msgid="1755705274910034772">"<xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> will have access to all of the information that is visible on your screen or played from your device while recording or casting. This includes information such as passwords, payment details, photos, messages and audio that you play."</string>
     <string name="media_projection_dialog_service_text" msgid="958000992162214611">"The service providing this function will have access to all of the information that is visible on your screen or played from your device while recording or casting. This includes information such as passwords, payment details, photos, messages and audio that you play."</string>
     <string name="media_projection_dialog_service_title" msgid="2888507074107884040">"Start recording or casting?"</string>
     <string name="media_projection_dialog_title" msgid="3316063622495360646">"Start recording or casting with <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g>?"</string>
@@ -1069,7 +1069,7 @@
     <string name="controls_seeding_in_progress" msgid="3033855341410264148">"Loading recommendations"</string>
     <string name="controls_media_title" msgid="1746947284862928133">"Media"</string>
     <string name="controls_media_close_session" msgid="3957093425905475065">"Hide the current session."</string>
-    <string name="controls_media_dismiss_button" msgid="4485675693008031646">"Hide"</string>
+    <string name="controls_media_dismiss_button" msgid="9081375542265132213">"Dismiss"</string>
     <string name="controls_media_resume" msgid="1933520684481586053">"Resume"</string>
     <string name="controls_media_settings_button" msgid="5815790345117172504">"Settings"</string>
     <string name="controls_error_timeout" msgid="794197289772728958">"Inactive, check app"</string>
@@ -1084,4 +1084,6 @@
     <string name="controls_added_tooltip" msgid="4842812921719153085">"Hold Power button to see new controls"</string>
     <string name="controls_menu_add" msgid="4447246119229920050">"Add controls"</string>
     <string name="controls_menu_edit" msgid="890623986951347062">"Edit controls"</string>
+    <string name="one_handed_tutorial_title" msgid="6312198435090726656">"Using one-handed mode"</string>
+    <string name="one_handed_tutorial_description" msgid="7674850272340517174">"To exit, swipe up from the bottom of the screen or tap anywhere above the app"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-en-rXC/strings.xml b/packages/SystemUI/res/values-en-rXC/strings.xml
index eacef26..4f4238a 100644
--- a/packages/SystemUI/res/values-en-rXC/strings.xml
+++ b/packages/SystemUI/res/values-en-rXC/strings.xml
@@ -1069,7 +1069,7 @@
     <string name="controls_seeding_in_progress" msgid="3033855341410264148">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‎‏‎‏‎‎‎‎‏‏‎‏‎‎‏‏‎‏‎‏‏‎‏‎‏‏‎‏‏‏‏‏‎‎‏‎‎‏‎‏‎‏‏‏‎‎‎‏‎‎‏‎‎‎‏‎‏‎‏‎‎‎Loading recommendations‎‏‎‎‏‎"</string>
     <string name="controls_media_title" msgid="1746947284862928133">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‏‏‏‎‎‎‎‎‏‏‏‏‏‎‎‏‏‎‎‏‏‏‎‏‎‎‏‏‎‎‏‏‎‎‎‎‏‏‏‎‎‏‎‏‏‎‏‎‎‎‎‏‎‏‎‎‎‎‎‏‎‏‎Media‎‏‎‎‏‎"</string>
     <string name="controls_media_close_session" msgid="3957093425905475065">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‏‎‏‏‎‏‏‏‎‏‎‏‎‎‏‏‎‏‎‏‏‏‎‎‎‎‎‎‏‎‏‎‏‏‎‏‎‎‎‏‎‎‎‎‏‎‎‎‏‏‏‎‏‏‏‏‏‏‎‎‏‎Hide the current session.‎‏‎‎‏‎"</string>
-    <string name="controls_media_dismiss_button" msgid="4485675693008031646">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‏‏‏‏‎‎‏‎‎‎‎‎‎‎‏‎‏‎‎‏‎‎‏‎‎‎‏‏‎‏‏‎‏‎‎‏‏‎‏‎‏‏‎‎‏‎‎‎‎‏‏‏‏‏‎‎‏‏‏‏‎‎Hide‎‏‎‎‏‎"</string>
+    <string name="controls_media_dismiss_button" msgid="9081375542265132213">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‏‏‏‎‎‎‎‎‎‏‏‏‏‎‎‎‎‏‏‎‏‏‏‏‎‎‎‎‏‏‎‎‎‏‎‎‏‎‏‎‎‎‏‎‎‎‏‏‎‏‎‎‏‎‏‏‎‏‎‏‎Dismiss‎‏‎‎‏‎"</string>
     <string name="controls_media_resume" msgid="1933520684481586053">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‏‏‏‎‏‎‏‏‎‏‎‏‎‏‎‎‏‏‏‏‏‎‏‏‎‏‎‎‎‏‎‏‏‏‏‏‎‎‎‏‏‎‎‏‎‏‎‎‏‎‎‎‏‏‏‎‎‎‎‏‎‏‎Resume‎‏‎‎‏‎"</string>
     <string name="controls_media_settings_button" msgid="5815790345117172504">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‎‎‎‎‏‎‏‏‎‏‎‏‏‏‎‏‎‏‏‎‏‎‎‎‎‎‎‏‎‏‏‎‏‎‎‎‏‏‎‏‏‎‏‏‎‎‎‎‎‎‏‏‎‎‎‏‏‎‎‎‎Settings‎‏‎‎‏‎"</string>
     <string name="controls_error_timeout" msgid="794197289772728958">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‎‏‎‏‏‎‎‎‎‎‏‎‏‏‎‎‎‏‏‏‎‎‎‏‏‏‎‏‎‎‎‏‎‏‎‏‎‎‏‏‏‏‎‏‏‎‏‎‎‎‎‏‎‎‏‏‏‏‏‏‎‎Inactive, check app‎‏‎‎‏‎"</string>
@@ -1084,4 +1084,6 @@
     <string name="controls_added_tooltip" msgid="4842812921719153085">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‎‎‎‏‏‎‎‏‏‎‏‎‏‎‎‏‎‎‎‎‎‏‎‏‏‎‎‎‏‏‎‏‎‏‎‎‎‏‎‏‏‏‏‎‏‏‏‏‎‎‎‎‏‏‎‏‏‏‏‎‏‎Hold Power button to see new controls‎‏‎‎‏‎"</string>
     <string name="controls_menu_add" msgid="4447246119229920050">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‏‏‏‎‏‏‎‏‏‎‏‏‏‏‏‎‎‏‎‏‎‏‏‎‎‏‎‎‎‎‏‏‎‏‏‎‏‎‏‏‎‏‏‏‏‎‎‎‏‎‎‏‏‎‎‏‏‎‎‏‎‎Add controls‎‏‎‎‏‎"</string>
     <string name="controls_menu_edit" msgid="890623986951347062">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‎‏‏‎‎‎‏‎‏‏‏‎‎‎‎‏‎‎‎‎‏‏‏‎‎‏‏‎‎‎‎‏‎‎‎‏‎‏‎‏‎‏‏‎‏‎‏‎‏‏‎‏‏‎‏‏‏‎‏‏‎‎Edit controls‎‏‎‎‏‎"</string>
+    <string name="one_handed_tutorial_title" msgid="6312198435090726656">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‎‏‏‏‏‎‎‏‏‎‎‏‎‏‏‎‏‏‏‏‎‎‎‎‏‎‎‎‏‎‎‎‎‎‏‏‎‏‎‏‏‎‏‏‏‏‏‏‏‏‏‏‎‎‎‎‎‎‎‎‎Using one-handed mode‎‏‎‎‏‎"</string>
+    <string name="one_handed_tutorial_description" msgid="7674850272340517174">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‎‏‎‏‎‏‎‎‎‎‎‏‎‏‎‎‎‏‎‏‏‏‎‏‎‏‎‎‎‏‏‎‏‏‎‏‏‎‎‏‎‏‎‏‎‏‎‏‎‎‎‎‏‎‎‏‏‎‏‏‎‎To exit, swipe up from the bottom of the screen or tap anywhere above the app‎‏‎‎‏‎"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-es-rUS/strings.xml b/packages/SystemUI/res/values-es-rUS/strings.xml
index 94a464b..38fa528 100644
--- a/packages/SystemUI/res/values-es-rUS/strings.xml
+++ b/packages/SystemUI/res/values-es-rUS/strings.xml
@@ -1069,7 +1069,7 @@
     <string name="controls_seeding_in_progress" msgid="3033855341410264148">"Cargando recomendaciones"</string>
     <string name="controls_media_title" msgid="1746947284862928133">"Contenido multimedia"</string>
     <string name="controls_media_close_session" msgid="3957093425905475065">"Oculta la sesión actual."</string>
-    <string name="controls_media_dismiss_button" msgid="4485675693008031646">"Ocultar"</string>
+    <string name="controls_media_dismiss_button" msgid="9081375542265132213">"Descartar"</string>
     <string name="controls_media_resume" msgid="1933520684481586053">"Reanudar"</string>
     <string name="controls_media_settings_button" msgid="5815790345117172504">"Configuración"</string>
     <string name="controls_error_timeout" msgid="794197289772728958">"Inactivo. Verifica la app"</string>
@@ -1084,4 +1084,6 @@
     <string name="controls_added_tooltip" msgid="4842812921719153085">"Mantén presionado el botón de encendido para ver los nuevos controles"</string>
     <string name="controls_menu_add" msgid="4447246119229920050">"Agregar controles"</string>
     <string name="controls_menu_edit" msgid="890623986951347062">"Editar controles"</string>
+    <string name="one_handed_tutorial_title" msgid="6312198435090726656">"Cómo usar el Modo de una mano"</string>
+    <string name="one_handed_tutorial_description" msgid="7674850272340517174">"Para salir, desliza el dedo hacia arriba desde la parte inferior de la pantalla o presiona cualquier parte arriba de la app"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-es/strings.xml b/packages/SystemUI/res/values-es/strings.xml
index 0e7d376e..10aa6ca 100644
--- a/packages/SystemUI/res/values-es/strings.xml
+++ b/packages/SystemUI/res/values-es/strings.xml
@@ -502,10 +502,10 @@
     <string name="battery_saver_notification_title" msgid="8419266546034372562">"Ahorro de batería activado"</string>
     <string name="battery_saver_notification_text" msgid="2617841636449016951">"Reduce el rendimiento y los datos en segundo plano"</string>
     <string name="battery_saver_notification_action_text" msgid="6022091913807026887">"Desactivar Ahorro de batería"</string>
-    <string name="media_projection_dialog_text" msgid="1755705274910034772">"<xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> tendrá acceso a toda la información que se muestre en pantalla o se reproduzca en el dispositivo mientras grabas o envías contenido, incluyendo contraseñas, detalles de pagos, fotos, mensajes y audios que reproduzcas."</string>
-    <string name="media_projection_dialog_service_text" msgid="958000992162214611">"El servicio que ofrece esta función tendrá acceso a toda la información que se muestre en pantalla o se reproduzca en el dispositivo mientras grabas o envías contenido, incluyendo contraseñas, detalles de pagos, fotos, mensajes y audios que reproduzcas."</string>
+    <string name="media_projection_dialog_text" msgid="1755705274910034772">"<xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> tendrá acceso a toda la información que se muestre en la pantalla o se reproduzca en el dispositivo mientras grabas o envías contenido, incluyendo contraseñas, detalles de pagos, fotos, mensajes y audios que reproduzcas."</string>
+    <string name="media_projection_dialog_service_text" msgid="958000992162214611">"El servicio que ofrece esta función tendrá acceso a toda la información que se muestre en la pantalla o se reproduzca en el dispositivo mientras grabas o envías contenido, incluyendo contraseñas, detalles de pagos, fotos, mensajes y audios que reproduzcas."</string>
     <string name="media_projection_dialog_service_title" msgid="2888507074107884040">"¿Empezar a grabar o enviar contenido?"</string>
-    <string name="media_projection_dialog_title" msgid="3316063622495360646">"¿Quieres iniciar la grabación o el envío de contenido con <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g>?"</string>
+    <string name="media_projection_dialog_title" msgid="3316063622495360646">"¿Iniciar grabación o el envío de contenido en <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g>?"</string>
     <string name="media_projection_remember_text" msgid="6896767327140422951">"No volver a mostrar"</string>
     <string name="clear_all_notifications_text" msgid="348312370303046130">"Borrar todo"</string>
     <string name="manage_notifications_text" msgid="6885645344647733116">"Gestionar"</string>
@@ -1069,7 +1069,7 @@
     <string name="controls_seeding_in_progress" msgid="3033855341410264148">"Cargando recomendaciones"</string>
     <string name="controls_media_title" msgid="1746947284862928133">"Multimedia"</string>
     <string name="controls_media_close_session" msgid="3957093425905475065">"Ocultar la sesión."</string>
-    <string name="controls_media_dismiss_button" msgid="4485675693008031646">"Ocultar"</string>
+    <string name="controls_media_dismiss_button" msgid="9081375542265132213">"Cerrar"</string>
     <string name="controls_media_resume" msgid="1933520684481586053">"Reanudar"</string>
     <string name="controls_media_settings_button" msgid="5815790345117172504">"Ajustes"</string>
     <string name="controls_error_timeout" msgid="794197289772728958">"Inactivo, comprobar aplicación"</string>
@@ -1084,4 +1084,6 @@
     <string name="controls_added_tooltip" msgid="4842812921719153085">"Mantén pulsado el botón de encendido para ver los controles nuevos"</string>
     <string name="controls_menu_add" msgid="4447246119229920050">"Añadir controles"</string>
     <string name="controls_menu_edit" msgid="890623986951347062">"Editar controles"</string>
+    <string name="one_handed_tutorial_title" msgid="6312198435090726656">"Utilizar el modo una mano"</string>
+    <string name="one_handed_tutorial_description" msgid="7674850272340517174">"Para salir, desliza dos dedos hacia arriba desde la parte inferior de la pantalla o toca cualquier zona que haya encima de la aplicación."</string>
 </resources>
diff --git a/packages/SystemUI/res/values-et/strings.xml b/packages/SystemUI/res/values-et/strings.xml
index fc9c73b..9122ce5 100644
--- a/packages/SystemUI/res/values-et/strings.xml
+++ b/packages/SystemUI/res/values-et/strings.xml
@@ -1069,7 +1069,7 @@
     <string name="controls_seeding_in_progress" msgid="3033855341410264148">"Soovituste laadimine"</string>
     <string name="controls_media_title" msgid="1746947284862928133">"Meedia"</string>
     <string name="controls_media_close_session" msgid="3957093425905475065">"Peidetakse praegune seanss."</string>
-    <string name="controls_media_dismiss_button" msgid="4485675693008031646">"Peida"</string>
+    <string name="controls_media_dismiss_button" msgid="9081375542265132213">"Loobu"</string>
     <string name="controls_media_resume" msgid="1933520684481586053">"Jätka"</string>
     <string name="controls_media_settings_button" msgid="5815790345117172504">"Seaded"</string>
     <string name="controls_error_timeout" msgid="794197289772728958">"Passiivne, vaadake rakendust"</string>
@@ -1084,4 +1084,6 @@
     <string name="controls_added_tooltip" msgid="4842812921719153085">"Uute juhtelementide vaatamiseks hoidke all toitenuppu"</string>
     <string name="controls_menu_add" msgid="4447246119229920050">"Lisa juhtelemente"</string>
     <string name="controls_menu_edit" msgid="890623986951347062">"Muuda juhtelemente"</string>
+    <string name="one_handed_tutorial_title" msgid="6312198435090726656">"Ühekäerežiimi kasutamine"</string>
+    <string name="one_handed_tutorial_description" msgid="7674850272340517174">"Väljumiseks pühkige ekraani alaosast üles või puudutage ekraani rakenduse kohal"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-eu/strings.xml b/packages/SystemUI/res/values-eu/strings.xml
index 041beab..38b2103d 100644
--- a/packages/SystemUI/res/values-eu/strings.xml
+++ b/packages/SystemUI/res/values-eu/strings.xml
@@ -1069,7 +1069,7 @@
     <string name="controls_seeding_in_progress" msgid="3033855341410264148">"Gomendioak kargatzen"</string>
     <string name="controls_media_title" msgid="1746947284862928133">"Multimedia-edukia"</string>
     <string name="controls_media_close_session" msgid="3957093425905475065">"Ezkutatu uneko saioa."</string>
-    <string name="controls_media_dismiss_button" msgid="4485675693008031646">"Ezkutatu"</string>
+    <string name="controls_media_dismiss_button" msgid="9081375542265132213">"Baztertu"</string>
     <string name="controls_media_resume" msgid="1933520684481586053">"Berrekin"</string>
     <string name="controls_media_settings_button" msgid="5815790345117172504">"Ezarpenak"</string>
     <string name="controls_error_timeout" msgid="794197289772728958">"Inaktibo; egiaztatu aplikazioa"</string>
@@ -1084,4 +1084,6 @@
     <string name="controls_added_tooltip" msgid="4842812921719153085">"Eduki sakatuta etengailua kontrolatzeko aukera berriak ikusteko"</string>
     <string name="controls_menu_add" msgid="4447246119229920050">"Gehitu aukerak"</string>
     <string name="controls_menu_edit" msgid="890623986951347062">"Editatu aukerak"</string>
+    <string name="one_handed_tutorial_title" msgid="6312198435090726656">"Esku bakarreko modua erabiltzea"</string>
+    <string name="one_handed_tutorial_description" msgid="7674850272340517174">"Ateratzeko, pasatu hatza pantailaren behealdetik gora edo sakatu aplikazioaren gainaldea"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-fa/strings.xml b/packages/SystemUI/res/values-fa/strings.xml
index 05e076f..d39d0c3 100644
--- a/packages/SystemUI/res/values-fa/strings.xml
+++ b/packages/SystemUI/res/values-fa/strings.xml
@@ -742,7 +742,7 @@
     <string name="feedback_promoted" msgid="8075757485407091976">"سیستمْ این اعلان را ارتقا داده است."</string>
     <string name="feedback_demoted" msgid="5848066008939031913">"سیستمْ این اعلان را تنزل داده است."</string>
     <string name="feedback_prompt" msgid="2278631214125128281">"این مورد درست بود؟"</string>
-    <string name="feedback_response" msgid="4671729244976641339">"از بازخورد شما سپاسگزاریم!"</string>
+    <string name="feedback_response" msgid="4671729244976641339">"از بازخورد شما سپاس‌گذاریم!"</string>
     <string name="feedback_ok" msgid="6481426753298857144">"تأیید"</string>
     <string name="notification_channel_controls_opened_accessibility" msgid="6111817750774381094">"کنترل‌های اعلان برای <xliff:g id="APP_NAME">%1$s</xliff:g> باز شد"</string>
     <string name="notification_channel_controls_closed_accessibility" msgid="1561909368876911701">"کنترل‌های اعلان برای <xliff:g id="APP_NAME">%1$s</xliff:g> بسته شد"</string>
@@ -1069,7 +1069,7 @@
     <string name="controls_seeding_in_progress" msgid="3033855341410264148">"درحال بار کردن توصیه‌ها"</string>
     <string name="controls_media_title" msgid="1746947284862928133">"رسانه"</string>
     <string name="controls_media_close_session" msgid="3957093425905475065">"جلسه فعلی پنهان شود."</string>
-    <string name="controls_media_dismiss_button" msgid="4485675693008031646">"پنهان کردن"</string>
+    <string name="controls_media_dismiss_button" msgid="9081375542265132213">"رد کردن"</string>
     <string name="controls_media_resume" msgid="1933520684481586053">"ازسرگیری"</string>
     <string name="controls_media_settings_button" msgid="5815790345117172504">"تنظیمات"</string>
     <string name="controls_error_timeout" msgid="794197289772728958">"غیرفعال، برنامه را بررسی کنید"</string>
@@ -1084,4 +1084,6 @@
     <string name="controls_added_tooltip" msgid="4842812921719153085">"برای دیدن کنترل‌های جدید، دکمه روشن/خاموش را پایین نگه دارید"</string>
     <string name="controls_menu_add" msgid="4447246119229920050">"افزودن کنترل‌ها"</string>
     <string name="controls_menu_edit" msgid="890623986951347062">"ویرایش کنترل‌ها"</string>
+    <string name="one_handed_tutorial_title" msgid="6312198435090726656">"استفاده از «حالت تک حرکت»"</string>
+    <string name="one_handed_tutorial_description" msgid="7674850272340517174">"برای خارج شدن، از پایین صفحه‌نمایش تند به‌طرف بالا بکشید یا در هر جایی از بالای برنامه که می‌خواهید ضربه بزنید"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-fi/strings.xml b/packages/SystemUI/res/values-fi/strings.xml
index 4fccaac..52e5e8c 100644
--- a/packages/SystemUI/res/values-fi/strings.xml
+++ b/packages/SystemUI/res/values-fi/strings.xml
@@ -1069,7 +1069,7 @@
     <string name="controls_seeding_in_progress" msgid="3033855341410264148">"Ladataan suosituksia"</string>
     <string name="controls_media_title" msgid="1746947284862928133">"Media"</string>
     <string name="controls_media_close_session" msgid="3957093425905475065">"Piilota nykyinen käyttökerta."</string>
-    <string name="controls_media_dismiss_button" msgid="4485675693008031646">"Piilota"</string>
+    <string name="controls_media_dismiss_button" msgid="9081375542265132213">"Ohita"</string>
     <string name="controls_media_resume" msgid="1933520684481586053">"Jatka"</string>
     <string name="controls_media_settings_button" msgid="5815790345117172504">"Asetukset"</string>
     <string name="controls_error_timeout" msgid="794197289772728958">"Epäaktiivinen, tarkista sovellus"</string>
@@ -1084,4 +1084,6 @@
     <string name="controls_added_tooltip" msgid="4842812921719153085">"Paina virtapainiketta pitkään nähdäksesi uudet säätimet"</string>
     <string name="controls_menu_add" msgid="4447246119229920050">"Lisää säätimiä"</string>
     <string name="controls_menu_edit" msgid="890623986951347062">"Muokkaa säätimiä"</string>
+    <string name="one_handed_tutorial_title" msgid="6312198435090726656">"Yhden käden moodin käyttö"</string>
+    <string name="one_handed_tutorial_description" msgid="7674850272340517174">"Poistu pyyhkäisemällä ylös näytön alareunasta tai napauttamalla sovelluksen yllä"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-fr-rCA/strings.xml b/packages/SystemUI/res/values-fr-rCA/strings.xml
index 4e2074c..4797f32 100644
--- a/packages/SystemUI/res/values-fr-rCA/strings.xml
+++ b/packages/SystemUI/res/values-fr-rCA/strings.xml
@@ -502,7 +502,7 @@
     <string name="battery_saver_notification_title" msgid="8419266546034372562">"Économiseur de pile activé"</string>
     <string name="battery_saver_notification_text" msgid="2617841636449016951">"Réduire les performances et de fond"</string>
     <string name="battery_saver_notification_action_text" msgid="6022091913807026887">"Désactiver la fonction Économiseur de pile"</string>
-    <string name="media_projection_dialog_text" msgid="1755705274910034772">"<xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> aura accès à toute l\'information visible sur votre écran ou qui joue sur votre appareil durant l\'enregistrement ou la diffusion. Cela comprend des renseignements comme les mots de passe, les détails du paiement, les photos, les messages et l\'audio que vous faites jouer."</string>
+    <string name="media_projection_dialog_text" msgid="1755705274910034772">"<xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> aura accès à toute l\'information visible sur votre écran ou qui joue sur votre appareil durant l\'enregistrement ou la diffusion. Cela comprend des renseignements comme les mots de passe, les détails du paiement, les photos, les messages et le contenu audio que vous faites jouer."</string>
     <string name="media_projection_dialog_service_text" msgid="958000992162214611">"Le service offrant cette fonction aura accès à toute l\'information qui est visible sur votre écran ou sur ce qui joue sur votre appareil durant l\'enregistrement ou la diffusion. Cela comprend des renseignements comme les mots de passe, les détails du paiement, les photos, les messages et le contenu audio que vous faites jouer."</string>
     <string name="media_projection_dialog_service_title" msgid="2888507074107884040">"Commencer à enregistrer ou à diffuser?"</string>
     <string name="media_projection_dialog_title" msgid="3316063622495360646">"Commencer à enregistrer ou à diffuser avec <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g>?"</string>
@@ -1069,7 +1069,7 @@
     <string name="controls_seeding_in_progress" msgid="3033855341410264148">"Chargement des recommandations…"</string>
     <string name="controls_media_title" msgid="1746947284862928133">"Commandes multimédias"</string>
     <string name="controls_media_close_session" msgid="3957093425905475065">"Masquer la session en cours."</string>
-    <string name="controls_media_dismiss_button" msgid="4485675693008031646">"Masquer"</string>
+    <string name="controls_media_dismiss_button" msgid="9081375542265132213">"Fermer"</string>
     <string name="controls_media_resume" msgid="1933520684481586053">"Reprendre"</string>
     <string name="controls_media_settings_button" msgid="5815790345117172504">"Paramètres"</string>
     <string name="controls_error_timeout" msgid="794197289772728958">"Délai expiré, vérifiez l\'appli"</string>
@@ -1084,4 +1084,6 @@
     <string name="controls_added_tooltip" msgid="4842812921719153085">"Maintenez enfoncé l\'interrupteur pour afficher les nouvelles commandes"</string>
     <string name="controls_menu_add" msgid="4447246119229920050">"Ajouter des commandes"</string>
     <string name="controls_menu_edit" msgid="890623986951347062">"Modifier des commandes"</string>
+    <string name="one_handed_tutorial_title" msgid="6312198435090726656">"Utiliser le mode Une main"</string>
+    <string name="one_handed_tutorial_description" msgid="7674850272340517174">"Pour quitter, balayez l\'écran du bas vers le haut, ou touchez n\'importe où sur l\'écran en haut de l\'application"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-fr/strings.xml b/packages/SystemUI/res/values-fr/strings.xml
index fedec56..e5df728 100644
--- a/packages/SystemUI/res/values-fr/strings.xml
+++ b/packages/SystemUI/res/values-fr/strings.xml
@@ -502,7 +502,7 @@
     <string name="battery_saver_notification_title" msgid="8419266546034372562">"Économiseur de batterie activé"</string>
     <string name="battery_saver_notification_text" msgid="2617841636449016951">"Limite les performances et les données en arrière-plan."</string>
     <string name="battery_saver_notification_action_text" msgid="6022091913807026887">"Désactiver l\'économiseur de batterie"</string>
-    <string name="media_projection_dialog_text" msgid="1755705274910034772">"<xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> aura accès à toutes les informations visibles sur votre écran ou lues depuis votre appareil lors d\'un enregistrement ou d\'une diffusion de contenu. Par exemple, vos mots de passe, vos données de paiement, vos photos, vos messages ou encore vos contenus audio lus."</string>
+    <string name="media_projection_dialog_text" msgid="1755705274910034772">"<xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> aura accès à toutes les informations visibles sur votre écran ou lues depuis votre appareil pendant un enregistrement ou une diffusion de contenu. Il peut s\'agir de mots de passe, données de paiement, photos, messages ou encore contenus audio lus."</string>
     <string name="media_projection_dialog_service_text" msgid="958000992162214611">"Le service qui fournit cette fonction aura accès à toutes les informations visibles sur votre écran ou lues depuis votre appareil lors d\'un enregistrement ou d\'une diffusion de contenu. Cela comprend, entre autres, vos mots de passe, vos données de paiement, vos photos, vos messages ou encore les contenus audio que vous lisez."</string>
     <string name="media_projection_dialog_service_title" msgid="2888507074107884040">"Démarrer l\'enregistrement ou la diffusion ?"</string>
     <string name="media_projection_dialog_title" msgid="3316063622495360646">"Démarrer l\'enregistrement ou la diffusion avec <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> ?"</string>
@@ -1069,7 +1069,7 @@
     <string name="controls_seeding_in_progress" msgid="3033855341410264148">"Chargement des recommandations"</string>
     <string name="controls_media_title" msgid="1746947284862928133">"Multimédia"</string>
     <string name="controls_media_close_session" msgid="3957093425905475065">"Masquer la session en cours."</string>
-    <string name="controls_media_dismiss_button" msgid="4485675693008031646">"Masquer"</string>
+    <string name="controls_media_dismiss_button" msgid="9081375542265132213">"Fermer"</string>
     <string name="controls_media_resume" msgid="1933520684481586053">"Reprendre"</string>
     <string name="controls_media_settings_button" msgid="5815790345117172504">"Paramètres"</string>
     <string name="controls_error_timeout" msgid="794197289772728958">"Délai expiré, vérifier l\'appli"</string>
@@ -1084,4 +1084,6 @@
     <string name="controls_added_tooltip" msgid="4842812921719153085">"Appuyez de manière prolongée sur le bouton Marche/Arrêt pour afficher les nouvelles commandes"</string>
     <string name="controls_menu_add" msgid="4447246119229920050">"Ajouter des commandes"</string>
     <string name="controls_menu_edit" msgid="890623986951347062">"Modifier des commandes"</string>
+    <string name="one_handed_tutorial_title" msgid="6312198435090726656">"Utiliser le mode une main"</string>
+    <string name="one_handed_tutorial_description" msgid="7674850272340517174">"Pour quitter, balayez l\'écran de bas en haut ou appuyez n\'importe où au-dessus de l\'application"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-gl/strings.xml b/packages/SystemUI/res/values-gl/strings.xml
index c1b9024..246fefd 100644
--- a/packages/SystemUI/res/values-gl/strings.xml
+++ b/packages/SystemUI/res/values-gl/strings.xml
@@ -1069,7 +1069,7 @@
     <string name="controls_seeding_in_progress" msgid="3033855341410264148">"Cargando recomendacións"</string>
     <string name="controls_media_title" msgid="1746947284862928133">"Contido multimedia"</string>
     <string name="controls_media_close_session" msgid="3957093425905475065">"Oculta a sesión actual."</string>
-    <string name="controls_media_dismiss_button" msgid="4485675693008031646">"Ocultar"</string>
+    <string name="controls_media_dismiss_button" msgid="9081375542265132213">"Ignorar"</string>
     <string name="controls_media_resume" msgid="1933520684481586053">"Retomar"</string>
     <string name="controls_media_settings_button" msgid="5815790345117172504">"Configuración"</string>
     <string name="controls_error_timeout" msgid="794197289772728958">"Inactivo. Comproba a app"</string>
@@ -1084,4 +1084,6 @@
     <string name="controls_added_tooltip" msgid="4842812921719153085">"Mantén premido o botón de acendido para ver os novos controis"</string>
     <string name="controls_menu_add" msgid="4447246119229920050">"Engadir controis"</string>
     <string name="controls_menu_edit" msgid="890623986951347062">"Editar controis"</string>
+    <string name="one_handed_tutorial_title" msgid="6312198435090726656">"Como se usa o modo dunha soa man?"</string>
+    <string name="one_handed_tutorial_description" msgid="7674850272340517174">"Para saír, pasa o dedo cara arriba desde a parte inferior da pantalla ou toca calquera lugar da zona situada encima da aplicación"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-gu/strings.xml b/packages/SystemUI/res/values-gu/strings.xml
index 9c2f717..beaacaa 100644
--- a/packages/SystemUI/res/values-gu/strings.xml
+++ b/packages/SystemUI/res/values-gu/strings.xml
@@ -125,7 +125,7 @@
     <string name="accessibility_accessibility_button" msgid="4089042473497107709">"ઍક્સેસિબિલિટી"</string>
     <string name="accessibility_rotate_button" msgid="1238584767612362586">"સ્ક્રીન ફેરવો"</string>
     <string name="accessibility_recent" msgid="901641734769533575">"ઝલક"</string>
-    <string name="accessibility_search_light" msgid="524741790416076988">"શોધો"</string>
+    <string name="accessibility_search_light" msgid="524741790416076988">"શોધ"</string>
     <string name="accessibility_camera_button" msgid="2938898391716647247">"કૅમેરો"</string>
     <string name="accessibility_phone_button" msgid="4256353121703100427">"ફોન"</string>
     <string name="accessibility_voice_assist_button" msgid="6497706615649754510">"વૉઇસ સહાય"</string>
@@ -1069,7 +1069,7 @@
     <string name="controls_seeding_in_progress" msgid="3033855341410264148">"સુઝાવ લોડ કરી રહ્યાં છીએ"</string>
     <string name="controls_media_title" msgid="1746947284862928133">"મીડિયા"</string>
     <string name="controls_media_close_session" msgid="3957093425905475065">"હાલનું સત્ર છુપાવો."</string>
-    <string name="controls_media_dismiss_button" msgid="4485675693008031646">"છુપાવો"</string>
+    <string name="controls_media_dismiss_button" msgid="9081375542265132213">"છોડી દો"</string>
     <string name="controls_media_resume" msgid="1933520684481586053">"ફરી શરૂ કરો"</string>
     <string name="controls_media_settings_button" msgid="5815790345117172504">"સેટિંગ"</string>
     <string name="controls_error_timeout" msgid="794197289772728958">"નિષ્ક્રિય, ઍપને ચેક કરો"</string>
@@ -1084,4 +1084,6 @@
     <string name="controls_added_tooltip" msgid="4842812921719153085">"નવા નિયંત્રણ જોવા માટે પાવર બટનને દબાવી રાખો"</string>
     <string name="controls_menu_add" msgid="4447246119229920050">"નિયંત્રણો ઉમેરો"</string>
     <string name="controls_menu_edit" msgid="890623986951347062">"નિયંત્રણોમાં ફેરફાર કરો"</string>
+    <string name="one_handed_tutorial_title" msgid="6312198435090726656">"એક-હાથે વાપરો મોડનો ઉપયોગ કરી રહ્યાં છીએ"</string>
+    <string name="one_handed_tutorial_description" msgid="7674850272340517174">"બહાર નીકળવા માટે, સ્ક્રીનની નીચેના ભાગથી ઉપરની તરફ સ્વાઇપ કરો અથવા ઍપના આઇકન પર ગમે ત્યાં ટૅપ કરો"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-hi/strings.xml b/packages/SystemUI/res/values-hi/strings.xml
index cc2faa2..de4fc48 100644
--- a/packages/SystemUI/res/values-hi/strings.xml
+++ b/packages/SystemUI/res/values-hi/strings.xml
@@ -1071,7 +1071,7 @@
     <string name="controls_seeding_in_progress" msgid="3033855341410264148">"सुझाव लोड हो रहे हैं"</string>
     <string name="controls_media_title" msgid="1746947284862928133">"मीडिया"</string>
     <string name="controls_media_close_session" msgid="3957093425905475065">"इस मीडिया सेशन को छिपाएं."</string>
-    <string name="controls_media_dismiss_button" msgid="4485675693008031646">"छिपाएं"</string>
+    <string name="controls_media_dismiss_button" msgid="9081375542265132213">"खारिज करें"</string>
     <string name="controls_media_resume" msgid="1933520684481586053">"फिर से शुरू करें"</string>
     <string name="controls_media_settings_button" msgid="5815790345117172504">"सेटिंग"</string>
     <string name="controls_error_timeout" msgid="794197289772728958">"काम नहीं कर रहा, ऐप जांचें"</string>
@@ -1086,4 +1086,6 @@
     <string name="controls_added_tooltip" msgid="4842812921719153085">"नए कंट्रोल देखने के लिए पावर बटन दबाकर रखें"</string>
     <string name="controls_menu_add" msgid="4447246119229920050">"कंट्राेल जोड़ें"</string>
     <string name="controls_menu_edit" msgid="890623986951347062">"कंट्रोल मेन्यू में बदलाव करें"</string>
+    <string name="one_handed_tutorial_title" msgid="6312198435090726656">"वन-हैंडेड मोड का इस्तेमाल करना"</string>
+    <string name="one_handed_tutorial_description" msgid="7674850272340517174">"इसे बंद करने के लिए, स्क्रीन के सबसे निचले हिस्से से ऊपर की ओर स्वाइप करें या ऐप्लिकेशन के आइकॉन के ऊपर कहीं भी टैप करें"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-hr/strings.xml b/packages/SystemUI/res/values-hr/strings.xml
index 90251995..7bf0b93 100644
--- a/packages/SystemUI/res/values-hr/strings.xml
+++ b/packages/SystemUI/res/values-hr/strings.xml
@@ -505,10 +505,10 @@
     <string name="battery_saver_notification_title" msgid="8419266546034372562">"Štednja baterije je uključena"</string>
     <string name="battery_saver_notification_text" msgid="2617841636449016951">"Smanjuje količinu rada i pozadinske podatke"</string>
     <string name="battery_saver_notification_action_text" msgid="6022091913807026887">"Isključite Štednju baterije"</string>
-    <string name="media_projection_dialog_text" msgid="1755705274910034772">"<xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> imat će pristup svim podacima koji su vidljivi na vašem zaslonu ili koji se reproduciraju s vašeg uređaja tijekom snimanja ili emitiranja. To uključuje podatke kao što su zaporke, podaci o plaćanju, fotografije, poruke i audiozapisi koje reproducirate."</string>
+    <string name="media_projection_dialog_text" msgid="1755705274910034772">"Aplikacija <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> imat će pristup svim podacima koji su vidljivi na vašem zaslonu ili koji se reproduciraju s vašeg uređaja tijekom snimanja ili emitiranja. To uključuje podatke kao što su zaporke, podaci o plaćanju, fotografije, poruke i audiozapisi koje reproducirate."</string>
     <string name="media_projection_dialog_service_text" msgid="958000992162214611">"Usluga koja pruža ovu funkcionalnost imat će pristup svim podacima koji su vidljivi na vašem zaslonu ili koji se reproduciraju s vašeg uređaja tijekom snimanja ili emitiranja. To uključuje podatke kao što su zaporke, podaci o plaćanju, fotografije, poruke i audiozapisi koje reproducirate."</string>
     <string name="media_projection_dialog_service_title" msgid="2888507074107884040">"Započeti snimanje ili emitiranje?"</string>
-    <string name="media_projection_dialog_title" msgid="3316063622495360646">"Započeti snimanje ili emitiranja pomoću aplikacije <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g>?"</string>
+    <string name="media_projection_dialog_title" msgid="3316063622495360646">"Započeti snimanje ili emitiranje pomoću aplikacije <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g>?"</string>
     <string name="media_projection_remember_text" msgid="6896767327140422951">"Ne prikazuj ponovo"</string>
     <string name="clear_all_notifications_text" msgid="348312370303046130">"Izbriši sve"</string>
     <string name="manage_notifications_text" msgid="6885645344647733116">"Upravljajte"</string>
@@ -1075,7 +1075,7 @@
     <string name="controls_seeding_in_progress" msgid="3033855341410264148">"Učitavanje preporuka"</string>
     <string name="controls_media_title" msgid="1746947284862928133">"Mediji"</string>
     <string name="controls_media_close_session" msgid="3957093425905475065">"Sakrij trenutačnu sesiju."</string>
-    <string name="controls_media_dismiss_button" msgid="4485675693008031646">"Sakrij"</string>
+    <string name="controls_media_dismiss_button" msgid="9081375542265132213">"Odbaci"</string>
     <string name="controls_media_resume" msgid="1933520684481586053">"Nastavi"</string>
     <string name="controls_media_settings_button" msgid="5815790345117172504">"Postavke"</string>
     <string name="controls_error_timeout" msgid="794197289772728958">"Neaktivno, provjerite aplik."</string>
@@ -1090,4 +1090,6 @@
     <string name="controls_added_tooltip" msgid="4842812921719153085">"Zadržite tipku za uključivanje/isključivanje za prikaz novih kontrola"</string>
     <string name="controls_menu_add" msgid="4447246119229920050">"Dodaj kontrole"</string>
     <string name="controls_menu_edit" msgid="890623986951347062">"Uredi kontrole"</string>
+    <string name="one_handed_tutorial_title" msgid="6312198435090726656">"Korištenje načina rada jednom rukom"</string>
+    <string name="one_handed_tutorial_description" msgid="7674850272340517174">"Za izlaz prijeđite prstom od dna zaslona prema gore ili dodirnite bio gdje iznad aplikacije"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-hu/strings.xml b/packages/SystemUI/res/values-hu/strings.xml
index 689d869..875a05f 100644
--- a/packages/SystemUI/res/values-hu/strings.xml
+++ b/packages/SystemUI/res/values-hu/strings.xml
@@ -1069,7 +1069,7 @@
     <string name="controls_seeding_in_progress" msgid="3033855341410264148">"Javaslatok betöltése…"</string>
     <string name="controls_media_title" msgid="1746947284862928133">"Média"</string>
     <string name="controls_media_close_session" msgid="3957093425905475065">"Jelenlegi munkamenet elrejtése."</string>
-    <string name="controls_media_dismiss_button" msgid="4485675693008031646">"Elrejtés"</string>
+    <string name="controls_media_dismiss_button" msgid="9081375542265132213">"Elvetés"</string>
     <string name="controls_media_resume" msgid="1933520684481586053">"Folytatás"</string>
     <string name="controls_media_settings_button" msgid="5815790345117172504">"Beállítások"</string>
     <string name="controls_error_timeout" msgid="794197289772728958">"Inaktív, ellenőrizze az appot"</string>
@@ -1084,4 +1084,6 @@
     <string name="controls_added_tooltip" msgid="4842812921719153085">"Az új vezérlők megtekintéséhez tartsa nyomva a bekapcsológombot"</string>
     <string name="controls_menu_add" msgid="4447246119229920050">"Vezérlők hozzáadása"</string>
     <string name="controls_menu_edit" msgid="890623986951347062">"Vezérlők szerkesztése"</string>
+    <string name="one_handed_tutorial_title" msgid="6312198435090726656">"Egykezes mód használata"</string>
+    <string name="one_handed_tutorial_description" msgid="7674850272340517174">"A kilépéshez csúsztasson felfelé a képernyő aljáról, vagy koppintson az alkalmazás felett a képernyő bármelyik részére"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-hy/strings.xml b/packages/SystemUI/res/values-hy/strings.xml
index a574f26..4cc406b 100644
--- a/packages/SystemUI/res/values-hy/strings.xml
+++ b/packages/SystemUI/res/values-hy/strings.xml
@@ -1069,7 +1069,7 @@
     <string name="controls_seeding_in_progress" msgid="3033855341410264148">"Բեռնման խորհուրդներ"</string>
     <string name="controls_media_title" msgid="1746947284862928133">"Մեդիա"</string>
     <string name="controls_media_close_session" msgid="3957093425905475065">"Թաքցրեք ընթացիկ աշխատաշրջանը"</string>
-    <string name="controls_media_dismiss_button" msgid="4485675693008031646">"Թաքցնել"</string>
+    <string name="controls_media_dismiss_button" msgid="9081375542265132213">"Փակել"</string>
     <string name="controls_media_resume" msgid="1933520684481586053">"Շարունակել"</string>
     <string name="controls_media_settings_button" msgid="5815790345117172504">"Կարգավորումներ"</string>
     <string name="controls_error_timeout" msgid="794197289772728958">"Ակտիվ չէ, ստուգեք հավելվածը"</string>
@@ -1084,4 +1084,6 @@
     <string name="controls_added_tooltip" msgid="4842812921719153085">"Սեղմած պահեք սնուցման կոճակը՝ կառավարման նոր տարրերը տեսնելու համար։"</string>
     <string name="controls_menu_add" msgid="4447246119229920050">"Ավելացնել կառավարման տարրեր"</string>
     <string name="controls_menu_edit" msgid="890623986951347062">"Փոփոխել կառավարման տարրերը"</string>
+    <string name="one_handed_tutorial_title" msgid="6312198435090726656">"Ինչպես օգտվել մեկ ձեռքի ռեժիմից"</string>
+    <string name="one_handed_tutorial_description" msgid="7674850272340517174">"Դուրս գալու համար մատը սահեցրեք էկրանի ներքևից վերև կամ հպեք հավելվածի վերևում որևէ տեղ։"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-in/strings.xml b/packages/SystemUI/res/values-in/strings.xml
index 7cc1b89..5f7dce4 100644
--- a/packages/SystemUI/res/values-in/strings.xml
+++ b/packages/SystemUI/res/values-in/strings.xml
@@ -855,7 +855,7 @@
   </string-array>
     <string name="menu_ime" msgid="5677467548258017952">"Pengalih keyboard"</string>
     <string name="save" msgid="3392754183673848006">"Simpan"</string>
-    <string name="reset" msgid="8715144064608810383">"Setel ulang"</string>
+    <string name="reset" msgid="8715144064608810383">"Reset"</string>
     <string name="adjust_button_width" msgid="8313444823666482197">"Sesuaikan lebar tombol"</string>
     <string name="clipboard" msgid="8517342737534284617">"Papan klip"</string>
     <string name="accessibility_key" msgid="3471162841552818281">"Tombol navigasi khusus"</string>
@@ -1069,7 +1069,7 @@
     <string name="controls_seeding_in_progress" msgid="3033855341410264148">"Memuat rekomendasi"</string>
     <string name="controls_media_title" msgid="1746947284862928133">"Media"</string>
     <string name="controls_media_close_session" msgid="3957093425905475065">"Menyembunyikan sesi saat ini."</string>
-    <string name="controls_media_dismiss_button" msgid="4485675693008031646">"Sembunyikan"</string>
+    <string name="controls_media_dismiss_button" msgid="9081375542265132213">"Tutup"</string>
     <string name="controls_media_resume" msgid="1933520684481586053">"Lanjutkan"</string>
     <string name="controls_media_settings_button" msgid="5815790345117172504">"Setelan"</string>
     <string name="controls_error_timeout" msgid="794197289772728958">"Nonaktif, periksa aplikasi"</string>
@@ -1084,4 +1084,6 @@
     <string name="controls_added_tooltip" msgid="4842812921719153085">"Tahan Tombol daya untuk melihat kontrol baru"</string>
     <string name="controls_menu_add" msgid="4447246119229920050">"Tambahkan kontrol"</string>
     <string name="controls_menu_edit" msgid="890623986951347062">"Edit kontrol"</string>
+    <string name="one_handed_tutorial_title" msgid="6312198435090726656">"Menggunakan mode satu tangan"</string>
+    <string name="one_handed_tutorial_description" msgid="7674850272340517174">"Untuk keluar, geser layar dari bawah ke atas atau ketuk di mana saja di atas aplikasi"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-is/strings.xml b/packages/SystemUI/res/values-is/strings.xml
index 8b686b8..b5f6e57 100644
--- a/packages/SystemUI/res/values-is/strings.xml
+++ b/packages/SystemUI/res/values-is/strings.xml
@@ -1069,7 +1069,7 @@
     <string name="controls_seeding_in_progress" msgid="3033855341410264148">"Hleður tillögum"</string>
     <string name="controls_media_title" msgid="1746947284862928133">"Margmiðlunarefni"</string>
     <string name="controls_media_close_session" msgid="3957093425905475065">"Fela núverandi lotu."</string>
-    <string name="controls_media_dismiss_button" msgid="4485675693008031646">"Fela"</string>
+    <string name="controls_media_dismiss_button" msgid="9081375542265132213">"Hunsa"</string>
     <string name="controls_media_resume" msgid="1933520684481586053">"Halda áfram"</string>
     <string name="controls_media_settings_button" msgid="5815790345117172504">"Stillingar"</string>
     <string name="controls_error_timeout" msgid="794197289772728958">"Óvirkt, athugaðu forrit"</string>
@@ -1084,4 +1084,6 @@
     <string name="controls_added_tooltip" msgid="4842812921719153085">"Haltu aflrofanum inni til að sjá nýjar stýringar"</string>
     <string name="controls_menu_add" msgid="4447246119229920050">"Bæta við stýringum"</string>
     <string name="controls_menu_edit" msgid="890623986951347062">"Breyta stýringum"</string>
+    <string name="one_handed_tutorial_title" msgid="6312198435090726656">"Notkun stillingar fyrir eina hönd"</string>
+    <string name="one_handed_tutorial_description" msgid="7674850272340517174">"Til að loka skaltu strjúka upp frá neðri hluta skjásins eða ýta hvar sem er fyrir ofan forritið"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-it/strings.xml b/packages/SystemUI/res/values-it/strings.xml
index 35742fd..f4453b7 100644
--- a/packages/SystemUI/res/values-it/strings.xml
+++ b/packages/SystemUI/res/values-it/strings.xml
@@ -1069,7 +1069,7 @@
     <string name="controls_seeding_in_progress" msgid="3033855341410264148">"Caricamento dei consigli"</string>
     <string name="controls_media_title" msgid="1746947284862928133">"Contenuti multimediali"</string>
     <string name="controls_media_close_session" msgid="3957093425905475065">"Nascondi la sessione attuale."</string>
-    <string name="controls_media_dismiss_button" msgid="4485675693008031646">"Nascondi"</string>
+    <string name="controls_media_dismiss_button" msgid="9081375542265132213">"Ignora"</string>
     <string name="controls_media_resume" msgid="1933520684481586053">"Riprendi"</string>
     <string name="controls_media_settings_button" msgid="5815790345117172504">"Impostazioni"</string>
     <string name="controls_error_timeout" msgid="794197289772728958">"Inattivo, controlla l\'app"</string>
@@ -1084,4 +1084,6 @@
     <string name="controls_added_tooltip" msgid="4842812921719153085">"Tieni premuto il tasto di accensione per visualizzare i nuovi controlli"</string>
     <string name="controls_menu_add" msgid="4447246119229920050">"Aggiungi controlli"</string>
     <string name="controls_menu_edit" msgid="890623986951347062">"Modifica controlli"</string>
+    <string name="one_handed_tutorial_title" msgid="6312198435090726656">"Usare la modalità one-hand"</string>
+    <string name="one_handed_tutorial_description" msgid="7674850272340517174">"Per uscire, scorri verso l\'alto dalla parte inferiore dello schermo oppure tocca un punto qualsiasi sopra l\'app"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-iw/strings.xml b/packages/SystemUI/res/values-iw/strings.xml
index 1029bb2..161fedc 100644
--- a/packages/SystemUI/res/values-iw/strings.xml
+++ b/packages/SystemUI/res/values-iw/strings.xml
@@ -508,7 +508,7 @@
     <string name="battery_saver_notification_title" msgid="8419266546034372562">"תכונת החיסכון בסוללה פועלת"</string>
     <string name="battery_saver_notification_text" msgid="2617841636449016951">"מפחית את הביצועים ונתונים ברקע"</string>
     <string name="battery_saver_notification_action_text" msgid="6022091913807026887">"כיבוי תכונת החיסכון בסוללה"</string>
-    <string name="media_projection_dialog_text" msgid="1755705274910034772">"‏לאפליקציה <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> תהיה גישה לכל הפרטים שגלויים במסך שלך או מופעלים מהמכשיר שלך בזמן הקלטה או העברה (cast). זה כולל פרטים כמו סיסמאות, פרטי תשלום, תמונות, הודעות ואודיו שמושמע מהמכשיר."</string>
+    <string name="media_projection_dialog_text" msgid="1755705274910034772">"‏לאפליקציית <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> תהיה גישה לכל המידע הגלוי במסך שלך ולכל תוכן שמופעל במכשיר שלך בזמן הקלטה או העברה (casting). המידע הזה כולל פרטים כמו סיסמאות, פרטי תשלום, תמונות, הודעות ואודיו שמושמע מהמכשיר."</string>
     <string name="media_projection_dialog_service_text" msgid="958000992162214611">"‏לשירות שמספק את הפונקציה הזו תהיה גישה לכל הפרטים שגלויים במסך שלך או מופעלים מהמכשיר שלך בזמן הקלטה או העברה (cast). זה כולל פרטים כמו סיסמאות, פרטי תשלום, תמונות, הודעות ואודיו שמושמע מהמכשיר."</string>
     <string name="media_projection_dialog_service_title" msgid="2888507074107884040">"‏להתחיל להקליט או להעביר (cast)?"</string>
     <string name="media_projection_dialog_title" msgid="3316063622495360646">"‏להתחיל להקליט או להעביר (cast) באמצעות <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g>?"</string>
@@ -522,7 +522,7 @@
     <string name="notification_section_header_conversations" msgid="821834744538345661">"שיחות"</string>
     <string name="accessibility_notification_section_header_gentle_clear_all" msgid="6490207897764933919">"ניקוי כל ההתראות השקטות"</string>
     <string name="dnd_suppressing_shade_text" msgid="5588252250634464042">"התראות הושהו על ידי מצב \'נא לא להפריע\'"</string>
-    <string name="media_projection_action_text" msgid="3634906766918186440">"התחל כעת"</string>
+    <string name="media_projection_action_text" msgid="3634906766918186440">"כן, אפשר להתחיל"</string>
     <string name="empty_shade_text" msgid="8935967157319717412">"אין התראות"</string>
     <string name="profile_owned_footer" msgid="2756770645766113964">"ייתכן שהפרופיל נתון למעקב"</string>
     <string name="vpn_footer" msgid="3457155078010607471">"ייתכן שהרשת נמצאת במעקב"</string>
@@ -1081,7 +1081,7 @@
     <string name="controls_seeding_in_progress" msgid="3033855341410264148">"בטעינת המלצות"</string>
     <string name="controls_media_title" msgid="1746947284862928133">"מדיה"</string>
     <string name="controls_media_close_session" msgid="3957093425905475065">"הסתרת הסשן הנוכחי."</string>
-    <string name="controls_media_dismiss_button" msgid="4485675693008031646">"הסתרה"</string>
+    <string name="controls_media_dismiss_button" msgid="9081375542265132213">"סגירה"</string>
     <string name="controls_media_resume" msgid="1933520684481586053">"המשך"</string>
     <string name="controls_media_settings_button" msgid="5815790345117172504">"הגדרות"</string>
     <string name="controls_error_timeout" msgid="794197289772728958">"לא פעיל, יש לבדוק את האפליקציה"</string>
@@ -1096,4 +1096,6 @@
     <string name="controls_added_tooltip" msgid="4842812921719153085">"ניתן ללחוץ על לחצן ההפעלה כדי להציג פקדים חדשים"</string>
     <string name="controls_menu_add" msgid="4447246119229920050">"הוספת פקדים"</string>
     <string name="controls_menu_edit" msgid="890623986951347062">"עריכת פקדים"</string>
+    <string name="one_handed_tutorial_title" msgid="6312198435090726656">"איך להשתמש במצב שימוש ביד אחת"</string>
+    <string name="one_handed_tutorial_description" msgid="7674850272340517174">"כדי לצאת, יש להחליק למעלה מתחתית המסך או להקיש במקום כלשהו במסך מעל האפליקציה"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-ja/strings.xml b/packages/SystemUI/res/values-ja/strings.xml
index fedd9bb7..fa9c040 100644
--- a/packages/SystemUI/res/values-ja/strings.xml
+++ b/packages/SystemUI/res/values-ja/strings.xml
@@ -502,10 +502,10 @@
     <string name="battery_saver_notification_title" msgid="8419266546034372562">"バッテリー セーバー ON"</string>
     <string name="battery_saver_notification_text" msgid="2617841636449016951">"パフォーマンスとバックグラウンドデータを制限します"</string>
     <string name="battery_saver_notification_action_text" msgid="6022091913807026887">"バッテリー セーバーを OFF"</string>
-    <string name="media_projection_dialog_text" msgid="1755705274910034772">"<xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g>は、記録中やキャスト中に画面上に表示またはデバイスから再生されるすべての情報にアクセスできます。これには、パスワード、お支払いの詳細、写真、メッセージ、再生される音声などの情報が含まれます。"</string>
-    <string name="media_projection_dialog_service_text" msgid="958000992162214611">"この機能を提供するサービスは、記録中やキャスト中に画面上に表示またはデバイスから再生されるすべての情報にアクセスできます。これには、パスワード、お支払いの詳細、写真、メッセージ、再生される音声などの情報が含まれます。"</string>
-    <string name="media_projection_dialog_service_title" msgid="2888507074107884040">"記録やキャストを開始しますか?"</string>
-    <string name="media_projection_dialog_title" msgid="3316063622495360646">"<xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g>で記録やキャストを開始しますか?"</string>
+    <string name="media_projection_dialog_text" msgid="1755705274910034772">"<xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> は、録画中やキャスト中に画面に表示されたり、デバイスで再生されるすべての情報にアクセスできます。これには、パスワード、お支払いの詳細、写真、メッセージ、再生される音声などが含まれます。"</string>
+    <string name="media_projection_dialog_service_text" msgid="958000992162214611">"この機能を提供するサービスは、録画中やキャスト中に画面に表示されたり、デバイスで再生されるすべての情報にアクセスできます。これには、パスワード、お支払いの詳細、写真、メッセージ、再生される音声などが含まれます。"</string>
+    <string name="media_projection_dialog_service_title" msgid="2888507074107884040">"録画やキャストを開始しますか?"</string>
+    <string name="media_projection_dialog_title" msgid="3316063622495360646">"<xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> で録画やキャストを開始しますか?"</string>
     <string name="media_projection_remember_text" msgid="6896767327140422951">"次回から表示しない"</string>
     <string name="clear_all_notifications_text" msgid="348312370303046130">"すべて消去"</string>
     <string name="manage_notifications_text" msgid="6885645344647733116">"管理"</string>
@@ -1069,7 +1069,7 @@
     <string name="controls_seeding_in_progress" msgid="3033855341410264148">"候補を読み込んでいます"</string>
     <string name="controls_media_title" msgid="1746947284862928133">"メディア"</string>
     <string name="controls_media_close_session" msgid="3957093425905475065">"現在のセッションを非表示にします。"</string>
-    <string name="controls_media_dismiss_button" msgid="4485675693008031646">"非表示"</string>
+    <string name="controls_media_dismiss_button" msgid="9081375542265132213">"閉じる"</string>
     <string name="controls_media_resume" msgid="1933520684481586053">"再開"</string>
     <string name="controls_media_settings_button" msgid="5815790345117172504">"設定"</string>
     <string name="controls_error_timeout" msgid="794197289772728958">"無効: アプリをご確認ください"</string>
@@ -1084,4 +1084,6 @@
     <string name="controls_added_tooltip" msgid="4842812921719153085">"電源ボタンを長押しすると、新しいコントロールが表示されます"</string>
     <string name="controls_menu_add" msgid="4447246119229920050">"コントロールを追加"</string>
     <string name="controls_menu_edit" msgid="890623986951347062">"コントロールを編集"</string>
+    <string name="one_handed_tutorial_title" msgid="6312198435090726656">"片手モードの使用"</string>
+    <string name="one_handed_tutorial_description" msgid="7674850272340517174">"終了するには、画面を下から上にスワイプするか、アプリの任意の場所をタップします"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-ka/strings.xml b/packages/SystemUI/res/values-ka/strings.xml
index 5c55112..a3ddf70 100644
--- a/packages/SystemUI/res/values-ka/strings.xml
+++ b/packages/SystemUI/res/values-ka/strings.xml
@@ -1069,7 +1069,7 @@
     <string name="controls_seeding_in_progress" msgid="3033855341410264148">"მიმდინარეობს რეკომენდაციების ჩატვირთვა"</string>
     <string name="controls_media_title" msgid="1746947284862928133">"მედია"</string>
     <string name="controls_media_close_session" msgid="3957093425905475065">"დაიმალოს მიმდინარე სესია"</string>
-    <string name="controls_media_dismiss_button" msgid="4485675693008031646">"დამალვა"</string>
+    <string name="controls_media_dismiss_button" msgid="9081375542265132213">"დახურვა"</string>
     <string name="controls_media_resume" msgid="1933520684481586053">"გაგრძელება"</string>
     <string name="controls_media_settings_button" msgid="5815790345117172504">"პარამეტრები"</string>
     <string name="controls_error_timeout" msgid="794197289772728958">"არააქტიურია, გადაამოწმეთ აპი"</string>
@@ -1084,4 +1084,6 @@
     <string name="controls_added_tooltip" msgid="4842812921719153085">"ხანგრძლივად დააჭირეთ ჩართვის ღილაკს მართვის ახალი საშუალებების სანახავად"</string>
     <string name="controls_menu_add" msgid="4447246119229920050">"მართვის საშუალებების დამატება"</string>
     <string name="controls_menu_edit" msgid="890623986951347062">"მართვის საშუალებათა რედაქტირება"</string>
+    <string name="one_handed_tutorial_title" msgid="6312198435090726656">"ცალი ხელის რეჟიმის გამოყენება"</string>
+    <string name="one_handed_tutorial_description" msgid="7674850272340517174">"გასასვლელად გადაფურცლეთ ეკრანის ქვედა კიდიდან ზემოთ ან შეეხეთ ნებისმიერ ადგილას აპის ზემოთ"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-kk/strings.xml b/packages/SystemUI/res/values-kk/strings.xml
index 94c8ea3..0d3f7f9 100644
--- a/packages/SystemUI/res/values-kk/strings.xml
+++ b/packages/SystemUI/res/values-kk/strings.xml
@@ -502,8 +502,8 @@
     <string name="battery_saver_notification_title" msgid="8419266546034372562">"Battery saver қосулы"</string>
     <string name="battery_saver_notification_text" msgid="2617841636449016951">"Өнімділікті және фондық деректерді азайтады"</string>
     <string name="battery_saver_notification_action_text" msgid="6022091913807026887">"Battery saver функциясын өшіру"</string>
-    <string name="media_projection_dialog_text" msgid="1755705274910034772">"<xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> экранда көрсетілетін немесе жазу не трансляциялау кезінде құрылғыда ойнатылған барлық ақпаратты пайдалана алады. Бұған құпия сөздер, төлем туралы мәліметтер, суреттер, хабарлар және ойнатылатын аудио сияқты ақпарат кіреді."</string>
-    <string name="media_projection_dialog_service_text" msgid="958000992162214611">"Осы функцияны ұсынатын қызмет экранда көрсетілетін немесе жазу не трансляциялау кезінде құрылғыда ойнатылған барлық ақпаратты пайдалана алады. Бұған құпия сөздер, төлем туралы мәліметтер, суреттер, хабарлар және ойнатылатын аудио сияқты ақпарат кіреді."</string>
+    <string name="media_projection_dialog_text" msgid="1755705274910034772">"<xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> жазу не трансляциялау кезінде экранда көрсетілетін немесе дыбысталатын барлық ақпаратты пайдалана алады. Бұған құпия сөздер, төлем туралы мәліметтер, суреттер, хабарлар және аудиоматериалдар кіреді."</string>
+    <string name="media_projection_dialog_service_text" msgid="958000992162214611">"Осы функцияны ұсынатын қызмет жазу не трансляциялау кезінде экранда көрсетілетін немесе құрылғыда дыбысталатын ақпаратты пайдалана алады. Бұған құпия сөздер, төлем туралы мәліметтер, суреттер, хабарлар және аудиоматериалдар кіреді."</string>
     <string name="media_projection_dialog_service_title" msgid="2888507074107884040">"Жазу немесе трансляциялау басталсын ба?"</string>
     <string name="media_projection_dialog_title" msgid="3316063622495360646">"<xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> арқылы жазу немесе трансляциялау басталсын ба?"</string>
     <string name="media_projection_remember_text" msgid="6896767327140422951">"Қайта көрсетпеу"</string>
@@ -1069,7 +1069,7 @@
     <string name="controls_seeding_in_progress" msgid="3033855341410264148">"Жүктеуге қатысты ұсыныстар"</string>
     <string name="controls_media_title" msgid="1746947284862928133">"Мультимедиа"</string>
     <string name="controls_media_close_session" msgid="3957093425905475065">"Ағымдағы сеансты жасыру"</string>
-    <string name="controls_media_dismiss_button" msgid="4485675693008031646">"Жасыру"</string>
+    <string name="controls_media_dismiss_button" msgid="9081375542265132213">"Жабу"</string>
     <string name="controls_media_resume" msgid="1933520684481586053">"Жалғастыру"</string>
     <string name="controls_media_settings_button" msgid="5815790345117172504">"Параметрлер"</string>
     <string name="controls_error_timeout" msgid="794197289772728958">"Өшірулі. Қолданба тексеріңіз."</string>
@@ -1084,4 +1084,6 @@
     <string name="controls_added_tooltip" msgid="4842812921719153085">"Жаңа басқару элементтерін көру үшін \"Қуат\" түймесін басып тұрыңыз."</string>
     <string name="controls_menu_add" msgid="4447246119229920050">"Басқару элементтерін енгізу"</string>
     <string name="controls_menu_edit" msgid="890623986951347062">"Басқару элементтерін өзгерту"</string>
+    <string name="one_handed_tutorial_title" msgid="6312198435090726656">"Бір қолмен енгізу режимін пайдалану"</string>
+    <string name="one_handed_tutorial_description" msgid="7674850272340517174">"Шығу үшін экранның төменгі жағынан жоғары қарай сипаңыз немесе қолданбаның үстінен кез келген жерден түртіңіз."</string>
 </resources>
diff --git a/packages/SystemUI/res/values-km/strings.xml b/packages/SystemUI/res/values-km/strings.xml
index 5ba460a..5abd740 100644
--- a/packages/SystemUI/res/values-km/strings.xml
+++ b/packages/SystemUI/res/values-km/strings.xml
@@ -502,10 +502,10 @@
     <string name="battery_saver_notification_title" msgid="8419266546034372562">"កម្មវិធីសន្សំថ្មបានបើក"</string>
     <string name="battery_saver_notification_text" msgid="2617841636449016951">"ការ​បន្ថយ​ការ​ប្រតិបត្តិ និង​ទិន្នន័យ​ផ្ទៃ​ខាងក្រោយ"</string>
     <string name="battery_saver_notification_action_text" msgid="6022091913807026887">"បិទ​កម្មវិធី​សន្សំ​ថ្ម"</string>
-    <string name="media_projection_dialog_text" msgid="1755705274910034772">"<xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> នឹងមានសិទ្ធិ​ចូលប្រើ​ព័ត៌មាន​ទាំងអស់​ដែលអាច​មើលឃើញ​នៅលើ​អេក្រង់​របស់អ្នក ឬដែលចាក់​ពីឧបករណ៍​របស់អ្នក នៅពេល​កំពុង​ថត ឬបញ្ជូន។ ព័ត៌មាន​នេះមាន​ដូចជា ពាក្យសម្ងាត់ ព័ត៌មាន​លម្អិត​អំពីការទូទាត់​ប្រាក់ រូបថត សារ និង​សំឡេង​ដែល​អ្នកចាក់​ជាដើម។"</string>
-    <string name="media_projection_dialog_service_text" msgid="958000992162214611">"សេវាកម្មដែល​ផ្ដល់​មុខងារ​នេះ​នឹងមាន​សិទ្ធិ​ចូលប្រើ​ព័ត៌មាន​ទាំងអស់​ដែល​អាច​មើលឃើញ​នៅលើ​អេក្រង់​របស់អ្នក ឬ​ដែលចាក់​ពីឧបករណ៍​របស់អ្នក នៅពេល​កំពុង​ថត ឬបញ្ជូន។ ព័ត៌មាន​នេះមាន​ដូចជា ពាក្យសម្ងាត់ ព័ត៌មាន​លម្អិត​អំពីការទូទាត់​ប្រាក់ រូបថត សារ និង​សំឡេង​ដែល​អ្នកចាក់​ជាដើម។"</string>
+    <string name="media_projection_dialog_text" msgid="1755705274910034772">"<xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> នឹងមានសិទ្ធិ​ចូលប្រើ​ព័ត៌មាន​ទាំងអស់​ដែលអាច​មើលឃើញ​នៅលើ​អេក្រង់​របស់អ្នក ឬដែលចាក់​ពីឧបករណ៍​របស់អ្នក នៅពេល​កំពុង​ថត ឬភ្ជាប់។ ព័ត៌មាន​នេះមាន​ដូចជា ពាក្យសម្ងាត់ ព័ត៌មាន​លម្អិត​អំពីការទូទាត់​ប្រាក់ រូបថត សារ និង​សំឡេង​ដែល​អ្នកចាក់​ជាដើម។"</string>
+    <string name="media_projection_dialog_service_text" msgid="958000992162214611">"សេវាកម្មដែល​ផ្ដល់​មុខងារ​នេះ​នឹងមាន​សិទ្ធិ​ចូលប្រើ​ព័ត៌មាន​ទាំងអស់​ដែល​អាច​មើលឃើញ​នៅលើ​អេក្រង់​របស់អ្នក ឬ​ដែលចាក់​ពីឧបករណ៍​របស់អ្នក នៅពេល​កំពុង​ថត ឬភ្ជាប់។ ព័ត៌មាន​នេះមាន​ដូចជា ពាក្យសម្ងាត់ ព័ត៌មាន​លម្អិត​អំពីការទូទាត់​ប្រាក់ រូបថត សារ និង​សំឡេង​ដែល​អ្នកចាក់​ជាដើម។"</string>
     <string name="media_projection_dialog_service_title" msgid="2888507074107884040">"ចាប់ផ្ដើម​ថត ឬបញ្ជូន​មែនទេ?"</string>
-    <string name="media_projection_dialog_title" msgid="3316063622495360646">"ចាប់ផ្ដើម​ថត ឬបញ្ជូន​ដោយប្រើ <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> ឬ?"</string>
+    <string name="media_projection_dialog_title" msgid="3316063622495360646">"ចាប់ផ្ដើម​ថត ឬភ្ជាប់​ដោយប្រើ <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> ឬ?"</string>
     <string name="media_projection_remember_text" msgid="6896767327140422951">"កុំ​បង្ហាញ​ម្ដងទៀត"</string>
     <string name="clear_all_notifications_text" msgid="348312370303046130">"សម្អាត​ទាំងអស់"</string>
     <string name="manage_notifications_text" msgid="6885645344647733116">"គ្រប់គ្រង"</string>
@@ -1069,7 +1069,7 @@
     <string name="controls_seeding_in_progress" msgid="3033855341410264148">"កំពុងផ្ទុក​ការណែនាំ"</string>
     <string name="controls_media_title" msgid="1746947284862928133">"មេឌៀ"</string>
     <string name="controls_media_close_session" msgid="3957093425905475065">"លាក់វគ្គ​បច្ចុប្បន្ន។"</string>
-    <string name="controls_media_dismiss_button" msgid="4485675693008031646">"លាក់"</string>
+    <string name="controls_media_dismiss_button" msgid="9081375542265132213">"ច្រាន​ចោល"</string>
     <string name="controls_media_resume" msgid="1933520684481586053">"បន្ត"</string>
     <string name="controls_media_settings_button" msgid="5815790345117172504">"ការកំណត់"</string>
     <string name="controls_error_timeout" msgid="794197289772728958">"អសកម្ម ពិនិត្យមើល​កម្មវិធី"</string>
@@ -1084,4 +1084,6 @@
     <string name="controls_added_tooltip" msgid="4842812921719153085">"សង្កត់​ប៊ូតុង​ថាមពល ដើម្បី​មើលឃើញ​ការគ្រប់គ្រង​ថ្មីៗ"</string>
     <string name="controls_menu_add" msgid="4447246119229920050">"បញ្ចូល​ផ្ទាំងគ្រប់គ្រង"</string>
     <string name="controls_menu_edit" msgid="890623986951347062">"កែ​ផ្ទាំងគ្រប់គ្រង"</string>
+    <string name="one_handed_tutorial_title" msgid="6312198435090726656">"កំពុងប្រើ​មុខងារប្រើដៃម្ខាង"</string>
+    <string name="one_handed_tutorial_description" msgid="7674850272340517174">"ដើម្បីចាកចេញ សូមអូសឡើងលើ​ពីផ្នែកខាងក្រោមអេក្រង់ ឬចុចផ្នែកណាមួយ​នៅខាងលើកម្មវិធី"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-kn/strings.xml b/packages/SystemUI/res/values-kn/strings.xml
index 7b8ed88..47f2385 100644
--- a/packages/SystemUI/res/values-kn/strings.xml
+++ b/packages/SystemUI/res/values-kn/strings.xml
@@ -125,7 +125,7 @@
     <string name="accessibility_accessibility_button" msgid="4089042473497107709">"ಪ್ರವೇಶಿಸುವಿಕೆ"</string>
     <string name="accessibility_rotate_button" msgid="1238584767612362586">"ಪರದೆಯನ್ನು ತಿರುಗಿಸಿ"</string>
     <string name="accessibility_recent" msgid="901641734769533575">"ಸಮಗ್ರ ನೋಟ"</string>
-    <string name="accessibility_search_light" msgid="524741790416076988">"ಹುಡುಕಿ"</string>
+    <string name="accessibility_search_light" msgid="524741790416076988">"Search"</string>
     <string name="accessibility_camera_button" msgid="2938898391716647247">"ಕ್ಯಾಮರಾ"</string>
     <string name="accessibility_phone_button" msgid="4256353121703100427">"ಫೋನ್"</string>
     <string name="accessibility_voice_assist_button" msgid="6497706615649754510">"ಧ್ವನಿ ಸಹಾಯಕ"</string>
@@ -441,7 +441,7 @@
     <string name="expanded_header_battery_charging_with_time" msgid="757991461445765011">"<xliff:g id="CHARGING_TIME">%s</xliff:g> ಪೂರ್ಣಗೊಳ್ಳುವವರೆಗೆ"</string>
     <string name="expanded_header_battery_not_charging" msgid="809409140358955848">"ಚಾರ್ಜ್‌ ಆಗುತ್ತಿಲ್ಲ"</string>
     <string name="ssl_ca_cert_warning" msgid="8373011375250324005">"ನೆಟ್‌ವರ್ಕ್\n ವೀಕ್ಷಿಸಬಹುದಾಗಿರುತ್ತದೆ"</string>
-    <string name="description_target_search" msgid="3875069993128855865">"ಹುಡುಕಿ"</string>
+    <string name="description_target_search" msgid="3875069993128855865">"Search"</string>
     <string name="description_direction_up" msgid="3632251507574121434">"<xliff:g id="TARGET_DESCRIPTION">%s</xliff:g> ಗಾಗಿ ಮೇಲಕ್ಕೆ ಸ್ಲೈಡ್ ಮಾಡಿ."</string>
     <string name="description_direction_left" msgid="4762708739096907741">"<xliff:g id="TARGET_DESCRIPTION">%s</xliff:g> ಗಾಗಿ ಎಡಕ್ಕೆ ಸ್ಲೈಡ್ ಮಾಡಿ."</string>
     <string name="zen_priority_introduction" msgid="3159291973383796646">"ಅಲಾರಾಂಗಳು, ಜ್ಞಾಪನೆಗಳು, ಈವೆಂಟ್‌ಗಳು ಹಾಗೂ ನೀವು ಸೂಚಿಸಿರುವ ಕರೆದಾರರನ್ನು ಹೊರತುಪಡಿಸಿ ಬೇರಾವುದೇ ಸದ್ದುಗಳು ಅಥವಾ ವೈಬ್ರೇಶನ್‌ಗಳು ನಿಮಗೆ ತೊಂದರೆ ನೀಡುವುದಿಲ್ಲ. ಹಾಗಿದ್ದರೂ, ನೀವು ಪ್ಲೇ ಮಾಡುವ ಸಂಗೀತ, ವೀಡಿಯೊಗಳು ಮತ್ತು ಆಟಗಳ ಆಡಿಯೊವನ್ನು ನೀವು ಕೇಳಿಸಿಕೊಳ್ಳುತ್ತೀರಿ."</string>
@@ -502,10 +502,10 @@
     <string name="battery_saver_notification_title" msgid="8419266546034372562">"ಬ್ಯಾಟರಿ ಸೇವರ್ ಆನ್ ಆಗಿದೆ"</string>
     <string name="battery_saver_notification_text" msgid="2617841636449016951">"ಕಾರ್ಯಕ್ಷಮತೆ ಮತ್ತು ಹಿನ್ನೆಲೆ ಡೇಟಾವನ್ನು ಕಡಿಮೆ ಮಾಡುತ್ತದೆ"</string>
     <string name="battery_saver_notification_action_text" msgid="6022091913807026887">"ಬ್ಯಾಟರಿ ಸೇವರ್‌ ಆಫ್ ಮಾಡಿ"</string>
-    <string name="media_projection_dialog_text" msgid="1755705274910034772">"<xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g>, ಸ್ಕ್ರೀನ್ ಮೇಲೆ ಗೋಚರಿಸುವ ಅಥವಾ ರೆಕಾರ್ಡಿಂಗ್ ಅಥವಾ ಬಿತ್ತರಿಸುವಾಗ ಸಾಧನದಲ್ಲಿ ಪ್ಲೇ ಆಗುವ ಎಲ್ಲಾ ಮಾಹಿತಿಗಳಿಗೆ ಪ್ರವೇಶವನ್ನು ಹೊಂದಿರುತ್ತವೆ. ಪಾಸ್‌ವರ್ಡ್‌ಗಳು, ಪಾವತಿ ವಿವರಗಳು, ಫೋಟೋಗಳು, ಸಂದೇಶಗಳು ಮತ್ತು ಆಡಿಯೊ ಪ್ಲೇಬ್ಯಾಕ್‌ನಂತಹ ಮಾಹಿತಿಯನ್ನು ಇದು ಒಳಗೊಂಡಿದೆ."</string>
+    <string name="media_projection_dialog_text" msgid="1755705274910034772">"ರೆಕಾರ್ಡ್ ಮಾಡುವಾಗ ಅಥವಾ ಬಿತ್ತರಿಸುವಾಗ ಸ್ಕ್ರೀನ್‌ ಮೇಲೆ ಕಾಣಿಸುವ ಸಕಲ ಮಾಹಿತಿಗೂ <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> ಪ್ರವೇಶ ಹೊಂದಿರುತ್ತದೆ. ಇದು ಪಾಸ್‌ವರ್ಡ್‌ಗಳು, ಪಾವತಿ ವಿವರಗಳು, ಫೋಟೋಗಳು, ಸಂದೇಶಗಳು ಮತ್ತು ಆಡಿಯೊ ಪ್ಲೇಬ್ಯಾಕ್‌ನಂತಹ ಮಾಹಿತಿಯನ್ನು ಕೂಡ ಒಳಗೊಂಡಿರುತ್ತದೆ."</string>
     <string name="media_projection_dialog_service_text" msgid="958000992162214611">"ಈ ವೈಶಿಷ್ಟ್ಯವು ಒದಗಿಸುವ ಸೇವೆಗಳು, ಸ್ಕ್ರೀನ್ ಮೇಲೆ ಗೋಚರಿಸುವ ಅಥವಾ ರೆಕಾರ್ಡಿಂಗ್ ಅಥವಾ ಬಿತ್ತರಿಸುವಾಗ ಸಾಧನದಲ್ಲಿ ಪ್ಲೇ ಆಗುವ ಎಲ್ಲಾ ಮಾಹಿತಿಗಳಿಗೆ ಪ್ರವೇಶವನ್ನು ಹೊಂದಿರುತ್ತವೆ. ಪಾಸ್‌ವರ್ಡ್‌ಗಳು, ಪಾವತಿ ವಿವರಗಳು, ಫೋಟೋಗಳು, ಸಂದೇಶಗಳು ಮತ್ತು ಆಡಿಯೊ ಪ್ಲೇಬ್ಯಾಕ್‌ನಂತಹ ಮಾಹಿತಿಯನ್ನು ಇದು ಒಳಗೊಂಡಿದೆ."</string>
     <string name="media_projection_dialog_service_title" msgid="2888507074107884040">"ರೆಕಾರ್ಡಿಂಗ್ ಅಥವಾ ಬಿತ್ತರಿಸುವಿಕೆಯನ್ನು ಪ್ರಾರಂಭಿಸಬೇಕೆ?"</string>
-    <string name="media_projection_dialog_title" msgid="3316063622495360646">"<xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> ಬಳಸಿಕೊಂಡು ರೆಕಾರ್ಡಿಂಗ್ ಅಥವಾ ಬಿತ್ತರಿಸುವುದನ್ನು ಪ್ರಾರಂಭಿಸುವುದೇ?"</string>
+    <string name="media_projection_dialog_title" msgid="3316063622495360646">"<xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> ಮೂಲಕ ರೆಕಾರ್ಡಿಂಗ್, ಬಿತ್ತರಿಸುವುದನ್ನು ಪ್ರಾರಂಭಿಸುವುದೇ?"</string>
     <string name="media_projection_remember_text" msgid="6896767327140422951">"ಮತ್ತೊಮ್ಮೆ ತೋರಿಸದಿರು"</string>
     <string name="clear_all_notifications_text" msgid="348312370303046130">"ಎಲ್ಲವನ್ನೂ ತೆರವುಗೊಳಿಸಿ"</string>
     <string name="manage_notifications_text" msgid="6885645344647733116">"ನಿರ್ವಹಿಸಿ"</string>
@@ -1069,7 +1069,7 @@
     <string name="controls_seeding_in_progress" msgid="3033855341410264148">"ಶಿಫಾರಸುಗಳು ಲೋಡ್ ಆಗುತ್ತಿವೆ"</string>
     <string name="controls_media_title" msgid="1746947284862928133">"ಮಾಧ್ಯಮ"</string>
     <string name="controls_media_close_session" msgid="3957093425905475065">"ಪ್ರಸ್ತುತ ಸೆಶನ್ ಅನ್ನು ಮರೆಮಾಡಿ."</string>
-    <string name="controls_media_dismiss_button" msgid="4485675693008031646">"ಮರೆಮಾಡಿ"</string>
+    <string name="controls_media_dismiss_button" msgid="9081375542265132213">"ವಜಾಗೊಳಿಸಿ"</string>
     <string name="controls_media_resume" msgid="1933520684481586053">"ಪುನರಾರಂಭಿಸಿ"</string>
     <string name="controls_media_settings_button" msgid="5815790345117172504">"ಸೆಟ್ಟಿಂಗ್‌ಗಳು"</string>
     <string name="controls_error_timeout" msgid="794197289772728958">"ನಿಷ್ಕ್ರಿಯ, ಆ್ಯಪ್ ಪರಿಶೀಲಿಸಿ"</string>
@@ -1084,4 +1084,6 @@
     <string name="controls_added_tooltip" msgid="4842812921719153085">"ಹೊಸ ನಿಯಂತ್ರಣಗಳನ್ನು ನೋಡಲು ಪವರ್ ಬಟನ್ ಹಿಡಿದುಕೊಳ್ಳಿ"</string>
     <string name="controls_menu_add" msgid="4447246119229920050">"ನಿಯಂತ್ರಣಗಳನ್ನು ಸೇರಿಸಿ"</string>
     <string name="controls_menu_edit" msgid="890623986951347062">"ನಿಯಂತ್ರಣಗಳನ್ನು ಎಡಿಟ್ ಮಾಡಿ"</string>
+    <string name="one_handed_tutorial_title" msgid="6312198435090726656">"ಒಂದು ಕೈ ಮೋಡ್ ಅನ್ನು ಬಳಸಲಾಗುತ್ತಿದೆ"</string>
+    <string name="one_handed_tutorial_description" msgid="7674850272340517174">"ನಿರ್ಗಮಿಸಲು, ಸ್ಕ್ರೀನ್‌ನ ಕೆಳಗಿನಿಂದ ಮೇಲಕ್ಕೆ ಸ್ವೈಪ್ ಮಾಡಿ ಅಥವಾ ಆ್ಯಪ್‌ನ ಮೇಲೆ ಎಲ್ಲಿಯಾದರೂ ಟ್ಯಾಪ್ ಮಾಡಿ"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-ko/strings.xml b/packages/SystemUI/res/values-ko/strings.xml
index 8d1177b..beb7eda 100644
--- a/packages/SystemUI/res/values-ko/strings.xml
+++ b/packages/SystemUI/res/values-ko/strings.xml
@@ -502,10 +502,10 @@
     <string name="battery_saver_notification_title" msgid="8419266546034372562">"절전 모드 사용 중"</string>
     <string name="battery_saver_notification_text" msgid="2617841636449016951">"성능 및 백그라운드 데이터를 줄입니다."</string>
     <string name="battery_saver_notification_action_text" msgid="6022091913807026887">"절전 모드 사용 중지"</string>
-    <string name="media_projection_dialog_text" msgid="1755705274910034772">"<xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g>이(가) 녹화 또는 전송 중에 화면에 표시되거나 기기에서 재생되는 모든 정보에 액세스할 수 있습니다. 여기에는 비밀번호, 결제 세부정보, 사진, 메시지, 재생하는 오디오 같은 정보가 포함됩니다."</string>
+    <string name="media_projection_dialog_text" msgid="1755705274910034772">"<xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g>이 녹화 또는 전송 중에 화면에 표시되거나 기기에서 재생되는 모든 정보에 액세스할 수 있습니다. 여기에는 비밀번호, 결제 세부정보, 사진, 메시지, 재생하는 오디오 같은 정보가 포함됩니다."</string>
     <string name="media_projection_dialog_service_text" msgid="958000992162214611">"이 기능을 제공하는 서비스는 녹화 또는 전송 중에 화면에 표시되거나 기기에서 재생되는 모든 정보에 액세스할 수 있습니다. 여기에는 비밀번호, 결제 세부정보, 사진, 메시지, 재생하는 오디오 같은 정보가 포함됩니다."</string>
     <string name="media_projection_dialog_service_title" msgid="2888507074107884040">"녹화 또는 전송을 시작하시겠습니까?"</string>
-    <string name="media_projection_dialog_title" msgid="3316063622495360646">"<xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g>(으)로 녹화 또는 전송을 시작하시겠습니까?"</string>
+    <string name="media_projection_dialog_title" msgid="3316063622495360646">"<xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g>으로 녹화 또는 전송을 시작하시겠습니까?"</string>
     <string name="media_projection_remember_text" msgid="6896767327140422951">"다시 표시 안함"</string>
     <string name="clear_all_notifications_text" msgid="348312370303046130">"모두 지우기"</string>
     <string name="manage_notifications_text" msgid="6885645344647733116">"관리"</string>
@@ -1069,7 +1069,7 @@
     <string name="controls_seeding_in_progress" msgid="3033855341410264148">"추천 제어 기능 로드 중"</string>
     <string name="controls_media_title" msgid="1746947284862928133">"미디어"</string>
     <string name="controls_media_close_session" msgid="3957093425905475065">"현재 세션을 숨깁니다."</string>
-    <string name="controls_media_dismiss_button" msgid="4485675693008031646">"숨기기"</string>
+    <string name="controls_media_dismiss_button" msgid="9081375542265132213">"닫기"</string>
     <string name="controls_media_resume" msgid="1933520684481586053">"다시 시작"</string>
     <string name="controls_media_settings_button" msgid="5815790345117172504">"설정"</string>
     <string name="controls_error_timeout" msgid="794197289772728958">"비활성. 앱을 확인하세요."</string>
@@ -1084,4 +1084,6 @@
     <string name="controls_added_tooltip" msgid="4842812921719153085">"새 컨트롤을 보려면 전원 버튼을 길게 누르세요."</string>
     <string name="controls_menu_add" msgid="4447246119229920050">"컨트롤 추가"</string>
     <string name="controls_menu_edit" msgid="890623986951347062">"컨트롤 수정"</string>
+    <string name="one_handed_tutorial_title" msgid="6312198435090726656">"한 손 사용 모드 사용하기"</string>
+    <string name="one_handed_tutorial_description" msgid="7674850272340517174">"화면 하단에서 위로 스와이프하거나 앱 상단을 탭하여 종료합니다."</string>
 </resources>
diff --git a/packages/SystemUI/res/values-ky/strings.xml b/packages/SystemUI/res/values-ky/strings.xml
index 794de98..4fbb09e 100644
--- a/packages/SystemUI/res/values-ky/strings.xml
+++ b/packages/SystemUI/res/values-ky/strings.xml
@@ -502,10 +502,10 @@
     <string name="battery_saver_notification_title" msgid="8419266546034372562">"Батареяны үнөмдөгүч режими күйүк"</string>
     <string name="battery_saver_notification_text" msgid="2617841636449016951">"Иштин майнаптуулугун начарлатып, фондук дайын-даректерди чектейт"</string>
     <string name="battery_saver_notification_action_text" msgid="6022091913807026887">"Батареяны үнөмдөгүчтү өчүрүү"</string>
-    <string name="media_projection_dialog_text" msgid="1755705274910034772">"Бул функцияны аткарган <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> кызматы экраныңызда көрүнүп турган бардык маалыматты же жаздыруу жана тышкы экранга чыгаруу учурунда түзмөгүңүздө ойнотулган маалыматты колдоно алат. Буга сырсөздөр, төлөмдүн чоо-жайы, сүрөттөр, билдирүүлөр жана ойнотулган аудио кирет."</string>
-    <string name="media_projection_dialog_service_text" msgid="958000992162214611">"Бул функцияны аткарган кызматка экраныңыздагы бардык маалымат же түзмөктө ойнотулуп жаткан нерсе, сырсөздөр, төлөмдөрдүн чоо-жайы, сүрөттөр, билдирүүлөр жана аудио файлдар жеткиликтүү болот."</string>
+    <string name="media_projection_dialog_text" msgid="1755705274910034772">"Жаздырып же тышкы экранга чыгарып жатканда, <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> колдонмосу экраныңыздагы бардык маалыматты же түзмөктө ойнолуп жаткан бардык нерселерди (сырсөздөрдү, төлөмдүн чоо-жайын, сүрөттөрдү, билдирүүлөрдү жана угуп жаткан аудиофайлдарды) көрө алат."</string>
+    <string name="media_projection_dialog_service_text" msgid="958000992162214611">"Жаздырып же тышкы экранга чыгарып жатканда, бул колдонмо экраныңыздагы бардык маалыматты же түзмөктө ойнолуп жаткан бардык нерселерди (сырсөздөрдү, төлөмдүн чоо-жайын, сүрөттөрдү, билдирүүлөрдү жана угуп жаткан аудиофайлдарды) көрө алат."</string>
     <string name="media_projection_dialog_service_title" msgid="2888507074107884040">"Жаздырып же тышкы экранга чыгарып баштайсызбы?"</string>
-    <string name="media_projection_dialog_title" msgid="3316063622495360646">"<xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> менен жаздырылып же тышкы экранга чыгарылып башталсынбы?"</string>
+    <string name="media_projection_dialog_title" msgid="3316063622495360646">"<xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> колдонмосу аркылуу жаздырып же тышкы экранга чыгарып баштайсызбы?"</string>
     <string name="media_projection_remember_text" msgid="6896767327140422951">"Экинчи көрүнбөсүн"</string>
     <string name="clear_all_notifications_text" msgid="348312370303046130">"Баарын тазалап салуу"</string>
     <string name="manage_notifications_text" msgid="6885645344647733116">"Башкаруу"</string>
@@ -1069,7 +1069,7 @@
     <string name="controls_seeding_in_progress" msgid="3033855341410264148">"Сунуштар жүктөлүүдө"</string>
     <string name="controls_media_title" msgid="1746947284862928133">"Медиа"</string>
     <string name="controls_media_close_session" msgid="3957093425905475065">"Учурдагы сеансты жашыруу."</string>
-    <string name="controls_media_dismiss_button" msgid="4485675693008031646">"Жашыруу"</string>
+    <string name="controls_media_dismiss_button" msgid="9081375542265132213">"Жабуу"</string>
     <string name="controls_media_resume" msgid="1933520684481586053">"Улантуу"</string>
     <string name="controls_media_settings_button" msgid="5815790345117172504">"Жөндөөлөр"</string>
     <string name="controls_error_timeout" msgid="794197289772728958">"Жигерсиз. Колдонмону текшериңиз"</string>
@@ -1084,4 +1084,6 @@
     <string name="controls_added_tooltip" msgid="4842812921719153085">"Башкаруу элементтерин көрүү үчүн күйгүзүү/өчүрүү баскычын коё бербей басып туруңуз"</string>
     <string name="controls_menu_add" msgid="4447246119229920050">"Башкаруу элементтерин кошуу"</string>
     <string name="controls_menu_edit" msgid="890623986951347062">"Башкаруу элементтерин түзөтүү"</string>
+    <string name="one_handed_tutorial_title" msgid="6312198435090726656">"Бир кол режимин колдонуу"</string>
+    <string name="one_handed_tutorial_description" msgid="7674850272340517174">"Чыгуу үчүн экранды ылдый жагынан өйдө көздөй сүрүңүз же колдонмонун өйдө жагын басыңыз"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-land-television/dimens.xml b/packages/SystemUI/res/values-land-television/dimens.xml
index 499341c..90fc652 100644
--- a/packages/SystemUI/res/values-land-television/dimens.xml
+++ b/packages/SystemUI/res/values-land-television/dimens.xml
@@ -17,5 +17,8 @@
 <resources>
   <!-- Width of volume bar -->
   <dimen name="volume_dialog_row_width">252dp</dimen>
-  <dimen name="volume_dialog_tap_target_size">36dp</dimen>
+  <dimen name="tv_volume_dialog_bubble_size">36dp</dimen>
+  <dimen name="tv_volume_dialog_corner_radius">40dp</dimen>
+  <dimen name="tv_volume_dialog_row_padding">5dp</dimen>
+  <dimen name="tv_volume_number_text_size">16dp</dimen>
 </resources>
diff --git a/packages/SystemUI/res/values-lo/strings.xml b/packages/SystemUI/res/values-lo/strings.xml
index 2fe3f80..6d7feab 100644
--- a/packages/SystemUI/res/values-lo/strings.xml
+++ b/packages/SystemUI/res/values-lo/strings.xml
@@ -1069,7 +1069,7 @@
     <string name="controls_seeding_in_progress" msgid="3033855341410264148">"ກຳລັງໂຫຼດຄຳແນະນຳ"</string>
     <string name="controls_media_title" msgid="1746947284862928133">"ມີເດຍ"</string>
     <string name="controls_media_close_session" msgid="3957093425905475065">"ເຊື່ອງເຊດຊັນປັດຈຸບັນ."</string>
-    <string name="controls_media_dismiss_button" msgid="4485675693008031646">"ເຊື່ອງ"</string>
+    <string name="controls_media_dismiss_button" msgid="9081375542265132213">"ປິດໄວ້"</string>
     <string name="controls_media_resume" msgid="1933520684481586053">"ສືບຕໍ່"</string>
     <string name="controls_media_settings_button" msgid="5815790345117172504">"ການຕັ້ງຄ່າ"</string>
     <string name="controls_error_timeout" msgid="794197289772728958">"ບໍ່ເຮັດວຽກ, ກະລຸນາກວດສອບແອັບ"</string>
@@ -1084,4 +1084,6 @@
     <string name="controls_added_tooltip" msgid="4842812921719153085">"ກົດປຸ່ມເປີດປິດຄ້າງໄວ້ເພື່ອເບິ່ງການຄວບຄຸມໃໝ່"</string>
     <string name="controls_menu_add" msgid="4447246119229920050">"ເພີ່ມການຄວບຄຸມ"</string>
     <string name="controls_menu_edit" msgid="890623986951347062">"ແກ້ໄຂການຄວບຄຸມ"</string>
+    <string name="one_handed_tutorial_title" msgid="6312198435090726656">"ກຳລັງໃຊ້ໂໝດມືດຽວ"</string>
+    <string name="one_handed_tutorial_description" msgid="7674850272340517174">"ເພື່ອອອກ, ໃຫ້ປັດຂຶ້ນຈາກລຸ່ມສຸດຂອງໜ້າຈໍ ຫຼື ແຕະບ່ອນໃດກໍໄດ້ຢູ່ເທິງແອັບ"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-lt/strings.xml b/packages/SystemUI/res/values-lt/strings.xml
index 4489b7d..21217a0 100644
--- a/packages/SystemUI/res/values-lt/strings.xml
+++ b/packages/SystemUI/res/values-lt/strings.xml
@@ -508,7 +508,7 @@
     <string name="battery_saver_notification_title" msgid="8419266546034372562">"Akumuliatoriaus tausojimo priemonė įjungta"</string>
     <string name="battery_saver_notification_text" msgid="2617841636449016951">"Sumažinamas našumas ir foninių duomenų naudojimas"</string>
     <string name="battery_saver_notification_action_text" msgid="6022091913807026887">"Išjungti Akumuliatoriaus tausojimo priemonę"</string>
-    <string name="media_projection_dialog_text" msgid="1755705274910034772">"„<xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g>“ galės pasiekti visą informaciją, matomą ekrane ir leidžiamą iš įrenginio įrašant ar perduodant turinį. Tai apima įvairią informaciją, pvz., slaptažodžius, išsamią mokėjimo informaciją, nuotraukas, pranešimus ir leidžiamus garso įrašus."</string>
+    <string name="media_projection_dialog_text" msgid="1755705274910034772">"<xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> galės pasiekti visą informaciją, matomą ekrane ir leidžiamą iš įrenginio įrašant ar perduodant turinį. Tai apima įvairią informaciją, pvz., slaptažodžius, išsamią mokėjimo informaciją, nuotraukas, pranešimus ir leidžiamus garso įrašus."</string>
     <string name="media_projection_dialog_service_text" msgid="958000992162214611">"Šią funkcija teikianti paslauga galės pasiekti visą informaciją, matomą ekrane ir leidžiamą iš įrenginio įrašant ar perduodant turinį. Tai apima įvairią informaciją, pvz., slaptažodžius, išsamią mokėjimo informaciją, nuotraukas, pranešimus ir leidžiamus garso įrašus."</string>
     <string name="media_projection_dialog_service_title" msgid="2888507074107884040">"Pradėti įrašyti ar perduoti turinį?"</string>
     <string name="media_projection_dialog_title" msgid="3316063622495360646">"Pradėti įrašyti ar perduoti turinį naudojant „<xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g>“?"</string>
@@ -1081,7 +1081,7 @@
     <string name="controls_seeding_in_progress" msgid="3033855341410264148">"Įkeliamos rekomendacijos"</string>
     <string name="controls_media_title" msgid="1746947284862928133">"Medija"</string>
     <string name="controls_media_close_session" msgid="3957093425905475065">"Slėpti dabartinį seansą."</string>
-    <string name="controls_media_dismiss_button" msgid="4485675693008031646">"Slėpti"</string>
+    <string name="controls_media_dismiss_button" msgid="9081375542265132213">"Atsisakyti"</string>
     <string name="controls_media_resume" msgid="1933520684481586053">"Tęsti"</string>
     <string name="controls_media_settings_button" msgid="5815790345117172504">"Nustatymai"</string>
     <string name="controls_error_timeout" msgid="794197289772728958">"Neaktyvu, patikrinkite progr."</string>
@@ -1096,4 +1096,6 @@
     <string name="controls_added_tooltip" msgid="4842812921719153085">"Jei norite peržiūrėti naujus valdiklius, laikykite paspaudę maitinimo mygtuką"</string>
     <string name="controls_menu_add" msgid="4447246119229920050">"Pridėti valdiklių"</string>
     <string name="controls_menu_edit" msgid="890623986951347062">"Redaguoti valdiklius"</string>
+    <string name="one_handed_tutorial_title" msgid="6312198435090726656">"Vienos rankos režimo naudojimas"</string>
+    <string name="one_handed_tutorial_description" msgid="7674850272340517174">"Jei norite išeiti, perbraukite aukštyn nuo ekrano apačios arba palieskite bet kur virš programos"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-lv/strings.xml b/packages/SystemUI/res/values-lv/strings.xml
index d4826e4..4945dfa 100644
--- a/packages/SystemUI/res/values-lv/strings.xml
+++ b/packages/SystemUI/res/values-lv/strings.xml
@@ -1075,7 +1075,7 @@
     <string name="controls_seeding_in_progress" msgid="3033855341410264148">"Notiek ieteikumu ielāde"</string>
     <string name="controls_media_title" msgid="1746947284862928133">"Multivide"</string>
     <string name="controls_media_close_session" msgid="3957093425905475065">"Paslēpiet pašreizējo sesiju."</string>
-    <string name="controls_media_dismiss_button" msgid="4485675693008031646">"Paslēpt"</string>
+    <string name="controls_media_dismiss_button" msgid="9081375542265132213">"Nerādīt"</string>
     <string name="controls_media_resume" msgid="1933520684481586053">"Atsākt"</string>
     <string name="controls_media_settings_button" msgid="5815790345117172504">"Iestatījumi"</string>
     <string name="controls_error_timeout" msgid="794197289772728958">"Neaktīva, pārbaudiet lietotni"</string>
@@ -1090,4 +1090,6 @@
     <string name="controls_added_tooltip" msgid="4842812921719153085">"Nospiediet barošanas pogu un turiet to, lai skatītu jaunas vadīklas"</string>
     <string name="controls_menu_add" msgid="4447246119229920050">"Pievienot vadīklas"</string>
     <string name="controls_menu_edit" msgid="890623986951347062">"Rediģēt vadīklas"</string>
+    <string name="one_handed_tutorial_title" msgid="6312198435090726656">"Vienas rokas režīma izmantošana"</string>
+    <string name="one_handed_tutorial_description" msgid="7674850272340517174">"Lai izietu, velciet augšup no ekrāna apakšdaļas vai pieskarieties jebkurā vietā virs lietotnes"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-mk/strings.xml b/packages/SystemUI/res/values-mk/strings.xml
index 4836ecf..40d4698 100644
--- a/packages/SystemUI/res/values-mk/strings.xml
+++ b/packages/SystemUI/res/values-mk/strings.xml
@@ -502,8 +502,8 @@
     <string name="battery_saver_notification_title" msgid="8419266546034372562">"Штедачот на батерија е вклучен"</string>
     <string name="battery_saver_notification_text" msgid="2617841636449016951">"Ја намалува изведбата и податоците во заднина"</string>
     <string name="battery_saver_notification_action_text" msgid="6022091913807026887">"Исклучете го штедачот на батерија"</string>
-    <string name="media_projection_dialog_text" msgid="1755705274910034772">"<xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> ќе има пристап до сите информации што се видливи на екранот или пуштени од вашиот уред додека се снима или емитува. Ова вклучува информации како, на пример, лозинки, детали на исплатата, фотографии, пораки и аудио што го пуштате."</string>
-    <string name="media_projection_dialog_service_text" msgid="958000992162214611">"Услугата што ја обезбедува функцијава ќе има пристап до сите информации што се видливи на екранот или пуштени од вашиот уред додека се снима или емитува. Ова вклучува информации како, на пример, лозинки, детали на исплатата, фотографии, пораки и аудио што го пуштате."</string>
+    <string name="media_projection_dialog_text" msgid="1755705274910034772">"<xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> ќе има пристап до сите податоци што се видливи на екранот или пуштени од вашиот уред додека се снима или емитува. Ова вклучува податоци како лозинки, детали за плаќање, фотографии, пораки, аудио што го пуштате итн."</string>
+    <string name="media_projection_dialog_service_text" msgid="958000992162214611">"Услугата што ја обезбедува функцијава ќе има пристап до сите податоци што се видливи на екранот или пуштени од вашиот уред додека се снима или емитува. Ова вклучува информации како лозинки, детали за плаќање, фотографии, пораки, аудио што го пуштате итн."</string>
     <string name="media_projection_dialog_service_title" msgid="2888507074107884040">"Да почне снимање или емитување?"</string>
     <string name="media_projection_dialog_title" msgid="3316063622495360646">"Да почне снимање или емитување со <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g>?"</string>
     <string name="media_projection_remember_text" msgid="6896767327140422951">"Не покажувај повторно"</string>
@@ -1069,7 +1069,7 @@
     <string name="controls_seeding_in_progress" msgid="3033855341410264148">"Се вчитуваат препораки"</string>
     <string name="controls_media_title" msgid="1746947284862928133">"Аудиовизуелни содржини"</string>
     <string name="controls_media_close_session" msgid="3957093425905475065">"Сокриј ја тековнава сесија."</string>
-    <string name="controls_media_dismiss_button" msgid="4485675693008031646">"Сокриј"</string>
+    <string name="controls_media_dismiss_button" msgid="9081375542265132213">"Отфрли"</string>
     <string name="controls_media_resume" msgid="1933520684481586053">"Продолжи"</string>
     <string name="controls_media_settings_button" msgid="5815790345117172504">"Поставки"</string>
     <string name="controls_error_timeout" msgid="794197289772728958">"Неактивна, провери апликација"</string>
@@ -1084,4 +1084,6 @@
     <string name="controls_added_tooltip" msgid="4842812921719153085">"Задржете го копчето за вклучување за да ги видите новите контроли"</string>
     <string name="controls_menu_add" msgid="4447246119229920050">"Додајте контроли"</string>
     <string name="controls_menu_edit" msgid="890623986951347062">"Изменете ги контролите"</string>
+    <string name="one_handed_tutorial_title" msgid="6312198435090726656">"Користење на режимот со една рака"</string>
+    <string name="one_handed_tutorial_description" msgid="7674850272340517174">"За да излезете, повлечете нагоре од дното на екранот или допрете каде било над апликацијата"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-ml/strings.xml b/packages/SystemUI/res/values-ml/strings.xml
index 863ce11..c3bd65e 100644
--- a/packages/SystemUI/res/values-ml/strings.xml
+++ b/packages/SystemUI/res/values-ml/strings.xml
@@ -125,7 +125,7 @@
     <string name="accessibility_accessibility_button" msgid="4089042473497107709">"ഉപയോഗസഹായി"</string>
     <string name="accessibility_rotate_button" msgid="1238584767612362586">"സ്‌ക്രീൻ തിരിക്കുക"</string>
     <string name="accessibility_recent" msgid="901641734769533575">"അവലോകനം"</string>
-    <string name="accessibility_search_light" msgid="524741790416076988">"തിരയൽ"</string>
+    <string name="accessibility_search_light" msgid="524741790416076988">"Search"</string>
     <string name="accessibility_camera_button" msgid="2938898391716647247">"ക്യാമറ"</string>
     <string name="accessibility_phone_button" msgid="4256353121703100427">"ഫോണ്‍"</string>
     <string name="accessibility_voice_assist_button" msgid="6497706615649754510">"വോയ്‌സ് സഹായം"</string>
@@ -441,7 +441,7 @@
     <string name="expanded_header_battery_charging_with_time" msgid="757991461445765011">"ഫുൾ ചാർജാകാൻ, <xliff:g id="CHARGING_TIME">%s</xliff:g>"</string>
     <string name="expanded_header_battery_not_charging" msgid="809409140358955848">"ചാർജ്ജുചെയ്യുന്നില്ല"</string>
     <string name="ssl_ca_cert_warning" msgid="8373011375250324005">"നെറ്റ്‌വർക്ക്\nനിരീക്ഷിക്കപ്പെടാം"</string>
-    <string name="description_target_search" msgid="3875069993128855865">"തിരയൽ"</string>
+    <string name="description_target_search" msgid="3875069993128855865">"Search"</string>
     <string name="description_direction_up" msgid="3632251507574121434">"<xliff:g id="TARGET_DESCRIPTION">%s</xliff:g> എന്നതിനായി മുകളിലേയ്‌ക്ക് സ്ലൈഡുചെയ്യുക."</string>
     <string name="description_direction_left" msgid="4762708739096907741">"<xliff:g id="TARGET_DESCRIPTION">%s</xliff:g> എന്നതിനായി ഇടത്തേയ്‌ക്ക് സ്ലൈഡുചെയ്യുക."</string>
     <string name="zen_priority_introduction" msgid="3159291973383796646">"നിങ്ങൾ സജ്ജീകരിച്ച അലാറങ്ങൾ, റിമൈൻഡറുകൾ, ഇവന്റുകൾ, കോളർമാർ എന്നിവയിൽ നിന്നുള്ള ശബ്‌ദങ്ങളും വൈബ്രേഷനുകളുമൊഴികെ മറ്റൊന്നും നിങ്ങളെ ശല്യപ്പെടുത്തുകയില്ല. സംഗീതം, വീഡിയോകൾ, ഗെയിമുകൾ എന്നിവയുൾപ്പെടെ പ്ലേ ചെയ്യുന്നതെന്തും നിങ്ങൾക്ക് ‌തുടർന്നും കേൾക്കാൻ കഴിയും."</string>
@@ -502,7 +502,7 @@
     <string name="battery_saver_notification_title" msgid="8419266546034372562">"ബാറ്ററി ലാഭിക്കൽ ഓണാണ്"</string>
     <string name="battery_saver_notification_text" msgid="2617841636449016951">"പ്രവർത്തനവും പശ്ചാത്തല ഡാറ്റയും കുറയ്‌ക്കുന്നു"</string>
     <string name="battery_saver_notification_action_text" msgid="6022091913807026887">"ബാറ്ററി ലാഭിക്കൽ ഓഫാക്കുക"</string>
-    <string name="media_projection_dialog_text" msgid="1755705274910034772">"റെക്കോർഡ് ചെയ്യുമ്പോഴോ കാസ്‌റ്റ് ചെയ്യുമ്പോഴോ നിങ്ങളുടെ ഉപകരണത്തിൽ നിന്ന് പ്ലേ ചെയ്യുന്നതോ നിങ്ങളുടെ സ്‌ക്രീനിൽ ദൃശ്യമാകുന്നതോ ആയ എല്ലാ വിവരങ്ങളിലേക്കും <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> എന്നതിന് ആക്‌സസ് ഉണ്ടായിരിക്കും. നിങ്ങൾ പ്ലേ ചെയ്യുന്ന ഒഡിയോ, സന്ദേശങ്ങൾ, ഫോട്ടോകൾ, പേയ്‌മെന്റ് വിശദാംശങ്ങൾ, പാസ്‌വേഡുകൾ എന്നിവ പോലുള്ള വിവരങ്ങൾ ഇതിൽ ഉൾപ്പെടുന്നു."</string>
+    <string name="media_projection_dialog_text" msgid="1755705274910034772">"റെക്കോർഡ് ചെയ്യുമ്പോഴോ കാസ്‌റ്റ് ചെയ്യുമ്പോഴോ നിങ്ങളുടെ ഉപകരണത്തിൽ നിന്ന് പ്ലേ ചെയ്യുന്നതോ നിങ്ങളുടെ സ്‌ക്രീനിൽ ദൃശ്യമാകുന്നതോ ആയ എല്ലാ വിവരങ്ങളിലേക്കും <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g>-ന് ആക്‌സസ് ഉണ്ടായിരിക്കും. നിങ്ങൾ പ്ലേ ചെയ്യുന്ന ഒഡിയോ, സന്ദേശങ്ങൾ, ഫോട്ടോകൾ, പേയ്‌മെന്റ് വിശദാംശങ്ങൾ, പാസ്‌വേഡുകൾ എന്നിവ പോലുള്ള വിവരങ്ങൾ ഇതിൽ ഉൾപ്പെടുന്നു."</string>
     <string name="media_projection_dialog_service_text" msgid="958000992162214611">"റെക്കോർഡ് ചെയ്യുമ്പോഴോ കാസ്‌റ്റ് ചെയ്യുമ്പോഴോ നിങ്ങളുടെ ഉപകരണത്തിൽ നിന്ന് പ്ലേ ചെയ്യുന്നതോ നിങ്ങളുടെ സ്‌ക്രീനിൽ ദൃശ്യമാകുന്നതോ ആയ എല്ലാ വിവരങ്ങളിലേക്കും ഈ ഫംഗ്‌ഷൻ ലഭ്യമാക്കുന്ന സേവനത്തിന് ആക്‌സസ് ഉണ്ടായിരിക്കും. നിങ്ങൾ പ്ലേ ചെയ്യുന്ന ഓഡിയോ, സന്ദേശങ്ങൾ, ഫോട്ടോകൾ, പേയ്‌മെന്റ് വിശദാംശങ്ങൾ, പാസ്‌വേഡുകൾ എന്നിവ പോലുള്ള വിവരങ്ങൾ ഇതിൽ ഉൾപ്പെടുന്നു."</string>
     <string name="media_projection_dialog_service_title" msgid="2888507074107884040">"റെക്കോർഡ് ചെയ്യൽ അല്ലെങ്കിൽ കാസ്റ്റ് ചെയ്യൽ ആരംഭിക്കണോ?"</string>
     <string name="media_projection_dialog_title" msgid="3316063622495360646">"<xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> ഉപയോഗിച്ച് റെക്കോർഡ് ചെയ്യൽ അല്ലെങ്കിൽ കാസ്‌റ്റ് ചെയ്യൽ ആരംഭിക്കണോ?"</string>
@@ -1069,7 +1069,7 @@
     <string name="controls_seeding_in_progress" msgid="3033855341410264148">"നിർദ്ദേശങ്ങൾ ലോഡ് ചെയ്യുന്നു"</string>
     <string name="controls_media_title" msgid="1746947284862928133">"മീഡിയ"</string>
     <string name="controls_media_close_session" msgid="3957093425905475065">"നിലവിലെ സെഷൻ മറയ്‌ക്കുക."</string>
-    <string name="controls_media_dismiss_button" msgid="4485675693008031646">"മറയ്‌ക്കുക"</string>
+    <string name="controls_media_dismiss_button" msgid="9081375542265132213">"ഡിസ്‌മിസ് ചെയ്യുക"</string>
     <string name="controls_media_resume" msgid="1933520684481586053">"പുനരാരംഭിക്കുക"</string>
     <string name="controls_media_settings_button" msgid="5815790345117172504">"ക്രമീകരണം"</string>
     <string name="controls_error_timeout" msgid="794197289772728958">"നിഷ്‌ക്രിയം, ആപ്പ് പരിശോധിക്കൂ"</string>
@@ -1084,4 +1084,6 @@
     <string name="controls_added_tooltip" msgid="4842812921719153085">"പുതിയ നിയന്ത്രണങ്ങൾ കാണാൻ പവർ ബട്ടൺ പിടിക്കുക"</string>
     <string name="controls_menu_add" msgid="4447246119229920050">"നിയന്ത്രണങ്ങൾ ചേർക്കുക"</string>
     <string name="controls_menu_edit" msgid="890623986951347062">"നിയന്ത്രണങ്ങൾ എഡിറ്റ് ചെയ്യുക"</string>
+    <string name="one_handed_tutorial_title" msgid="6312198435090726656">"ഒറ്റക്കൈ മോഡ് എങ്ങനെ ഉപയോഗിക്കാം"</string>
+    <string name="one_handed_tutorial_description" msgid="7674850272340517174">"പുറത്ത് കടക്കാൻ, സ്ക്രീനിന്റെ ചുവടെ നിന്ന് മുകളിലേക്ക് സ്വൈപ്പ് ചെയ്യുക അല്ലെങ്കിൽ ആപ്പിന് മുകളിലായി എവിടെയെങ്കിലും ടാപ്പ് ചെയ്യുക"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-mn/strings.xml b/packages/SystemUI/res/values-mn/strings.xml
index 107c310..5f08f05 100644
--- a/packages/SystemUI/res/values-mn/strings.xml
+++ b/packages/SystemUI/res/values-mn/strings.xml
@@ -1069,7 +1069,7 @@
     <string name="controls_seeding_in_progress" msgid="3033855341410264148">"Зөвлөмжүүдийг ачаалж байна"</string>
     <string name="controls_media_title" msgid="1746947284862928133">"Медиа"</string>
     <string name="controls_media_close_session" msgid="3957093425905475065">"Одоогийн харилцан үйлдлийг нуугаарай."</string>
-    <string name="controls_media_dismiss_button" msgid="4485675693008031646">"Нуух"</string>
+    <string name="controls_media_dismiss_button" msgid="9081375542265132213">"Хаах"</string>
     <string name="controls_media_resume" msgid="1933520684481586053">"Үргэлжлүүлэх"</string>
     <string name="controls_media_settings_button" msgid="5815790345117172504">"Тохиргоо"</string>
     <string name="controls_error_timeout" msgid="794197289772728958">"Идэвхгүй байна, аппыг шалгана уу"</string>
@@ -1084,4 +1084,6 @@
     <string name="controls_added_tooltip" msgid="4842812921719153085">"Шинэ хяналтыг харахын тулд асаах товчийг удаан дарна уу"</string>
     <string name="controls_menu_add" msgid="4447246119229920050">"Хяналт нэмэх"</string>
     <string name="controls_menu_edit" msgid="890623986951347062">"Хяналтыг өөрчлөх"</string>
+    <string name="one_handed_tutorial_title" msgid="6312198435090726656">"Нэг гарын горимыг ашиглаж байна"</string>
+    <string name="one_handed_tutorial_description" msgid="7674850272340517174">"Гарахын тулд дэлгэцийн доод хэсгээс дээш шудрах эсвэл аппын дээд хэсэгт хүссэн газраа товшино уу"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-mr/strings.xml b/packages/SystemUI/res/values-mr/strings.xml
index bd2c3a5..6d525df 100644
--- a/packages/SystemUI/res/values-mr/strings.xml
+++ b/packages/SystemUI/res/values-mr/strings.xml
@@ -125,7 +125,7 @@
     <string name="accessibility_accessibility_button" msgid="4089042473497107709">"अ‍ॅक्सेसिबिलिटी"</string>
     <string name="accessibility_rotate_button" msgid="1238584767612362586">"स्क्रीन फिरवा"</string>
     <string name="accessibility_recent" msgid="901641734769533575">"अवलोकन"</string>
-    <string name="accessibility_search_light" msgid="524741790416076988">"शोधा"</string>
+    <string name="accessibility_search_light" msgid="524741790416076988">"Search"</string>
     <string name="accessibility_camera_button" msgid="2938898391716647247">"कॅमेरा"</string>
     <string name="accessibility_phone_button" msgid="4256353121703100427">"फोन"</string>
     <string name="accessibility_voice_assist_button" msgid="6497706615649754510">"व्हॉइस सहाय्य"</string>
@@ -441,7 +441,7 @@
     <string name="expanded_header_battery_charging_with_time" msgid="757991461445765011">"<xliff:g id="CHARGING_TIME">%s</xliff:g> पूर्ण होईपर्यंत"</string>
     <string name="expanded_header_battery_not_charging" msgid="809409140358955848">"चार्ज होत नाही"</string>
     <string name="ssl_ca_cert_warning" msgid="8373011375250324005">"नेटवर्कचे परीक्षण\nकेले जाऊ शकते"</string>
-    <string name="description_target_search" msgid="3875069993128855865">"शोध"</string>
+    <string name="description_target_search" msgid="3875069993128855865">"Search"</string>
     <string name="description_direction_up" msgid="3632251507574121434">"<xliff:g id="TARGET_DESCRIPTION">%s</xliff:g> साठी वर स्लाइड करा."</string>
     <string name="description_direction_left" msgid="4762708739096907741">"<xliff:g id="TARGET_DESCRIPTION">%s</xliff:g> साठी डावीकडे स्लाइड करा."</string>
     <string name="zen_priority_introduction" msgid="3159291973383796646">"अलार्म, रिमाइंडर, इव्‍हेंट आणि तुम्ही निश्चित केलेल्या कॉलर व्यतिरिक्त तुम्हाला कोणत्याही आवाज आणि कंपनांचा व्यत्त्यय आणला जाणार नाही. तरीही तुम्ही प्ले करायचे ठरवलेले कोणतेही संगीत, व्हिडिओ आणि गेमचे आवाज ऐकू शकतात."</string>
@@ -504,8 +504,8 @@
     <string name="battery_saver_notification_action_text" msgid="6022091913807026887">"बॅटरी सेव्हर बंद करा"</string>
     <string name="media_projection_dialog_text" msgid="1755705274910034772">"तुमच्या स्क्रीनवर दृश्यमान असलेल्या किंवा रेकॉर्ड किंवा कास्ट करताना तुमच्या डिव्हाइसमधून प्ले केलेल्या सर्व माहितीचा अ‍ॅक्सेस <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> ला असेल. यामध्ये पासवर्ड, पेमेंट तपशील, फोटो, मेसेज आणि तुम्ही प्ले केलेला ऑडिओ यासारख्या माहितीचा समावेश असतो."</string>
     <string name="media_projection_dialog_service_text" msgid="958000992162214611">"हे कार्य पुरवठा करणाऱ्या सेवेस तुमच्या स्क्रीनवर दृश्यमान असलेल्या किंवा रेकॉर्ड किंवा कास्ट करताना तुमच्या डिव्हाइसमधून प्ले केलेल्या सर्व माहितीचा अ‍ॅक्सेस असेल. यामध्ये पासवर्ड, पेमेंट तपशील, फोटो, मेसेज आणि तुम्ही प्ले केलेला ऑडिओ यासारख्या माहितीचा समावेश असतो."</string>
-    <string name="media_projection_dialog_service_title" msgid="2888507074107884040">"रेकॉर्ड किंवा कास्ट करणे सुरू करायचे आहे का ?"</string>
-    <string name="media_projection_dialog_title" msgid="3316063622495360646">"<xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> ने रेकॉर्ड करणे किंवा कास्ट करणे सुरू करा?"</string>
+    <string name="media_projection_dialog_service_title" msgid="2888507074107884040">"रेकॉर्ड करणे किंवा कास्ट करणे सुरू करायचे का ?"</string>
+    <string name="media_projection_dialog_title" msgid="3316063622495360646">"<xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> ने रेकॉर्ड करणे किंवा कास्ट करणे सुरू करायचे का?"</string>
     <string name="media_projection_remember_text" msgid="6896767327140422951">"पुन्हा दर्शवू नका"</string>
     <string name="clear_all_notifications_text" msgid="348312370303046130">"सर्व साफ करा"</string>
     <string name="manage_notifications_text" msgid="6885645344647733116">"व्यवस्थापित करा"</string>
@@ -1069,7 +1069,7 @@
     <string name="controls_seeding_in_progress" msgid="3033855341410264148">"शिफारशी लोड करत आहे"</string>
     <string name="controls_media_title" msgid="1746947284862928133">"मीडिया"</string>
     <string name="controls_media_close_session" msgid="3957093425905475065">"सध्याचे सेशन लपवा."</string>
-    <string name="controls_media_dismiss_button" msgid="4485675693008031646">"लपवा"</string>
+    <string name="controls_media_dismiss_button" msgid="9081375542265132213">"डिसमिस करा"</string>
     <string name="controls_media_resume" msgid="1933520684481586053">"पुन्हा सुरू करा"</string>
     <string name="controls_media_settings_button" msgid="5815790345117172504">"सेटिंग्ज"</string>
     <string name="controls_error_timeout" msgid="794197289772728958">"निष्क्रिय, ॲप तपासा"</string>
@@ -1084,4 +1084,6 @@
     <string name="controls_added_tooltip" msgid="4842812921719153085">"नवीन नियंत्रणे पाहण्यासाठी पॉवर बटण धरून ठेवा"</string>
     <string name="controls_menu_add" msgid="4447246119229920050">"नियंत्रणे जोडा"</string>
     <string name="controls_menu_edit" msgid="890623986951347062">"नियंत्रणे व्यवस्थापित करा"</string>
+    <string name="one_handed_tutorial_title" msgid="6312198435090726656">"एकहाती मोड वापरणे"</string>
+    <string name="one_handed_tutorial_description" msgid="7674850272340517174">"बाहेर पडण्यासाठी स्क्रीनच्या खालून वरच्या दिशेने स्वाइप करा किंवा ॲप आयकनच्या वर कोठेही टॅप करा"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-ms/strings.xml b/packages/SystemUI/res/values-ms/strings.xml
index 029571b..4f832e4 100644
--- a/packages/SystemUI/res/values-ms/strings.xml
+++ b/packages/SystemUI/res/values-ms/strings.xml
@@ -1069,7 +1069,7 @@
     <string name="controls_seeding_in_progress" msgid="3033855341410264148">"Memuatkan cadangan"</string>
     <string name="controls_media_title" msgid="1746947284862928133">"Media"</string>
     <string name="controls_media_close_session" msgid="3957093425905475065">"Sembunyikan sesi semasa."</string>
-    <string name="controls_media_dismiss_button" msgid="4485675693008031646">"Sembunyikan"</string>
+    <string name="controls_media_dismiss_button" msgid="9081375542265132213">"Tolak"</string>
     <string name="controls_media_resume" msgid="1933520684481586053">"Sambung semula"</string>
     <string name="controls_media_settings_button" msgid="5815790345117172504">"Tetapan"</string>
     <string name="controls_error_timeout" msgid="794197289772728958">"Tidak aktif, semak apl"</string>
@@ -1084,4 +1084,6 @@
     <string name="controls_added_tooltip" msgid="4842812921719153085">"Tahan butang Kuasa untuk melihat kawalan baharu"</string>
     <string name="controls_menu_add" msgid="4447246119229920050">"Tambah kawalan"</string>
     <string name="controls_menu_edit" msgid="890623986951347062">"Edit kawalan"</string>
+    <string name="one_handed_tutorial_title" msgid="6312198435090726656">"Menggunakan mod sebelah tangan"</string>
+    <string name="one_handed_tutorial_description" msgid="7674850272340517174">"Untuk keluar, leret ke atas daripada bahagian bawah skrin atau ketik pada mana-mana di bahagian atas apl"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-my/strings.xml b/packages/SystemUI/res/values-my/strings.xml
index 6e632af..150ed94e 100644
--- a/packages/SystemUI/res/values-my/strings.xml
+++ b/packages/SystemUI/res/values-my/strings.xml
@@ -502,8 +502,8 @@
     <string name="battery_saver_notification_title" msgid="8419266546034372562">"ဘက်ထရီ အားထိန်းကို ဖွင့်ထားခြင်း"</string>
     <string name="battery_saver_notification_text" msgid="2617841636449016951">"လုပ်ကိုင်မှုကို လျှော့ချလျက် နောက်ခံ ဒေတာကို ကန့်သတ်သည်"</string>
     <string name="battery_saver_notification_action_text" msgid="6022091913807026887">"ဘက်ထရီ အားထိန်းကို ပိတ်ရန်"</string>
-    <string name="media_projection_dialog_text" msgid="1755705274910034772">"<xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> သည် အသံဖမ်းနေစဉ် (သို့) ကာစ်လုပ်နေစဉ် သင့်မျက်နှာပြင်တွင် မြင်ရသော (သို့) သင့်စက်တွင် ဖွင့်ထားသော အချက်အလက်မှန်သမျှကို သုံးနိုင်ပါမည်။ ၎င်းတွင် စကားဝှက်များ၊ ငွေပေးချေမှုအသေးစိတ်များ၊ ဓာတ်ပုံများ၊ မက်ဆေ့ဂျ်များနှင့် သင်ဖွင့်သည့်အသံကဲ့သို့သော အချက်အလက်များ ပါဝင်သည်။"</string>
-    <string name="media_projection_dialog_service_text" msgid="958000992162214611">"ဤလုပ်ရပ်အတွက် ဝန်ဆောင်မှုသည် အသံဖမ်းနေစဉ် (သို့) ကာစ်လုပ်နေစဉ် သင့်မျက်နှာပြင်တွင် မြင်ရသော (သို့) သင့်စက်တွင် ဖွင့်ထားသော အချက်အလက်မှန်သမျှကို သုံးနိုင်ပါမည်။ ၎င်းတွင် စကားဝှက်များ၊ ငွေပေးချေမှုအသေးစိတ်များ၊ ဓာတ်ပုံများ၊ မက်ဆေ့ဂျ်များနှင့် သင်ဖွင့်သည့်အသံကဲ့သို့သော အချက်အလက်များ ပါဝင်သည်။"</string>
+    <string name="media_projection_dialog_text" msgid="1755705274910034772">"<xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> သည် အသံဖမ်းနေစဉ် (သို့) ကာစ်လုပ်နေစဉ် သင့်ဖန်သားပြင်တွင် မြင်ရသော (သို့) သင့်စက်တွင် ဖွင့်ထားသော အချက်အလက်မှန်သမျှကို သုံးနိုင်နိုင်ပါမည်။ ၎င်းတွင် စကားဝှက်များ၊ ငွေပေးချေမှုအသေးစိတ်များ၊ ဓာတ်ပုံများ၊ မက်ဆေ့ဂျ်များနှင့် သင်ဖွင့်သည့်အသံကဲ့သို့သော အချက်အလက်များ ပါဝင်သည်။"</string>
+    <string name="media_projection_dialog_service_text" msgid="958000992162214611">"ဤလုပ်ရပ်အတွက် ဝန်ဆောင်မှုသည် အသံဖမ်းနေစဉ် (သို့) ကာစ်လုပ်နေစဉ် သင့်ဖန်သားပြင်တွင် မြင်ရသော (သို့) သင့်စက်တွင် ဖွင့်ထားသော အချက်အလက်မှန်သမျှကို သုံးနိုင်ပြီး သင့်စက်မှ ဖွင့်နိုင်ပါမည်။ ၎င်းတွင် စကားဝှက်များ၊ ငွေပေးချေမှုအသေးစိတ်များ၊ ဓာတ်ပုံများ၊ မက်ဆေ့ဂျ်များနှင့် သင်ဖွင့်သည့်အသံကဲ့သို့သော အချက်အလက်များ ပါဝင်သည်။"</string>
     <string name="media_projection_dialog_service_title" msgid="2888507074107884040">"ဖမ်းယူခြင်း သို့မဟုတ် ကာစ်လုပ်ခြင်း စတင်မလား။"</string>
     <string name="media_projection_dialog_title" msgid="3316063622495360646">"<xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> နှင့် ဖမ်းယူခြင်း သို့မဟုတ် ကာစ်လုပ်ခြင်း စတင်မလား။"</string>
     <string name="media_projection_remember_text" msgid="6896767327140422951">"နောက်ထပ် မပြပါနှင့်"</string>
@@ -1069,7 +1069,7 @@
     <string name="controls_seeding_in_progress" msgid="3033855341410264148">"အကြံပြုချက်များ ဖွင့်နေသည်"</string>
     <string name="controls_media_title" msgid="1746947284862928133">"မီဒီယာ"</string>
     <string name="controls_media_close_session" msgid="3957093425905475065">"လက်ရှိ စက်ရှင်ကို ဖျောက်ထားမည်။"</string>
-    <string name="controls_media_dismiss_button" msgid="4485675693008031646">"ဖျောက်ထားမည်"</string>
+    <string name="controls_media_dismiss_button" msgid="9081375542265132213">"ပယ်ရန်"</string>
     <string name="controls_media_resume" msgid="1933520684481586053">"ဆက်လုပ်ရန်"</string>
     <string name="controls_media_settings_button" msgid="5815790345117172504">"ဆက်တင်များ"</string>
     <string name="controls_error_timeout" msgid="794197289772728958">"ရပ်နေသည်၊ အက်ပ်ကို စစ်ဆေးပါ"</string>
@@ -1084,4 +1084,6 @@
     <string name="controls_added_tooltip" msgid="4842812921719153085">"ထိန်းချုပ်မှုအသစ်များ ကြည့်ရန် ဖွင့်ပိတ်ခလုတ်ကို ဖိထားပါ"</string>
     <string name="controls_menu_add" msgid="4447246119229920050">"ထိန်းချုပ်မှုများ ထည့်ရန်"</string>
     <string name="controls_menu_edit" msgid="890623986951347062">"ထိန်းချုပ်မှုများ တည်းဖြတ်ရန်"</string>
+    <string name="one_handed_tutorial_title" msgid="6312198435090726656">"လက်တစ်ဖက်သုံးမုဒ် အသုံးပြုခြင်း"</string>
+    <string name="one_handed_tutorial_description" msgid="7674850272340517174">"ထွက်ရန် ဖန်သားပြင်၏အောက်ခြေမှ အပေါ်သို့ပွတ်ဆွဲပါ သို့မဟုတ် အက်ပ်အပေါ်ဘက် မည်သည့်နေရာတွင်မဆို တို့ပါ"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-nb/strings.xml b/packages/SystemUI/res/values-nb/strings.xml
index 5e29ca4..849c2e9 100644
--- a/packages/SystemUI/res/values-nb/strings.xml
+++ b/packages/SystemUI/res/values-nb/strings.xml
@@ -1069,7 +1069,7 @@
     <string name="controls_seeding_in_progress" msgid="3033855341410264148">"Laster inn anbefalinger"</string>
     <string name="controls_media_title" msgid="1746947284862928133">"Medier"</string>
     <string name="controls_media_close_session" msgid="3957093425905475065">"Skjul den nåværende økten."</string>
-    <string name="controls_media_dismiss_button" msgid="4485675693008031646">"Skjul"</string>
+    <string name="controls_media_dismiss_button" msgid="9081375542265132213">"Lukk"</string>
     <string name="controls_media_resume" msgid="1933520684481586053">"Gjenoppta"</string>
     <string name="controls_media_settings_button" msgid="5815790345117172504">"Innstillinger"</string>
     <string name="controls_error_timeout" msgid="794197289772728958">"Inaktiv. Sjekk appen"</string>
@@ -1084,4 +1084,6 @@
     <string name="controls_added_tooltip" msgid="4842812921719153085">"Hold inne av/på-knappen for å se kontroller"</string>
     <string name="controls_menu_add" msgid="4447246119229920050">"Legg til kontroller"</string>
     <string name="controls_menu_edit" msgid="890623986951347062">"Endre kontroller"</string>
+    <string name="one_handed_tutorial_title" msgid="6312198435090726656">"Bruk av enhåndsmodus"</string>
+    <string name="one_handed_tutorial_description" msgid="7674850272340517174">"For å avslutte, sveip opp fra bunnen av skjermen eller trykk hvor som helst over appen"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-ne/strings.xml b/packages/SystemUI/res/values-ne/strings.xml
index 098e0b1..2ac442e 100644
--- a/packages/SystemUI/res/values-ne/strings.xml
+++ b/packages/SystemUI/res/values-ne/strings.xml
@@ -731,11 +731,11 @@
     <string name="see_more_title" msgid="7409317011708185729">"थप हेर्नुहोस्"</string>
     <string name="appops_camera" msgid="5215967620896725715">"यो अनुप्रयोगले क्यामेराको प्रयोग गर्दै छ।"</string>
     <string name="appops_microphone" msgid="8805468338613070149">"यो अनुप्रयोगले माइक्रोफोनको प्रयोग गर्दै छ।"</string>
-    <string name="appops_overlay" msgid="4822261562576558490">"यो अनुप्रयोगले तपाईंको स्क्रिनका अन्य अनुप्रयोगहरूमाथि प्रदर्शन गर्दै छ।"</string>
+    <string name="appops_overlay" msgid="4822261562576558490">"यो अनुप्रयोगले तपाईंको स्क्रिनका अन्य एपमाथि प्रदर्शन गर्दै छ।"</string>
     <string name="appops_camera_mic" msgid="7032239823944420431">"यो अनुप्रयोगले माइक्रोफोन र क्यामेराको प्रयोग गर्दै छ।"</string>
-    <string name="appops_camera_overlay" msgid="6466845606058816484">"यो अनुप्रयोगले तपाईंको स्क्रिनका अन्य अनुप्रयोगहरूमाथि प्रदर्शन गर्नुका साथै क्यामेराको प्रयोग गर्दै छ।"</string>
-    <string name="appops_mic_overlay" msgid="4609326508944233061">"यो अनुप्रयोगले तपाईंको स्क्रिनका अन्य अनुप्रयोगहरूमाथि प्रदर्शन गर्नुका साथै माइक्रोफोनको प्रयोग गर्दै छ।"</string>
-    <string name="appops_camera_mic_overlay" msgid="5584311236445644095">"यो अनुप्रयोगले तपाईंको स्क्रिनका अन्य अनुप्रयोगहरूमाथि प्रदर्शन गर्नुका साथै माइक्रोफोन र क्यामेराको प्रयोग गर्दै छ।"</string>
+    <string name="appops_camera_overlay" msgid="6466845606058816484">"यो अनुप्रयोगले तपाईंको स्क्रिनका अन्य एपमाथि प्रदर्शन गर्नुका साथै क्यामेराको प्रयोग गर्दै छ।"</string>
+    <string name="appops_mic_overlay" msgid="4609326508944233061">"यो अनुप्रयोगले तपाईंको स्क्रिनका अन्य एपमाथि प्रदर्शन गर्नुका साथै माइक्रोफोनको प्रयोग गर्दै छ।"</string>
+    <string name="appops_camera_mic_overlay" msgid="5584311236445644095">"यो अनुप्रयोगले तपाईंको स्क्रिनका अन्य एपमाथि प्रदर्शन गर्नुका साथै माइक्रोफोन र क्यामेराको प्रयोग गर्दै छ।"</string>
     <string name="notification_appops_settings" msgid="5208974858340445174">"सेटिङहरू"</string>
     <string name="notification_appops_ok" msgid="2177609375872784124">"ठिक छ"</string>
     <string name="feedback_silenced" msgid="5382212321253328247">"सिस्टमले यो सूचना आउँदा बज्ने ध्वनि बन्द गरेको छ।"</string>
@@ -1069,7 +1069,7 @@
     <string name="controls_seeding_in_progress" msgid="3033855341410264148">"सिफारिसहरू लोड गर्दै"</string>
     <string name="controls_media_title" msgid="1746947284862928133">"मिडिया"</string>
     <string name="controls_media_close_session" msgid="3957093425905475065">"हालको सत्र लुकाउनुहोस्।"</string>
-    <string name="controls_media_dismiss_button" msgid="4485675693008031646">"लुकाउनुहोस्"</string>
+    <string name="controls_media_dismiss_button" msgid="9081375542265132213">"हटाउनुहोस्"</string>
     <string name="controls_media_resume" msgid="1933520684481586053">"सुचारु गर्नुहोस्"</string>
     <string name="controls_media_settings_button" msgid="5815790345117172504">"सेटिङ"</string>
     <string name="controls_error_timeout" msgid="794197289772728958">"निष्क्रिय छ, एप जाँच गर्नु…"</string>
@@ -1084,4 +1084,6 @@
     <string name="controls_added_tooltip" msgid="4842812921719153085">"नयाँ नियन्त्रण सुविधाहरू हेर्न पावर बटन थिचिराख्नुहोस्"</string>
     <string name="controls_menu_add" msgid="4447246119229920050">"नियन्त्रण सुविधाहरू थप्नुहोस्"</string>
     <string name="controls_menu_edit" msgid="890623986951347062">"नियन्त्रण सुविधाहरू सम्पादन गर्नु…"</string>
+    <string name="one_handed_tutorial_title" msgid="6312198435090726656">"एक हाते मोड प्रयोग गरिँदै छ"</string>
+    <string name="one_handed_tutorial_description" msgid="7674850272340517174">"बाहिर निस्कन, स्क्रिनको पुछारबाट माथितिर स्वाइप गर्नुहोस् वा एपभन्दा माथि जुनसुकै ठाउँमा ट्याप गर्नुहोस्"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-nl/strings.xml b/packages/SystemUI/res/values-nl/strings.xml
index e501218..da0f427 100644
--- a/packages/SystemUI/res/values-nl/strings.xml
+++ b/packages/SystemUI/res/values-nl/strings.xml
@@ -1069,7 +1069,7 @@
     <string name="controls_seeding_in_progress" msgid="3033855341410264148">"Aanbevelingen laden"</string>
     <string name="controls_media_title" msgid="1746947284862928133">"Media"</string>
     <string name="controls_media_close_session" msgid="3957093425905475065">"De huidige sessie verbergen."</string>
-    <string name="controls_media_dismiss_button" msgid="4485675693008031646">"Verbergen"</string>
+    <string name="controls_media_dismiss_button" msgid="9081375542265132213">"Sluiten"</string>
     <string name="controls_media_resume" msgid="1933520684481586053">"Hervatten"</string>
     <string name="controls_media_settings_button" msgid="5815790345117172504">"Instellingen"</string>
     <string name="controls_error_timeout" msgid="794197289772728958">"Inactief, check de app"</string>
@@ -1084,4 +1084,6 @@
     <string name="controls_added_tooltip" msgid="4842812921719153085">"Houd de aan/uit-knop ingedrukt om nieuwe bedieningselementen te bekijken"</string>
     <string name="controls_menu_add" msgid="4447246119229920050">"Bedieningselementen toevoegen"</string>
     <string name="controls_menu_edit" msgid="890623986951347062">"Bedieningselementen bewerken"</string>
+    <string name="one_handed_tutorial_title" msgid="6312198435090726656">"Bediening met één hand gebruiken"</string>
+    <string name="one_handed_tutorial_description" msgid="7674850272340517174">"Als je wilt afsluiten, swipe je omhoog vanaf de onderkant van het scherm of tik je ergens boven de app"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-or/strings.xml b/packages/SystemUI/res/values-or/strings.xml
index bfd4bd7..029aa69 100644
--- a/packages/SystemUI/res/values-or/strings.xml
+++ b/packages/SystemUI/res/values-or/strings.xml
@@ -125,7 +125,7 @@
     <string name="accessibility_accessibility_button" msgid="4089042473497107709">"ଆକ୍ସେସିବିଲିଟୀ"</string>
     <string name="accessibility_rotate_button" msgid="1238584767612362586">"ସ୍କ୍ରୀନ୍‌କୁ ଘୁରାନ୍ତୁ"</string>
     <string name="accessibility_recent" msgid="901641734769533575">"ଓଭରଭିଉ"</string>
-    <string name="accessibility_search_light" msgid="524741790416076988">"ସର୍ଚ୍ଚ କରନ୍ତୁ"</string>
+    <string name="accessibility_search_light" msgid="524741790416076988">"Search"</string>
     <string name="accessibility_camera_button" msgid="2938898391716647247">"କ୍ୟାମେରା"</string>
     <string name="accessibility_phone_button" msgid="4256353121703100427">"ଫୋନ୍‍"</string>
     <string name="accessibility_voice_assist_button" msgid="6497706615649754510">"ଭଏସ୍‌ ସହାୟକ"</string>
@@ -441,7 +441,7 @@
     <string name="expanded_header_battery_charging_with_time" msgid="757991461445765011">"ପୂର୍ଣ୍ଣ ଚାର୍ଜ ହେବାକୁ ଆଉ <xliff:g id="CHARGING_TIME">%s</xliff:g> ଅଛି"</string>
     <string name="expanded_header_battery_not_charging" msgid="809409140358955848">"ଚାର୍ଜ ହେଉନାହିଁ"</string>
     <string name="ssl_ca_cert_warning" msgid="8373011375250324005">"ନେଟ୍‍ୱର୍କ\nମନିଟର୍‍ କରାଯାଇପାରେ"</string>
-    <string name="description_target_search" msgid="3875069993128855865">"ସର୍ଚ୍ଚ"</string>
+    <string name="description_target_search" msgid="3875069993128855865">"Search"</string>
     <string name="description_direction_up" msgid="3632251507574121434">"<xliff:g id="TARGET_DESCRIPTION">%s</xliff:g> ପାଇଁ ଉପରକୁ ସ୍ଲାଇଡ୍‍ କରନ୍ତୁ।"</string>
     <string name="description_direction_left" msgid="4762708739096907741">"<xliff:g id="TARGET_DESCRIPTION">%s</xliff:g> ପାଇଁ ବାମକୁ ସ୍ଲାଇଡ୍ କରନ୍ତୁ"</string>
     <string name="zen_priority_introduction" msgid="3159291973383796646">"ଆଲାର୍ମ, ରିମାଇଣ୍ଡର୍‌, ଇଭେଣ୍ଟ ଏବଂ ଆପଣ ନିର୍ଦ୍ଦିଷ୍ଟ କରିଥିବା କଲର୍‌ଙ୍କ ବ୍ୟତୀତ ଆପଣଙ୍କ ଧ୍ୟାନ ଅନ୍ୟ କୌଣସି ଧ୍ୱନୀ ଏବଂ ଭାଇବ୍ରେଶନ୍‌ରେ ଆକର୍ଷଣ କରାଯିବନାହିଁ। ମ୍ୟୁଜିକ୍‍, ଭିଡିଓ ଏବଂ ଗେମ୍‌ ସମେତ ନିଜେ ଚଲାଇବାକୁ ବାଛିଥିବା ଅନ୍ୟ ସବୁକିଛି ଆପଣ ଶୁଣିପାରିବେ।"</string>
@@ -1069,7 +1069,7 @@
     <string name="controls_seeding_in_progress" msgid="3033855341410264148">"ସୁପାରିଶଗୁଡ଼ିକ ଲୋଡ୍ କରାଯାଉଛି"</string>
     <string name="controls_media_title" msgid="1746947284862928133">"ମିଡିଆ"</string>
     <string name="controls_media_close_session" msgid="3957093425905475065">"ବର୍ତ୍ତମାନର ସେସନ୍ ଲୁଚାନ୍ତୁ।"</string>
-    <string name="controls_media_dismiss_button" msgid="4485675693008031646">"ଲୁଚାନ୍ତୁ"</string>
+    <string name="controls_media_dismiss_button" msgid="9081375542265132213">"ଖାରଜ କରନ୍ତୁ"</string>
     <string name="controls_media_resume" msgid="1933520684481586053">"ପୁଣି ଆରମ୍ଭ କରନ୍ତୁ"</string>
     <string name="controls_media_settings_button" msgid="5815790345117172504">"ସେଟିଂସ୍"</string>
     <string name="controls_error_timeout" msgid="794197289772728958">"ନିଷ୍କ୍ରିୟ ଅଛି, ଆପ ଯାଞ୍ଚ କରନ୍ତୁ"</string>
@@ -1084,4 +1084,6 @@
     <string name="controls_added_tooltip" msgid="4842812921719153085">"ନୂଆ ନିୟନ୍ତ୍ରଣଗୁଡ଼ିକୁ ଦେଖିବା ପାଇଁ ପାୱାର ବଟନକୁ ଧରି ରଖନ୍ତୁ"</string>
     <string name="controls_menu_add" msgid="4447246119229920050">"ନିୟନ୍ତ୍ରଣଗୁଡ଼ିକ ଯୋଗ କରନ୍ତୁ"</string>
     <string name="controls_menu_edit" msgid="890623986951347062">"ନିୟନ୍ତ୍ରଣଗୁଡ଼ିକ ସମ୍ପାଦନ କରନ୍ତୁ"</string>
+    <string name="one_handed_tutorial_title" msgid="6312198435090726656">"ଏକ-ହାତ ମୋଡ୍ ବ୍ୟବହାର କରି"</string>
+    <string name="one_handed_tutorial_description" msgid="7674850272340517174">"ବାହାରି ଯିବା ପାଇଁ, ତଳୁ ଉପରକୁ ସ୍ୱାଇପ୍ କରନ୍ତୁ କିମ୍ବା ଆପ୍ ଆଇକନର ଉପରେ ଯେ କୌଣସି ସ୍ଥାନରେ ଟାପ୍ କରନ୍ତୁ"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-pa/strings.xml b/packages/SystemUI/res/values-pa/strings.xml
index 1df9192..ed5f40c 100644
--- a/packages/SystemUI/res/values-pa/strings.xml
+++ b/packages/SystemUI/res/values-pa/strings.xml
@@ -1069,7 +1069,7 @@
     <string name="controls_seeding_in_progress" msgid="3033855341410264148">"ਸਿਫ਼ਾਰਸ਼ਾਂ ਲੋਡ ਹੋ ਰਹੀਆਂ ਹਨ"</string>
     <string name="controls_media_title" msgid="1746947284862928133">"ਮੀਡੀਆ"</string>
     <string name="controls_media_close_session" msgid="3957093425905475065">"ਮੌਜੂਦਾ ਸੈਸ਼ਨ ਨੂੰ ਲੁਕਾਓ।"</string>
-    <string name="controls_media_dismiss_button" msgid="4485675693008031646">"ਲੁਕਾਓ"</string>
+    <string name="controls_media_dismiss_button" msgid="9081375542265132213">"ਖਾਰਜ ਕਰੋ"</string>
     <string name="controls_media_resume" msgid="1933520684481586053">"ਮੁੜ-ਚਾਲੂ ਕਰੋ"</string>
     <string name="controls_media_settings_button" msgid="5815790345117172504">"ਸੈਟਿੰਗਾਂ"</string>
     <string name="controls_error_timeout" msgid="794197289772728958">"ਅਕਿਰਿਆਸ਼ੀਲ, ਐਪ ਦੀ ਜਾਂਚ ਕਰੋ"</string>
@@ -1084,4 +1084,6 @@
     <string name="controls_added_tooltip" msgid="4842812921719153085">"ਨਵੇਂ ਕੰਟਰੋਲ ਦੇਖਣ ਲਈ ਪਾਵਰ ਬਟਨ ਦਬਾਈ ਰੱਖੋ"</string>
     <string name="controls_menu_add" msgid="4447246119229920050">"ਕੰਟਰੋਲ ਸ਼ਾਮਲ ਕਰੋ"</string>
     <string name="controls_menu_edit" msgid="890623986951347062">"ਕੰਟਰੋਲਾਂ ਦਾ ਸੰਪਾਦਨ ਕਰੋ"</string>
+    <string name="one_handed_tutorial_title" msgid="6312198435090726656">"ਇੱਕ ਹੱਥ ਮੋਡ ਵਰਤਣਾ"</string>
+    <string name="one_handed_tutorial_description" msgid="7674850272340517174">"ਬਾਹਰ ਜਾਣ ਲਈ, ਸਕ੍ਰੀਨ ਦੇ ਹੇਠਾਂ ਤੋਂ ਉੱਪਰ ਵੱਲ ਸਵਾਈਪ ਕਰੋ ਜਾਂ ਐਪ \'ਤੇ ਕਿਤੇ ਵੀ ਟੈਪ ਕਰੋ"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-pl/strings.xml b/packages/SystemUI/res/values-pl/strings.xml
index 56fcbb2..ba30ced 100644
--- a/packages/SystemUI/res/values-pl/strings.xml
+++ b/packages/SystemUI/res/values-pl/strings.xml
@@ -1081,7 +1081,7 @@
     <string name="controls_seeding_in_progress" msgid="3033855341410264148">"Wczytuję rekomendacje"</string>
     <string name="controls_media_title" msgid="1746947284862928133">"Multimedia"</string>
     <string name="controls_media_close_session" msgid="3957093425905475065">"Ukryj bieżącą sesję."</string>
-    <string name="controls_media_dismiss_button" msgid="4485675693008031646">"Ukryj"</string>
+    <string name="controls_media_dismiss_button" msgid="9081375542265132213">"Odrzuć"</string>
     <string name="controls_media_resume" msgid="1933520684481586053">"Wznów"</string>
     <string name="controls_media_settings_button" msgid="5815790345117172504">"Ustawienia"</string>
     <string name="controls_error_timeout" msgid="794197289772728958">"Nieaktywny, sprawdź aplikację"</string>
@@ -1096,4 +1096,6 @@
     <string name="controls_added_tooltip" msgid="4842812921719153085">"Przytrzymaj przycisk zasilania, by zobaczyć nowe elementy sterujące"</string>
     <string name="controls_menu_add" msgid="4447246119229920050">"Dodaj elementy sterujące"</string>
     <string name="controls_menu_edit" msgid="890623986951347062">"Edytuj elementy sterujące"</string>
+    <string name="one_handed_tutorial_title" msgid="6312198435090726656">"Korzystanie z trybu jednej ręki"</string>
+    <string name="one_handed_tutorial_description" msgid="7674850272340517174">"Aby zamknąć, przesuń palcem z dołu ekranu w górę lub kliknij dowolne miejsce nad aplikacją"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-pt-rBR/strings.xml b/packages/SystemUI/res/values-pt-rBR/strings.xml
index 88f6032..715b0e4 100644
--- a/packages/SystemUI/res/values-pt-rBR/strings.xml
+++ b/packages/SystemUI/res/values-pt-rBR/strings.xml
@@ -502,7 +502,7 @@
     <string name="battery_saver_notification_title" msgid="8419266546034372562">"Economia de bateria ativada"</string>
     <string name="battery_saver_notification_text" msgid="2617841636449016951">"Reduz o desempenho e os dados em segundo plano"</string>
     <string name="battery_saver_notification_action_text" msgid="6022091913807026887">"Desativar a Economia de bateria"</string>
-    <string name="media_projection_dialog_text" msgid="1755705274910034772">"O app <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> terá acesso a todas as informações visíveis na tela ou tocadas no dispositivo, como gravação ou transmissão Isso inclui informações como senhas, detalhes de pagamento, fotos, mensagens e áudio que você toca."</string>
+    <string name="media_projection_dialog_text" msgid="1755705274910034772">"O app <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> terá acesso a todas as informações visíveis na tela ou tocadas no dispositivo, como gravação ou transmissão. Isso inclui informações como senhas, detalhes de pagamento, fotos, mensagens e o áudio que você tocar."</string>
     <string name="media_projection_dialog_service_text" msgid="958000992162214611">"O serviço que oferece essa função terá acesso a todas as informações visíveis na tela ou reproduzidas durante uma gravação ou transmissão. Isso inclui senhas, detalhes de pagamento, fotos, mensagens e áudio."</string>
     <string name="media_projection_dialog_service_title" msgid="2888507074107884040">"Iniciar gravação ou transmissão?"</string>
     <string name="media_projection_dialog_title" msgid="3316063622495360646">"Iniciar gravação ou transmissão com o app <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g>?"</string>
@@ -1069,7 +1069,7 @@
     <string name="controls_seeding_in_progress" msgid="3033855341410264148">"Carregando recomendações"</string>
     <string name="controls_media_title" msgid="1746947284862928133">"Mídia"</string>
     <string name="controls_media_close_session" msgid="3957093425905475065">"Ocultar a sessão atual."</string>
-    <string name="controls_media_dismiss_button" msgid="4485675693008031646">"Ocultar"</string>
+    <string name="controls_media_dismiss_button" msgid="9081375542265132213">"Dispensar"</string>
     <string name="controls_media_resume" msgid="1933520684481586053">"Retomar"</string>
     <string name="controls_media_settings_button" msgid="5815790345117172504">"Configurações"</string>
     <string name="controls_error_timeout" msgid="794197289772728958">"Inativo, verifique o app"</string>
@@ -1084,4 +1084,6 @@
     <string name="controls_added_tooltip" msgid="4842812921719153085">"Mantenha o botão liga/desliga pressionado para ver os novos controles"</string>
     <string name="controls_menu_add" msgid="4447246119229920050">"Adicionar controles"</string>
     <string name="controls_menu_edit" msgid="890623986951347062">"Editar controles"</string>
+    <string name="one_handed_tutorial_title" msgid="6312198435090726656">"Como usar o modo para uma mão"</string>
+    <string name="one_handed_tutorial_description" msgid="7674850272340517174">"Para sair, deslize de baixo para cima na tela ou toque em qualquer lugar acima do app"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-pt-rPT/strings.xml b/packages/SystemUI/res/values-pt-rPT/strings.xml
index 5fdb285..1a59de4 100644
--- a/packages/SystemUI/res/values-pt-rPT/strings.xml
+++ b/packages/SystemUI/res/values-pt-rPT/strings.xml
@@ -1069,7 +1069,7 @@
     <string name="controls_seeding_in_progress" msgid="3033855341410264148">"A carregar recomendações…"</string>
     <string name="controls_media_title" msgid="1746947284862928133">"Multimédia"</string>
     <string name="controls_media_close_session" msgid="3957093425905475065">"Oculte a sessão atual."</string>
-    <string name="controls_media_dismiss_button" msgid="4485675693008031646">"Ocultar"</string>
+    <string name="controls_media_dismiss_button" msgid="9081375542265132213">"Ignorar"</string>
     <string name="controls_media_resume" msgid="1933520684481586053">"Retomar"</string>
     <string name="controls_media_settings_button" msgid="5815790345117172504">"Definições"</string>
     <string name="controls_error_timeout" msgid="794197289772728958">"Inativa. Consulte a app."</string>
@@ -1084,4 +1084,6 @@
     <string name="controls_added_tooltip" msgid="4842812921719153085">"Mantenha premido o botão ligar/desligar para ver os novos controlos."</string>
     <string name="controls_menu_add" msgid="4447246119229920050">"Adicionar controlos"</string>
     <string name="controls_menu_edit" msgid="890623986951347062">"Editar controlos"</string>
+    <string name="one_handed_tutorial_title" msgid="6312198435090726656">"Utilizar o modo para uma mão"</string>
+    <string name="one_handed_tutorial_description" msgid="7674850272340517174">"Para sair, deslize rapidamente para cima a partir da parte inferior do ecrã ou toque em qualquer ponto acima da app."</string>
 </resources>
diff --git a/packages/SystemUI/res/values-pt/strings.xml b/packages/SystemUI/res/values-pt/strings.xml
index 88f6032..715b0e4 100644
--- a/packages/SystemUI/res/values-pt/strings.xml
+++ b/packages/SystemUI/res/values-pt/strings.xml
@@ -502,7 +502,7 @@
     <string name="battery_saver_notification_title" msgid="8419266546034372562">"Economia de bateria ativada"</string>
     <string name="battery_saver_notification_text" msgid="2617841636449016951">"Reduz o desempenho e os dados em segundo plano"</string>
     <string name="battery_saver_notification_action_text" msgid="6022091913807026887">"Desativar a Economia de bateria"</string>
-    <string name="media_projection_dialog_text" msgid="1755705274910034772">"O app <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> terá acesso a todas as informações visíveis na tela ou tocadas no dispositivo, como gravação ou transmissão Isso inclui informações como senhas, detalhes de pagamento, fotos, mensagens e áudio que você toca."</string>
+    <string name="media_projection_dialog_text" msgid="1755705274910034772">"O app <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> terá acesso a todas as informações visíveis na tela ou tocadas no dispositivo, como gravação ou transmissão. Isso inclui informações como senhas, detalhes de pagamento, fotos, mensagens e o áudio que você tocar."</string>
     <string name="media_projection_dialog_service_text" msgid="958000992162214611">"O serviço que oferece essa função terá acesso a todas as informações visíveis na tela ou reproduzidas durante uma gravação ou transmissão. Isso inclui senhas, detalhes de pagamento, fotos, mensagens e áudio."</string>
     <string name="media_projection_dialog_service_title" msgid="2888507074107884040">"Iniciar gravação ou transmissão?"</string>
     <string name="media_projection_dialog_title" msgid="3316063622495360646">"Iniciar gravação ou transmissão com o app <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g>?"</string>
@@ -1069,7 +1069,7 @@
     <string name="controls_seeding_in_progress" msgid="3033855341410264148">"Carregando recomendações"</string>
     <string name="controls_media_title" msgid="1746947284862928133">"Mídia"</string>
     <string name="controls_media_close_session" msgid="3957093425905475065">"Ocultar a sessão atual."</string>
-    <string name="controls_media_dismiss_button" msgid="4485675693008031646">"Ocultar"</string>
+    <string name="controls_media_dismiss_button" msgid="9081375542265132213">"Dispensar"</string>
     <string name="controls_media_resume" msgid="1933520684481586053">"Retomar"</string>
     <string name="controls_media_settings_button" msgid="5815790345117172504">"Configurações"</string>
     <string name="controls_error_timeout" msgid="794197289772728958">"Inativo, verifique o app"</string>
@@ -1084,4 +1084,6 @@
     <string name="controls_added_tooltip" msgid="4842812921719153085">"Mantenha o botão liga/desliga pressionado para ver os novos controles"</string>
     <string name="controls_menu_add" msgid="4447246119229920050">"Adicionar controles"</string>
     <string name="controls_menu_edit" msgid="890623986951347062">"Editar controles"</string>
+    <string name="one_handed_tutorial_title" msgid="6312198435090726656">"Como usar o modo para uma mão"</string>
+    <string name="one_handed_tutorial_description" msgid="7674850272340517174">"Para sair, deslize de baixo para cima na tela ou toque em qualquer lugar acima do app"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-ro/strings.xml b/packages/SystemUI/res/values-ro/strings.xml
index d5eecc7..cf428a3 100644
--- a/packages/SystemUI/res/values-ro/strings.xml
+++ b/packages/SystemUI/res/values-ro/strings.xml
@@ -1075,7 +1075,7 @@
     <string name="controls_seeding_in_progress" msgid="3033855341410264148">"Se încarcă recomandările"</string>
     <string name="controls_media_title" msgid="1746947284862928133">"Media"</string>
     <string name="controls_media_close_session" msgid="3957093425905475065">"Ascunde sesiunea actuală."</string>
-    <string name="controls_media_dismiss_button" msgid="4485675693008031646">"Ascunde"</string>
+    <string name="controls_media_dismiss_button" msgid="9081375542265132213">"Închideți"</string>
     <string name="controls_media_resume" msgid="1933520684481586053">"Reia"</string>
     <string name="controls_media_settings_button" msgid="5815790345117172504">"Setări"</string>
     <string name="controls_error_timeout" msgid="794197289772728958">"Inactiv, verificați aplicația"</string>
@@ -1090,4 +1090,6 @@
     <string name="controls_added_tooltip" msgid="4842812921719153085">"Apăsați butonul de alimentare pentru a vedea noile comenzi"</string>
     <string name="controls_menu_add" msgid="4447246119229920050">"Adăugați comenzi"</string>
     <string name="controls_menu_edit" msgid="890623986951347062">"Editați comenzile"</string>
+    <string name="one_handed_tutorial_title" msgid="6312198435090726656">"Folosirea modului cu o mână"</string>
+    <string name="one_handed_tutorial_description" msgid="7674850272340517174">"Pentru a ieși, glisați în sus din partea de jos a ecranului sau atingeți oriunde deasupra ferestrei aplicației"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-ru/strings.xml b/packages/SystemUI/res/values-ru/strings.xml
index 5085022..ba86e95 100644
--- a/packages/SystemUI/res/values-ru/strings.xml
+++ b/packages/SystemUI/res/values-ru/strings.xml
@@ -1081,7 +1081,7 @@
     <string name="controls_seeding_in_progress" msgid="3033855341410264148">"Загрузка рекомендаций…"</string>
     <string name="controls_media_title" msgid="1746947284862928133">"Медиа"</string>
     <string name="controls_media_close_session" msgid="3957093425905475065">"Скрыть текущий сеанс?"</string>
-    <string name="controls_media_dismiss_button" msgid="4485675693008031646">"Скрыть"</string>
+    <string name="controls_media_dismiss_button" msgid="9081375542265132213">"Скрыть"</string>
     <string name="controls_media_resume" msgid="1933520684481586053">"Возобновить"</string>
     <string name="controls_media_settings_button" msgid="5815790345117172504">"Настройки"</string>
     <string name="controls_error_timeout" msgid="794197289772728958">"Нет ответа. Проверьте приложение."</string>
@@ -1096,4 +1096,6 @@
     <string name="controls_added_tooltip" msgid="4842812921719153085">"Удерживайте кнопку питания, чтобы увидеть новые элементы управления"</string>
     <string name="controls_menu_add" msgid="4447246119229920050">"Добавить виджеты"</string>
     <string name="controls_menu_edit" msgid="890623986951347062">"Изменить виджеты"</string>
+    <string name="one_handed_tutorial_title" msgid="6312198435090726656">"Использование режима управления одной рукой"</string>
+    <string name="one_handed_tutorial_description" msgid="7674850272340517174">"Чтобы выйти, проведите по экрану снизу вверх или нажмите в любой области над значком приложения."</string>
 </resources>
diff --git a/packages/SystemUI/res/values-si/strings.xml b/packages/SystemUI/res/values-si/strings.xml
index ff7e816..1cd1ceb 100644
--- a/packages/SystemUI/res/values-si/strings.xml
+++ b/packages/SystemUI/res/values-si/strings.xml
@@ -1069,7 +1069,7 @@
     <string name="controls_seeding_in_progress" msgid="3033855341410264148">"නිර්දේශ පූරණය කරමින්"</string>
     <string name="controls_media_title" msgid="1746947284862928133">"මාධ්‍ය"</string>
     <string name="controls_media_close_session" msgid="3957093425905475065">"වත්මන් සැසිය සඟවන්න."</string>
-    <string name="controls_media_dismiss_button" msgid="4485675693008031646">"සඟවන්න"</string>
+    <string name="controls_media_dismiss_button" msgid="9081375542265132213">"ඉවත ලන්න"</string>
     <string name="controls_media_resume" msgid="1933520684481586053">"නැවත පටන් ගන්න"</string>
     <string name="controls_media_settings_button" msgid="5815790345117172504">"සැකසීම්"</string>
     <string name="controls_error_timeout" msgid="794197289772728958">"අක්‍රියයි, යෙදුම පරීක්ෂා කරන්න"</string>
@@ -1084,4 +1084,6 @@
     <string name="controls_added_tooltip" msgid="4842812921719153085">"නව පාලන බැලීමට බල බොත්තම අල්ලාගෙන සිටින්න"</string>
     <string name="controls_menu_add" msgid="4447246119229920050">"පාලන එක් කරන්න"</string>
     <string name="controls_menu_edit" msgid="890623986951347062">"පාලන සංස්කරණය කරන්න"</string>
+    <string name="one_handed_tutorial_title" msgid="6312198435090726656">"තනි-අත් ප්‍රකාරය භාවිත කරමින්"</string>
+    <string name="one_handed_tutorial_description" msgid="7674850272340517174">"පිටවීමට, තිරයේ පහළ සිට ඉහළට ස්වයිප් කරන්න හෝ යෙදුමට ඉහළින් ඕනෑම තැනක තට්ටු කරන්න"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-sk/strings.xml b/packages/SystemUI/res/values-sk/strings.xml
index a86da96..7548607 100644
--- a/packages/SystemUI/res/values-sk/strings.xml
+++ b/packages/SystemUI/res/values-sk/strings.xml
@@ -1081,7 +1081,7 @@
     <string name="controls_seeding_in_progress" msgid="3033855341410264148">"Načítavajú sa odporúčania"</string>
     <string name="controls_media_title" msgid="1746947284862928133">"Médiá"</string>
     <string name="controls_media_close_session" msgid="3957093425905475065">"Skryť aktuálnu reláciu."</string>
-    <string name="controls_media_dismiss_button" msgid="4485675693008031646">"Skryť"</string>
+    <string name="controls_media_dismiss_button" msgid="9081375542265132213">"Zavrieť"</string>
     <string name="controls_media_resume" msgid="1933520684481586053">"Pokračovať"</string>
     <string name="controls_media_settings_button" msgid="5815790345117172504">"Nastavenia"</string>
     <string name="controls_error_timeout" msgid="794197289772728958">"Neaktívne, preverte aplikáciu"</string>
@@ -1096,4 +1096,6 @@
     <string name="controls_added_tooltip" msgid="4842812921719153085">"Pridržaním vypínača zobrazíte nové ovládacie prvky"</string>
     <string name="controls_menu_add" msgid="4447246119229920050">"Pridať ovládače"</string>
     <string name="controls_menu_edit" msgid="890623986951347062">"Upraviť ovládače"</string>
+    <string name="one_handed_tutorial_title" msgid="6312198435090726656">"Používanie režimu jednej ruky"</string>
+    <string name="one_handed_tutorial_description" msgid="7674850272340517174">"Ukončíte potiahnutím z dolnej časti obrazovky nahor alebo klepnutím kdekoľvek nad aplikáciu"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-sl/strings.xml b/packages/SystemUI/res/values-sl/strings.xml
index b9039d8..07ad8c1 100644
--- a/packages/SystemUI/res/values-sl/strings.xml
+++ b/packages/SystemUI/res/values-sl/strings.xml
@@ -1081,7 +1081,7 @@
     <string name="controls_seeding_in_progress" msgid="3033855341410264148">"Nalaganje priporočil"</string>
     <string name="controls_media_title" msgid="1746947284862928133">"Predstavnost"</string>
     <string name="controls_media_close_session" msgid="3957093425905475065">"Skrije trenutno sejo."</string>
-    <string name="controls_media_dismiss_button" msgid="4485675693008031646">"Skrij"</string>
+    <string name="controls_media_dismiss_button" msgid="9081375542265132213">"Opusti"</string>
     <string name="controls_media_resume" msgid="1933520684481586053">"Nadaljuj"</string>
     <string name="controls_media_settings_button" msgid="5815790345117172504">"Nastavitve"</string>
     <string name="controls_error_timeout" msgid="794197289772728958">"Neaktivno, poglejte aplikacijo"</string>
@@ -1096,4 +1096,6 @@
     <string name="controls_added_tooltip" msgid="4842812921719153085">"Za ogled novih kontrolnikov pridržite gumb za vklop"</string>
     <string name="controls_menu_add" msgid="4447246119229920050">"Dodaj kontrolnike"</string>
     <string name="controls_menu_edit" msgid="890623986951347062">"Uredi kontrolnike"</string>
+    <string name="one_handed_tutorial_title" msgid="6312198435090726656">"Uporaba enoročnega načina"</string>
+    <string name="one_handed_tutorial_description" msgid="7674850272340517174">"Za izhod povlecite z dna zaslona navzgor ali se dotaknite na poljubnem mestu nad aplikacijo"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-sq/strings.xml b/packages/SystemUI/res/values-sq/strings.xml
index 2972c53..b92744d 100644
--- a/packages/SystemUI/res/values-sq/strings.xml
+++ b/packages/SystemUI/res/values-sq/strings.xml
@@ -502,8 +502,8 @@
     <string name="battery_saver_notification_title" msgid="8419266546034372562">"\"Kursyesi i baterisë\" është i aktivizuar"</string>
     <string name="battery_saver_notification_text" msgid="2617841636449016951">"Pakëson veprimtarinë dhe të dhënat në sfond"</string>
     <string name="battery_saver_notification_action_text" msgid="6022091913807026887">"Çaktivizo \"Kursyesin e baterisë\""</string>
-    <string name="media_projection_dialog_text" msgid="1755705274910034772">"<xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> do të ketë qasje te të gjitha informacionet që janë të dukshme në ekran ose që luhen nga pajisja jote gjatë regjistrimit ose transmetimit. Kjo përfshin informacione si p.sh. fjalëkalimet, detajet e pagesave, fotografitë, mesazhet dhe audion që luan ti."</string>
-    <string name="media_projection_dialog_service_text" msgid="958000992162214611">"Shërbimi që e ofron këtë funksion do të ketë qasje te të gjitha informacionet që janë të dukshme në ekran ose që luhen nga pajisja jote gjatë regjistrimit ose transmetimit. Kjo përfshin informacione si p.sh. fjalëkalimet, detajet e pagesave, fotografitë, mesazhet dhe audion që luan ti."</string>
+    <string name="media_projection_dialog_text" msgid="1755705274910034772">"<xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> do të ketë qasje te të gjitha informacionet që janë të dukshme në ekran ose që luhen nga pajisja jote gjatë regjistrimit ose transmetimit. Kjo përfshin informacione, si p.sh.: fjalëkalimet, detajet e pagesave, fotografitë, mesazhet dhe audion që luan ti."</string>
+    <string name="media_projection_dialog_service_text" msgid="958000992162214611">"Shërbimi që e ofron këtë funksion do të ketë qasje te të gjitha informacionet që janë të dukshme në ekran ose që luhen nga pajisja jote gjatë regjistrimit ose transmetimit. Kjo përfshin informacione, si p.sh.: fjalëkalimet, detajet e pagesave, fotografitë, mesazhet dhe audion që luan ti."</string>
     <string name="media_projection_dialog_service_title" msgid="2888507074107884040">"Do të fillosh regjistrimin ose transmetimin?"</string>
     <string name="media_projection_dialog_title" msgid="3316063622495360646">"Fillo regjistrimin ose transmetimin me <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g>?"</string>
     <string name="media_projection_remember_text" msgid="6896767327140422951">"Mos e shfaq sërish"</string>
@@ -1069,7 +1069,7 @@
     <string name="controls_seeding_in_progress" msgid="3033855341410264148">"Po ngarkon rekomandimet"</string>
     <string name="controls_media_title" msgid="1746947284862928133">"Media"</string>
     <string name="controls_media_close_session" msgid="3957093425905475065">"Fshih sesionin aktual."</string>
-    <string name="controls_media_dismiss_button" msgid="4485675693008031646">"Fshih"</string>
+    <string name="controls_media_dismiss_button" msgid="9081375542265132213">"Hiq"</string>
     <string name="controls_media_resume" msgid="1933520684481586053">"Vazhdo"</string>
     <string name="controls_media_settings_button" msgid="5815790345117172504">"Cilësimet"</string>
     <string name="controls_error_timeout" msgid="794197289772728958">"Joaktive, kontrollo aplikacionin"</string>
@@ -1084,4 +1084,6 @@
     <string name="controls_added_tooltip" msgid="4842812921719153085">"Mbaj shtypur butonin e energjisë për të parë kontrollet e reja"</string>
     <string name="controls_menu_add" msgid="4447246119229920050">"Shto kontrollet"</string>
     <string name="controls_menu_edit" msgid="890623986951347062">"Modifiko kontrollet"</string>
+    <string name="one_handed_tutorial_title" msgid="6312198435090726656">"Po përdor modalitetin e përdorimit me një dorë"</string>
+    <string name="one_handed_tutorial_description" msgid="7674850272340517174">"Për të dalë, rrëshqit lart nga fundi i ekranit ose trokit diku mbi aplikacion"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-sr/strings.xml b/packages/SystemUI/res/values-sr/strings.xml
index 77cbb96b..f57d92b 100644
--- a/packages/SystemUI/res/values-sr/strings.xml
+++ b/packages/SystemUI/res/values-sr/strings.xml
@@ -1075,7 +1075,7 @@
     <string name="controls_seeding_in_progress" msgid="3033855341410264148">"Учитавају се препоруке"</string>
     <string name="controls_media_title" msgid="1746947284862928133">"Медији"</string>
     <string name="controls_media_close_session" msgid="3957093425905475065">"Сакријте актуелну сесију."</string>
-    <string name="controls_media_dismiss_button" msgid="4485675693008031646">"Сакриј"</string>
+    <string name="controls_media_dismiss_button" msgid="9081375542265132213">"Одбаци"</string>
     <string name="controls_media_resume" msgid="1933520684481586053">"Настави"</string>
     <string name="controls_media_settings_button" msgid="5815790345117172504">"Подешавања"</string>
     <string name="controls_error_timeout" msgid="794197289772728958">"Неактивно. Видите апликацију"</string>
@@ -1090,4 +1090,6 @@
     <string name="controls_added_tooltip" msgid="4842812921719153085">"Задржите дугме за укључивање да бисте видели нове контроле"</string>
     <string name="controls_menu_add" msgid="4447246119229920050">"Додај контроле"</string>
     <string name="controls_menu_edit" msgid="890623986951347062">"Измени контроле"</string>
+    <string name="one_handed_tutorial_title" msgid="6312198435090726656">"Коришћење режима једном руком"</string>
+    <string name="one_handed_tutorial_description" msgid="7674850272340517174">"Да бисте изашли, превуците нагоре од дна екрана или додирните било где изнад апликације"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-sv/strings.xml b/packages/SystemUI/res/values-sv/strings.xml
index c7eda45..876e337 100644
--- a/packages/SystemUI/res/values-sv/strings.xml
+++ b/packages/SystemUI/res/values-sv/strings.xml
@@ -1069,7 +1069,7 @@
     <string name="controls_seeding_in_progress" msgid="3033855341410264148">"Rekommendationer läses in"</string>
     <string name="controls_media_title" msgid="1746947284862928133">"Media"</string>
     <string name="controls_media_close_session" msgid="3957093425905475065">"Dölj den aktuella sessionen."</string>
-    <string name="controls_media_dismiss_button" msgid="4485675693008031646">"Dölj"</string>
+    <string name="controls_media_dismiss_button" msgid="9081375542265132213">"Stäng"</string>
     <string name="controls_media_resume" msgid="1933520684481586053">"Återuppta"</string>
     <string name="controls_media_settings_button" msgid="5815790345117172504">"Inställningar"</string>
     <string name="controls_error_timeout" msgid="794197289772728958">"Inaktiv, kolla appen"</string>
@@ -1084,4 +1084,6 @@
     <string name="controls_added_tooltip" msgid="4842812921719153085">"De nya snabbkontrollerna visas om du håller strömbrytaren nedtryckt"</string>
     <string name="controls_menu_add" msgid="4447246119229920050">"Lägg till snabbkontroller"</string>
     <string name="controls_menu_edit" msgid="890623986951347062">"Redigera snabbkontroller"</string>
+    <string name="one_handed_tutorial_title" msgid="6312198435090726656">"Använda enhandsläge"</string>
+    <string name="one_handed_tutorial_description" msgid="7674850272340517174">"Avsluta genom att svepa uppåt från skärmens nederkant eller trycka ovanför appen"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-sw/strings.xml b/packages/SystemUI/res/values-sw/strings.xml
index 2804f2f..25e3493 100644
--- a/packages/SystemUI/res/values-sw/strings.xml
+++ b/packages/SystemUI/res/values-sw/strings.xml
@@ -1069,7 +1069,7 @@
     <string name="controls_seeding_in_progress" msgid="3033855341410264148">"Inapakia mapendekezo"</string>
     <string name="controls_media_title" msgid="1746947284862928133">"Maudhui"</string>
     <string name="controls_media_close_session" msgid="3957093425905475065">"Ficha kipindi cha sasa."</string>
-    <string name="controls_media_dismiss_button" msgid="4485675693008031646">"Ficha"</string>
+    <string name="controls_media_dismiss_button" msgid="9081375542265132213">"Ondoa"</string>
     <string name="controls_media_resume" msgid="1933520684481586053">"Endelea"</string>
     <string name="controls_media_settings_button" msgid="5815790345117172504">"Mipangilio"</string>
     <string name="controls_error_timeout" msgid="794197289772728958">"Haitumiki, angalia programu"</string>
@@ -1084,4 +1084,6 @@
     <string name="controls_added_tooltip" msgid="4842812921719153085">"Shikilia kitufe cha kuwasha/kuzima ili uone vidhibiti vipya"</string>
     <string name="controls_menu_add" msgid="4447246119229920050">"Weka vidhibiti"</string>
     <string name="controls_menu_edit" msgid="890623986951347062">"Badilisha vidhibiti"</string>
+    <string name="one_handed_tutorial_title" msgid="6312198435090726656">"Kutumia hali ya kutumia kwa mkono mmoja"</string>
+    <string name="one_handed_tutorial_description" msgid="7674850272340517174">"Ili ufunge, telezesha kidole juu kutoka sehemu ya chini ya skrini au uguse mahali popote juu ya programu"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-ta/strings.xml b/packages/SystemUI/res/values-ta/strings.xml
index ee176fa..19e67db 100644
--- a/packages/SystemUI/res/values-ta/strings.xml
+++ b/packages/SystemUI/res/values-ta/strings.xml
@@ -1069,7 +1069,7 @@
     <string name="controls_seeding_in_progress" msgid="3033855341410264148">"பரிந்துரைகளை ஏற்றுகிறது"</string>
     <string name="controls_media_title" msgid="1746947284862928133">"மீடியா"</string>
     <string name="controls_media_close_session" msgid="3957093425905475065">"இந்த அமர்வை மறையுங்கள்."</string>
-    <string name="controls_media_dismiss_button" msgid="4485675693008031646">"மறை"</string>
+    <string name="controls_media_dismiss_button" msgid="9081375542265132213">"மூடுக"</string>
     <string name="controls_media_resume" msgid="1933520684481586053">"தொடர்க"</string>
     <string name="controls_media_settings_button" msgid="5815790345117172504">"அமைப்புகள்"</string>
     <string name="controls_error_timeout" msgid="794197289772728958">"செயலில் இல்லை , சரிபார்க்கவும்"</string>
@@ -1084,4 +1084,6 @@
     <string name="controls_added_tooltip" msgid="4842812921719153085">"புதிய கட்டுப்பாடுகளைப் பார்க்க பவர் பட்டனைப் பிடித்திருக்கவும்"</string>
     <string name="controls_menu_add" msgid="4447246119229920050">"கட்டுப்பாடுகளைச் சேர்த்தல்"</string>
     <string name="controls_menu_edit" msgid="890623986951347062">"கட்டுப்பாடுகளை மாற்றுதல்"</string>
+    <string name="one_handed_tutorial_title" msgid="6312198435090726656">"ஒற்றைக் கைப் பயன்முறையைப் பயன்படுத்துதல்"</string>
+    <string name="one_handed_tutorial_description" msgid="7674850272340517174">"வெளியேற, திரையின் கீழிருந்து மேல்நோக்கி ஸ்வைப் செய்யவும் அல்லது ஆப்ஸுக்கு மேலே எங்காவது தட்டவும்"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-te/strings.xml b/packages/SystemUI/res/values-te/strings.xml
index dc5ea1f..34ff9b1 100644
--- a/packages/SystemUI/res/values-te/strings.xml
+++ b/packages/SystemUI/res/values-te/strings.xml
@@ -125,7 +125,7 @@
     <string name="accessibility_accessibility_button" msgid="4089042473497107709">"యాక్సెస్ సామర్థ్యం"</string>
     <string name="accessibility_rotate_button" msgid="1238584767612362586">"స్క్రీన్‌ను తిప్పండి"</string>
     <string name="accessibility_recent" msgid="901641734769533575">"ఓవర్‌వ్యూ"</string>
-    <string name="accessibility_search_light" msgid="524741790416076988">"వెతుకు"</string>
+    <string name="accessibility_search_light" msgid="524741790416076988">"సెర్చ్"</string>
     <string name="accessibility_camera_button" msgid="2938898391716647247">"కెమెరా"</string>
     <string name="accessibility_phone_button" msgid="4256353121703100427">"ఫోన్"</string>
     <string name="accessibility_voice_assist_button" msgid="6497706615649754510">"వాయిస్ అసిస్టెంట్"</string>
@@ -441,7 +441,7 @@
     <string name="expanded_header_battery_charging_with_time" msgid="757991461445765011">"పూర్తిగా నిండటానికి <xliff:g id="CHARGING_TIME">%s</xliff:g>"</string>
     <string name="expanded_header_battery_not_charging" msgid="809409140358955848">"ఛార్జ్ కావడం లేదు"</string>
     <string name="ssl_ca_cert_warning" msgid="8373011375250324005">"నెట్‌వర్క్\nపర్యవేక్షించబడవచ్చు"</string>
-    <string name="description_target_search" msgid="3875069993128855865">"శోధించండి"</string>
+    <string name="description_target_search" msgid="3875069993128855865">"సెర్చ్"</string>
     <string name="description_direction_up" msgid="3632251507574121434">"<xliff:g id="TARGET_DESCRIPTION">%s</xliff:g> కోసం పైకి స్లైడ్ చేయండి."</string>
     <string name="description_direction_left" msgid="4762708739096907741">"<xliff:g id="TARGET_DESCRIPTION">%s</xliff:g> కోసం ఎడమవైపుకు స్లైడ్ చేయండి."</string>
     <string name="zen_priority_introduction" msgid="3159291973383796646">"మీరు పేర్కొనే అలారాలు, రిమైండర్‌లు, ఈవెంట్‌లు మరియు కాలర్‌ల నుండి మినహా మరే ఇతర ధ్వనులు మరియు వైబ్రేషన్‌లతో మీకు అంతరాయం కలగదు. మీరు ఇప్పటికీ సంగీతం, వీడియోలు మరియు గేమ్‌లతో సహా మీరు ప్లే చేయడానికి ఎంచుకున్నవి ఏవైనా వింటారు."</string>
@@ -1069,7 +1069,7 @@
     <string name="controls_seeding_in_progress" msgid="3033855341410264148">"సిఫార్సులు లోడ్ అవుతున్నాయి"</string>
     <string name="controls_media_title" msgid="1746947284862928133">"మీడియా"</string>
     <string name="controls_media_close_session" msgid="3957093425905475065">"ప్రస్తుత సెషన్‌ను దాచు."</string>
-    <string name="controls_media_dismiss_button" msgid="4485675693008031646">"దాచు"</string>
+    <string name="controls_media_dismiss_button" msgid="9081375542265132213">"విస్మరించు"</string>
     <string name="controls_media_resume" msgid="1933520684481586053">"కొనసాగించండి"</string>
     <string name="controls_media_settings_button" msgid="5815790345117172504">"సెట్టింగ్‌లు"</string>
     <string name="controls_error_timeout" msgid="794197289772728958">"ఇన్‌యాక్టివ్, యాప్ చెక్ చేయండి"</string>
@@ -1084,4 +1084,6 @@
     <string name="controls_added_tooltip" msgid="4842812921719153085">"కొత్త నియంత్రణలను చూడడానికి పవర్ బటన్‌ని నొక్కి పట్టుకోండి"</string>
     <string name="controls_menu_add" msgid="4447246119229920050">"నియంత్రణలను జోడించండి"</string>
     <string name="controls_menu_edit" msgid="890623986951347062">"నియంత్రణలను ఎడిట్ చేయండి"</string>
+    <string name="one_handed_tutorial_title" msgid="6312198435090726656">"వన్-హ్యాండెడ్ మోడ్‌ను ఉపయోగించడం"</string>
+    <string name="one_handed_tutorial_description" msgid="7674850272340517174">"నిష్క్రమించడానికి, స్క్రీన్ కింది భాగం నుండి పైకి స్వైప్ చేయండి లేదా యాప్ పైన ఎక్కడైనా ట్యాప్ చేయండి"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-television/config.xml b/packages/SystemUI/res/values-television/config.xml
index 1696aab..b2d057b 100644
--- a/packages/SystemUI/res/values-television/config.xml
+++ b/packages/SystemUI/res/values-television/config.xml
@@ -28,7 +28,6 @@
     <string-array name="config_systemUIServiceComponents" translatable="false">
         <item>com.android.systemui.util.NotificationChannels</item>
         <item>com.android.systemui.volume.VolumeUI</item>
-        <item>com.android.systemui.stackdivider.Divider</item>
         <item>com.android.systemui.statusbar.tv.TvStatusBar</item>
         <item>com.android.systemui.usb.StorageNotification</item>
         <item>com.android.systemui.power.PowerUI</item>
@@ -42,5 +41,9 @@
         <item>com.android.systemui.statusbar.notification.InstantAppNotifier</item>
         <item>com.android.systemui.toast.ToastUI</item>
         <item>com.android.systemui.onehanded.OneHandedUI</item>
+        <item>com.android.systemui.wmshell.WMShell</item>
     </string-array>
+
+    <!-- Show a separate icon for low and high volume on the volume dialog -->
+    <bool name="config_showLowMediaVolumeIcon">true</bool>
 </resources>
diff --git a/packages/SystemUI/res/values-television/styles.xml b/packages/SystemUI/res/values-television/styles.xml
index b01c5d8..4cf7034a 100644
--- a/packages/SystemUI/res/values-television/styles.xml
+++ b/packages/SystemUI/res/values-television/styles.xml
@@ -22,4 +22,8 @@
         <item name="android:windowEnterAnimation">@null</item>
         <item name="android:windowExitAnimation">@null</item>
     </style>
+
+    <style name="volume_dialog_theme" parent="qs_theme">
+        <item name="android:colorAccent">@color/tv_volume_dialog_accent</item>
+    </style>
 </resources>
diff --git a/packages/SystemUI/res/values-th/strings.xml b/packages/SystemUI/res/values-th/strings.xml
index ba9fd29..d37be57 100644
--- a/packages/SystemUI/res/values-th/strings.xml
+++ b/packages/SystemUI/res/values-th/strings.xml
@@ -1069,7 +1069,7 @@
     <string name="controls_seeding_in_progress" msgid="3033855341410264148">"กำลังโหลดคำแนะนำ"</string>
     <string name="controls_media_title" msgid="1746947284862928133">"สื่อ"</string>
     <string name="controls_media_close_session" msgid="3957093425905475065">"ซ่อนเซสชันปัจจุบัน"</string>
-    <string name="controls_media_dismiss_button" msgid="4485675693008031646">"ซ่อน"</string>
+    <string name="controls_media_dismiss_button" msgid="9081375542265132213">"ปิด"</string>
     <string name="controls_media_resume" msgid="1933520684481586053">"เล่นต่อ"</string>
     <string name="controls_media_settings_button" msgid="5815790345117172504">"การตั้งค่า"</string>
     <string name="controls_error_timeout" msgid="794197289772728958">"ไม่มีการใช้งาน โปรดตรวจสอบแอป"</string>
@@ -1084,4 +1084,6 @@
     <string name="controls_added_tooltip" msgid="4842812921719153085">"กดปุ่มเปิด/ปิดค้างไว้เพื่อดูตัวควบคุมใหม่ๆ"</string>
     <string name="controls_menu_add" msgid="4447246119229920050">"เพิ่มตัวควบคุม"</string>
     <string name="controls_menu_edit" msgid="890623986951347062">"แก้ไขตัวควบคุม"</string>
+    <string name="one_handed_tutorial_title" msgid="6312198435090726656">"การใช้โหมดมือเดียว"</string>
+    <string name="one_handed_tutorial_description" msgid="7674850272340517174">"หากต้องการออก ให้เลื่อนขึ้นจากด้านล่างของหน้าจอหรือแตะที่ใดก็ได้เหนือแอป"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-tl/strings.xml b/packages/SystemUI/res/values-tl/strings.xml
index 069438f..af474c7 100644
--- a/packages/SystemUI/res/values-tl/strings.xml
+++ b/packages/SystemUI/res/values-tl/strings.xml
@@ -1069,7 +1069,7 @@
     <string name="controls_seeding_in_progress" msgid="3033855341410264148">"Nilo-load ang rekomendasyon"</string>
     <string name="controls_media_title" msgid="1746947284862928133">"Media"</string>
     <string name="controls_media_close_session" msgid="3957093425905475065">"Itago ang kasalukuyang session."</string>
-    <string name="controls_media_dismiss_button" msgid="4485675693008031646">"Itago"</string>
+    <string name="controls_media_dismiss_button" msgid="9081375542265132213">"I-dismiss"</string>
     <string name="controls_media_resume" msgid="1933520684481586053">"Ituloy"</string>
     <string name="controls_media_settings_button" msgid="5815790345117172504">"Mga Setting"</string>
     <string name="controls_error_timeout" msgid="794197289772728958">"Hindi aktibo, tingnan ang app"</string>
@@ -1084,4 +1084,6 @@
     <string name="controls_added_tooltip" msgid="4842812921719153085">"Pindutin nang matagal ang Power button para makita ang mga bagong kontrol"</string>
     <string name="controls_menu_add" msgid="4447246119229920050">"Magdagdag ng mga kontrol"</string>
     <string name="controls_menu_edit" msgid="890623986951347062">"Mag-edit ng mga kontrol"</string>
+    <string name="one_handed_tutorial_title" msgid="6312198435090726656">"Gamit ang one-hand mode"</string>
+    <string name="one_handed_tutorial_description" msgid="7674850272340517174">"Para lumabas, mag-swipe pataas mula sa ibaba ng screen o mag-tap kahit saan sa itaas ng app"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-tr/strings.xml b/packages/SystemUI/res/values-tr/strings.xml
index 649b9af..ee494a2 100644
--- a/packages/SystemUI/res/values-tr/strings.xml
+++ b/packages/SystemUI/res/values-tr/strings.xml
@@ -1069,7 +1069,7 @@
     <string name="controls_seeding_in_progress" msgid="3033855341410264148">"Öneriler yükleniyor"</string>
     <string name="controls_media_title" msgid="1746947284862928133">"Medya"</string>
     <string name="controls_media_close_session" msgid="3957093425905475065">"Mevcut oturumu gizle."</string>
-    <string name="controls_media_dismiss_button" msgid="4485675693008031646">"Gizle"</string>
+    <string name="controls_media_dismiss_button" msgid="9081375542265132213">"Kapat"</string>
     <string name="controls_media_resume" msgid="1933520684481586053">"Devam ettir"</string>
     <string name="controls_media_settings_button" msgid="5815790345117172504">"Ayarlar"</string>
     <string name="controls_error_timeout" msgid="794197289772728958">"Devre dışı, uygulamaya bakın"</string>
@@ -1084,4 +1084,6 @@
     <string name="controls_added_tooltip" msgid="4842812921719153085">"Yeni kontrolleri görmek için Güç düğmesini basılı tutun"</string>
     <string name="controls_menu_add" msgid="4447246119229920050">"Denetim ekle"</string>
     <string name="controls_menu_edit" msgid="890623986951347062">"Denetimleri düzenle"</string>
+    <string name="one_handed_tutorial_title" msgid="6312198435090726656">"Tek el modunu kullanma"</string>
+    <string name="one_handed_tutorial_description" msgid="7674850272340517174">"Çıkmak için ekranın alt kısmından yukarı kaydırın veya uygulamanın üzerinde herhangi bir yere dokunun"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-uk/strings.xml b/packages/SystemUI/res/values-uk/strings.xml
index 6cb3ac8..c444c47 100644
--- a/packages/SystemUI/res/values-uk/strings.xml
+++ b/packages/SystemUI/res/values-uk/strings.xml
@@ -1081,7 +1081,7 @@
     <string name="controls_seeding_in_progress" msgid="3033855341410264148">"Завантаження рекомендацій"</string>
     <string name="controls_media_title" msgid="1746947284862928133">"Медіа"</string>
     <string name="controls_media_close_session" msgid="3957093425905475065">"Приховати поточний сеанс."</string>
-    <string name="controls_media_dismiss_button" msgid="4485675693008031646">"Приховати"</string>
+    <string name="controls_media_dismiss_button" msgid="9081375542265132213">"Закрити"</string>
     <string name="controls_media_resume" msgid="1933520684481586053">"Відновити"</string>
     <string name="controls_media_settings_button" msgid="5815790345117172504">"Налаштування"</string>
     <string name="controls_error_timeout" msgid="794197289772728958">"Неактивно, перейдіть у додаток"</string>
@@ -1096,4 +1096,6 @@
     <string name="controls_added_tooltip" msgid="4842812921719153085">"Утримуйте кнопку живлення, щоб переглянути нові елементи керування"</string>
     <string name="controls_menu_add" msgid="4447246119229920050">"Додати елементи керування"</string>
     <string name="controls_menu_edit" msgid="890623986951347062">"Змінити елементи керування"</string>
+    <string name="one_handed_tutorial_title" msgid="6312198435090726656">"Як користуватись режимом керування однією рукою"</string>
+    <string name="one_handed_tutorial_description" msgid="7674850272340517174">"Щоб вийти, проведіть пальцем вверх від низу екрана або торкніться екрана над додатком"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-ur/strings.xml b/packages/SystemUI/res/values-ur/strings.xml
index 804c334..729ed5a 100644
--- a/packages/SystemUI/res/values-ur/strings.xml
+++ b/packages/SystemUI/res/values-ur/strings.xml
@@ -1069,7 +1069,7 @@
     <string name="controls_seeding_in_progress" msgid="3033855341410264148">"تجاویز لوڈ ہو رہی ہیں"</string>
     <string name="controls_media_title" msgid="1746947284862928133">"میڈیا"</string>
     <string name="controls_media_close_session" msgid="3957093425905475065">"موجودہ سیشن چھپائیں۔"</string>
-    <string name="controls_media_dismiss_button" msgid="4485675693008031646">"چھپائیں"</string>
+    <string name="controls_media_dismiss_button" msgid="9081375542265132213">"برخاست کریں"</string>
     <string name="controls_media_resume" msgid="1933520684481586053">"دوبارہ شروع کریں"</string>
     <string name="controls_media_settings_button" msgid="5815790345117172504">"ترتیبات"</string>
     <string name="controls_error_timeout" msgid="794197289772728958">"غیر فعال، ایپ چیک کریں"</string>
@@ -1084,4 +1084,6 @@
     <string name="controls_added_tooltip" msgid="4842812921719153085">"نئے کنٹرولز دیکھنے کے لیے پاور بٹن کو دبائے رکھیں"</string>
     <string name="controls_menu_add" msgid="4447246119229920050">"کنٹرولز شامل کریں"</string>
     <string name="controls_menu_edit" msgid="890623986951347062">"کنٹرولز میں ترمیم کریں"</string>
+    <string name="one_handed_tutorial_title" msgid="6312198435090726656">"ایک ہاتھ کی وضع کا استعمال کرنا"</string>
+    <string name="one_handed_tutorial_description" msgid="7674850272340517174">"باہر نکلنے کے لئے، اسکرین کے نیچے سے اوپر کی طرف سوائپ کریں یا ایپ کے اوپر کہیں بھی تھپتھپائیں"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-uz/strings.xml b/packages/SystemUI/res/values-uz/strings.xml
index 6193d33..cf1da75 100644
--- a/packages/SystemUI/res/values-uz/strings.xml
+++ b/packages/SystemUI/res/values-uz/strings.xml
@@ -227,7 +227,7 @@
     <string name="data_connection_roaming" msgid="375650836665414797">"Rouming"</string>
     <string name="data_connection_edge" msgid="6316755666481405762">"EDGE"</string>
     <string name="accessibility_data_connection_wifi" msgid="4422160347472742434">"Wi-Fi"</string>
-    <string name="accessibility_no_sim" msgid="1140839832913084973">"SIM karta solinmagan."</string>
+    <string name="accessibility_no_sim" msgid="1140839832913084973">"SIM kartasiz."</string>
     <string name="accessibility_cell_data" msgid="172950885786007392">"Mobil internet"</string>
     <string name="accessibility_cell_data_on" msgid="691666434519443162">"Mobil internet yoniq"</string>
     <string name="cell_data_off_content_description" msgid="9165555931499878044">"Mobil internet yoqilmagan"</string>
@@ -505,7 +505,7 @@
     <string name="media_projection_dialog_text" msgid="1755705274910034772">"<xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> ekranda chiqqan yoki yozib olish va translatsiya vaqtida ijro etilgan barcha axborotlarga ruxsat oladi. Bu axborotlar parollar, toʻlov tafsilotlari, rasmlar, xabarlar va ijro etilgan audiolardan iborat boʻlishi mumkin."</string>
     <string name="media_projection_dialog_service_text" msgid="958000992162214611">"Bu funksiyani taʼminlovchi xizmat ekranda chiqqan yoki yozib olish va translatsiya vaqtida ijro etilgan barcha axborotlarga ruxsat oladi. Bu axborotlar parollar, toʻlov tafsilotlari, rasmlar, xabarlar va ijro etilgan audiolardan iborat boʻlishi mumkin."</string>
     <string name="media_projection_dialog_service_title" msgid="2888507074107884040">"Yozib olish yoki translatsiya boshlansinmi?"</string>
-    <string name="media_projection_dialog_title" msgid="3316063622495360646">"<xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> bilan yozib olinsin yoki translatsiya qilinsinmi?"</string>
+    <string name="media_projection_dialog_title" msgid="3316063622495360646">"<xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> orqali yozib olish yoki translatsiya boshlansinmi?"</string>
     <string name="media_projection_remember_text" msgid="6896767327140422951">"Boshqa ko‘rsatilmasin"</string>
     <string name="clear_all_notifications_text" msgid="348312370303046130">"Hammasini tozalash"</string>
     <string name="manage_notifications_text" msgid="6885645344647733116">"Boshqarish"</string>
@@ -1069,7 +1069,7 @@
     <string name="controls_seeding_in_progress" msgid="3033855341410264148">"Tavsiyalar yuklanmoqda"</string>
     <string name="controls_media_title" msgid="1746947284862928133">"Media"</string>
     <string name="controls_media_close_session" msgid="3957093425905475065">"Joriy seans berkitilsin."</string>
-    <string name="controls_media_dismiss_button" msgid="4485675693008031646">"Berkitish"</string>
+    <string name="controls_media_dismiss_button" msgid="9081375542265132213">"Yopish"</string>
     <string name="controls_media_resume" msgid="1933520684481586053">"Davom etish"</string>
     <string name="controls_media_settings_button" msgid="5815790345117172504">"Sozlamalar"</string>
     <string name="controls_error_timeout" msgid="794197289772728958">"Nofaol. Ilovani tekshiring"</string>
@@ -1084,4 +1084,6 @@
     <string name="controls_added_tooltip" msgid="4842812921719153085">"Yangi boshqaruv elementlari bilan tanishish uchun quvvat tugmasini bosib turing"</string>
     <string name="controls_menu_add" msgid="4447246119229920050">"Element kiritish"</string>
     <string name="controls_menu_edit" msgid="890623986951347062">"Elementlarni tahrirlash"</string>
+    <string name="one_handed_tutorial_title" msgid="6312198435090726656">"Ixcham rejimdan foydalaning"</string>
+    <string name="one_handed_tutorial_description" msgid="7674850272340517174">"Chiqish uchun ekran pastidan tepaga suring yoki ilovaning tepasidagi istalgan joyga bosing."</string>
 </resources>
diff --git a/packages/SystemUI/res/values-vi/strings.xml b/packages/SystemUI/res/values-vi/strings.xml
index 97ffbc6..5794bdf 100644
--- a/packages/SystemUI/res/values-vi/strings.xml
+++ b/packages/SystemUI/res/values-vi/strings.xml
@@ -502,8 +502,8 @@
     <string name="battery_saver_notification_title" msgid="8419266546034372562">"Trình tiết kiệm pin đang bật"</string>
     <string name="battery_saver_notification_text" msgid="2617841636449016951">"Giảm hiệu suất và dữ liệu nền"</string>
     <string name="battery_saver_notification_action_text" msgid="6022091913807026887">"Tắt trình tiết kiệm pin"</string>
-    <string name="media_projection_dialog_text" msgid="1755705274910034772">"<xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> sẽ có quyền truy cập vào tất cả các thông tin hiển thị trên màn hình của bạn hoặc phát từ thiết bị trong khi ghi âm/ghi hình hoặc truyền, bao gồm cả thông tin như mật khẩu, chi tiết thanh toán, ảnh, tin nhắn và âm thanh mà bạn phát."</string>
-    <string name="media_projection_dialog_service_text" msgid="958000992162214611">"Dịch vụ cung cấp chức năng này có quyền truy cập vào tất cả các thông tin hiển thị trên màn hình của bạn hoặc phát từ thiết bị trong khi ghi âm/ghi hình hoặc truyền, bao gồm cả thông tin như mật khẩu, chi tiết thanh toán, ảnh, tin nhắn và âm thanh mà bạn phát."</string>
+    <string name="media_projection_dialog_text" msgid="1755705274910034772">"<xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> sẽ có quyền truy cập vào tất cả các thông tin hiển thị trên màn hình của bạn hoặc phát trên thiết bị của bạn trong khi ghi âm/ghi hình hoặc truyền, bao gồm cả thông tin như mật khẩu, chi tiết thanh toán, ảnh, tin nhắn và âm thanh mà bạn phát."</string>
+    <string name="media_projection_dialog_service_text" msgid="958000992162214611">"Dịch vụ cung cấp chức năng này có quyền truy cập vào tất cả các thông tin hiển thị trên màn hình của bạn hoặc phát trên thiết bị của bạn trong khi ghi âm/ghi hình hoặc truyền, bao gồm cả thông tin như mật khẩu, chi tiết thanh toán, ảnh, tin nhắn và âm thanh mà bạn phát."</string>
     <string name="media_projection_dialog_service_title" msgid="2888507074107884040">"Bắt đầu ghi âm/ghi hình hoặc truyền?"</string>
     <string name="media_projection_dialog_title" msgid="3316063622495360646">"Bắt đầu ghi âm/ghi hình hoặc truyền bằng <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g>?"</string>
     <string name="media_projection_remember_text" msgid="6896767327140422951">"Không hiển thị lại"</string>
@@ -1069,7 +1069,7 @@
     <string name="controls_seeding_in_progress" msgid="3033855341410264148">"Đang tải các đề xuất"</string>
     <string name="controls_media_title" msgid="1746947284862928133">"Nội dung nghe nhìn"</string>
     <string name="controls_media_close_session" msgid="3957093425905475065">"Ẩn phiên hiện tại."</string>
-    <string name="controls_media_dismiss_button" msgid="4485675693008031646">"Ẩn"</string>
+    <string name="controls_media_dismiss_button" msgid="9081375542265132213">"Đóng"</string>
     <string name="controls_media_resume" msgid="1933520684481586053">"Tiếp tục"</string>
     <string name="controls_media_settings_button" msgid="5815790345117172504">"Cài đặt"</string>
     <string name="controls_error_timeout" msgid="794197289772728958">"Không hoạt động, hãy kiểm tra ứng dụng"</string>
@@ -1084,4 +1084,6 @@
     <string name="controls_added_tooltip" msgid="4842812921719153085">"Giữ nút Nguồn để xem các tùy chọn điều khiển mới"</string>
     <string name="controls_menu_add" msgid="4447246119229920050">"Thêm các tùy chọn điều khiển"</string>
     <string name="controls_menu_edit" msgid="890623986951347062">"Chỉnh sửa tùy chọn điều khiển"</string>
+    <string name="one_handed_tutorial_title" msgid="6312198435090726656">"Cách dùng chế độ một tay"</string>
+    <string name="one_handed_tutorial_description" msgid="7674850272340517174">"Để thoát, hãy vuốt lên từ cuối màn hình hoặc nhấn vào vị trí bất kỳ phía trên ứng dụng"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-zh-rCN/strings.xml b/packages/SystemUI/res/values-zh-rCN/strings.xml
index b9b146c..41c132c 100644
--- a/packages/SystemUI/res/values-zh-rCN/strings.xml
+++ b/packages/SystemUI/res/values-zh-rCN/strings.xml
@@ -1069,7 +1069,7 @@
     <string name="controls_seeding_in_progress" msgid="3033855341410264148">"正在加载推荐内容"</string>
     <string name="controls_media_title" msgid="1746947284862928133">"媒体"</string>
     <string name="controls_media_close_session" msgid="3957093425905475065">"隐藏当前会话。"</string>
-    <string name="controls_media_dismiss_button" msgid="4485675693008031646">"隐藏"</string>
+    <string name="controls_media_dismiss_button" msgid="9081375542265132213">"关闭"</string>
     <string name="controls_media_resume" msgid="1933520684481586053">"继续播放"</string>
     <string name="controls_media_settings_button" msgid="5815790345117172504">"设置"</string>
     <string name="controls_error_timeout" msgid="794197289772728958">"无效,请检查应用"</string>
@@ -1084,4 +1084,6 @@
     <string name="controls_added_tooltip" msgid="4842812921719153085">"按住电源按钮即可查看新控件"</string>
     <string name="controls_menu_add" msgid="4447246119229920050">"添加控件"</string>
     <string name="controls_menu_edit" msgid="890623986951347062">"修改控件"</string>
+    <string name="one_handed_tutorial_title" msgid="6312198435090726656">"使用单手模式"</string>
+    <string name="one_handed_tutorial_description" msgid="7674850272340517174">"如需退出,请从屏幕底部向上滑动,或点按应用上方的任意位置"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-zh-rHK/strings.xml b/packages/SystemUI/res/values-zh-rHK/strings.xml
index f2cb185..b2e1b90 100644
--- a/packages/SystemUI/res/values-zh-rHK/strings.xml
+++ b/packages/SystemUI/res/values-zh-rHK/strings.xml
@@ -1069,7 +1069,7 @@
     <string name="controls_seeding_in_progress" msgid="3033855341410264148">"正在載入建議"</string>
     <string name="controls_media_title" msgid="1746947284862928133">"媒體"</string>
     <string name="controls_media_close_session" msgid="3957093425905475065">"隱藏目前的工作階段。"</string>
-    <string name="controls_media_dismiss_button" msgid="4485675693008031646">"隱藏"</string>
+    <string name="controls_media_dismiss_button" msgid="9081375542265132213">"關閉"</string>
     <string name="controls_media_resume" msgid="1933520684481586053">"繼續播放"</string>
     <string name="controls_media_settings_button" msgid="5815790345117172504">"設定"</string>
     <string name="controls_error_timeout" msgid="794197289772728958">"已停用,請檢查應用程式"</string>
@@ -1084,4 +1084,6 @@
     <string name="controls_added_tooltip" msgid="4842812921719153085">"按住「開關」按鈕以查看新控制項"</string>
     <string name="controls_menu_add" msgid="4447246119229920050">"新增控制項"</string>
     <string name="controls_menu_edit" msgid="890623986951347062">"編輯控制項"</string>
+    <string name="one_handed_tutorial_title" msgid="6312198435090726656">"使用單手模式"</string>
+    <string name="one_handed_tutorial_description" msgid="7674850272340517174">"如要退出,請從螢幕底部向上滑動,或輕按應用程式上方的任何位置"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-zh-rTW/strings.xml b/packages/SystemUI/res/values-zh-rTW/strings.xml
index 69b5294..346b1239 100644
--- a/packages/SystemUI/res/values-zh-rTW/strings.xml
+++ b/packages/SystemUI/res/values-zh-rTW/strings.xml
@@ -502,7 +502,7 @@
     <string name="battery_saver_notification_title" msgid="8419266546034372562">"省電模式已開啟"</string>
     <string name="battery_saver_notification_text" msgid="2617841636449016951">"降低效能並限制背景數據傳輸"</string>
     <string name="battery_saver_notification_action_text" msgid="6022091913807026887">"關閉省電模式"</string>
-    <string name="media_projection_dialog_text" msgid="1755705274910034772">"在錄製或投放內容時,「<xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g>」可存取畫面上顯示的任何資訊或裝置播放的任何內容,包括密碼、付款詳情、相片、訊息和你播放的音訊。"</string>
+    <string name="media_projection_dialog_text" msgid="1755705274910034772">"在錄製或投放內容時,<xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g>可存取畫面上顯示的任何資訊或裝置播放的任何內容,包括密碼、付款詳情、相片、訊息和你播放的音訊。"</string>
     <string name="media_projection_dialog_service_text" msgid="958000992162214611">"在錄製或投放內容時,提供這項功能的服務可存取畫面上顯示的任何資訊或裝置播放的任何內容,包括密碼、付款詳情、相片、訊息和你播放的音訊。"</string>
     <string name="media_projection_dialog_service_title" msgid="2888507074107884040">"要開始錄製或投放內容嗎?"</string>
     <string name="media_projection_dialog_title" msgid="3316063622495360646">"要使用「<xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g>」開始錄製或投放內容嗎?"</string>
@@ -1069,7 +1069,7 @@
     <string name="controls_seeding_in_progress" msgid="3033855341410264148">"正在載入建議控制項"</string>
     <string name="controls_media_title" msgid="1746947284862928133">"媒體"</string>
     <string name="controls_media_close_session" msgid="3957093425905475065">"隱藏目前的工作階段。"</string>
-    <string name="controls_media_dismiss_button" msgid="4485675693008031646">"隱藏"</string>
+    <string name="controls_media_dismiss_button" msgid="9081375542265132213">"關閉"</string>
     <string name="controls_media_resume" msgid="1933520684481586053">"繼續播放"</string>
     <string name="controls_media_settings_button" msgid="5815790345117172504">"設定"</string>
     <string name="controls_error_timeout" msgid="794197289772728958">"無效,請查看應用程式"</string>
@@ -1084,4 +1084,6 @@
     <string name="controls_added_tooltip" msgid="4842812921719153085">"按住電源按鈕即可查看新的控制項"</string>
     <string name="controls_menu_add" msgid="4447246119229920050">"新增控制項"</string>
     <string name="controls_menu_edit" msgid="890623986951347062">"編輯控制項"</string>
+    <string name="one_handed_tutorial_title" msgid="6312198435090726656">"使用單手模式"</string>
+    <string name="one_handed_tutorial_description" msgid="7674850272340517174">"如要退出,請從螢幕底部向上滑動,或輕觸應用程式上方的任何位置"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-zu/strings.xml b/packages/SystemUI/res/values-zu/strings.xml
index 5b899f9..5abce7f 100644
--- a/packages/SystemUI/res/values-zu/strings.xml
+++ b/packages/SystemUI/res/values-zu/strings.xml
@@ -1069,7 +1069,7 @@
     <string name="controls_seeding_in_progress" msgid="3033855341410264148">"Ilayisha izincomo"</string>
     <string name="controls_media_title" msgid="1746947284862928133">"Imidiya"</string>
     <string name="controls_media_close_session" msgid="3957093425905475065">"Fihla iseshini yamanje."</string>
-    <string name="controls_media_dismiss_button" msgid="4485675693008031646">"Fihla"</string>
+    <string name="controls_media_dismiss_button" msgid="9081375542265132213">"Cashisa"</string>
     <string name="controls_media_resume" msgid="1933520684481586053">"Qalisa kabusha"</string>
     <string name="controls_media_settings_button" msgid="5815790345117172504">"Izilungiselelo"</string>
     <string name="controls_error_timeout" msgid="794197289772728958">"Akusebenzi, hlola uhlelo lokusebenza"</string>
@@ -1084,4 +1084,6 @@
     <string name="controls_added_tooltip" msgid="4842812921719153085">"Bamba Inkinobho yamandla ukuze ubone izilawuli ezintsha"</string>
     <string name="controls_menu_add" msgid="4447246119229920050">"Engeza Izilawuli"</string>
     <string name="controls_menu_edit" msgid="890623986951347062">"Hlela izilawuli"</string>
+    <string name="one_handed_tutorial_title" msgid="6312198435090726656">"Ukusebenzisa imodi yesandla esisodwa"</string>
+    <string name="one_handed_tutorial_description" msgid="7674850272340517174">"Ukuze uphume, swayipha ngaphezulu kusuka ngezansi kwesikrini noma thepha noma kuphi ngenhla kohlelo lokusebenza"</string>
 </resources>
diff --git a/packages/SystemUI/res/values/attrs.xml b/packages/SystemUI/res/values/attrs.xml
index 84dbd60..a625029 100644
--- a/packages/SystemUI/res/values/attrs.xml
+++ b/packages/SystemUI/res/values/attrs.xml
@@ -159,7 +159,7 @@
 
     <declare-styleable name="UdfpsView">
         <attr name="sensorRadius" format="dimension"/>
-        <attr name="sensorMarginBottom" format="dimension"/>
+        <attr name="sensorCenterY" format="dimension"/>
         <attr name="sensorPressureCoefficient" format="float"/>
         <attr name="sensorTouchAreaCoefficient" format="float"/>
     </declare-styleable>
diff --git a/packages/SystemUI/res/values/colors_tv.xml b/packages/SystemUI/res/values/colors_tv.xml
index 53cd971..cb49918 100644
--- a/packages/SystemUI/res/values/colors_tv.xml
+++ b/packages/SystemUI/res/values/colors_tv.xml
@@ -24,7 +24,11 @@
 
     <!-- Background color for audio recording indicator (G800) -->
     <color name="tv_audio_recording_indicator_background">#FF3C4043</color>
-    <color name="tv_audio_recording_indicator_pulse">#4DFFFFFF</color>
+    <color name="tv_audio_recording_indicator_icon_background">#CC000000</color>
+    <color name="tv_audio_recording_indicator_stroke">#33FFFFFF</color>
 
     <color name="red">#FFCC0000</color>
+    <color name="tv_volume_dialog_background">#E61F232B</color>
+    <color name="tv_volume_dialog_circle">#08FFFFFF</color>
+    <color name="tv_volume_dialog_accent">#FFDADCE0</color>
 </resources>
diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml
index fa620df..130bb4f 100644
--- a/packages/SystemUI/res/values/config.xml
+++ b/packages/SystemUI/res/values/config.xml
@@ -301,7 +301,6 @@
         <item>com.android.systemui.keyguard.KeyguardViewMediator</item>
         <item>com.android.systemui.recents.Recents</item>
         <item>com.android.systemui.volume.VolumeUI</item>
-        <item>com.android.systemui.stackdivider.Divider</item>
         <item>com.android.systemui.statusbar.phone.StatusBar</item>
         <item>com.android.systemui.usb.StorageNotification</item>
         <item>com.android.systemui.power.PowerUI</item>
@@ -323,6 +322,7 @@
         <item>com.android.systemui.accessibility.SystemActions</item>
         <item>com.android.systemui.toast.ToastUI</item>
         <item>com.android.systemui.onehanded.OneHandedUI</item>
+        <item>com.android.systemui.wmshell.WMShell</item>
     </string-array>
 
     <!-- QS tile shape store width. negative implies fill configuration instead of stroke-->
@@ -515,13 +515,11 @@
     <!-- Whether or not to add a "people" notifications section -->
     <bool name="config_usePeopleFiltering">false</bool>
 
-    <!-- Defines the blacklist for system icons.  That is to say, the icons in the status bar that
-         are part of the blacklist are never displayed. Each item in the blacklist must be a string
-         defined in core/res/res/config.xml to properly blacklist the icon.
-
-         TODO: See if we can rename this config variable.
+    <!-- Defines system icons to be excluded from the display. That is to say, the icons in the
+         status bar that are part of this list are never displayed. Each item in the list must be a
+         string defined in core/res/res/config.xml to properly exclude the icon.
      -->
-    <string-array name="config_statusBarIconBlackList" translatable="false">
+    <string-array name="config_statusBarIconsToExclude" translatable="false">
         <item>@*android:string/status_bar_rotate</item>
         <item>@*android:string/status_bar_headset</item>
     </string-array>
@@ -567,13 +565,9 @@
     <!-- If the config font scale is >= this value, potentially adjust the number of columns-->
     <item name="controls_max_columns_adjust_above_font_scale" translatable="false" format="float" type="dimen">1.25</item>
 
-    <!-- One handed mode default offset % of display size -->
-    <fraction name="config_one_handed_offset">50%</fraction>
-
     <!-- Allow one handed to enable round corner -->
     <bool name="config_one_handed_enable_round_corner">true</bool>
 
-    <!-- Animation duration for translating of one handed when trigger / dismiss. -->
-    <integer name="config_one_handed_translate_animation_duration">150</integer>
-
+    <!-- Show a separate icon for low and high volume on the volume dialog -->
+    <bool name="config_showLowMediaVolumeIcon">false</bool>
 </resources>
diff --git a/packages/SystemUI/res/values/config_tv.xml b/packages/SystemUI/res/values/config_tv.xml
index 5cb840f..7451ba8 100644
--- a/packages/SystemUI/res/values/config_tv.xml
+++ b/packages/SystemUI/res/values/config_tv.xml
@@ -22,9 +22,4 @@
     <!-- Whether to enable microphone disclosure indicator
          (com.android.systemui.statusbar.tv.micdisclosure.AudioRecordingDisclosureBar). -->
     <bool name="audio_recording_disclosure_enabled">true</bool>
-
-    <!-- Whether the indicator should expand and show the recording application's label.
-         When disabled (false) the "minimized" indicator would appear on the screen whenever an
-         application is recording, but will not reveal to the user what application this is.  -->
-    <bool name="audio_recording_disclosure_reveal_packages">false</bool>
 </resources>
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index 122fcb2..d12f010 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -1036,6 +1036,11 @@
     <!-- The maximum offset in either direction that icons move to prevent burn-in on AOD. -->
     <dimen name="default_burn_in_prevention_offset">15dp</dimen>
 
+    <!-- The maximum offset for the under-display fingerprint sensor (UDFPS) icon in either
+         direction that elements aer moved to prevent burn-in on AOD-->
+    <dimen name="udfps_burn_in_offset_x">8dp</dimen>
+    <dimen name="udfps_burn_in_offset_y">8dp</dimen>
+
     <dimen name="corner_size">8dp</dimen>
     <dimen name="top_padding">0dp</dimen>
     <dimen name="bottom_padding">48dp</dimen>
@@ -1337,7 +1342,7 @@
     <dimen name="controls_app_divider_height">2dp</dimen>
     <dimen name="controls_app_divider_side_margin">32dp</dimen>
 
-    <dimen name="controls_card_margin">2dp</dimen>
+    <dimen name="controls_card_margin">@dimen/control_base_item_margin</dimen>
     <item name="control_card_elevation" type="dimen" format="float">15</item>
 
     <dimen name="controls_dialog_padding">32dp</dimen>
diff --git a/packages/SystemUI/res/values/donottranslate.xml b/packages/SystemUI/res/values/donottranslate.xml
index 67293c5..f05be06 100644
--- a/packages/SystemUI/res/values/donottranslate.xml
+++ b/packages/SystemUI/res/values/donottranslate.xml
@@ -21,5 +21,5 @@
     <string name="system_ui_date_pattern" translatable="false">@*android:string/system_ui_date_pattern</string>
 
     <!-- Date format for the always on display.  -->
-    <item type="string" name="system_ui_aod_date_pattern" translatable="false">eeeMMMd</item>
+    <item type="string" name="system_ui_aod_date_pattern" translatable="false">EEEMMMd</item>
 </resources>
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 77d3f45..bfa7532 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -278,14 +278,10 @@
     <string name="screenrecord_cancel_label">Cancel</string>
     <!-- Label for notification action to share screen recording [CHAR LIMIT=35] -->
     <string name="screenrecord_share_label">Share</string>
-    <!-- Label for notification action to delete a screen recording file [CHAR LIMIT=35] -->
-    <string name="screenrecord_delete_label">Delete</string>
     <!-- A toast message shown after successfully canceling a screen recording [CHAR LIMIT=NONE] -->
     <string name="screenrecord_cancel_success">Screen recording canceled</string>
     <!-- Notification text shown after saving a screen recording to prompt the user to view it [CHAR LIMIT=100] -->
     <string name="screenrecord_save_message">Screen recording saved, tap to view</string>
-    <!-- A toast message shown after successfully deleting a screen recording [CHAR LIMIT=NONE] -->
-    <string name="screenrecord_delete_description">Screen recording deleted</string>
     <!-- A toast message shown when there is an error deleting a screen recording [CHAR LIMIT=NONE] -->
     <string name="screenrecord_delete_error">Error deleting screen recording</string>
     <!-- A toast message shown when the screen recording cannot be started due to insufficient permissions [CHAR LIMIT=NONE] -->
@@ -2799,7 +2795,7 @@
     <!-- Explanation for closing controls associated with a specific media session [CHAR_LIMIT=NONE] -->
     <string name="controls_media_close_session">Hide the current session.</string>
     <!-- Label for a button that will hide media controls [CHAR_LIMIT=30] -->
-    <string name="controls_media_dismiss_button">Hide</string>
+    <string name="controls_media_dismiss_button">Dismiss</string>
     <!-- Label for button to resume media playback [CHAR_LIMIT=NONE] -->
     <string name="controls_media_resume">Resume</string>
     <!-- Label for button to go to media control settings screen [CHAR_LIMIT=30] -->
diff --git a/packages/SystemUI/res/values/styles.xml b/packages/SystemUI/res/values/styles.xml
index 9e5b94e..ee07e61 100644
--- a/packages/SystemUI/res/values/styles.xml
+++ b/packages/SystemUI/res/values/styles.xml
@@ -387,6 +387,9 @@
         <item name="android:homeAsUpIndicator">@drawable/ic_arrow_back</item>
     </style>
 
+    <!-- Overridden by values-television/styles.xml with tv-specific settings -->
+    <style name="volume_dialog_theme" parent="qs_theme"/>
+
     <style name="systemui_theme_remote_input" parent="@android:style/Theme.DeviceDefault.Light">
         <item name="android:colorAccent">@color/remote_input_accent</item>
     </style>
diff --git a/media/java/android/media/IStrategyPreferredDeviceDispatcher.aidl b/packages/SystemUI/shared/src/com/android/systemui/shared/system/ContextUtils.java
similarity index 66%
copy from media/java/android/media/IStrategyPreferredDeviceDispatcher.aidl
copy to packages/SystemUI/shared/src/com/android/systemui/shared/system/ContextUtils.java
index b1f99e6..1de740a 100644
--- a/media/java/android/media/IStrategyPreferredDeviceDispatcher.aidl
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/ContextUtils.java
@@ -14,17 +14,15 @@
  * limitations under the License.
  */
 
-package android.media;
+package com.android.systemui.shared.system;
 
-import android.media.AudioDeviceAttributes;
+import android.annotation.UserIdInt;
+import android.content.Context;
 
-/**
- * AIDL for AudioService to signal audio strategy-preferred device updates.
- *
- * {@hide}
- */
-oneway interface IStrategyPreferredDeviceDispatcher {
+public class ContextUtils {
 
-    void dispatchPrefDeviceChanged(int strategyId, in AudioDeviceAttributes device);
-
+    /** Get the user associated with this context */
+    public static @UserIdInt int getUserId(Context context) {
+        return context.getUserId();
+    }
 }
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/WallpaperEngineCompat.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/WallpaperEngineCompat.java
index 4d968f1..73dc60d 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/WallpaperEngineCompat.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/WallpaperEngineCompat.java
@@ -26,6 +26,17 @@
 
     private static final String TAG = "WallpaperEngineCompat";
 
+    /**
+     * Returns true if {@link IWallpaperEngine#scalePreview(Rect)} is available.
+     */
+    public static boolean supportsScalePreview() {
+        try {
+            return IWallpaperEngine.class.getMethod("scalePreview", Rect.class) != null;
+        } catch (NoSuchMethodException | SecurityException e) {
+            return false;
+        }
+    }
+
     private final IWallpaperEngine mWrappedEngine;
 
     public WallpaperEngineCompat(IWallpaperEngine wrappedEngine) {
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardDisplayManager.java b/packages/SystemUI/src/com/android/keyguard/KeyguardDisplayManager.java
index d6fabd6..6f19613 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardDisplayManager.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardDisplayManager.java
@@ -36,8 +36,8 @@
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.systemui.Dependency;
 import com.android.systemui.R;
-import com.android.systemui.statusbar.NavigationBarController;
-import com.android.systemui.statusbar.phone.NavigationBarView;
+import com.android.systemui.navigationbar.NavigationBarController;
+import com.android.systemui.navigationbar.NavigationBarView;
 import com.android.systemui.util.InjectionInflationController;
 
 public class KeyguardDisplayManager {
@@ -54,9 +54,6 @@
 
     private final SparseArray<Presentation> mPresentations = new SparseArray<>();
 
-    private final NavigationBarController mNavBarController =
-            Dependency.get(NavigationBarController.class);
-
     private final DisplayManager.DisplayListener mDisplayListener =
             new DisplayManager.DisplayListener() {
 
@@ -227,7 +224,8 @@
         // Leave this task to {@link StatusBarKeyguardViewManager}
         if (displayId == DEFAULT_DISPLAY) return;
 
-        NavigationBarView navBarView = mNavBarController.getNavigationBarView(displayId);
+        NavigationBarView navBarView = Dependency.get(NavigationBarController.class)
+                .getNavigationBarView(displayId);
         // We may not have nav bar on a display.
         if (navBarView == null) return;
 
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java
index 1db2e32..b048333 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java
@@ -256,7 +256,7 @@
         mUpdateMonitor = Dependency.get(KeyguardUpdateMonitor.class);
         mSpringAnimation = new SpringAnimation(this, DynamicAnimation.Y);
         mInjectionInflationController =  new InjectionInflationController(
-            SystemUIFactory.getInstance().getRootComponent());
+            SystemUIFactory.getInstance().getSysUIComponent().createViewInstanceCreatorFactory());
         mViewConfiguration = ViewConfiguration.get(context);
         mKeyguardStateController = Dependency.get(KeyguardStateController.class);
         mSecondaryLockScreenController = new AdminSecondaryLockScreenController(context, this,
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityModel.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityModel.java
index 17abfae..ac2160e 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityModel.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityModel.java
@@ -24,11 +24,11 @@
 
 import com.android.internal.widget.LockPatternUtils;
 import com.android.systemui.Dependency;
+import com.android.systemui.dagger.SysUISingleton;
 
 import javax.inject.Inject;
-import javax.inject.Singleton;
 
-@Singleton
+@SysUISingleton
 public class KeyguardSecurityModel {
 
     /**
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
index 878947f..c354241 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
@@ -94,6 +94,7 @@
 import com.android.systemui.Dumpable;
 import com.android.systemui.R;
 import com.android.systemui.broadcast.BroadcastDispatcher;
+import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dagger.qualifiers.Background;
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.dump.DumpManager;
@@ -124,14 +125,13 @@
 import java.util.function.Consumer;
 
 import javax.inject.Inject;
-import javax.inject.Singleton;
 
 /**
  * Watches for updates that may be interesting to the keyguard, and provides
  * the up to date information as well as a registration for callbacks that care
  * to be updated.
  */
-@Singleton
+@SysUISingleton
 public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpable {
 
     private static final String TAG = "KeyguardUpdateMonitor";
diff --git a/packages/SystemUI/src/com/android/keyguard/clock/ClockManager.java b/packages/SystemUI/src/com/android/keyguard/clock/ClockManager.java
index 2200b22..3775628 100644
--- a/packages/SystemUI/src/com/android/keyguard/clock/ClockManager.java
+++ b/packages/SystemUI/src/com/android/keyguard/clock/ClockManager.java
@@ -34,6 +34,7 @@
 
 import com.android.systemui.broadcast.BroadcastDispatcher;
 import com.android.systemui.colorextraction.SysuiColorExtractor;
+import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dock.DockManager;
 import com.android.systemui.dock.DockManager.DockEventListener;
 import com.android.systemui.plugins.ClockPlugin;
@@ -50,12 +51,11 @@
 import java.util.function.Supplier;
 
 import javax.inject.Inject;
-import javax.inject.Singleton;
 
 /**
  * Manages custom clock faces for AOD and lock screen.
  */
-@Singleton
+@SysUISingleton
 public final class ClockManager {
 
     private static final String TAG = "ClockOptsProvider";
diff --git a/packages/SystemUI/src/com/android/systemui/ActivityIntentHelper.java b/packages/SystemUI/src/com/android/systemui/ActivityIntentHelper.java
index 63840bc..43b3929 100644
--- a/packages/SystemUI/src/com/android/systemui/ActivityIntentHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/ActivityIntentHelper.java
@@ -22,15 +22,16 @@
 import android.content.pm.PackageManager;
 import android.content.pm.ResolveInfo;
 
+import com.android.systemui.dagger.SysUISingleton;
+
 import java.util.List;
 
 import javax.inject.Inject;
-import javax.inject.Singleton;
 
 /**
  * Contains useful methods for querying properties of an Activity Intent.
  */
-@Singleton
+@SysUISingleton
 public class ActivityIntentHelper {
 
     private final Context mContext;
diff --git a/packages/SystemUI/src/com/android/systemui/ActivityStarterDelegate.java b/packages/SystemUI/src/com/android/systemui/ActivityStarterDelegate.java
index 47a10af..3d6d381 100644
--- a/packages/SystemUI/src/com/android/systemui/ActivityStarterDelegate.java
+++ b/packages/SystemUI/src/com/android/systemui/ActivityStarterDelegate.java
@@ -18,13 +18,13 @@
 import android.content.Intent;
 import android.view.View;
 
+import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.plugins.ActivityStarter;
 import com.android.systemui.statusbar.phone.StatusBar;
 
 import java.util.Optional;
 
 import javax.inject.Inject;
-import javax.inject.Singleton;
 
 import dagger.Lazy;
 
@@ -33,7 +33,7 @@
  * delegates to an actual implementation (StatusBar).
  */
 @SuppressWarnings("OptionalUsedAsFieldOrParameterType")
-@Singleton
+@SysUISingleton
 public class ActivityStarterDelegate implements ActivityStarter {
 
     private Optional<Lazy<StatusBar>> mActualStarter;
diff --git a/packages/SystemUI/src/com/android/systemui/BootCompleteCacheImpl.kt b/packages/SystemUI/src/com/android/systemui/BootCompleteCacheImpl.kt
index aef1872..9eaf4c9 100644
--- a/packages/SystemUI/src/com/android/systemui/BootCompleteCacheImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/BootCompleteCacheImpl.kt
@@ -18,13 +18,13 @@
 
 import android.util.Log
 import com.android.internal.annotations.GuardedBy
+import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dump.DumpManager
 import java.io.FileDescriptor
 import java.io.PrintWriter
 import java.lang.ref.WeakReference
 import java.util.concurrent.atomic.AtomicBoolean
 import javax.inject.Inject
-import javax.inject.Singleton
 
 /**
  * Caches whether the device has reached [SystemService.PHASE_BOOT_COMPLETED].
@@ -32,7 +32,7 @@
  * This class is constructed and set by [SystemUIApplication] and will notify all listeners when
  * boot is completed.
  */
-@Singleton
+@SysUISingleton
 class BootCompleteCacheImpl @Inject constructor(dumpManager: DumpManager) :
         BootCompleteCache, Dumpable {
 
diff --git a/packages/SystemUI/src/com/android/systemui/DemoMode.java b/packages/SystemUI/src/com/android/systemui/DemoMode.java
deleted file mode 100644
index 5c39715..0000000
--- a/packages/SystemUI/src/com/android/systemui/DemoMode.java
+++ /dev/null
@@ -1,41 +0,0 @@
-/*
- * Copyright (C) 2013 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui;
-
-import android.os.Bundle;
-
-public interface DemoMode {
-
-    public static final String DEMO_MODE_ALLOWED = "sysui_demo_allowed";
-
-    void dispatchDemoCommand(String command, Bundle args);
-
-    public static final String ACTION_DEMO = "com.android.systemui.demo";
-
-    public static final String EXTRA_COMMAND = "command";
-
-    public static final String COMMAND_ENTER = "enter";
-    public static final String COMMAND_EXIT = "exit";
-    public static final String COMMAND_CLOCK = "clock";
-    public static final String COMMAND_BATTERY = "battery";
-    public static final String COMMAND_NETWORK = "network";
-    public static final String COMMAND_BARS = "bars";
-    public static final String COMMAND_STATUS = "status";
-    public static final String COMMAND_NOTIFICATIONS = "notifications";
-    public static final String COMMAND_VOLUME = "volume";
-    public static final String COMMAND_OPERATOR = "operator";
-}
diff --git a/packages/SystemUI/src/com/android/systemui/Dependency.java b/packages/SystemUI/src/com/android/systemui/Dependency.java
index 58f8c07..27809b5 100644
--- a/packages/SystemUI/src/com/android/systemui/Dependency.java
+++ b/packages/SystemUI/src/com/android/systemui/Dependency.java
@@ -39,6 +39,7 @@
 import com.android.systemui.broadcast.BroadcastDispatcher;
 import com.android.systemui.bubbles.BubbleController;
 import com.android.systemui.colorextraction.SysuiColorExtractor;
+import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dagger.qualifiers.Background;
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.dock.DockManager;
@@ -47,6 +48,8 @@
 import com.android.systemui.keyguard.ScreenLifecycle;
 import com.android.systemui.keyguard.WakefulnessLifecycle;
 import com.android.systemui.model.SysUiState;
+import com.android.systemui.navigationbar.NavigationBarController;
+import com.android.systemui.navigationbar.NavigationModeController;
 import com.android.systemui.plugins.ActivityStarter;
 import com.android.systemui.plugins.DarkIconDispatcher;
 import com.android.systemui.plugins.PluginDependencyProvider;
@@ -62,20 +65,19 @@
 import com.android.systemui.shared.system.ActivityManagerWrapper;
 import com.android.systemui.shared.system.DevicePolicyManagerWrapper;
 import com.android.systemui.shared.system.PackageManagerWrapper;
-import com.android.systemui.stackdivider.Divider;
 import com.android.systemui.statusbar.CommandQueue;
-import com.android.systemui.statusbar.NavigationBarController;
 import com.android.systemui.statusbar.NotificationListener;
 import com.android.systemui.statusbar.NotificationLockscreenUserManager;
 import com.android.systemui.statusbar.NotificationMediaManager;
 import com.android.systemui.statusbar.NotificationRemoteInputManager;
+import com.android.systemui.statusbar.NotificationShadeWindowController;
 import com.android.systemui.statusbar.NotificationViewHierarchyManager;
 import com.android.systemui.statusbar.SmartReplyController;
 import com.android.systemui.statusbar.VibratorHelper;
 import com.android.systemui.statusbar.notification.NotificationEntryManager;
 import com.android.systemui.statusbar.notification.NotificationEntryManager.KeyguardEnvironment;
 import com.android.systemui.statusbar.notification.NotificationFilter;
-import com.android.systemui.statusbar.notification.VisualStabilityManager;
+import com.android.systemui.statusbar.notification.collection.legacy.VisualStabilityManager;
 import com.android.systemui.statusbar.notification.logging.NotificationLogger;
 import com.android.systemui.statusbar.notification.row.NotificationBlockingHelperManager;
 import com.android.systemui.statusbar.notification.row.NotificationGutsManager;
@@ -85,10 +87,8 @@
 import com.android.systemui.statusbar.phone.LightBarController;
 import com.android.systemui.statusbar.phone.LockscreenGestureLogger;
 import com.android.systemui.statusbar.phone.ManagedProfileController;
-import com.android.systemui.statusbar.phone.NavigationModeController;
 import com.android.systemui.statusbar.phone.NotificationGroupAlertTransferHelper;
 import com.android.systemui.statusbar.phone.NotificationGroupManager;
-import com.android.systemui.statusbar.phone.NotificationShadeWindowController;
 import com.android.systemui.statusbar.phone.ShadeController;
 import com.android.systemui.statusbar.phone.StatusBar;
 import com.android.systemui.statusbar.phone.StatusBarIconController;
@@ -131,7 +131,6 @@
 
 import javax.inject.Inject;
 import javax.inject.Named;
-import javax.inject.Singleton;
 
 import dagger.Lazy;
 
@@ -150,7 +149,7 @@
  * they have no clients they should not have any registered resources like bound
  * services, registered receivers, etc.
  */
-@Singleton
+@SysUISingleton
 public class Dependency {
     /**
      * Key for getting a the main looper.
@@ -324,7 +323,6 @@
     @Inject Lazy<DisplayImeController> mDisplayImeController;
     @Inject Lazy<RecordingController> mRecordingController;
     @Inject Lazy<ProtoTracer> mProtoTracer;
-    @Inject Lazy<Divider> mDivider;
 
     @Inject
     public Dependency() {
@@ -521,7 +519,6 @@
         mProviders.put(AutoHideController.class, mAutoHideController::get);
 
         mProviders.put(RecordingController.class, mRecordingController::get);
-        mProviders.put(Divider.class, mDivider::get);
 
         Dependency.setInstance(this);
     }
diff --git a/packages/SystemUI/src/com/android/systemui/ExpandHelper.java b/packages/SystemUI/src/com/android/systemui/ExpandHelper.java
index 59af458..17b840cc 100644
--- a/packages/SystemUI/src/com/android/systemui/ExpandHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/ExpandHelper.java
@@ -162,8 +162,8 @@
      *
      * @param context application context
      * @param callback the container that holds the items to be manipulated
-     * @param small the smallest allowable size for the manuipulated items.
-     * @param large the largest allowable size for the manuipulated items.
+     * @param small the smallest allowable size for the manipulated items.
+     * @param large the largest allowable size for the manipulated items.
      */
     public ExpandHelper(Context context, Callback callback, int small, int large) {
         mSmallSize = small;
diff --git a/packages/SystemUI/src/com/android/systemui/ForegroundServiceController.java b/packages/SystemUI/src/com/android/systemui/ForegroundServiceController.java
index 2deeb12..d859a63 100644
--- a/packages/SystemUI/src/com/android/systemui/ForegroundServiceController.java
+++ b/packages/SystemUI/src/com/android/systemui/ForegroundServiceController.java
@@ -24,36 +24,26 @@
 
 import com.android.internal.messages.nano.SystemMessageProto;
 import com.android.systemui.appops.AppOpsController;
+import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dagger.qualifiers.Main;
-import com.android.systemui.statusbar.notification.NotificationEntryManager;
-import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 import com.android.systemui.util.Assert;
 
-import java.util.Set;
-
 import javax.inject.Inject;
-import javax.inject.Singleton;
 
 /**
  * Tracks state of foreground services and notifications related to foreground services per user.
  */
-@Singleton
+@SysUISingleton
 public class ForegroundServiceController {
-    public static final int[] APP_OPS = new int[] {AppOpsManager.OP_CAMERA,
-            AppOpsManager.OP_SYSTEM_ALERT_WINDOW,
-            AppOpsManager.OP_RECORD_AUDIO,
-            AppOpsManager.OP_COARSE_LOCATION,
-            AppOpsManager.OP_FINE_LOCATION};
+    public static final int[] APP_OPS = new int[] {AppOpsManager.OP_SYSTEM_ALERT_WINDOW};
 
     private final SparseArray<ForegroundServicesUserState> mUserServices = new SparseArray<>();
     private final Object mMutex = new Object();
-    private final NotificationEntryManager mEntryManager;
     private final Handler mMainHandler;
 
     @Inject
-    public ForegroundServiceController(NotificationEntryManager entryManager,
-            AppOpsController appOpsController, @Main Handler mainHandler) {
-        mEntryManager = entryManager;
+    public ForegroundServiceController(AppOpsController appOpsController,
+            @Main Handler mainHandler) {
         mMainHandler = mainHandler;
         appOpsController.addCallback(APP_OPS, (code, uid, packageName, active) -> {
             mMainHandler.post(() -> {
@@ -87,19 +77,6 @@
     }
 
     /**
-     * Returns the keys for notifications from this package using the standard template,
-     * if they exist.
-     */
-    @Nullable
-    public ArraySet<String> getStandardLayoutKeys(int userId, String pkg) {
-        synchronized (mMutex) {
-            final ForegroundServicesUserState services = mUserServices.get(userId);
-            if (services == null) return null;
-            return services.getStandardLayoutKeys(pkg);
-        }
-    }
-
-    /**
      * Gets active app ops for this user and package
      */
     @Nullable
@@ -140,31 +117,6 @@
                 userServices.removeOp(packageName, appOpCode);
             }
         }
-
-        // TODO: (b/145659174) remove when moving to NewNotifPipeline. Replaced by
-        //  AppOpsCoordinator
-        // Update appOps if there are associated pending or visible notifications
-        final Set<String> notificationKeys = getStandardLayoutKeys(userId, packageName);
-        if (notificationKeys != null) {
-            boolean changed = false;
-            for (String key : notificationKeys) {
-                final NotificationEntry entry = mEntryManager.getPendingOrActiveNotif(key);
-                if (entry != null
-                        && uid == entry.getSbn().getUid()
-                        && packageName.equals(entry.getSbn().getPackageName())) {
-                    synchronized (entry.mActiveAppOps) {
-                        if (active) {
-                            changed |= entry.mActiveAppOps.add(appOpCode);
-                        } else {
-                            changed |= entry.mActiveAppOps.remove(appOpCode);
-                        }
-                    }
-                }
-            }
-            if (changed) {
-                mEntryManager.updateNotifications("appOpChanged pkg=" + packageName);
-            }
-        }
     }
 
     /**
diff --git a/packages/SystemUI/src/com/android/systemui/ForegroundServiceNotificationListener.java b/packages/SystemUI/src/com/android/systemui/ForegroundServiceNotificationListener.java
index bb44583..d6cb114 100644
--- a/packages/SystemUI/src/com/android/systemui/ForegroundServiceNotificationListener.java
+++ b/packages/SystemUI/src/com/android/systemui/ForegroundServiceNotificationListener.java
@@ -21,10 +21,10 @@
 import android.content.Context;
 import android.os.Bundle;
 import android.service.notification.StatusBarNotification;
-import android.util.ArraySet;
 import android.util.Log;
 
 import com.android.internal.statusbar.NotificationVisibility;
+import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.statusbar.notification.NotificationEntryListener;
 import com.android.systemui.statusbar.notification.NotificationEntryManager;
 import com.android.systemui.statusbar.notification.collection.NotifPipeline;
@@ -33,10 +33,9 @@
 import com.android.systemui.util.time.SystemClock;
 
 import javax.inject.Inject;
-import javax.inject.Singleton;
 
 /** Updates foreground service notification state in response to notification data events. */
-@Singleton
+@SysUISingleton
 public class ForegroundServiceNotificationListener {
 
     private static final String TAG = "FgServiceController";
@@ -172,24 +171,8 @@
                                     sbn.getPackageName(), sbn.getKey());
                         }
                     }
-                    tagAppOps(entry);
                     return true;
                 },
                 true /* create if not found */);
     }
-
-    // TODO: (b/145659174) remove when moving to NewNotifPipeline. Replaced by
-    //  AppOpsCoordinator
-    private void tagAppOps(NotificationEntry entry) {
-        final StatusBarNotification sbn = entry.getSbn();
-        ArraySet<Integer> activeOps = mForegroundServiceController.getAppOps(
-                sbn.getUserId(),
-                sbn.getPackageName());
-        synchronized (entry.mActiveAppOps) {
-            entry.mActiveAppOps.clear();
-            if (activeOps != null) {
-                entry.mActiveAppOps.addAll(activeOps);
-            }
-        }
-    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/InitController.java b/packages/SystemUI/src/com/android/systemui/InitController.java
index a2dd123..9bb576b 100644
--- a/packages/SystemUI/src/com/android/systemui/InitController.java
+++ b/packages/SystemUI/src/com/android/systemui/InitController.java
@@ -14,16 +14,17 @@
 
 package com.android.systemui;
 
+import com.android.systemui.dagger.SysUISingleton;
+
 import java.util.ArrayList;
 
 import javax.inject.Inject;
-import javax.inject.Singleton;
 
 /**
  * Created by {@link Dependency} on SystemUI startup. Add tasks which need to be executed only
  * after all other dependencies have been created.
  */
-@Singleton
+@SysUISingleton
 public class InitController {
 
     /**
diff --git a/packages/SystemUI/src/com/android/systemui/LatencyTester.java b/packages/SystemUI/src/com/android/systemui/LatencyTester.java
index dc0cb03..9c5a40c 100644
--- a/packages/SystemUI/src/com/android/systemui/LatencyTester.java
+++ b/packages/SystemUI/src/com/android/systemui/LatencyTester.java
@@ -30,16 +30,16 @@
 import com.android.internal.util.LatencyTracker;
 import com.android.keyguard.KeyguardUpdateMonitor;
 import com.android.systemui.broadcast.BroadcastDispatcher;
+import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.statusbar.phone.BiometricUnlockController;
 
 import javax.inject.Inject;
-import javax.inject.Singleton;
 
 /**
  * Class that only runs on debuggable builds that listens to broadcasts that simulate actions in the
  * system that are used for testing the latency.
  */
-@Singleton
+@SysUISingleton
 public class LatencyTester extends SystemUI {
 
     private static final String
diff --git a/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java b/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java
index ad11d71..f663d1a 100644
--- a/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java
+++ b/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java
@@ -83,6 +83,7 @@
 import com.android.internal.util.Preconditions;
 import com.android.systemui.RegionInterceptingFrameLayout.RegionInterceptableView;
 import com.android.systemui.broadcast.BroadcastDispatcher;
+import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.qs.SecureSetting;
 import com.android.systemui.tuner.TunerService;
@@ -92,13 +93,12 @@
 import java.util.List;
 
 import javax.inject.Inject;
-import javax.inject.Singleton;
 
 /**
  * An overlay that draws screen decorations in software (e.g for rounded corners or display cutout)
  * for antialiasing and emulation purposes.
  */
-@Singleton
+@SysUISingleton
 public class ScreenDecorations extends SystemUI implements Tunable {
     private static final boolean DEBUG = false;
     private static final String TAG = "ScreenDecorations";
diff --git a/packages/SystemUI/src/com/android/systemui/SizeCompatModeActivityController.java b/packages/SystemUI/src/com/android/systemui/SizeCompatModeActivityController.java
index 73295a3..34efa35 100644
--- a/packages/SystemUI/src/com/android/systemui/SizeCompatModeActivityController.java
+++ b/packages/SystemUI/src/com/android/systemui/SizeCompatModeActivityController.java
@@ -41,6 +41,7 @@
 import android.widget.PopupWindow;
 
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.shared.system.ActivityManagerWrapper;
 import com.android.systemui.shared.system.TaskStackChangeListener;
 import com.android.systemui.statusbar.CommandQueue;
@@ -48,10 +49,9 @@
 import java.lang.ref.WeakReference;
 
 import javax.inject.Inject;
-import javax.inject.Singleton;
 
 /** Shows a restart-activity button when the foreground activity is in size compatibility mode. */
-@Singleton
+@SysUISingleton
 public class SizeCompatModeActivityController extends SystemUI implements CommandQueue.Callbacks {
     private static final String TAG = "SizeCompatMode";
 
diff --git a/packages/SystemUI/src/com/android/systemui/SliceBroadcastRelayHandler.java b/packages/SystemUI/src/com/android/systemui/SliceBroadcastRelayHandler.java
index 7a4ef2b..0b997d0 100644
--- a/packages/SystemUI/src/com/android/systemui/SliceBroadcastRelayHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/SliceBroadcastRelayHandler.java
@@ -29,15 +29,15 @@
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.settingslib.SliceBroadcastRelay;
 import com.android.systemui.broadcast.BroadcastDispatcher;
+import com.android.systemui.dagger.SysUISingleton;
 
 import javax.inject.Inject;
-import javax.inject.Singleton;
 
 /**
  * Allows settings to register certain broadcasts to launch the settings app for pinned slices.
  * @see SliceBroadcastRelay
  */
-@Singleton
+@SysUISingleton
 public class SliceBroadcastRelayHandler extends SystemUI {
     private static final String TAG = "SliceBroadcastRelay";
     private static final boolean DEBUG = false;
diff --git a/packages/SystemUI/src/com/android/systemui/SlicePermissionActivity.java b/packages/SystemUI/src/com/android/systemui/SlicePermissionActivity.java
index 449ed8c..2365f12 100644
--- a/packages/SystemUI/src/com/android/systemui/SlicePermissionActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/SlicePermissionActivity.java
@@ -16,6 +16,7 @@
 
 import static android.view.WindowManager.LayoutParams.SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS;
 
+import android.annotation.Nullable;
 import android.app.Activity;
 import android.app.AlertDialog;
 import android.app.slice.SliceManager;
@@ -29,6 +30,7 @@
 import android.net.Uri;
 import android.os.Bundle;
 import android.text.BidiFormatter;
+import android.util.EventLog;
 import android.util.Log;
 import android.widget.CheckBox;
 import android.widget.TextView;
@@ -50,10 +52,17 @@
 
         mUri = getIntent().getParcelableExtra(SliceProvider.EXTRA_BIND_URI);
         mCallingPkg = getIntent().getStringExtra(SliceProvider.EXTRA_PKG);
-        mProviderPkg = getIntent().getStringExtra(SliceProvider.EXTRA_PROVIDER_PKG);
+        if (mUri == null) {
+            Log.e(TAG, SliceProvider.EXTRA_BIND_URI + " wasn't provided");
+            finish();
+            return;
+        }
 
         try {
             PackageManager pm = getPackageManager();
+            mProviderPkg = pm.resolveContentProvider(mUri.getAuthority(),
+                    PackageManager.GET_META_DATA).applicationInfo.packageName;
+            verifyCallingPkg();
             CharSequence app1 = BidiFormatter.getInstance().unicodeWrap(pm.getApplicationInfo(
                     mCallingPkg, 0).loadSafeLabel(pm, PackageItemInfo.DEFAULT_MAX_LABEL_SIZE_PX,
                     PackageItemInfo.SAFE_LABEL_FLAG_TRIM
@@ -97,4 +106,29 @@
     public void onDismiss(DialogInterface dialog) {
         finish();
     }
+
+    private void verifyCallingPkg() {
+        final String providerPkg = getIntent().getStringExtra("provider_pkg");
+        if (providerPkg == null || mProviderPkg.equals(providerPkg)) return;
+        final String callingPkg = getCallingPkg();
+        EventLog.writeEvent(0x534e4554, "159145361", getUid(callingPkg), String.format(
+                "pkg %s (disguised as %s) attempted to request permission to show %s slices in %s",
+                callingPkg, providerPkg, mProviderPkg, mCallingPkg));
+    }
+
+    @Nullable
+    private String getCallingPkg() {
+        final Uri referrer = getReferrer();
+        if (referrer == null) return null;
+        return referrer.getHost();
+    }
+
+    private int getUid(@Nullable final String pkg) {
+        if (pkg == null) return -1;
+        try {
+            return getPackageManager().getApplicationInfo(pkg, 0).uid;
+        } catch (NameNotFoundException e) {
+        }
+        return -1;
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/SystemUIAppComponentFactory.java b/packages/SystemUI/src/com/android/systemui/SystemUIAppComponentFactory.java
index cacbf96..bb7906e 100644
--- a/packages/SystemUI/src/com/android/systemui/SystemUIAppComponentFactory.java
+++ b/packages/SystemUI/src/com/android/systemui/SystemUIAppComponentFactory.java
@@ -29,6 +29,10 @@
 import androidx.core.app.AppComponentFactory;
 
 import com.android.systemui.dagger.ContextComponentHelper;
+import com.android.systemui.dagger.SysUIComponent;
+
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
 
 import javax.inject.Inject;
 
@@ -61,7 +65,7 @@
             ((ContextInitializer) app).setContextAvailableCallback(
                     context -> {
                         SystemUIFactory.createFromConfig(context);
-                        SystemUIFactory.getInstance().getRootComponent().inject(
+                        SystemUIFactory.getInstance().getSysUIComponent().inject(
                                 SystemUIAppComponentFactory.this);
                     }
             );
@@ -81,8 +85,17 @@
             ((ContextInitializer) contentProvider).setContextAvailableCallback(
                     context -> {
                         SystemUIFactory.createFromConfig(context);
-                        SystemUIFactory.getInstance().getRootComponent().inject(
-                                contentProvider);
+                        SysUIComponent rootComponent =
+                                SystemUIFactory.getInstance().getSysUIComponent();
+                        try {
+                            Method injectMethod = rootComponent.getClass()
+                                    .getMethod("inject", contentProvider.getClass());
+                            injectMethod.invoke(rootComponent, contentProvider);
+                        } catch (NoSuchMethodException
+                                | IllegalAccessException
+                                | InvocationTargetException e) {
+                            // no-op
+                        }
                     }
             );
         }
@@ -98,7 +111,7 @@
         if (mComponentHelper == null) {
             // This shouldn't happen, but is seen on occasion.
             // Bug filed against framework to take a look: http://b/141008541
-            SystemUIFactory.getInstance().getRootComponent().inject(
+            SystemUIFactory.getInstance().getSysUIComponent().inject(
                     SystemUIAppComponentFactory.this);
         }
         Activity activity = mComponentHelper.resolveActivity(className);
@@ -116,7 +129,7 @@
         if (mComponentHelper == null) {
             // This shouldn't happen, but does when a device is freshly formatted.
             // Bug filed against framework to take a look: http://b/141008541
-            SystemUIFactory.getInstance().getRootComponent().inject(
+            SystemUIFactory.getInstance().getSysUIComponent().inject(
                     SystemUIAppComponentFactory.this);
         }
         Service service = mComponentHelper.resolveService(className);
@@ -134,7 +147,7 @@
         if (mComponentHelper == null) {
             // This shouldn't happen, but does when a device is freshly formatted.
             // Bug filed against framework to take a look: http://b/141008541
-            SystemUIFactory.getInstance().getRootComponent().inject(
+            SystemUIFactory.getInstance().getSysUIComponent().inject(
                     SystemUIAppComponentFactory.this);
         }
         BroadcastReceiver receiver = mComponentHelper.resolveBroadcastReceiver(className);
diff --git a/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java b/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java
index c84701c..7dcec3d 100644
--- a/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java
+++ b/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java
@@ -32,7 +32,8 @@
 import android.util.TimingsTraceLog;
 
 import com.android.systemui.dagger.ContextComponentHelper;
-import com.android.systemui.dagger.SystemUIRootComponent;
+import com.android.systemui.dagger.GlobalRootComponent;
+import com.android.systemui.dagger.SysUIComponent;
 import com.android.systemui.dump.DumpManager;
 import com.android.systemui.util.NotificationChannels;
 
@@ -57,7 +58,8 @@
     private SystemUI[] mServices;
     private boolean mServicesStarted;
     private SystemUIAppComponentFactory.ContextAvailableCallback mContextAvailableCallback;
-    private SystemUIRootComponent mRootComponent;
+    private GlobalRootComponent mRootComponent;
+    private SysUIComponent mSysUIComponent;
 
     public SystemUIApplication() {
         super();
@@ -75,8 +77,9 @@
         log.traceBegin("DependencyInjection");
         mContextAvailableCallback.onContextAvailable(this);
         mRootComponent = SystemUIFactory.getInstance().getRootComponent();
-        mComponentHelper = mRootComponent.getContextComponentHelper();
-        mBootCompleteCache = mRootComponent.provideBootCacheImpl();
+        mSysUIComponent = SystemUIFactory.getInstance().getSysUIComponent();
+        mComponentHelper = mSysUIComponent.getContextComponentHelper();
+        mBootCompleteCache = mSysUIComponent.provideBootCacheImpl();
         log.traceEnd();
 
         // Set the application theme that is inherited by all services. Note that setting the
@@ -172,7 +175,7 @@
             }
         }
 
-        final DumpManager dumpManager = mRootComponent.createDumpManager();
+        final DumpManager dumpManager = mSysUIComponent.createDumpManager();
 
         Log.v(TAG, "Starting SystemUI services for user " +
                 Process.myUserHandle().getIdentifier() + ".");
@@ -215,7 +218,7 @@
 
             dumpManager.registerDumpable(mServices[i].getClass().getName(), mServices[i]);
         }
-        mRootComponent.getInitController().executePostInitTasks();
+        mSysUIComponent.getInitController().executePostInitTasks();
         log.traceEnd();
 
         mServicesStarted = true;
@@ -224,7 +227,7 @@
     @Override
     public void onConfigurationChanged(Configuration newConfig) {
         if (mServicesStarted) {
-            mRootComponent.getConfigurationController().onConfigurationChanged(newConfig);
+            mSysUIComponent.getConfigurationController().onConfigurationChanged(newConfig);
             int len = mServices.length;
             for (int i = 0; i < len; i++) {
                 if (mServices[i] != null) {
diff --git a/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java b/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java
index 1a15c0a..f5c3649 100644
--- a/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java
+++ b/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java
@@ -27,21 +27,15 @@
 import com.android.internal.widget.LockPatternUtils;
 import com.android.keyguard.KeyguardUpdateMonitor;
 import com.android.keyguard.ViewMediatorCallback;
-import com.android.systemui.bubbles.BubbleController;
-import com.android.systemui.dagger.DaggerSystemUIRootComponent;
-import com.android.systemui.dagger.SystemUIRootComponent;
+import com.android.systemui.dagger.DaggerGlobalRootComponent;
+import com.android.systemui.dagger.GlobalRootComponent;
+import com.android.systemui.dagger.SysUIComponent;
+import com.android.systemui.dagger.WMComponent;
 import com.android.systemui.keyguard.DismissCallbackRegistry;
 import com.android.systemui.plugins.FalsingManager;
-import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.screenshot.ScreenshotNotificationSmartActionsProvider;
-import com.android.systemui.statusbar.NotificationListener;
-import com.android.systemui.statusbar.NotificationMediaManager;
-import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator;
-import com.android.systemui.statusbar.phone.DozeParameters;
 import com.android.systemui.statusbar.phone.KeyguardBouncer;
 import com.android.systemui.statusbar.phone.KeyguardBypassController;
-import com.android.systemui.statusbar.phone.NotificationIconAreaController;
-import com.android.systemui.statusbar.phone.StatusBar;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
 
 import java.util.concurrent.Executor;
@@ -53,7 +47,9 @@
     private static final String TAG = "SystemUIFactory";
 
     static SystemUIFactory mFactory;
-    private SystemUIRootComponent mRootComponent;
+    private GlobalRootComponent mRootComponent;
+    private WMComponent mWMComponent;
+    private SysUIComponent mSysUIComponent;
 
     public static <T extends SystemUIFactory> T getInstance() {
         return (T) mFactory;
@@ -88,24 +84,31 @@
     public SystemUIFactory() {}
 
     private void init(Context context) {
-        mRootComponent = buildSystemUIRootComponent(context);
+        mRootComponent = buildGlobalRootComponent(context);
+        mWMComponent = mRootComponent.getWMComponentBuilder().build();
+        // TODO: use WMComponent to pass APIs into the SysUIComponent.
+        mSysUIComponent = mRootComponent.getSysUIComponent().build();
 
         // Every other part of our codebase currently relies on Dependency, so we
         // really need to ensure the Dependency gets initialized early on.
-        Dependency dependency = mRootComponent.createDependency();
+        Dependency dependency = mSysUIComponent.createDependency();
         dependency.start();
     }
 
-    protected SystemUIRootComponent buildSystemUIRootComponent(Context context) {
-        return DaggerSystemUIRootComponent.builder()
+    protected GlobalRootComponent buildGlobalRootComponent(Context context) {
+        return DaggerGlobalRootComponent.builder()
                 .context(context)
                 .build();
     }
 
-    public SystemUIRootComponent getRootComponent() {
+    public GlobalRootComponent getRootComponent() {
         return mRootComponent;
     }
 
+    public SysUIComponent getSysUIComponent() {
+        return mSysUIComponent;
+    }
+
     /** Returns the list of system UI components that should be started. */
     public String[] getSystemUIServiceComponents(Resources resources) {
         return resources.getStringArray(R.array.config_systemUIServiceComponents);
@@ -139,17 +142,4 @@
                 Dependency.get(KeyguardUpdateMonitor.class), bypassController,
                 new Handler(Looper.getMainLooper()));
     }
-
-    public NotificationIconAreaController createNotificationIconAreaController(Context context,
-            StatusBar statusBar,
-            NotificationWakeUpCoordinator wakeUpCoordinator,
-            KeyguardBypassController keyguardBypassController,
-            StatusBarStateController statusBarStateController) {
-        return new NotificationIconAreaController(context, statusBar, statusBarStateController,
-                wakeUpCoordinator, keyguardBypassController,
-                Dependency.get(NotificationMediaManager.class),
-                Dependency.get(NotificationListener.class),
-                Dependency.get(DozeParameters.class),
-                Dependency.get(BubbleController.class));
-    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/UiOffloadThread.java b/packages/SystemUI/src/com/android/systemui/UiOffloadThread.java
index d5a46de..e26dc7f 100644
--- a/packages/SystemUI/src/com/android/systemui/UiOffloadThread.java
+++ b/packages/SystemUI/src/com/android/systemui/UiOffloadThread.java
@@ -16,18 +16,19 @@
 
 package com.android.systemui;
 
+import com.android.systemui.dagger.SysUISingleton;
+
 import java.util.concurrent.ExecutorService;
 import java.util.concurrent.Executors;
 import java.util.concurrent.Future;
 
 import javax.inject.Inject;
-import javax.inject.Singleton;
 
 /**
  * Thread that offloads work from the UI thread but that is still perceptible to the user, so the
  * priority is the same as the main thread.
  */
-@Singleton
+@SysUISingleton
 public class UiOffloadThread {
 
     private final ExecutorService mExecutorService = Executors.newSingleThreadExecutor();
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/ModeSwitchesController.java b/packages/SystemUI/src/com/android/systemui/accessibility/ModeSwitchesController.java
index fe07ab6..123d678 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/ModeSwitchesController.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/ModeSwitchesController.java
@@ -22,8 +22,7 @@
 import android.view.Display;
 
 import com.android.internal.annotations.VisibleForTesting;
-
-import javax.inject.Singleton;
+import com.android.systemui.dagger.SysUISingleton;
 
 /**
  * A class to control {@link MagnificationModeSwitch}. It should show the button UI with following
@@ -33,7 +32,7 @@
  *   <li> The magnification scale is changed by a user.</li>
  * <ol>
  */
-@Singleton
+@SysUISingleton
 public class ModeSwitchesController {
 
     private final DisplayIdIndexSupplier<MagnificationModeSwitch> mSwitchSupplier;
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/SystemActions.java b/packages/SystemUI/src/com/android/systemui/accessibility/SystemActions.java
index 0135e4c..e49a5be 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/SystemActions.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/SystemActions.java
@@ -51,18 +51,18 @@
 import com.android.internal.util.ScreenshotHelper;
 import com.android.systemui.Dependency;
 import com.android.systemui.SystemUI;
+import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.recents.Recents;
 import com.android.systemui.statusbar.phone.StatusBar;
 
 import java.util.Locale;
 
 import javax.inject.Inject;
-import javax.inject.Singleton;
 
 /**
  * Class to register system actions with accessibility framework.
  */
-@Singleton
+@SysUISingleton
 public class SystemActions extends SystemUI {
     private static final String TAG = "SystemActions";
 
@@ -130,8 +130,6 @@
 
     private static final String PERMISSION_SELF = "com.android.systemui.permission.SELF";
 
-    private Recents mRecents;
-    private StatusBar mStatusBar;
     private SystemActionsBroadcastReceiver mReceiver;
     private Locale mLocale;
     private AccessibilityManager mA11yManager;
@@ -139,8 +137,6 @@
     @Inject
     public SystemActions(Context context) {
         super(context);
-        mRecents = Dependency.get(Recents.class);
-        mStatusBar = Dependency.get(StatusBar.class);
         mReceiver = new SystemActionsBroadcastReceiver();
         mLocale = mContext.getResources().getConfiguration().getLocales().get(0);
         mA11yManager = (AccessibilityManager) mContext.getSystemService(
@@ -317,15 +313,15 @@
     }
 
     private void handleRecents() {
-        mRecents.toggleRecentApps();
+        Dependency.get(Recents.class).toggleRecentApps();
     }
 
     private void handleNotifications() {
-        mStatusBar.animateExpandNotificationsPanel();
+        Dependency.get(StatusBar.class).animateExpandNotificationsPanel();
     }
 
     private void handleQuickSettings() {
-        mStatusBar.animateExpandSettingsPanel(null);
+        Dependency.get(StatusBar.class).animateExpandSettingsPanel(null);
     }
 
     private void handlePowerDialog() {
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnification.java b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnification.java
index 816bcf8..f601c52 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnification.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnification.java
@@ -33,11 +33,11 @@
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.graphics.SfVsyncFrameCallbackProvider;
 import com.android.systemui.SystemUI;
+import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.statusbar.CommandQueue;
 
 import javax.inject.Inject;
-import javax.inject.Singleton;
 
 /**
  * Class to handle the interaction with
@@ -45,7 +45,7 @@
  * {@link AccessibilityManager#setWindowMagnificationConnection(IWindowMagnificationConnection)}
  * when {@code IStatusBar#requestWindowMagnificationConnection(boolean)} is called.
  */
-@Singleton
+@SysUISingleton
 public class WindowMagnification extends SystemUI implements WindowMagnifierCallback,
         CommandQueue.Callbacks {
     private static final String TAG = "WindowMagnification";
@@ -53,8 +53,8 @@
             ActivityInfo.CONFIG_DENSITY | ActivityInfo.CONFIG_ORIENTATION;
 
     @VisibleForTesting
-    protected WindowMagnificationController mWindowMagnificationController;
-    protected final ModeSwitchesController mModeSwitchesController;
+    protected WindowMagnificationAnimationController mWindowMagnificationAnimationController;
+    private final ModeSwitchesController mModeSwitchesController;
     private final Handler mHandler;
     private final AccessibilityManager mAccessibilityManager;
     private final CommandQueue mCommandQueue;
@@ -72,6 +72,11 @@
                 Context.ACCESSIBILITY_SERVICE);
         mCommandQueue = commandQueue;
         mModeSwitchesController = modeSwitchesController;
+        final WindowMagnificationController controller = new WindowMagnificationController(mContext,
+                mHandler, new SfVsyncFrameCallbackProvider(), null,
+                new SurfaceControl.Transaction(), this);
+        mWindowMagnificationAnimationController = new WindowMagnificationAnimationController(
+                mContext, controller);
     }
 
     @Override
@@ -81,9 +86,7 @@
             return;
         }
         mLastConfiguration.setTo(newConfig);
-        if (mWindowMagnificationController != null) {
-            mWindowMagnificationController.onConfigurationChanged(configDiff);
-        }
+        mWindowMagnificationAnimationController.onConfigurationChanged(configDiff);
         if (mModeSwitchesController != null) {
             mModeSwitchesController.onConfigurationChanged(configDiff);
         }
@@ -97,39 +100,25 @@
     @MainThread
     void enableWindowMagnification(int displayId, float scale, float centerX, float centerY) {
         //TODO: b/144080869 support multi-display.
-        if (mWindowMagnificationController == null) {
-            mWindowMagnificationController = new WindowMagnificationController(mContext,
-                    mHandler,
-                    new SfVsyncFrameCallbackProvider(),
-                    null, new SurfaceControl.Transaction(),
-                    this);
-        }
-        mWindowMagnificationController.enableWindowMagnification(scale, centerX, centerY);
+        mWindowMagnificationAnimationController.enableWindowMagnification(scale, centerX, centerY);
     }
 
     @MainThread
     void setScale(int displayId, float scale) {
         //TODO: b/144080869 support multi-display.
-        if (mWindowMagnificationController != null) {
-            mWindowMagnificationController.setScale(scale);
-        }
+        mWindowMagnificationAnimationController.setScale(scale);
     }
 
     @MainThread
     void moveWindowMagnifier(int displayId, float offsetX, float offsetY) {
         //TODO: b/144080869 support multi-display.
-        if (mWindowMagnificationController != null) {
-            mWindowMagnificationController.moveWindowMagnifier(offsetX, offsetY);
-        }
+        mWindowMagnificationAnimationController.moveWindowMagnifier(offsetX, offsetY);
     }
 
     @MainThread
     void disableWindowMagnification(int displayId) {
         //TODO: b/144080869 support multi-display.
-        if (mWindowMagnificationController != null) {
-            mWindowMagnificationController.deleteWindowMagnification();
-        }
-        mWindowMagnificationController = null;
+        mWindowMagnificationAnimationController.deleteWindowMagnification();
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationAnimationController.java b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationAnimationController.java
new file mode 100644
index 0000000..ae51623
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationAnimationController.java
@@ -0,0 +1,272 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.accessibility;
+
+import android.animation.Animator;
+import android.animation.ValueAnimator;
+import android.annotation.IntDef;
+import android.content.Context;
+import android.content.res.Resources;
+import android.util.Log;
+import android.view.animation.AccelerateInterpolator;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.systemui.R;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * Provides same functionality of {@link WindowMagnificationController}. Some methods run with
+ * the animation.
+ */
+class WindowMagnificationAnimationController implements ValueAnimator.AnimatorUpdateListener,
+        Animator.AnimatorListener {
+
+    private static final String TAG = "WindowMagnificationBridge";
+    private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
+
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef({STATE_DISABLED, STATE_ENABLED, STATE_DISABLING, STATE_ENABLING})
+    @interface MagnificationState {}
+
+    //The window magnification is disabled.
+    private static final int STATE_DISABLED = 0;
+    //The window magnification is enabled.
+    private static final int STATE_ENABLED = 1;
+    //The window magnification is going to be disabled when the animation is end.
+    private  static final int STATE_DISABLING = 2;
+    //The animation is running for enabling the window magnification.
+    private static final int STATE_ENABLING = 3;
+
+    private final WindowMagnificationController mController;
+    private final ValueAnimator mValueAnimator;
+    private final AnimationSpec mStartSpec = new AnimationSpec();
+    private final AnimationSpec mEndSpec = new AnimationSpec();
+    private final Context mContext;
+
+    @MagnificationState
+    private int mState = STATE_DISABLED;
+
+    WindowMagnificationAnimationController(
+            Context context, WindowMagnificationController controller) {
+        this(context, controller, newValueAnimator(context.getResources()));
+    }
+
+    @VisibleForTesting
+    WindowMagnificationAnimationController(Context context,
+            WindowMagnificationController controller, ValueAnimator valueAnimator) {
+        mContext = context;
+        mController = controller;
+        mValueAnimator = valueAnimator;
+        mValueAnimator.addUpdateListener(this);
+        mValueAnimator.addListener(this);
+    }
+
+    /**
+     * Wraps {@link WindowMagnificationController#enableWindowMagnification(float, float, float)}
+     * with transition animation. If the window magnification is not enabled, the scale will start
+     * from 1.0 and the center won't be changed during the animation. If {@link #mState} is
+     * {@code STATE_DISABLING}, the animation runs in reverse.
+     *
+     * @param scale   the target scale, or {@link Float#NaN} to leave unchanged.
+     * @param centerX the screen-relative X coordinate around which to center,
+     *                or {@link Float#NaN} to leave unchanged.
+     * @param centerY the screen-relative Y coordinate around which to center,
+     *                or {@link Float#NaN} to leave unchanged.
+     *
+     * @see #onAnimationUpdate(ValueAnimator)
+     */
+    void enableWindowMagnification(float scale, float centerX, float centerY) {
+        if (mState == STATE_ENABLING) {
+            mValueAnimator.cancel();
+        }
+        setupEnableAnimationSpecs(scale, centerX, centerY);
+
+        if (mEndSpec.equals(mStartSpec)) {
+            setState(STATE_ENABLED);
+        } else {
+            if (mState == STATE_DISABLING) {
+                mValueAnimator.reverse();
+            } else {
+                mValueAnimator.start();
+            }
+            setState(STATE_ENABLING);
+        }
+    }
+
+    private void setupEnableAnimationSpecs(float scale, float centerX, float centerY) {
+        final float currentScale = mController.getScale();
+        final float currentCenterX = mController.getCenterX();
+        final float currentCenterY = mController.getCenterY();
+
+        if (mState == STATE_DISABLED) {
+            //We don't need to offset the center during the animation.
+            mStartSpec.set(/* scale*/ 1.0f, centerX, centerY);
+            mEndSpec.set(Float.isNaN(scale) ? mContext.getResources().getInteger(
+                    R.integer.magnification_default_scale) : scale, centerX, centerY);
+        } else {
+            mStartSpec.set(currentScale, currentCenterX, currentCenterY);
+            mEndSpec.set(Float.isNaN(scale) ? currentScale : scale,
+                    Float.isNaN(centerX) ? currentCenterX : centerX,
+                    Float.isNaN(centerY) ? currentCenterY : centerY);
+        }
+        if (DEBUG) {
+            Log.d(TAG, "SetupEnableAnimationSpecs : mStartSpec = " + mStartSpec + ", endSpec = "
+                    + mEndSpec);
+        }
+    }
+
+    /**
+     * Wraps {@link WindowMagnificationController#setScale(float)}. If the animation is
+     * running, it has no effect.
+     */
+    void setScale(float scale) {
+        if (mValueAnimator.isRunning()) {
+            return;
+        }
+        mController.setScale(scale);
+    }
+
+    /**
+     * Wraps {@link WindowMagnificationController#deleteWindowMagnification()}} with transition
+     * animation. If the window magnification is enabling, it runs the animation in reverse.
+     */
+    void deleteWindowMagnification() {
+        if (mState == STATE_DISABLED || mState == STATE_DISABLING) {
+            return;
+        }
+        mStartSpec.set(/* scale*/ 1.0f, Float.NaN, Float.NaN);
+        mEndSpec.set(/* scale*/ mController.getScale(), Float.NaN, Float.NaN);
+
+        mValueAnimator.reverse();
+        setState(STATE_DISABLING);
+    }
+
+    /**
+     * Wraps {@link WindowMagnificationController#moveWindowMagnifier(float, float)}. If the
+     * animation is running, it has no effect.
+     * @param offsetX the amount in pixels to offset the window magnifier in the X direction, in
+     *                current screen pixels.
+     * @param offsetY the amount in pixels to offset the window magnifier in the Y direction, in
+     *                current screen pixels.
+     */
+    void moveWindowMagnifier(float offsetX, float offsetY) {
+        if (mValueAnimator.isRunning()) {
+            return;
+        }
+        mController.moveWindowMagnifier(offsetX, offsetY);
+    }
+
+    void onConfigurationChanged(int configDiff) {
+        mController.onConfigurationChanged(configDiff);
+    }
+
+    private void setState(@MagnificationState int state) {
+        if (DEBUG) {
+            Log.d(TAG, "setState from " + mState + " to " + state);
+        }
+        mState = state;
+    }
+
+    @Override
+    public void onAnimationStart(Animator animation) {
+    }
+
+    @Override
+    public void onAnimationEnd(Animator animation) {
+        if (mState == STATE_DISABLING) {
+            mController.deleteWindowMagnification();
+            setState(STATE_DISABLED);
+        } else if (mState == STATE_ENABLING) {
+            setState(STATE_ENABLED);
+        } else {
+            Log.w(TAG, "onAnimationEnd unexpected state:" + mState);
+        }
+    }
+
+    @Override
+    public void onAnimationCancel(Animator animation) {
+    }
+
+    @Override
+    public void onAnimationRepeat(Animator animation) {
+    }
+
+    @Override
+    public void onAnimationUpdate(ValueAnimator animation) {
+        final float fract = animation.getAnimatedFraction();
+        final float sentScale = mStartSpec.mScale + (mEndSpec.mScale - mStartSpec.mScale) * fract;
+        final float centerX =
+                mStartSpec.mCenterX + (mEndSpec.mCenterX - mStartSpec.mCenterX) * fract;
+        final float centerY =
+                mStartSpec.mCenterY + (mEndSpec.mCenterY - mStartSpec.mCenterY) * fract;
+        mController.enableWindowMagnification(sentScale, centerX, centerY);
+    }
+
+    private static ValueAnimator newValueAnimator(Resources resources) {
+        final ValueAnimator valueAnimator = new ValueAnimator();
+        valueAnimator.setDuration(
+                resources.getInteger(com.android.internal.R.integer.config_longAnimTime));
+        valueAnimator.setInterpolator(new AccelerateInterpolator(2.5f));
+        valueAnimator.setFloatValues(0.0f, 1.0f);
+        return valueAnimator;
+    }
+
+    private static class AnimationSpec {
+        private float mScale = Float.NaN;
+        private float mCenterX = Float.NaN;
+        private float mCenterY = Float.NaN;
+
+        @Override
+        public boolean equals(Object other) {
+            if (this == other) {
+                return true;
+            }
+
+            if (other == null || getClass() != other.getClass()) {
+                return false;
+            }
+
+            final AnimationSpec s = (AnimationSpec) other;
+            return mScale == s.mScale && mCenterX == s.mCenterX && mCenterY == s.mCenterY;
+        }
+
+        @Override
+        public int hashCode() {
+            int result = (mScale != +0.0f ? Float.floatToIntBits(mScale) : 0);
+            result = 31 * result + (mCenterX != +0.0f ? Float.floatToIntBits(mCenterX) : 0);
+            result = 31 * result + (mCenterY != +0.0f ? Float.floatToIntBits(mCenterY) : 0);
+            return result;
+        }
+
+        void set(float scale, float centerX, float centerY) {
+            mScale = scale;
+            mCenterX = centerX;
+            mCenterY = centerY;
+        }
+
+        @Override
+        public String toString() {
+            return "AnimationSpec{"
+                    + "mScale=" + mScale
+                    + ", mCenterX=" + mCenterX
+                    + ", mCenterY=" + mCenterY
+                    + '}';
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java
index 798b751..494a0f64 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java
@@ -150,7 +150,7 @@
 
         mMirrorViewGeometryVsyncCallback =
                 l -> {
-                    if (mMirrorView != null && mMirrorSurface != null) {
+                    if (isWindowVisible() && mMirrorSurface != null) {
                         calculateSourceBounds(mMagnificationFrame, mScale);
                         // The final destination for the magnification surface should be at 0,0
                         // since the ViewRootImpl's position will change
@@ -203,13 +203,13 @@
      * @param configDiff a bit mask of the differences between the configurations
      */
     void onConfigurationChanged(int configDiff) {
+        if (!isWindowVisible()) {
+            return;
+        }
         if ((configDiff & ActivityInfo.CONFIG_DENSITY) != 0) {
             updateDimensions();
-            // TODO(b/145780606): update toggle button UI.
-            if (mMirrorView != null) {
-                mWm.removeView(mMirrorView);
-                createMirrorWindow();
-            }
+            mWm.removeView(mMirrorView);
+            createMirrorWindow();
         } else if ((configDiff & ActivityInfo.CONFIG_ORIENTATION) != 0) {
             onRotate();
         }
@@ -502,7 +502,7 @@
     /**
      * Enables window magnification with specified parameters.
      *
-     * @param scale   the target scale
+     * @param scale   the target scale, or {@link Float#NaN} to leave unchanged
      * @param centerX the screen-relative X coordinate around which to center,
      *                or {@link Float#NaN} to leave unchanged.
      * @param centerY the screen-relative Y coordinate around which to center,
@@ -513,10 +513,10 @@
                 : centerX - mMagnificationFrame.exactCenterX();
         final float offsetY = Float.isNaN(centerY) ? 0
                 : centerY - mMagnificationFrame.exactCenterY();
-        mScale = scale;
+        mScale = Float.isNaN(scale) ? mScale : scale;
         setMagnificationFrameBoundary();
         updateMagnificationFramePosition((int) offsetX, (int) offsetY);
-        if (mMirrorView == null) {
+        if (!isWindowVisible()) {
             createMirrorWindow();
             showControls();
         } else {
@@ -527,10 +527,10 @@
     /**
      * Sets the scale of the magnified region if it's visible.
      *
-     * @param scale the target scale
+     * @param scale the target scale, or {@link Float#NaN} to leave unchanged
      */
     void setScale(float scale) {
-        if (mMirrorView == null || mScale == scale) {
+        if (!isWindowVisible() || mScale == scale) {
             return;
         }
         enableWindowMagnification(scale, Float.NaN, Float.NaN);
@@ -552,4 +552,35 @@
             modifyWindowMagnification(mTransaction);
         }
     }
+
+    /**
+     * Gets the scale.
+     * @return {@link Float#NaN} if the window is invisible.
+     */
+    float getScale() {
+        return isWindowVisible() ? mScale : Float.NaN;
+    }
+
+    /**
+     * Returns the screen-relative X coordinate of the center of the magnified bounds.
+     *
+     * @return the X coordinate. {@link Float#NaN} if the window is invisible.
+     */
+    float getCenterX() {
+        return isWindowVisible() ? mMagnificationFrame.exactCenterX() : Float.NaN;
+    }
+
+    /**
+     * Returns the screen-relative Y coordinate of the center of the magnified bounds.
+     *
+     * @return the Y coordinate. {@link Float#NaN} if the window is invisible.
+     */
+    float getCenterY() {
+        return isWindowVisible() ? mMagnificationFrame.exactCenterY() : Float.NaN;
+    }
+
+    //The window is visible when it is existed.
+    private boolean isWindowVisible() {
+        return mMirrorView != null;
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/appops/AppOpItem.java b/packages/SystemUI/src/com/android/systemui/appops/AppOpItem.java
index 7e5b426..93a8df4 100644
--- a/packages/SystemUI/src/com/android/systemui/appops/AppOpItem.java
+++ b/packages/SystemUI/src/com/android/systemui/appops/AppOpItem.java
@@ -25,7 +25,9 @@
     private int mUid;
     private String mPackageName;
     private long mTimeStarted;
-    private String mState;
+    private StringBuilder mState;
+    // This is only used for items with mCode == AppOpsManager.OP_RECORD_AUDIO
+    private boolean mSilenced;
 
     public AppOpItem(int code, int uid, String packageName, long timeStarted) {
         this.mCode = code;
@@ -36,9 +38,8 @@
                 .append("AppOpItem(")
                 .append("Op code=").append(code).append(", ")
                 .append("UID=").append(uid).append(", ")
-                .append("Package name=").append(packageName)
-                .append(")")
-                .toString();
+                .append("Package name=").append(packageName).append(", ")
+                .append("Paused=");
     }
 
     public int getCode() {
@@ -57,8 +58,16 @@
         return mTimeStarted;
     }
 
+    public void setSilenced(boolean silenced) {
+        mSilenced = silenced;
+    }
+
+    public boolean isSilenced() {
+        return mSilenced;
+    }
+
     @Override
     public String toString() {
-        return mState;
+        return mState.append(mSilenced).append(")").toString();
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/appops/AppOpsControllerImpl.java b/packages/SystemUI/src/com/android/systemui/appops/AppOpsControllerImpl.java
index 4df6660..a3339f6 100644
--- a/packages/SystemUI/src/com/android/systemui/appops/AppOpsControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/appops/AppOpsControllerImpl.java
@@ -19,6 +19,9 @@
 import android.app.AppOpsManager;
 import android.content.Context;
 import android.content.pm.PackageManager;
+import android.location.LocationManager;
+import android.media.AudioManager;
+import android.media.AudioRecordingConfiguration;
 import android.os.Handler;
 import android.os.Looper;
 import android.os.UserHandle;
@@ -31,6 +34,7 @@
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.systemui.Dumpable;
+import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dagger.qualifiers.Background;
 import com.android.systemui.dump.DumpManager;
 import com.android.systemui.util.Assert;
@@ -42,7 +46,6 @@
 import java.util.Set;
 
 import javax.inject.Inject;
-import javax.inject.Singleton;
 
 /**
  * Controller to keep track of applications that have requested access to given App Ops
@@ -50,7 +53,7 @@
  * It can be subscribed to with callbacks. Additionally, it passes on the information to
  * NotificationPresenter to be displayed to the user.
  */
-@Singleton
+@SysUISingleton
 public class AppOpsControllerImpl implements AppOpsController,
         AppOpsManager.OnOpActiveChangedInternalListener,
         AppOpsManager.OnOpNotedListener, Dumpable {
@@ -63,6 +66,14 @@
     private static final boolean DEBUG = false;
 
     private final AppOpsManager mAppOps;
+    private final AudioManager mAudioManager;
+    private final LocationManager mLocationManager;
+
+    // mLocationProviderPackages are cached and updated only occasionally
+    private static final long LOCATION_PROVIDER_UPDATE_FREQUENCY_MS = 30000;
+    private long mLastLocationProviderPackageUpdate;
+    private List<String> mLocationProviderPackages;
+
     private H mBGHandler;
     private final List<AppOpsController.Callback> mCallbacks = new ArrayList<>();
     private final SparseArray<Set<Callback>> mCallbacksByCode = new SparseArray<>();
@@ -73,12 +84,17 @@
     private final List<AppOpItem> mActiveItems = new ArrayList<>();
     @GuardedBy("mNotedItems")
     private final List<AppOpItem> mNotedItems = new ArrayList<>();
+    @GuardedBy("mActiveItems")
+    private final SparseArray<ArrayList<AudioRecordingConfiguration>> mRecordingsByUid =
+            new SparseArray<>();
 
     protected static final int[] OPS = new int[] {
             AppOpsManager.OP_MONITOR_HIGH_POWER_LOCATION,
             AppOpsManager.OP_CAMERA,
+            AppOpsManager.OP_PHONE_CALL_CAMERA,
             AppOpsManager.OP_SYSTEM_ALERT_WINDOW,
             AppOpsManager.OP_RECORD_AUDIO,
+            AppOpsManager.OP_PHONE_CALL_MICROPHONE,
             AppOpsManager.OP_COARSE_LOCATION,
             AppOpsManager.OP_FINE_LOCATION
     };
@@ -88,7 +104,8 @@
             Context context,
             @Background Looper bgLooper,
             DumpManager dumpManager,
-            PermissionFlagsCache cache
+            PermissionFlagsCache cache,
+            AudioManager audioManager
     ) {
         mAppOps = (AppOpsManager) context.getSystemService(Context.APP_OPS_SERVICE);
         mFlagsCache = cache;
@@ -97,6 +114,8 @@
         for (int i = 0; i < numOps; i++) {
             mCallbacksByCode.put(OPS[i], new ArraySet<>());
         }
+        mAudioManager = audioManager;
+        mLocationManager = context.getSystemService(LocationManager.class);
         dumpManager.registerDumpable(TAG, this);
     }
 
@@ -111,12 +130,19 @@
         if (listening) {
             mAppOps.startWatchingActive(OPS, this);
             mAppOps.startWatchingNoted(OPS, this);
+            mAudioManager.registerAudioRecordingCallback(mAudioRecordingCallback, mBGHandler);
+            mBGHandler.post(() -> mAudioRecordingCallback.onRecordingConfigChanged(
+                    mAudioManager.getActiveRecordingConfigurations()));
+
         } else {
             mAppOps.stopWatchingActive(this);
             mAppOps.stopWatchingNoted(this);
+            mAudioManager.unregisterAudioRecordingCallback(mAudioRecordingCallback);
+
             mBGHandler.removeCallbacksAndMessages(null); // null removes all
             synchronized (mActiveItems) {
                 mActiveItems.clear();
+                mRecordingsByUid.clear();
             }
             synchronized (mNotedItems) {
                 mNotedItems.clear();
@@ -189,9 +215,12 @@
             AppOpItem item = getAppOpItemLocked(mActiveItems, code, uid, packageName);
             if (item == null && active) {
                 item = new AppOpItem(code, uid, packageName, System.currentTimeMillis());
+                if (code == AppOpsManager.OP_RECORD_AUDIO) {
+                    item.setSilenced(isAnyRecordingPausedLocked(uid));
+                }
                 mActiveItems.add(item);
                 if (DEBUG) Log.w(TAG, "Added item: " + item.toString());
-                return true;
+                return !item.isSilenced();
             } else if (item != null && !active) {
                 mActiveItems.remove(item);
                 if (DEBUG) Log.w(TAG, "Removed item: " + item.toString());
@@ -215,7 +244,7 @@
             active = getAppOpItemLocked(mActiveItems, code, uid, packageName) != null;
         }
         if (!active) {
-            notifySuscribers(code, uid, packageName, false);
+            notifySuscribersWorker(code, uid, packageName, false);
         }
     }
 
@@ -270,6 +299,26 @@
         return isUserVisible(item.getCode(), item.getUid(), item.getPackageName());
     }
 
+    /**
+     * Checks if a package is the current location provider.
+     *
+     * <p>Data is cached to avoid too many calls into system server
+     *
+     * @param packageName The package that might be the location provider
+     *
+     * @return {@code true} iff the package is the location provider.
+     */
+    private boolean isLocationProvider(String packageName) {
+        long now = System.currentTimeMillis();
+
+        if (mLastLocationProviderPackageUpdate + LOCATION_PROVIDER_UPDATE_FREQUENCY_MS < now) {
+            mLastLocationProviderPackageUpdate = now;
+            mLocationProviderPackages = mLocationManager.getProviderPackages(
+                    LocationManager.FUSED_PROVIDER);
+        }
+
+        return mLocationProviderPackages.contains(packageName);
+    }
 
     /**
      * Does the app-op, uid and package name, refer to an operation that should be shown to the
@@ -281,9 +330,17 @@
      * @return {@code true} iff the app-op for should be shown to the user
      */
     private boolean isUserVisible(int appOpCode, int uid, String packageName) {
-        // currently OP_SYSTEM_ALERT_WINDOW does not correspond to a platform permission
-        // which may be user senstive, so for now always show it to the user.
-        if (appOpCode == AppOpsManager.OP_SYSTEM_ALERT_WINDOW) {
+        // currently OP_SYSTEM_ALERT_WINDOW and OP_MONITOR_HIGH_POWER_LOCATION
+        // does not correspond to a platform permission
+        // which may be user sensitive, so for now always show it to the user.
+        if (appOpCode == AppOpsManager.OP_SYSTEM_ALERT_WINDOW
+                || appOpCode == AppOpsManager.OP_MONITOR_HIGH_POWER_LOCATION
+                || appOpCode == AppOpsManager.OP_PHONE_CALL_CAMERA
+                || appOpCode == AppOpsManager.OP_PHONE_CALL_MICROPHONE) {
+            return true;
+        }
+
+        if (appOpCode == AppOpsManager.OP_CAMERA && isLocationProvider(packageName)) {
             return true;
         }
 
@@ -322,7 +379,7 @@
                 AppOpItem item = mActiveItems.get(i);
                 if ((userId == UserHandle.USER_ALL
                         || UserHandle.getUserId(item.getUid()) == userId)
-                        && isUserVisible(item)) {
+                        && isUserVisible(item) && !item.isSilenced()) {
                     list.add(item);
                 }
             }
@@ -341,6 +398,10 @@
         return list;
     }
 
+    private void notifySuscribers(int code, int uid, String packageName, boolean active) {
+        mBGHandler.post(() -> notifySuscribersWorker(code, uid, packageName, active));
+    }
+
     @Override
     public void onOpActiveChanged(int code, int uid, String packageName, boolean active) {
         if (DEBUG) {
@@ -358,7 +419,7 @@
         // If active is false, we only send the update if the op is not actively noted (prevent
         // early removal)
         if (!alsoNoted) {
-            mBGHandler.post(() -> notifySuscribers(code, uid, packageName, active));
+            notifySuscribers(code, uid, packageName, active);
         }
     }
 
@@ -376,11 +437,11 @@
             alsoActive = getAppOpItemLocked(mActiveItems, code, uid, packageName) != null;
         }
         if (!alsoActive) {
-            mBGHandler.post(() -> notifySuscribers(code, uid, packageName, true));
+            notifySuscribers(code, uid, packageName, true);
         }
     }
 
-    private void notifySuscribers(int code, int uid, String packageName, boolean active) {
+    private void notifySuscribersWorker(int code, int uid, String packageName, boolean active) {
         if (mCallbacksByCode.contains(code) && isUserVisible(code, uid, packageName)) {
             if (DEBUG) Log.d(TAG, "Notifying of change in package " + packageName);
             for (Callback cb: mCallbacksByCode.get(code)) {
@@ -406,6 +467,61 @@
 
     }
 
+    private boolean isAnyRecordingPausedLocked(int uid) {
+        List<AudioRecordingConfiguration> configs = mRecordingsByUid.get(uid);
+        if (configs == null) return false;
+        int configsNum = configs.size();
+        for (int i = 0; i < configsNum; i++) {
+            AudioRecordingConfiguration config = configs.get(i);
+            if (config.isClientSilenced()) return true;
+        }
+        return false;
+    }
+
+    private void updateRecordingPausedStatus() {
+        synchronized (mActiveItems) {
+            int size = mActiveItems.size();
+            for (int i = 0; i < size; i++) {
+                AppOpItem item = mActiveItems.get(i);
+                if (item.getCode() == AppOpsManager.OP_RECORD_AUDIO) {
+                    boolean paused = isAnyRecordingPausedLocked(item.getUid());
+                    if (item.isSilenced() != paused) {
+                        item.setSilenced(paused);
+                        notifySuscribers(
+                                item.getCode(),
+                                item.getUid(),
+                                item.getPackageName(),
+                                !item.isSilenced()
+                        );
+                    }
+                }
+            }
+        }
+    }
+
+    private AudioManager.AudioRecordingCallback mAudioRecordingCallback =
+            new AudioManager.AudioRecordingCallback() {
+        @Override
+        public void onRecordingConfigChanged(List<AudioRecordingConfiguration> configs) {
+            synchronized (mActiveItems) {
+                mRecordingsByUid.clear();
+                final int recordingsCount = configs.size();
+                for (int i = 0; i < recordingsCount; i++) {
+                    AudioRecordingConfiguration recording = configs.get(i);
+
+                    ArrayList<AudioRecordingConfiguration> recordings = mRecordingsByUid.get(
+                            recording.getClientUid());
+                    if (recordings == null) {
+                        recordings = new ArrayList<>();
+                        mRecordingsByUid.put(recording.getClientUid(), recordings);
+                    }
+                    recordings.add(recording);
+                }
+            }
+            updateRecordingPausedStatus();
+        }
+    };
+
     protected class H extends Handler {
         H(Looper looper) {
             super(looper);
diff --git a/packages/SystemUI/src/com/android/systemui/appops/PermissionFlagsCache.kt b/packages/SystemUI/src/com/android/systemui/appops/PermissionFlagsCache.kt
index 45ed78f..3fd838b 100644
--- a/packages/SystemUI/src/com/android/systemui/appops/PermissionFlagsCache.kt
+++ b/packages/SystemUI/src/com/android/systemui/appops/PermissionFlagsCache.kt
@@ -19,11 +19,11 @@
 import android.content.pm.PackageManager
 import android.os.UserHandle
 import androidx.annotation.WorkerThread
+import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Background
 import com.android.systemui.util.Assert
 import java.util.concurrent.Executor
 import javax.inject.Inject
-import javax.inject.Singleton
 
 private data class PermissionFlagKey(
     val permission: String,
@@ -37,7 +37,7 @@
  * After a specific `{permission, package, uid}` has been requested, updates to it will be tracked,
  * and changes to the uid will trigger new requests (in the background).
  */
-@Singleton
+@SysUISingleton
 class PermissionFlagsCache @Inject constructor(
     private val packageManager: PackageManager,
     @Background private val executor: Executor
diff --git a/packages/SystemUI/src/com/android/systemui/assist/AssistHandleBehaviorController.java b/packages/SystemUI/src/com/android/systemui/assist/AssistHandleBehaviorController.java
index 7ae3e73..4390d51 100644
--- a/packages/SystemUI/src/com/android/systemui/assist/AssistHandleBehaviorController.java
+++ b/packages/SystemUI/src/com/android/systemui/assist/AssistHandleBehaviorController.java
@@ -33,9 +33,10 @@
 import com.android.internal.config.sysui.SystemUiDeviceConfigFlags;
 import com.android.keyguard.KeyguardUpdateMonitor;
 import com.android.systemui.Dumpable;
+import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dump.DumpManager;
+import com.android.systemui.navigationbar.NavigationModeController;
 import com.android.systemui.shared.system.QuickStepContract;
-import com.android.systemui.statusbar.phone.NavigationModeController;
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
@@ -45,7 +46,6 @@
 import javax.inject.Inject;
 import javax.inject.Named;
 import javax.inject.Provider;
-import javax.inject.Singleton;
 
 import dagger.Lazy;
 
@@ -55,7 +55,7 @@
  * Controls when visual handles for Assistant gesture affordance should be shown or hidden using an
  * {@link AssistHandleBehavior}.
  */
-@Singleton
+@SysUISingleton
 public final class AssistHandleBehaviorController implements AssistHandleCallbacks, Dumpable {
 
     private static final String TAG = "AssistHandleBehavior";
diff --git a/packages/SystemUI/src/com/android/systemui/assist/AssistHandleLikeHomeBehavior.java b/packages/SystemUI/src/com/android/systemui/assist/AssistHandleLikeHomeBehavior.java
index ccca447..5d8ec4b 100644
--- a/packages/SystemUI/src/com/android/systemui/assist/AssistHandleLikeHomeBehavior.java
+++ b/packages/SystemUI/src/com/android/systemui/assist/AssistHandleLikeHomeBehavior.java
@@ -21,6 +21,7 @@
 import androidx.annotation.Nullable;
 
 import com.android.systemui.assist.AssistHandleBehaviorController.BehaviorController;
+import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.keyguard.WakefulnessLifecycle;
 import com.android.systemui.model.SysUiState;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
@@ -29,7 +30,6 @@
 import java.io.PrintWriter;
 
 import javax.inject.Inject;
-import javax.inject.Singleton;
 
 import dagger.Lazy;
 
@@ -37,7 +37,7 @@
  * Assistant Handle behavior that makes Assistant handles show/hide when the home handle is
  * shown/hidden, respectively.
  */
-@Singleton
+@SysUISingleton
 final class AssistHandleLikeHomeBehavior implements BehaviorController {
 
     private final StatusBarStateController.StateListener mStatusBarStateListener =
diff --git a/packages/SystemUI/src/com/android/systemui/assist/AssistHandleOffBehavior.java b/packages/SystemUI/src/com/android/systemui/assist/AssistHandleOffBehavior.java
index df913f9..86d3254 100644
--- a/packages/SystemUI/src/com/android/systemui/assist/AssistHandleOffBehavior.java
+++ b/packages/SystemUI/src/com/android/systemui/assist/AssistHandleOffBehavior.java
@@ -19,12 +19,12 @@
 import android.content.Context;
 
 import com.android.systemui.assist.AssistHandleBehaviorController.BehaviorController;
+import com.android.systemui.dagger.SysUISingleton;
 
 import javax.inject.Inject;
-import javax.inject.Singleton;
 
 /** Assistant handle behavior that hides the Assistant handles. */
-@Singleton
+@SysUISingleton
 final class AssistHandleOffBehavior implements BehaviorController {
 
     @Inject
diff --git a/packages/SystemUI/src/com/android/systemui/assist/AssistHandleReminderExpBehavior.java b/packages/SystemUI/src/com/android/systemui/assist/AssistHandleReminderExpBehavior.java
index 8e49d58..1b2e4c6 100644
--- a/packages/SystemUI/src/com/android/systemui/assist/AssistHandleReminderExpBehavior.java
+++ b/packages/SystemUI/src/com/android/systemui/assist/AssistHandleReminderExpBehavior.java
@@ -36,6 +36,7 @@
 import com.android.systemui.BootCompleteCache;
 import com.android.systemui.assist.AssistHandleBehaviorController.BehaviorController;
 import com.android.systemui.broadcast.BroadcastDispatcher;
+import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.keyguard.WakefulnessLifecycle;
 import com.android.systemui.model.SysUiState;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
@@ -54,7 +55,6 @@
 
 import javax.inject.Inject;
 import javax.inject.Named;
-import javax.inject.Singleton;
 
 import dagger.Lazy;
 
@@ -63,7 +63,7 @@
  * shows the handles when on lockscreen, and shows the handles temporarily when changing tasks or
  * entering overview.
  */
-@Singleton
+@SysUISingleton
 final class AssistHandleReminderExpBehavior implements BehaviorController {
 
     private static final String LEARNING_TIME_ELAPSED_KEY = "reminder_exp_learning_time_elapsed";
diff --git a/packages/SystemUI/src/com/android/systemui/assist/AssistHandleViewController.java b/packages/SystemUI/src/com/android/systemui/assist/AssistHandleViewController.java
index 5010f31..f19e53f 100644
--- a/packages/SystemUI/src/com/android/systemui/assist/AssistHandleViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/assist/AssistHandleViewController.java
@@ -30,7 +30,7 @@
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.systemui.CornerHandleView;
 import com.android.systemui.R;
-import com.android.systemui.statusbar.phone.NavigationBarTransitions;
+import com.android.systemui.navigationbar.NavigationBarTransitions;
 
 /**
  * A class for managing Assistant handle show, hide and animation.
diff --git a/packages/SystemUI/src/com/android/systemui/assist/AssistLogger.kt b/packages/SystemUI/src/com/android/systemui/assist/AssistLogger.kt
index 08edad3..4d0fd43 100644
--- a/packages/SystemUI/src/com/android/systemui/assist/AssistLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/assist/AssistLogger.kt
@@ -28,11 +28,11 @@
 import com.android.keyguard.KeyguardUpdateMonitor
 import com.android.systemui.assist.AssistantInvocationEvent.Companion.deviceStateFromLegacyDeviceState
 import com.android.systemui.assist.AssistantInvocationEvent.Companion.eventFromLegacyInvocationType
+import com.android.systemui.dagger.SysUISingleton
 import javax.inject.Inject
-import javax.inject.Singleton
 
 /** Class for reporting events related to Assistant sessions. */
-@Singleton
+@SysUISingleton
 open class AssistLogger @Inject constructor(
     protected val context: Context,
     protected val uiEventLogger: UiEventLogger,
diff --git a/packages/SystemUI/src/com/android/systemui/assist/AssistManager.java b/packages/SystemUI/src/com/android/systemui/assist/AssistManager.java
index 6d179f2..e85cafa 100644
--- a/packages/SystemUI/src/com/android/systemui/assist/AssistManager.java
+++ b/packages/SystemUI/src/com/android/systemui/assist/AssistManager.java
@@ -46,6 +46,7 @@
 import com.android.settingslib.applications.InterestingConfigChanges;
 import com.android.systemui.R;
 import com.android.systemui.assist.ui.DefaultUiController;
+import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.model.SysUiState;
 import com.android.systemui.recents.OverviewProxyService;
 import com.android.systemui.statusbar.CommandQueue;
@@ -53,14 +54,13 @@
 import com.android.systemui.statusbar.policy.DeviceProvisionedController;
 
 import javax.inject.Inject;
-import javax.inject.Singleton;
 
 import dagger.Lazy;
 
 /**
  * Class to manage everything related to assist in SystemUI.
  */
-@Singleton
+@SysUISingleton
 public class AssistManager {
 
     /**
diff --git a/packages/SystemUI/src/com/android/systemui/assist/AssistModule.java b/packages/SystemUI/src/com/android/systemui/assist/AssistModule.java
index 6f5a17d..ef43f87 100644
--- a/packages/SystemUI/src/com/android/systemui/assist/AssistModule.java
+++ b/packages/SystemUI/src/com/android/systemui/assist/AssistModule.java
@@ -25,13 +25,13 @@
 import androidx.slice.Clock;
 
 import com.android.internal.app.AssistUtils;
-import com.android.systemui.statusbar.NavigationBarController;
+import com.android.systemui.dagger.SysUISingleton;
+import com.android.systemui.navigationbar.NavigationBarController;
 
 import java.util.EnumMap;
 import java.util.Map;
 
 import javax.inject.Named;
-import javax.inject.Singleton;
 
 import dagger.Module;
 import dagger.Provides;
@@ -44,7 +44,7 @@
     static final String UPTIME_NAME = "uptime";
 
     @Provides
-    @Singleton
+    @SysUISingleton
     @Named(ASSIST_HANDLE_THREAD_NAME)
     static Handler provideBackgroundHandler() {
         final HandlerThread backgroundHandlerThread =
@@ -54,7 +54,7 @@
     }
 
     @Provides
-    @Singleton
+    @SysUISingleton
     static Map<AssistHandleBehavior, AssistHandleBehaviorController.BehaviorController>
             provideAssistHandleBehaviorControllerMap(
                     AssistHandleOffBehavior offBehavior,
@@ -76,13 +76,13 @@
     }
 
     @Provides
-    @Singleton
+    @SysUISingleton
     static AssistUtils provideAssistUtils(Context context) {
         return new AssistUtils(context);
     }
 
     @Provides
-    @Singleton
+    @SysUISingleton
     @Named(UPTIME_NAME)
     static Clock provideSystemClock() {
         return SystemClock::uptimeMillis;
diff --git a/packages/SystemUI/src/com/android/systemui/assist/AssistantSessionEvent.kt b/packages/SystemUI/src/com/android/systemui/assist/AssistantSessionEvent.kt
index 8b953fa..be089b1 100644
--- a/packages/SystemUI/src/com/android/systemui/assist/AssistantSessionEvent.kt
+++ b/packages/SystemUI/src/com/android/systemui/assist/AssistantSessionEvent.kt
@@ -21,7 +21,7 @@
 
 enum class AssistantSessionEvent(private val id: Int) : UiEventLogger.UiEventEnum {
     @UiEvent(doc = "Unknown assistant session event")
-    ASSISTANT_SESSION_UNKNOWN(523),
+    ASSISTANT_SESSION_UNKNOWN(0),
 
     @UiEvent(doc = "Assistant session dismissed due to timeout")
     ASSISTANT_SESSION_TIMEOUT_DISMISS(524),
diff --git a/packages/SystemUI/src/com/android/systemui/assist/DeviceConfigHelper.java b/packages/SystemUI/src/com/android/systemui/assist/DeviceConfigHelper.java
index 86b7c74..4d5c44c 100644
--- a/packages/SystemUI/src/com/android/systemui/assist/DeviceConfigHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/assist/DeviceConfigHelper.java
@@ -22,17 +22,18 @@
 
 import androidx.annotation.Nullable;
 
+import com.android.systemui.dagger.SysUISingleton;
+
 import java.util.concurrent.Executor;
 
 import javax.inject.Inject;
-import javax.inject.Singleton;
 
 /**
  * Wrapper class for retrieving System UI device configuration values.
  *
  * Can be mocked in tests for ease of testing the effects of particular values.
  */
-@Singleton
+@SysUISingleton
 public class DeviceConfigHelper {
 
     @Inject
diff --git a/packages/SystemUI/src/com/android/systemui/assist/PhoneStateMonitor.java b/packages/SystemUI/src/com/android/systemui/assist/PhoneStateMonitor.java
index 257ad50..50d559b 100644
--- a/packages/SystemUI/src/com/android/systemui/assist/PhoneStateMonitor.java
+++ b/packages/SystemUI/src/com/android/systemui/assist/PhoneStateMonitor.java
@@ -30,6 +30,7 @@
 import com.android.systemui.BootCompleteCache;
 import com.android.systemui.Dependency;
 import com.android.systemui.broadcast.BroadcastDispatcher;
+import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.shared.system.ActivityManagerWrapper;
 import com.android.systemui.shared.system.PackageManagerWrapper;
@@ -42,12 +43,11 @@
 import java.util.Optional;
 
 import javax.inject.Inject;
-import javax.inject.Singleton;
 
 import dagger.Lazy;
 
 /** Class to monitor and report the state of the phone. */
-@Singleton
+@SysUISingleton
 public final class PhoneStateMonitor {
 
     public static final int PHONE_STATE_AOD1 = 1;
diff --git a/packages/SystemUI/src/com/android/systemui/assist/ui/DefaultUiController.java b/packages/SystemUI/src/com/android/systemui/assist/ui/DefaultUiController.java
index 05f3617..1d90096 100644
--- a/packages/SystemUI/src/com/android/systemui/assist/ui/DefaultUiController.java
+++ b/packages/SystemUI/src/com/android/systemui/assist/ui/DefaultUiController.java
@@ -41,18 +41,18 @@
 import com.android.systemui.assist.AssistLogger;
 import com.android.systemui.assist.AssistManager;
 import com.android.systemui.assist.AssistantSessionEvent;
-import com.android.systemui.statusbar.NavigationBarController;
+import com.android.systemui.dagger.SysUISingleton;
+import com.android.systemui.navigationbar.NavigationBarController;
 
 import java.util.Locale;
 
 import javax.inject.Inject;
-import javax.inject.Singleton;
 
 /**
  * Default UiController implementation. Shows white edge lights along the bottom of the phone,
  * expanding from the corners to meet in the center.
  */
-@Singleton
+@SysUISingleton
 public class DefaultUiController implements AssistManager.UiController {
 
     private static final String TAG = "DefaultUiController";
diff --git a/packages/SystemUI/src/com/android/systemui/assist/ui/InvocationLightsView.java b/packages/SystemUI/src/com/android/systemui/assist/ui/InvocationLightsView.java
index e5121a8..ac39ed5 100644
--- a/packages/SystemUI/src/com/android/systemui/assist/ui/InvocationLightsView.java
+++ b/packages/SystemUI/src/com/android/systemui/assist/ui/InvocationLightsView.java
@@ -33,9 +33,9 @@
 import com.android.settingslib.Utils;
 import com.android.systemui.Dependency;
 import com.android.systemui.R;
-import com.android.systemui.statusbar.NavigationBarController;
-import com.android.systemui.statusbar.phone.NavigationBarFragment;
-import com.android.systemui.statusbar.phone.NavigationBarTransitions;
+import com.android.systemui.navigationbar.NavigationBarController;
+import com.android.systemui.navigationbar.NavigationBar;
+import com.android.systemui.navigationbar.NavigationBarTransitions;
 
 import java.util.ArrayList;
 
@@ -284,7 +284,7 @@
                 return;
             }
 
-            NavigationBarFragment navBar = controller.getDefaultNavigationBarFragment();
+            NavigationBar navBar = controller.getDefaultNavigationBar();
             if (navBar == null) {
                 return;
             }
@@ -301,7 +301,7 @@
                 return;
             }
 
-            NavigationBarFragment navBar = controller.getDefaultNavigationBarFragment();
+            NavigationBar navBar = controller.getDefaultNavigationBar();
             if (navBar == null) {
                 return;
             }
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
index 361ea67..ea18b11 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
@@ -49,25 +49,28 @@
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.os.SomeArgs;
 import com.android.systemui.SystemUI;
+import com.android.systemui.dagger.SysUISingleton;
+import com.android.systemui.doze.DozeReceiver;
+import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.statusbar.CommandQueue;
 
 import java.util.List;
 
 import javax.inject.Inject;
-import javax.inject.Singleton;
 
 /**
  * Receives messages sent from {@link com.android.server.biometrics.BiometricService} and shows the
  * appropriate biometric UI (e.g. BiometricDialogView).
  */
-@Singleton
+@SysUISingleton
 public class AuthController extends SystemUI implements CommandQueue.Callbacks,
-        AuthDialogCallback {
+        AuthDialogCallback, DozeReceiver {
 
-    private static final String TAG = "BiometricPrompt/AuthController";
+    private static final String TAG = "AuthController";
     private static final boolean DEBUG = true;
 
     private final CommandQueue mCommandQueue;
+    private final StatusBarStateController mStatusBarStateController;
     private final Injector mInjector;
 
     // TODO: These should just be saved from onSaveState
@@ -77,6 +80,7 @@
 
     private Handler mHandler = new Handler(Looper.getMainLooper());
     private WindowManager mWindowManager;
+    @Nullable
     private UdfpsController mUdfpsController;
     @VisibleForTesting
     IActivityTaskManager mActivityTaskManager;
@@ -143,6 +147,13 @@
     };
 
     @Override
+    public void dozeTimeTick() {
+        if (mUdfpsController != null) {
+            mUdfpsController.dozeTimeTick();
+        }
+    }
+
+    @Override
     public void onTryAgainPressed() {
         if (mReceiver == null) {
             Log.e(TAG, "onTryAgainPressed: Receiver is null");
@@ -251,14 +262,17 @@
     }
 
     @Inject
-    public AuthController(Context context, CommandQueue commandQueue) {
-        this(context, commandQueue, new Injector());
+    public AuthController(Context context, CommandQueue commandQueue,
+            StatusBarStateController statusBarStateController) {
+        this(context, commandQueue, statusBarStateController, new Injector());
     }
 
     @VisibleForTesting
-    AuthController(Context context, CommandQueue commandQueue, Injector injector) {
+    AuthController(Context context, CommandQueue commandQueue,
+            StatusBarStateController statusBarStateController, Injector injector) {
         super(context);
         mCommandQueue = commandQueue;
+        mStatusBarStateController = statusBarStateController;
         mInjector = injector;
 
         IntentFilter filter = new IntentFilter();
@@ -280,7 +294,7 @@
                     fpm.getSensorProperties();
             for (FingerprintSensorProperties props : fingerprintSensorProperties) {
                 if (props.sensorType == FingerprintSensorProperties.TYPE_UDFPS) {
-                    mUdfpsController = new UdfpsController(mContext, mWindowManager);
+                    mUdfpsController = new UdfpsController(mContext, mStatusBarStateController);
                     break;
                 }
             }
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthCredentialPasswordView.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthCredentialPasswordView.java
index 95bbea1..0892612 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthCredentialPasswordView.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthCredentialPasswordView.java
@@ -16,6 +16,7 @@
 
 package com.android.systemui.biometrics;
 
+import android.annotation.NonNull;
 import android.content.Context;
 import android.os.UserHandle;
 import android.text.InputType;
@@ -27,7 +28,9 @@
 import android.widget.TextView;
 
 import com.android.internal.widget.LockPatternChecker;
+import com.android.internal.widget.LockPatternUtils;
 import com.android.internal.widget.LockscreenCredential;
+import com.android.internal.widget.VerifyCredentialResponse;
 import com.android.systemui.R;
 
 /**
@@ -104,18 +107,21 @@
                 return;
             }
 
+            // Request LockSettingsService to return the Gatekeeper Password in the
+            // VerifyCredentialResponse so that we can request a Gatekeeper HAT with the
+            // Gatekeeper Password and operationId.
             mPendingLockCheck = LockPatternChecker.verifyCredential(mLockPatternUtils,
-                    password, mOperationId, mEffectiveUserId, this::onCredentialVerified);
+                    password, mEffectiveUserId, LockPatternUtils.VERIFY_FLAG_REQUEST_GK_PW_HANDLE,
+                    this::onCredentialVerified);
         }
     }
 
     @Override
-    protected void onCredentialVerified(byte[] attestation, int timeoutMs) {
-        super.onCredentialVerified(attestation, timeoutMs);
+    protected void onCredentialVerified(@NonNull VerifyCredentialResponse response,
+            int timeoutMs) {
+        super.onCredentialVerified(response, timeoutMs);
 
-        final boolean matched = attestation != null;
-
-        if (matched) {
+        if (response.isMatched()) {
             mImm.hideSoftInputFromWindow(getWindowToken(), 0 /* flags */);
         } else {
             mPasswordField.setText("");
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthCredentialPatternView.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthCredentialPatternView.java
index 6d16f43..ab8162f 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthCredentialPatternView.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthCredentialPatternView.java
@@ -16,6 +16,7 @@
 
 package com.android.systemui.biometrics;
 
+import android.annotation.NonNull;
 import android.content.Context;
 import android.util.AttributeSet;
 
@@ -23,6 +24,7 @@
 import com.android.internal.widget.LockPatternUtils;
 import com.android.internal.widget.LockPatternView;
 import com.android.internal.widget.LockscreenCredential;
+import com.android.internal.widget.VerifyCredentialResponse;
 import com.android.systemui.R;
 
 import java.util.List;
@@ -61,22 +63,25 @@
 
             if (pattern.size() < LockPatternUtils.MIN_PATTERN_REGISTER_FAIL) {
                 // Pattern size is less than the minimum, do not count it as a failed attempt.
-                onPatternVerified(null /* attestation */, 0 /* timeoutMs */);
+                onPatternVerified(VerifyCredentialResponse.ERROR, 0 /* timeoutMs */);
                 return;
             }
 
             try (LockscreenCredential credential = LockscreenCredential.createPattern(pattern)) {
+                // Request LockSettingsService to return the Gatekeeper Password in the
+                // VerifyCredentialResponse so that we can request a Gatekeeper HAT with the
+                // Gatekeeper Password and operationId.
                 mPendingLockCheck = LockPatternChecker.verifyCredential(
                         mLockPatternUtils,
                         credential,
-                        mOperationId,
                         mEffectiveUserId,
+                        LockPatternUtils.VERIFY_FLAG_REQUEST_GK_PW_HANDLE,
                         this::onPatternVerified);
             }
         }
 
-        private void onPatternVerified(byte[] attestation, int timeoutMs) {
-            AuthCredentialPatternView.this.onCredentialVerified(attestation, timeoutMs);
+        private void onPatternVerified(@NonNull VerifyCredentialResponse response, int timeoutMs) {
+            AuthCredentialPatternView.this.onCredentialVerified(response, timeoutMs);
             if (timeoutMs > 0) {
                 mLockPatternView.setEnabled(false);
             } else {
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthCredentialView.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthCredentialView.java
index 2695c69..b44ff29 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthCredentialView.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthCredentialView.java
@@ -16,6 +16,9 @@
 
 package com.android.systemui.biometrics;
 
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.app.AlertDialog;
 import android.app.admin.DevicePolicyManager;
 import android.content.Context;
@@ -38,12 +41,10 @@
 import android.widget.LinearLayout;
 import android.widget.TextView;
 
-import androidx.annotation.IntDef;
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
 import androidx.annotation.StringRes;
 
 import com.android.internal.widget.LockPatternUtils;
+import com.android.internal.widget.VerifyCredentialResponse;
 import com.android.systemui.Interpolators;
 import com.android.systemui.R;
 
@@ -283,14 +284,20 @@
 
     protected void onErrorTimeoutFinish() {}
 
-    protected void onCredentialVerified(byte[] attestation, int timeoutMs) {
-
-        final boolean matched = attestation != null;
-
-        if (matched) {
+    protected void onCredentialVerified(@NonNull VerifyCredentialResponse response, int timeoutMs) {
+        if (response.isMatched()) {
             mClearErrorRunnable.run();
             mLockPatternUtils.userPresent(mEffectiveUserId);
-            mCallback.onCredentialMatched(attestation);
+
+            // The response passed into this method contains the Gatekeeper Password. We still
+            // have to request Gatekeeper to create a Hardware Auth Token with the
+            // Gatekeeper Password and Challenge (keystore operationId in this case)
+            final long pwHandle = response.getGatekeeperPasswordHandle();
+            final VerifyCredentialResponse gkResponse = mLockPatternUtils
+                    .verifyGatekeeperPasswordHandle(pwHandle, mOperationId, mEffectiveUserId);
+
+            mCallback.onCredentialMatched(gkResponse.getGatekeeperHAT());
+            mLockPatternUtils.removeGatekeeperPasswordHandle(pwHandle);
         } else {
             if (timeoutMs > 0) {
                 mHandler.removeCallbacks(mClearErrorRunnable);
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
index 739c2b1..82fb808 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
@@ -16,23 +16,32 @@
 
 package com.android.systemui.biometrics;
 
+import android.annotation.NonNull;
 import android.annotation.SuppressLint;
+import android.content.ContentResolver;
 import android.content.Context;
+import android.content.res.TypedArray;
 import android.graphics.PixelFormat;
 import android.graphics.Point;
 import android.hardware.fingerprint.FingerprintManager;
-import android.hardware.fingerprint.IFingerprintService;
 import android.hardware.fingerprint.IUdfpsOverlayController;
 import android.os.Handler;
 import android.os.Looper;
-import android.os.RemoteException;
+import android.os.PowerManager;
+import android.os.UserHandle;
+import android.provider.Settings;
+import android.text.TextUtils;
 import android.util.Log;
+import android.util.MathUtils;
+import android.util.Spline;
 import android.view.LayoutInflater;
 import android.view.MotionEvent;
 import android.view.WindowManager;
-import android.widget.LinearLayout;
 
+import com.android.internal.BrightnessSynchronizer;
 import com.android.systemui.R;
+import com.android.systemui.doze.DozeReceiver;
+import com.android.systemui.plugins.statusbar.StatusBarStateController;
 
 import java.io.FileWriter;
 import java.io.IOException;
@@ -41,19 +50,34 @@
  * Shows and hides the under-display fingerprint sensor (UDFPS) overlay, handles UDFPS touch events,
  * and coordinates triggering of the high-brightness mode (HBM).
  */
-class UdfpsController {
+class UdfpsController implements DozeReceiver {
     private static final String TAG = "UdfpsController";
+    // Gamma approximation for the sRGB color space.
+    private static final float DISPLAY_GAMMA = 2.2f;
 
-    private final Context mContext;
     private final FingerprintManager mFingerprintManager;
     private final WindowManager mWindowManager;
+    private final ContentResolver mContentResolver;
     private final Handler mHandler;
-
-    private UdfpsView mView;
-    private WindowManager.LayoutParams mLayoutParams;
-    private String mHbmPath;
-    private String mHbmEnableCommand;
-    private String mHbmDisableCommand;
+    private final WindowManager.LayoutParams mLayoutParams;
+    private final UdfpsView mView;
+    // Debugfs path to control the high-brightness mode.
+    private final String mHbmPath;
+    private final String mHbmEnableCommand;
+    private final String mHbmDisableCommand;
+    private final boolean mHbmSupported;
+    // Brightness in nits in the high-brightness mode.
+    private final float mHbmNits;
+    // A spline mapping from the device's backlight value, normalized to the range [0, 1.0], to a
+    // brightness in nits.
+    private final Spline mBacklightToNitsSpline;
+    // A spline mapping from a value in nits to a backlight value of a hypothetical panel whose
+    // maximum backlight value corresponds to our panel's high-brightness mode.
+    // The output is normalized to the range [0, 1.0].
+    private Spline mNitsToHbmBacklightSpline;
+    // Default non-HBM backlight value normalized to the range [0, 1.0]. Used as a fallback when the
+    // actual brightness value cannot be retrieved.
+    private final float mDefaultBrightness;
     private boolean mIsOverlayShowing;
 
     public class UdfpsOverlayController extends IUdfpsOverlayController.Stub {
@@ -66,26 +90,33 @@
         public void hideUdfpsOverlay() {
             UdfpsController.this.hideUdfpsOverlay();
         }
+
+        @Override
+        public void setDebugMessage(String message) {
+            mView.setDebugMessage(message);
+        }
     }
 
     @SuppressLint("ClickableViewAccessibility")
     private final UdfpsView.OnTouchListener mOnTouchListener = (v, event) -> {
+        UdfpsView view = (UdfpsView) v;
+        final boolean isFingerDown = view.isScrimShowing();
         switch (event.getAction()) {
             case MotionEvent.ACTION_DOWN:
             case MotionEvent.ACTION_MOVE:
-                boolean isValidTouch = mView.isValidTouch(event.getX(), event.getY(),
+                final boolean isValidTouch = view.isValidTouch(event.getX(), event.getY(),
                         event.getPressure());
-                if (!mView.isFingerDown() && isValidTouch) {
+                if (!isFingerDown && isValidTouch) {
                     onFingerDown((int) event.getX(), (int) event.getY(), event.getTouchMinor(),
                             event.getTouchMajor());
-                } else if (mView.isFingerDown() && !isValidTouch) {
+                } else if (isFingerDown && !isValidTouch) {
                     onFingerUp();
                 }
                 return true;
 
             case MotionEvent.ACTION_UP:
             case MotionEvent.ACTION_CANCEL:
-                if (mView.isFingerDown()) {
+                if (isFingerDown) {
                     onFingerUp();
                 }
                 return true;
@@ -95,94 +126,200 @@
         }
     };
 
-    UdfpsController(Context context, WindowManager windowManager) {
-        mContext = context;
+    UdfpsController(@NonNull Context context,
+            @NonNull StatusBarStateController statusBarStateController) {
         mFingerprintManager = context.getSystemService(FingerprintManager.class);
-        mWindowManager = windowManager;
+        mWindowManager = context.getSystemService(WindowManager.class);
+        mContentResolver = context.getContentResolver();
         mHandler = new Handler(Looper.getMainLooper());
-        start();
-    }
+        mLayoutParams = createLayoutParams(context);
 
-    private void start() {
-        Log.v(TAG, "start");
+        mView = (UdfpsView) LayoutInflater.from(context).inflate(R.layout.udfps_view, null, false);
 
-        Point displaySize = new Point();
-        mWindowManager.getDefaultDisplay().getRealSize(displaySize);
+        mHbmPath = context.getResources().getString(R.string.udfps_hbm_sysfs_path);
+        mHbmEnableCommand = context.getResources().getString(R.string.udfps_hbm_enable_command);
+        mHbmDisableCommand = context.getResources().getString(R.string.udfps_hbm_disable_command);
+
+        mHbmSupported = !TextUtils.isEmpty(mHbmPath);
+        mView.setHbmSupported(mHbmSupported);
+        statusBarStateController.addCallback(mView);
+
+        // This range only consists of the minimum and maximum values, which only cover
+        // non-high-brightness mode.
+        float[] nitsRange = toFloatArray(context.getResources().obtainTypedArray(
+                com.android.internal.R.array.config_screenBrightnessNits));
+
+        // The last value of this range corresponds to the high-brightness mode.
+        float[] nitsAutoBrightnessValues = toFloatArray(context.getResources().obtainTypedArray(
+                com.android.internal.R.array.config_autoBrightnessDisplayValuesNits));
+
+        mHbmNits = nitsAutoBrightnessValues[nitsAutoBrightnessValues.length - 1];
+        float[] hbmNitsRange = {nitsRange[0], mHbmNits};
+
+        // This range only consists of the minimum and maximum backlight values, which only apply
+        // in non-high-brightness mode.
+        float[] normalizedBacklightRange = normalizeBacklightRange(
+                context.getResources().getIntArray(
+                        com.android.internal.R.array.config_screenBrightnessBacklight));
+
+        mBacklightToNitsSpline = Spline.createSpline(normalizedBacklightRange, nitsRange);
+        mNitsToHbmBacklightSpline = Spline.createSpline(hbmNitsRange, normalizedBacklightRange);
+        mDefaultBrightness = obtainDefaultBrightness(context);
+
         // TODO(b/160025856): move to the "dump" method.
-        Log.v(TAG, "UdfpsController | display size: " + displaySize.x + "x"
-                + displaySize.y);
-
-        mLayoutParams = new WindowManager.LayoutParams(
-                displaySize.x,
-                displaySize.y,
-                // TODO(b/152419866): Use the UDFPS window type when it becomes available.
-                WindowManager.LayoutParams.TYPE_BOOT_PROGRESS,
-                WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN
-                        | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL,
-                PixelFormat.TRANSLUCENT);
-        mLayoutParams.setTitle(TAG);
-        mLayoutParams.windowAnimations = 0;
-
-        LinearLayout layout = new LinearLayout(mContext);
-        layout.setLayoutParams(mLayoutParams);
-        mView = (UdfpsView) LayoutInflater.from(mContext).inflate(R.layout.udfps_view, layout,
-                false);
-        mView.setOnTouchListener(mOnTouchListener);
-
-        mHbmPath = mContext.getResources().getString(R.string.udfps_hbm_sysfs_path);
-        mHbmEnableCommand = mContext.getResources().getString(R.string.udfps_hbm_enable_command);
-        mHbmDisableCommand = mContext.getResources().getString(R.string.udfps_hbm_disable_command);
+        Log.v(TAG, String.format("ctor | mNitsRange: [%f, %f]", nitsRange[0], nitsRange[1]));
+        Log.v(TAG, String.format("ctor | mHbmNitsRange: [%f, %f]", hbmNitsRange[0],
+                hbmNitsRange[1]));
+        Log.v(TAG, String.format("ctor | mNormalizedBacklightRange: [%f, %f]",
+                normalizedBacklightRange[0], normalizedBacklightRange[1]));
 
         mFingerprintManager.setUdfpsOverlayController(new UdfpsOverlayController());
         mIsOverlayShowing = false;
     }
 
+    @Override
+    public void dozeTimeTick() {
+        mView.dozeTimeTick();
+    }
+
     private void showUdfpsOverlay() {
         mHandler.post(() -> {
-            Log.v(TAG, "showUdfpsOverlay | adding window");
             if (!mIsOverlayShowing) {
                 try {
+                    Log.v(TAG, "showUdfpsOverlay | adding window");
                     mWindowManager.addView(mView, mLayoutParams);
                     mIsOverlayShowing = true;
+                    mView.setOnTouchListener(mOnTouchListener);
                 } catch (RuntimeException e) {
                     Log.e(TAG, "showUdfpsOverlay | failed to add window", e);
                 }
+            } else {
+                Log.v(TAG, "showUdfpsOverlay | the overlay is already showing");
             }
         });
     }
 
     private void hideUdfpsOverlay() {
-        onFingerUp();
         mHandler.post(() -> {
-            Log.v(TAG, "hideUdfpsOverlay | removing window");
             if (mIsOverlayShowing) {
+                Log.v(TAG, "hideUdfpsOverlay | removing window");
+                mView.setOnTouchListener(null);
+                // Reset the controller back to its starting state.
+                onFingerUp();
                 mWindowManager.removeView(mView);
                 mIsOverlayShowing = false;
+            } else {
+                Log.v(TAG, "hideUdfpsOverlay | the overlay is already hidden");
             }
         });
     }
 
+    // Returns a value in the range of [0, 255].
+    private int computeScrimOpacity() {
+        // Backlight setting can be NaN, -1.0f, and [0.0f, 1.0f].
+        float backlightSetting = Settings.System.getFloatForUser(mContentResolver,
+                Settings.System.SCREEN_BRIGHTNESS_FLOAT, mDefaultBrightness,
+                UserHandle.USER_CURRENT);
+
+        // Constrain the backlight setting to [0.0f, 1.0f].
+        float backlightValue = MathUtils.constrain(backlightSetting,
+                PowerManager.BRIGHTNESS_MIN,
+                PowerManager.BRIGHTNESS_MAX);
+
+        // Interpolate the backlight value to nits.
+        float nits = mBacklightToNitsSpline.interpolate(backlightValue);
+
+        // Interpolate nits to a backlight value for a panel with enabled HBM.
+        float interpolatedHbmBacklightValue = mNitsToHbmBacklightSpline.interpolate(nits);
+
+        float gammaCorrectedHbmBacklightValue = (float) Math.pow(interpolatedHbmBacklightValue,
+                1.0f / DISPLAY_GAMMA);
+        float scrimOpacity = PowerManager.BRIGHTNESS_MAX - gammaCorrectedHbmBacklightValue;
+
+        // Interpolate the opacity value from [0.0f, 1.0f] to [0, 255].
+        return BrightnessSynchronizer.brightnessFloatToInt(scrimOpacity);
+    }
+
     private void onFingerDown(int x, int y, float minor, float major) {
+        mView.setScrimAlpha(computeScrimOpacity());
+        mView.showScrimAndDot();
         try {
-            FileWriter fw = new FileWriter(mHbmPath);
-            fw.write(mHbmEnableCommand);
-            fw.close();
+            if (mHbmSupported) {
+                FileWriter fw = new FileWriter(mHbmPath);
+                fw.write(mHbmEnableCommand);
+                fw.close();
+            }
+            mFingerprintManager.onFingerDown(x, y, minor, major);
         } catch (IOException e) {
+            mView.hideScrimAndDot();
             Log.e(TAG, "onFingerDown | failed to enable HBM: " + e.getMessage());
         }
-        mView.onFingerDown();
-        mFingerprintManager.onFingerDown(x, y, minor, major);
     }
 
     private void onFingerUp() {
         mFingerprintManager.onFingerUp();
-        mView.onFingerUp();
-        try {
-            FileWriter fw = new FileWriter(mHbmPath);
-            fw.write(mHbmDisableCommand);
-            fw.close();
-        } catch (IOException e) {
-            Log.e(TAG, "onFingerUp | failed to disable HBM: " + e.getMessage());
+        // Hiding the scrim before disabling HBM results in less noticeable flicker.
+        mView.hideScrimAndDot();
+        if (mHbmSupported) {
+            try {
+                FileWriter fw = new FileWriter(mHbmPath);
+                fw.write(mHbmDisableCommand);
+                fw.close();
+            } catch (IOException e) {
+                mView.showScrimAndDot();
+                Log.e(TAG, "onFingerUp | failed to disable HBM: " + e.getMessage());
+            }
         }
     }
+
+    private static WindowManager.LayoutParams createLayoutParams(Context context) {
+        Point displaySize = new Point();
+        context.getDisplay().getRealSize(displaySize);
+        // TODO(b/160025856): move to the "dump" method.
+        Log.v(TAG, "createLayoutParams | display size: " + displaySize.x + "x"
+                + displaySize.y);
+        WindowManager.LayoutParams lp = new WindowManager.LayoutParams(
+                displaySize.x,
+                displaySize.y,
+                // TODO(b/152419866): Use the UDFPS window type when it becomes available.
+                WindowManager.LayoutParams.TYPE_BOOT_PROGRESS,
+                WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN
+                        | WindowManager.LayoutParams.FLAG_LAYOUT_INSET_DECOR
+                        | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
+                        | WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED,
+                PixelFormat.TRANSLUCENT);
+        lp.setTitle(TAG);
+        lp.setFitInsetsTypes(0);
+        return lp;
+    }
+
+    private static float obtainDefaultBrightness(Context context) {
+        PowerManager powerManager = context.getSystemService(PowerManager.class);
+        if (powerManager == null) {
+            Log.e(TAG, "PowerManager is unavailable. Can't obtain default brightness.");
+            return 0f;
+        }
+        return MathUtils.constrain(powerManager.getBrightnessConstraint(
+                PowerManager.BRIGHTNESS_CONSTRAINT_TYPE_DEFAULT), PowerManager.BRIGHTNESS_MIN,
+                PowerManager.BRIGHTNESS_MAX);
+    }
+
+    private static float[] toFloatArray(TypedArray array) {
+        final int n = array.length();
+        float[] vals = new float[n];
+        for (int i = 0; i < n; i++) {
+            vals[i] = array.getFloat(i, PowerManager.BRIGHTNESS_OFF_FLOAT);
+        }
+        array.recycle();
+        return vals;
+    }
+
+    private static float[] normalizeBacklightRange(int[] backlight) {
+        final int n = backlight.length;
+        float[] normalizedBacklight = new float[n];
+        for (int i = 0; i < n; i++) {
+            normalizedBacklight[i] = BrightnessSynchronizer.brightnessIntToFloat(backlight[i]);
+        }
+        return normalizedBacklight;
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsView.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsView.java
index 8190550..d7e9138 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsView.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsView.java
@@ -16,6 +16,8 @@
 
 package com.android.systemui.biometrics;
 
+import static com.android.systemui.doze.util.BurnInHelperKt.getBurnInOffset;
+
 import android.content.Context;
 import android.content.res.TypedArray;
 import android.graphics.Canvas;
@@ -23,34 +25,57 @@
 import android.graphics.Paint;
 import android.graphics.Rect;
 import android.graphics.RectF;
+import android.text.TextUtils;
 import android.util.AttributeSet;
 import android.util.Log;
+import android.util.MathUtils;
 import android.view.View;
 import android.view.ViewTreeObserver;
 
 import com.android.systemui.R;
+import com.android.systemui.doze.DozeReceiver;
+import com.android.systemui.plugins.statusbar.StatusBarStateController;
 
 /**
  * A full screen view with a configurable illumination dot and scrim.
  */
-public class UdfpsView extends View {
+public class UdfpsView extends View implements DozeReceiver,
+        StatusBarStateController.StateListener {
     private static final String TAG = "UdfpsView";
 
+    // Values in pixels.
+    private static final float SENSOR_SHADOW_RADIUS = 2.0f;
+    private static final float SENSOR_OUTLINE_WIDTH = 2.0f;
+
+    private static final int DEBUG_TEXT_SIZE_PX = 32;
+
     private final Rect mScrimRect;
     private final Paint mScrimPaint;
+    private final Paint mDebugTextPaint;
 
-    private float mSensorX;
-    private float mSensorY;
     private final RectF mSensorRect;
     private final Paint mSensorPaint;
     private final float mSensorRadius;
-    private final float mSensorMarginBottom;
+    private final float mSensorCenterY;
     private final float mSensorTouchAreaCoefficient;
+    private final int mMaxBurnInOffsetX;
+    private final int mMaxBurnInOffsetY;
 
     private final Rect mTouchableRegion;
     private final ViewTreeObserver.OnComputeInternalInsetsListener mInsetsListener;
 
-    private boolean mIsFingerDown;
+    // This is calculated from the screen's dimensions at runtime, as opposed to mSensorCenterY,
+    // which is defined in layout.xml
+    private float mSensorCenterX;
+
+    // AOD anti-burn-in offsets
+    private float mInterpolatedDarkAmount;
+    private float mBurnInOffsetX;
+    private float mBurnInOffsetY;
+
+    private boolean mIsScrimShowing;
+    private boolean mHbmSupported;
+    private String mDebugMessage;
 
     public UdfpsView(Context context, AttributeSet attrs) {
         super(context, attrs);
@@ -61,7 +86,7 @@
             if (!a.hasValue(R.styleable.UdfpsView_sensorRadius)) {
                 throw new IllegalArgumentException("UdfpsView must contain sensorRadius");
             }
-            if (!a.hasValue(R.styleable.UdfpsView_sensorMarginBottom)) {
+            if (!a.hasValue(R.styleable.UdfpsView_sensorCenterY)) {
                 throw new IllegalArgumentException("UdfpsView must contain sensorMarginBottom");
             }
             if (!a.hasValue(R.styleable.UdfpsView_sensorTouchAreaCoefficient)) {
@@ -69,22 +94,35 @@
                         "UdfpsView must contain sensorTouchAreaCoefficient");
             }
             mSensorRadius = a.getDimension(R.styleable.UdfpsView_sensorRadius, 0f);
-            mSensorMarginBottom = a.getDimension(R.styleable.UdfpsView_sensorMarginBottom, 0f);
+            mSensorCenterY = a.getDimension(R.styleable.UdfpsView_sensorCenterY, 0f);
             mSensorTouchAreaCoefficient = a.getFloat(
                     R.styleable.UdfpsView_sensorTouchAreaCoefficient, 0f);
         } finally {
             a.recycle();
         }
 
+        mMaxBurnInOffsetX = getResources()
+                .getDimensionPixelSize(R.dimen.udfps_burn_in_offset_x);
+        mMaxBurnInOffsetY = getResources()
+                .getDimensionPixelSize(R.dimen.udfps_burn_in_offset_y);
 
         mScrimRect = new Rect();
         mScrimPaint = new Paint(0 /* flags */);
-        mScrimPaint.setARGB(110 /* a */, 0 /* r */, 0 /* g */, 0 /* b */);
+        mScrimPaint.setColor(Color.BLACK);
 
         mSensorRect = new RectF();
         mSensorPaint = new Paint(0 /* flags */);
+        mSensorPaint.setAntiAlias(true);
         mSensorPaint.setColor(Color.WHITE);
         mSensorPaint.setStyle(Paint.Style.STROKE);
+        mSensorPaint.setStrokeWidth(SENSOR_OUTLINE_WIDTH);
+        mSensorPaint.setShadowLayer(SENSOR_SHADOW_RADIUS, 0, 0, Color.BLACK);
+        mSensorPaint.setAntiAlias(true);
+
+        mDebugTextPaint = new Paint();
+        mDebugTextPaint.setAntiAlias(true);
+        mDebugTextPaint.setColor(Color.BLUE);
+        mDebugTextPaint.setTextSize(DEBUG_TEXT_SIZE_PX);
 
         mTouchableRegion = new Rect();
         mInsetsListener = internalInsetsInfo -> {
@@ -93,7 +131,30 @@
             internalInsetsInfo.touchableRegion.set(mTouchableRegion);
         };
 
-        mIsFingerDown = false;
+        mIsScrimShowing = false;
+    }
+
+    @Override
+    public void dozeTimeTick() {
+        updateAodPosition();
+    }
+
+    @Override
+    public void onDozeAmountChanged(float linear, float eased) {
+        mInterpolatedDarkAmount = eased;
+        updateAodPosition();
+    }
+
+    private void updateAodPosition() {
+        mBurnInOffsetX = MathUtils.lerp(0f,
+                getBurnInOffset(mMaxBurnInOffsetX * 2, true /* xAxis */)
+                        - mMaxBurnInOffsetX,
+                mInterpolatedDarkAmount);
+        mBurnInOffsetY = MathUtils.lerp(0f,
+                getBurnInOffset(mMaxBurnInOffsetY * 2, false /* xAxis */)
+                        - 0.5f * mMaxBurnInOffsetY,
+                mInterpolatedDarkAmount);
+        postInvalidate();
     }
 
     @Override
@@ -104,10 +165,11 @@
         final int h = getLayoutParams().height;
         final int w = getLayoutParams().width;
         mScrimRect.set(0 /* left */, 0 /* top */, w, h);
-        mSensorX = w / 2f;
-        mSensorY = h - mSensorMarginBottom - mSensorRadius;
-        mSensorRect.set(mSensorX - mSensorRadius, mSensorY - mSensorRadius,
-                mSensorX + mSensorRadius, mSensorY + mSensorRadius);
+        mSensorCenterX = w / 2f;
+        mSensorRect.set(mSensorCenterX - mSensorRadius, mSensorCenterY - mSensorRadius,
+                mSensorCenterX + mSensorRadius, mSensorCenterY + mSensorRadius);
+
+        // Sets mTouchableRegion with rounded up values from mSensorRect.
         mSensorRect.roundOut(mTouchableRegion);
 
         getViewTreeObserver().addOnComputeInternalInsetsListener(mInsetsListener);
@@ -123,32 +185,55 @@
     @Override
     protected void onDraw(Canvas canvas) {
         super.onDraw(canvas);
-        if (mIsFingerDown) {
+
+        if (mIsScrimShowing && mHbmSupported) {
+            // Only draw the scrim if HBM is supported.
             canvas.drawRect(mScrimRect, mScrimPaint);
         }
+
+        // Translation should affect everything but the scrim.
+        canvas.save();
+        canvas.translate(mBurnInOffsetX, mBurnInOffsetY);
+        if (!TextUtils.isEmpty(mDebugMessage)) {
+            canvas.drawText(mDebugMessage, 0, 160, mDebugTextPaint);
+        }
         canvas.drawOval(mSensorRect, mSensorPaint);
+        canvas.restore();
+    }
+
+    void setHbmSupported(boolean hbmSupported) {
+        mHbmSupported = hbmSupported;
+    }
+
+    void setDebugMessage(String message) {
+        mDebugMessage = message;
+        postInvalidate();
     }
 
     boolean isValidTouch(float x, float y, float pressure) {
-        return x > (mSensorX - mSensorRadius * mSensorTouchAreaCoefficient)
-                && x < (mSensorX + mSensorRadius * mSensorTouchAreaCoefficient)
-                && y > (mSensorY - mSensorRadius * mSensorTouchAreaCoefficient)
-                && y < (mSensorY + mSensorRadius * mSensorTouchAreaCoefficient);
+        return x > (mSensorCenterX - mSensorRadius * mSensorTouchAreaCoefficient)
+                && x < (mSensorCenterX + mSensorRadius * mSensorTouchAreaCoefficient)
+                && y > (mSensorCenterY - mSensorRadius * mSensorTouchAreaCoefficient)
+                && y < (mSensorCenterY + mSensorRadius * mSensorTouchAreaCoefficient);
     }
 
-    boolean isFingerDown() {
-        return mIsFingerDown;
+    void setScrimAlpha(int alpha) {
+        mScrimPaint.setAlpha(alpha);
     }
 
-    void onFingerDown() {
-        mIsFingerDown = true;
+    boolean isScrimShowing() {
+        return mIsScrimShowing;
+    }
+
+    void showScrimAndDot() {
+        mIsScrimShowing = true;
         mSensorPaint.setStyle(Paint.Style.FILL);
-        postInvalidate();
+        invalidate();
     }
 
-    void onFingerUp() {
-        mIsFingerDown = false;
+    void hideScrimAndDot() {
+        mIsScrimShowing = false;
         mSensorPaint.setStyle(Paint.Style.STROKE);
-        postInvalidate();
+        invalidate();
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/Bubble.java b/packages/SystemUI/src/com/android/systemui/bubbles/Bubble.java
index 8748926..27863ba 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/Bubble.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/Bubble.java
@@ -697,6 +697,9 @@
         pw.print("  desiredHeight: "); pw.println(getDesiredHeightString());
         pw.print("  suppressNotif: "); pw.println(shouldSuppressNotification());
         pw.print("  autoExpand:    "); pw.println(shouldAutoExpand());
+        if (mExpandedView != null) {
+            mExpandedView.dump(fd, pw, args);
+        }
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java
index bb57219..9e9d85a 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java
@@ -95,6 +95,7 @@
 import com.android.systemui.statusbar.FeatureFlags;
 import com.android.systemui.statusbar.NotificationLockscreenUserManager;
 import com.android.systemui.statusbar.NotificationRemoveInterceptor;
+import com.android.systemui.statusbar.NotificationShadeWindowController;
 import com.android.systemui.statusbar.ScrimView;
 import com.android.systemui.statusbar.notification.NotificationChannelHelper;
 import com.android.systemui.statusbar.notification.NotificationEntryListener;
@@ -108,7 +109,6 @@
 import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProvider;
 import com.android.systemui.statusbar.notification.logging.NotificationLogger;
 import com.android.systemui.statusbar.phone.NotificationGroupManager;
-import com.android.systemui.statusbar.phone.NotificationShadeWindowController;
 import com.android.systemui.statusbar.phone.ScrimController;
 import com.android.systemui.statusbar.phone.ShadeController;
 import com.android.systemui.statusbar.phone.StatusBar;
@@ -138,7 +138,8 @@
     @IntDef({DISMISS_USER_GESTURE, DISMISS_AGED, DISMISS_TASK_FINISHED, DISMISS_BLOCKED,
             DISMISS_NOTIF_CANCEL, DISMISS_ACCESSIBILITY_ACTION, DISMISS_NO_LONGER_BUBBLE,
             DISMISS_USER_CHANGED, DISMISS_GROUP_CANCELLED, DISMISS_INVALID_INTENT,
-            DISMISS_OVERFLOW_MAX_REACHED, DISMISS_SHORTCUT_REMOVED, DISMISS_PACKAGE_REMOVED})
+            DISMISS_OVERFLOW_MAX_REACHED, DISMISS_SHORTCUT_REMOVED, DISMISS_PACKAGE_REMOVED,
+            DISMISS_NO_BUBBLE_UP})
     @Target({FIELD, LOCAL_VARIABLE, PARAMETER})
     @interface DismissReason {}
 
@@ -155,6 +156,7 @@
     static final int DISMISS_OVERFLOW_MAX_REACHED = 11;
     static final int DISMISS_SHORTCUT_REMOVED = 12;
     static final int DISMISS_PACKAGE_REMOVED = 13;
+    static final int DISMISS_NO_BUBBLE_UP = 14;
 
     private final Context mContext;
     private final NotificationEntryManager mNotificationEntryManager;
@@ -173,6 +175,12 @@
     @Nullable private BubbleStackView mStackView;
     private BubbleIconFactory mBubbleIconFactory;
 
+    /**
+     * The relative position of the stack when we removed it and nulled it out. If the stack is
+     * re-created, it will re-appear at this position.
+     */
+    @Nullable private BubbleStackView.RelativeStackPosition mPositionFromRemovedStack;
+
     // Tracks the id of the current (foreground) user.
     private int mCurrentUserId;
     // Saves notification keys of active bubbles when users are switched.
@@ -736,6 +744,7 @@
                     mContext, mBubbleData, mSurfaceSynchronizer, mFloatingContentCoordinator,
                     mSysUiState, this::onAllBubblesAnimatedOut, this::onImeVisibilityChanged,
                     this::hideCurrentInputMethod);
+            mStackView.setStackStartPosition(mPositionFromRemovedStack);
             mStackView.addView(mBubbleScrim);
             if (mExpandListener != null) {
                 mStackView.setExpandListener(mExpandListener);
@@ -806,6 +815,7 @@
         try {
             mAddedToWindowManager = false;
             if (mStackView != null) {
+                mPositionFromRemovedStack = mStackView.getRelativeStackPosition();
                 mWindowManager.removeView(mStackView);
                 mStackView.removeView(mBubbleScrim);
                 mStackView = null;
@@ -1253,8 +1263,18 @@
             rankingMap.getRanking(key, mTmpRanking);
             boolean isActiveBubble = mBubbleData.hasAnyBubbleWithKey(key);
             if (isActiveBubble && !mTmpRanking.canBubble()) {
-                mBubbleData.dismissBubbleWithKey(entry.getKey(),
-                        BubbleController.DISMISS_BLOCKED);
+                // If this entry is no longer allowed to bubble, dismiss with the BLOCKED reason.
+                // This means that the app or channel's ability to bubble has been revoked.
+                mBubbleData.dismissBubbleWithKey(
+                        key, BubbleController.DISMISS_BLOCKED);
+            } else if (isActiveBubble
+                    && !mNotificationInterruptStateProvider.shouldBubbleUp(entry)) {
+                // If this entry is allowed to bubble, but cannot currently bubble up, dismiss it.
+                // This happens when DND is enabled and configured to hide bubbles. Dismissing with
+                // the reason DISMISS_NO_BUBBLE_UP will retain the underlying notification, so that
+                // the bubble will be re-created if shouldBubbleUp returns true.
+                mBubbleData.dismissBubbleWithKey(
+                        key, BubbleController.DISMISS_NO_BUBBLE_UP);
             } else if (entry != null && mTmpRanking.isBubble() && !isActiveBubble) {
                 entry.setFlagBubble(true);
                 onEntryUpdated(entry);
@@ -1331,8 +1351,10 @@
                     mStackView.removeBubble(bubble);
                 }
 
-                // If the bubble is removed for user switching, leave the notification in place.
-                if (reason == DISMISS_USER_CHANGED) {
+                // Leave the notification in place if we're dismissing due to user switching, or
+                // because DND is suppressing the bubble. In both of those cases, we need to be able
+                // to restore the bubble from the notification later.
+                if (reason == DISMISS_USER_CHANGED || reason == DISMISS_NO_BUBBLE_UP) {
                     continue;
                 }
                 if (reason == DISMISS_NOTIF_CANCEL) {
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleData.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleData.java
index 10f4385..5c6d16d 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleData.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleData.java
@@ -34,7 +34,7 @@
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.systemui.R;
 import com.android.systemui.bubbles.BubbleController.DismissReason;
-import com.android.systemui.shared.system.SysUiStatsLog;
+import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.statusbar.notification.NotificationEntryManager;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 
@@ -52,12 +52,11 @@
 import java.util.function.Predicate;
 
 import javax.inject.Inject;
-import javax.inject.Singleton;
 
 /**
  * Keeps track of active bubbles.
  */
-@Singleton
+@SysUISingleton
 public class BubbleData {
 
     private BubbleLoggerImpl mLogger = new BubbleLoggerImpl();
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleDataRepository.kt b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleDataRepository.kt
index db64a13..f129d31 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleDataRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleDataRepository.kt
@@ -18,25 +18,24 @@
 import android.annotation.SuppressLint
 import android.annotation.UserIdInt
 import android.content.pm.LauncherApps
+import android.content.pm.LauncherApps.ShortcutQuery.FLAG_MATCH_CACHED
 import android.content.pm.LauncherApps.ShortcutQuery.FLAG_MATCH_DYNAMIC
 import android.content.pm.LauncherApps.ShortcutQuery.FLAG_MATCH_PINNED_BY_ANY_LAUNCHER
-import android.content.pm.LauncherApps.ShortcutQuery.FLAG_MATCH_CACHED
 import android.os.UserHandle
 import android.util.Log
 import com.android.systemui.bubbles.storage.BubbleEntity
 import com.android.systemui.bubbles.storage.BubblePersistentRepository
 import com.android.systemui.bubbles.storage.BubbleVolatileRepository
+import com.android.systemui.dagger.SysUISingleton
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.Dispatchers
 import kotlinx.coroutines.Job
 import kotlinx.coroutines.cancelAndJoin
 import kotlinx.coroutines.launch
 import kotlinx.coroutines.yield
-
 import javax.inject.Inject
-import javax.inject.Singleton
 
-@Singleton
+@SysUISingleton
 internal class BubbleDataRepository @Inject constructor(
     private val volatileRepository: BubbleVolatileRepository,
     private val persistentRepository: BubblePersistentRepository,
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExpandedView.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExpandedView.java
index 318a987..80150c9 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExpandedView.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExpandedView.java
@@ -34,6 +34,7 @@
 import static com.android.systemui.bubbles.BubbleDebugConfig.TAG_BUBBLES;
 import static com.android.systemui.bubbles.BubbleDebugConfig.TAG_WITH_CLASS_NAME;
 
+import android.annotation.NonNull;
 import android.annotation.SuppressLint;
 import android.app.ActivityManager;
 import android.app.ActivityOptions;
@@ -77,6 +78,9 @@
 import com.android.systemui.recents.TriangleShape;
 import com.android.systemui.statusbar.AlphaOptimizedButton;
 
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+
 /**
  * Container for the expanded bubble view, handles rendering the caret and settings icon.
  */
@@ -301,12 +305,11 @@
 
         mActivityView = new ActivityView(mContext, null /* attrs */, 0 /* defStyle */,
                 true /* singleTaskInstance */, false /* usePublicVirtualDisplay*/,
-                true /* disableSurfaceViewBackgroundLayer */);
+                true /* disableSurfaceViewBackgroundLayer */, true /* useTrustedDisplay */);
 
         // Set ActivityView's alpha value as zero, since there is no view content to be shown.
         setContentVisibility(false);
 
-        mActivityViewContainer.setBackgroundColor(Color.WHITE);
         mActivityViewContainer.setOutlineProvider(new ViewOutlineProvider() {
             @Override
             public void getOutline(View view, Outline outline) {
@@ -435,9 +438,11 @@
     }
 
     void applyThemeAttrs() {
-        final TypedArray ta = mContext.obtainStyledAttributes(
-                new int[] {android.R.attr.dialogCornerRadius});
+        final TypedArray ta = mContext.obtainStyledAttributes(new int[] {
+                android.R.attr.dialogCornerRadius,
+                android.R.attr.colorBackgroundFloating});
         mCornerRadius = ta.getDimensionPixelSize(0, 0);
+        mActivityViewContainer.setBackgroundColor(ta.getColor(1, Color.WHITE));
         ta.recycle();
 
         if (mActivityView != null && ScreenDecorationsUtils.supportsRoundedCornersOnWindows(
@@ -593,14 +598,17 @@
      */
     void update(Bubble bubble) {
         if (DEBUG_BUBBLE_EXPANDED_VIEW) {
-            Log.d(TAG, "update: bubble=" + (bubble != null ? bubble.getKey() : "null"));
+            Log.d(TAG, "update: bubble=" + bubble);
+        }
+        if (mStackView == null) {
+            Log.w(TAG, "Stack is null for bubble: " + bubble);
+            return;
         }
         boolean isNew = mBubble == null || didBackingContentChange(bubble);
         if (isNew || bubble != null && bubble.getKey().equals(mBubble.getKey())) {
             mBubble = bubble;
             mSettingsIcon.setContentDescription(getResources().getString(
                     R.string.bubbles_settings_button_description, bubble.getAppName()));
-
             mSettingsIcon.setAccessibilityDelegate(
                     new AccessibilityDelegate() {
                         @Override
@@ -808,4 +816,15 @@
         }
         return null;
     }
+
+    /**
+     * Description of current expanded view state.
+     */
+    public void dump(
+            @NonNull FileDescriptor fd, @NonNull PrintWriter pw, @NonNull String[] args) {
+        pw.print("BubbleExpandedView");
+        pw.print("  taskId:               "); pw.println(mTaskId);
+        pw.print("  activityViewStatus:   "); pw.println(mActivityViewStatus);
+        pw.print("  stackView:            "); pw.println(mStackView);
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java
index b3fbfd6..64df2b9 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java
@@ -19,11 +19,7 @@
 import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
 import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
 
-import static com.android.systemui.Interpolators.FAST_OUT_SLOW_IN;
-import static com.android.systemui.Prefs.Key.HAS_SEEN_BUBBLES_EDUCATION;
-import static com.android.systemui.Prefs.Key.HAS_SEEN_BUBBLES_MANAGE_EDUCATION;
 import static com.android.systemui.bubbles.BubbleDebugConfig.DEBUG_BUBBLE_STACK_VIEW;
-import static com.android.systemui.bubbles.BubbleDebugConfig.DEBUG_USER_EDUCATION;
 import static com.android.systemui.bubbles.BubbleDebugConfig.TAG_BUBBLES;
 import static com.android.systemui.bubbles.BubbleDebugConfig.TAG_WITH_CLASS_NAME;
 
@@ -38,7 +34,6 @@
 import android.content.res.Configuration;
 import android.content.res.Resources;
 import android.content.res.TypedArray;
-import android.graphics.Color;
 import android.graphics.ColorMatrix;
 import android.graphics.ColorMatrixColorFilter;
 import android.graphics.Outline;
@@ -54,7 +49,6 @@
 import android.util.Log;
 import android.view.Choreographer;
 import android.view.DisplayCutout;
-import android.view.Gravity;
 import android.view.LayoutInflater;
 import android.view.MotionEvent;
 import android.view.SurfaceControl;
@@ -70,10 +64,8 @@
 import android.view.animation.AccelerateDecelerateInterpolator;
 import android.widget.FrameLayout;
 import android.widget.ImageView;
-import android.widget.LinearLayout;
 import android.widget.TextView;
 
-import androidx.annotation.MainThread;
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 import androidx.dynamicanimation.animation.DynamicAnimation;
@@ -82,7 +74,6 @@
 import androidx.dynamicanimation.animation.SpringForce;
 
 import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.util.ContrastColorUtil;
 import com.android.systemui.Interpolators;
 import com.android.systemui.Prefs;
 import com.android.systemui.R;
@@ -115,10 +106,6 @@
         implements ViewTreeObserver.OnComputeInternalInsetsListener {
     private static final String TAG = TAG_WITH_CLASS_NAME ? "BubbleStackView" : TAG_BUBBLES;
 
-    /** Animation durations for bubble stack user education views. **/
-    static final int ANIMATE_STACK_USER_EDUCATION_DURATION = 200;
-    private static final int ANIMATE_STACK_USER_EDUCATION_DURATION_SHORT = 40;
-
     /** How far the flyout needs to be dragged before it's dismissed regardless of velocity. */
     static final float FLYOUT_DRAG_PERCENT_DISMISS = 0.25f;
 
@@ -253,13 +240,8 @@
 
     /** Layout change listener that moves the stack to the nearest valid position on rotation. */
     private OnLayoutChangeListener mOrientationChangedListener;
-    /** Whether the stack was on the left side of the screen prior to rotation. */
-    private boolean mWasOnLeftBeforeRotation = false;
-    /**
-     * How far down the screen the stack was before rotation, in terms of percentage of the way down
-     * the allowable region. Defaults to -1 if not set.
-     */
-    private float mVerticalPosPercentBeforeRotation = -1;
+
+    @Nullable private RelativeStackPosition mRelativeStackPositionBeforeRotation;
 
     private int mMaxBubbles;
     private int mBubbleSize;
@@ -561,7 +543,7 @@
                 // Otherwise, we either tapped the stack (which means we're collapsed
                 // and should expand) or the currently selected bubble (we're expanded
                 // and should collapse).
-                if (!maybeShowStackUserEducation()) {
+                if (!maybeShowStackEdu()) {
                     mBubbleData.setExpanded(!mBubbleData.isExpanded());
                 }
             }
@@ -587,7 +569,9 @@
             }
 
             if (mBubbleData.isExpanded()) {
-                maybeShowManageEducation(false /* show */);
+                if (mManageEduView != null) {
+                    mManageEduView.hide(false /* show */);
+                }
 
                 // If we're expanded, tell the animation controller to prepare to drag this bubble,
                 // dispatching to the individual bubble magnet listener.
@@ -642,7 +626,9 @@
                     mExpandedAnimationController.dragBubbleOut(
                             v, viewInitialX + dx, viewInitialY + dy);
                 } else {
-                    hideStackUserEducation(false /* fromExpansion */);
+                    if (mStackEduView != null) {
+                        mStackEduView.hide(false /* fromExpansion */);
+                    }
                     mStackAnimationController.moveStackFromTouch(
                             viewInitialX + dx, viewInitialY + dy);
                 }
@@ -689,7 +675,7 @@
     private OnClickListener mFlyoutClickListener = new OnClickListener() {
         @Override
         public void onClick(View view) {
-            if (maybeShowStackUserEducation()) {
+            if (maybeShowStackEdu()) {
                 // If we're showing user education, don't open the bubble show the education first
                 mBubbleToExpandAfterFlyoutCollapse = null;
             } else {
@@ -733,7 +719,7 @@
             mFlyout.removeCallbacks(mHideFlyout);
             animateFlyoutCollapsed(shouldDismiss, velX);
 
-            maybeShowStackUserEducation();
+            maybeShowStackEdu();
         }
     };
 
@@ -742,14 +728,8 @@
 
     @Nullable
     private BubbleOverflow mBubbleOverflow;
-
-    private boolean mShouldShowUserEducation;
-    private boolean mAnimatingEducationAway;
-    private View mUserEducationView;
-
-    private boolean mShouldShowManageEducation;
-    private ManageEducationView mManageEducationView;
-    private boolean mAnimatingManageEducationAway;
+    private StackEducationView mStackEduView;
+    private ManageEducationView mManageEduView;
 
     private ViewGroup mManageMenu;
     private ImageView mManageSettingsIcon;
@@ -810,8 +790,6 @@
                 onBubbleAnimatedOut);
         mSurfaceSynchronizer = synchronizer != null ? synchronizer : DEFAULT_SURFACE_SYNCHRONIZER;
 
-        setUpUserEducation();
-
         // Force LTR by default since most of the Bubbles UI is positioned manually by the user, or
         // is centered. It greatly simplifies translation positioning/animations. Views that will
         // actually lay out differently in RTL, such as the flyout and expanded view, will set their
@@ -824,6 +802,8 @@
         mBubbleContainer.setClipChildren(false);
         addView(mBubbleContainer, new FrameLayout.LayoutParams(MATCH_PARENT, MATCH_PARENT));
 
+        updateUserEdu();
+
         mExpandedViewContainer = new FrameLayout(context);
         mExpandedViewContainer.setElevation(elevation);
         mExpandedViewContainer.setClipChildren(false);
@@ -940,9 +920,10 @@
                         mExpandedViewContainer.setTranslationY(getExpandedViewY());
                         mExpandedViewContainer.setAlpha(1f);
                     }
-                    if (mVerticalPosPercentBeforeRotation >= 0) {
-                        mStackAnimationController.moveStackToSimilarPositionAfterRotation(
-                                mWasOnLeftBeforeRotation, mVerticalPosPercentBeforeRotation);
+                    if (mRelativeStackPositionBeforeRotation != null) {
+                        mStackAnimationController.setStackPosition(
+                                mRelativeStackPositionBeforeRotation);
+                        mRelativeStackPositionBeforeRotation = null;
                     }
                     removeOnLayoutChangeListener(mOrientationChangedListener);
                 };
@@ -1096,48 +1077,66 @@
         addView(mManageMenu);
     }
 
-    private void setUpUserEducation() {
-        if (mUserEducationView != null) {
-            removeView(mUserEducationView);
+    /**
+     * Whether the educational view should show for the expanded view "manage" menu.
+     */
+    private boolean shouldShowManageEdu() {
+        final boolean seen = Prefs.getBoolean(mContext,
+                Prefs.Key.HAS_SEEN_BUBBLES_MANAGE_EDUCATION, false /* default */);
+        final boolean shouldShow = (!seen || BubbleDebugConfig.forceShowUserEducation(mContext))
+                && mExpandedBubble != null;
+        if (BubbleDebugConfig.DEBUG_USER_EDUCATION) {
+            Log.d(TAG, "Show manage edu: " + shouldShow);
         }
-        mShouldShowUserEducation = shouldShowBubblesEducation();
-        if (DEBUG_USER_EDUCATION) {
-            Log.d(TAG, "shouldShowUserEducation: " + mShouldShowUserEducation);
-        }
-        if (mShouldShowUserEducation) {
-            mUserEducationView = mInflater.inflate(R.layout.bubble_stack_user_education, this,
-                    false /* attachToRoot */);
-            mUserEducationView.setVisibility(GONE);
+        return shouldShow;
+    }
 
-            final TypedArray ta = mContext.obtainStyledAttributes(
-                    new int[] {android.R.attr.colorAccent,
-                            android.R.attr.textColorPrimaryInverse});
-            final int bgColor = ta.getColor(0, Color.BLACK);
-            int textColor = ta.getColor(1, Color.WHITE);
-            ta.recycle();
-            textColor = ContrastColorUtil.ensureTextContrast(textColor, bgColor, true);
-
-            TextView title = mUserEducationView.findViewById(R.id.user_education_title);
-            TextView description = mUserEducationView.findViewById(R.id.user_education_description);
-            title.setTextColor(textColor);
-            description.setTextColor(textColor);
-
-            updateUserEducationForLayoutDirection();
-            addView(mUserEducationView);
+    private void maybeShowManageEdu() {
+        if (!shouldShowManageEdu()) {
+            return;
         }
+        if (mManageEduView == null) {
+            mManageEduView = new ManageEducationView(mContext);
+            addView(mManageEduView);
+        }
+        mManageEduView.show(mExpandedBubble.getExpandedView(), mTempRect);
+    }
 
-        if (mManageEducationView != null) {
-            removeView(mManageEducationView);
+    /**
+     * Whether education view should show for the collapsed stack.
+     */
+    private boolean shouldShowStackEdu() {
+        final boolean seen = Prefs.getBoolean(getContext(),
+                Prefs.Key.HAS_SEEN_BUBBLES_EDUCATION, false /* default */);
+        final boolean shouldShow = !seen || BubbleDebugConfig.forceShowUserEducation(mContext);
+        if (BubbleDebugConfig.DEBUG_USER_EDUCATION) {
+            Log.d(TAG, "Show stack edu: " + shouldShow);
         }
-        mShouldShowManageEducation = shouldShowManageEducation();
-        if (DEBUG_USER_EDUCATION) {
-            Log.d(TAG, "shouldShowManageEducation: " + mShouldShowManageEducation);
+        return shouldShow;
+    }
+
+    /**
+     * @return true if education view for collapsed stack should show and was not showing before.
+     */
+    private boolean maybeShowStackEdu() {
+        if (!shouldShowStackEdu()) {
+            return false;
         }
-        if (mShouldShowManageEducation) {
-            mManageEducationView = (ManageEducationView)
-                    mInflater.inflate(R.layout.bubbles_manage_button_education, this /* root */,
-                            false /* attachToRoot */);
-            addView(mManageEducationView);
+        if (mStackEduView == null) {
+            mStackEduView = new StackEducationView(mContext);
+            addView(mStackEduView);
+        }
+        return mStackEduView.show(mStackAnimationController.getStartPosition());
+    }
+
+    private void updateUserEdu() {
+        maybeShowStackEdu();
+        if (mManageEduView != null) {
+            mManageEduView.invalidate();
+        }
+        maybeShowManageEdu();
+        if (mStackEduView != null) {
+            mStackEduView.invalidate();
         }
     }
 
@@ -1168,9 +1167,9 @@
      */
     public void onThemeChanged() {
         setUpFlyout();
-        setUpUserEducation();
         setUpManageMenu();
         updateOverflow();
+        updateUserEdu();
         updateExpandedViewTheme();
     }
 
@@ -1189,13 +1188,7 @@
                 com.android.internal.R.dimen.status_bar_height);
         mBubblePaddingTop = res.getDimensionPixelSize(R.dimen.bubble_padding_top);
 
-        final RectF allowablePos = mStackAnimationController.getAllowableStackPositionRegion();
-        mWasOnLeftBeforeRotation = mStackAnimationController.isStackOnLeftSide();
-        mVerticalPosPercentBeforeRotation =
-                (mStackAnimationController.getStackPosition().y - allowablePos.top)
-                        / (allowablePos.bottom - allowablePos.top);
-        mVerticalPosPercentBeforeRotation =
-                Math.max(0f, Math.min(1f, mVerticalPosPercentBeforeRotation));
+        mRelativeStackPositionBeforeRotation = mStackAnimationController.getRelativeStackPosition();
         addOnLayoutChangeListener(mOrientationChangedListener);
         hideFlyoutImmediate();
 
@@ -1207,12 +1200,11 @@
     public void onLayoutDirectionChanged(int direction) {
         mManageMenu.setLayoutDirection(direction);
         mFlyout.setLayoutDirection(direction);
-        if (mUserEducationView != null) {
-            mUserEducationView.setLayoutDirection(direction);
-            updateUserEducationForLayoutDirection();
+        if (mStackEduView != null) {
+            mStackEduView.setLayoutDirection(direction);
         }
-        if (mManageEducationView != null) {
-            mManageEducationView.setLayoutDirection(direction);
+        if (mManageEduView != null) {
+            mManageEduView.setLayoutDirection(direction);
         }
         updateExpandedViewDirection(direction);
     }
@@ -1456,10 +1448,10 @@
             Log.d(TAG, "addBubble: " + bubble);
         }
 
-        if (getBubbleCount() == 0 && mShouldShowUserEducation) {
+        if (getBubbleCount() == 0 && shouldShowStackEdu()) {
             // Override the default stack position if we're showing user education.
             mStackAnimationController.setStackPosition(
-                    mStackAnimationController.getDefaultStartPosition());
+                    mStackAnimationController.getStartPosition());
         }
 
         if (getBubbleCount() == 0) {
@@ -1659,115 +1651,6 @@
         notifyExpansionChanged(mExpandedBubble, mIsExpanded);
     }
 
-    /**
-     * If necessary, shows the user education view for the bubble stack. This appears the first
-     * time a user taps on a bubble.
-     *
-     * @return true if user education was shown, false otherwise.
-     */
-    private boolean maybeShowStackUserEducation() {
-        if (mShouldShowUserEducation && mUserEducationView.getVisibility() != VISIBLE) {
-            mUserEducationView.setAlpha(0);
-            mUserEducationView.setVisibility(VISIBLE);
-            updateUserEducationForLayoutDirection();
-
-            // Post so we have height of mUserEducationView
-            mUserEducationView.post(() -> {
-                final int viewHeight = mUserEducationView.getHeight();
-                PointF stackPosition = mStackAnimationController.getDefaultStartPosition();
-                final float translationY = stackPosition.y + (mBubbleSize / 2) - (viewHeight / 2);
-                mUserEducationView.setTranslationY(translationY);
-                mUserEducationView.animate()
-                        .setDuration(ANIMATE_STACK_USER_EDUCATION_DURATION)
-                        .setInterpolator(FAST_OUT_SLOW_IN)
-                        .alpha(1);
-            });
-            Prefs.putBoolean(getContext(), HAS_SEEN_BUBBLES_EDUCATION, true);
-            return true;
-        }
-        return false;
-    }
-
-    private void updateUserEducationForLayoutDirection() {
-        if (mUserEducationView == null) {
-            return;
-        }
-        LinearLayout textLayout =  mUserEducationView.findViewById(R.id.user_education_view);
-        TextView title = mUserEducationView.findViewById(R.id.user_education_title);
-        TextView description = mUserEducationView.findViewById(R.id.user_education_description);
-        boolean isLtr =
-                getResources().getConfiguration().getLayoutDirection() == LAYOUT_DIRECTION_LTR;
-        if (isLtr) {
-            mUserEducationView.setLayoutDirection(LAYOUT_DIRECTION_LTR);
-            textLayout.setBackgroundResource(R.drawable.bubble_stack_user_education_bg);
-            title.setGravity(Gravity.LEFT);
-            description.setGravity(Gravity.LEFT);
-        } else {
-            mUserEducationView.setLayoutDirection(LAYOUT_DIRECTION_RTL);
-            textLayout.setBackgroundResource(R.drawable.bubble_stack_user_education_bg_rtl);
-            title.setGravity(Gravity.RIGHT);
-            description.setGravity(Gravity.RIGHT);
-        }
-    }
-
-    /**
-     * If necessary, hides the user education view for the bubble stack.
-     *
-     * @param fromExpansion if true this indicates the hide is happening due to the bubble being
-     *                      expanded, false if due to a touch outside of the bubble stack.
-     */
-    void hideStackUserEducation(boolean fromExpansion) {
-        if (mShouldShowUserEducation
-                && mUserEducationView.getVisibility() == VISIBLE
-                && !mAnimatingEducationAway) {
-            mAnimatingEducationAway = true;
-            mUserEducationView.animate()
-                    .alpha(0)
-                    .setDuration(fromExpansion
-                            ? ANIMATE_STACK_USER_EDUCATION_DURATION_SHORT
-                            : ANIMATE_STACK_USER_EDUCATION_DURATION)
-                    .withEndAction(() -> {
-                        mAnimatingEducationAway = false;
-                        mShouldShowUserEducation = shouldShowBubblesEducation();
-                        mUserEducationView.setVisibility(GONE);
-                    });
-        }
-    }
-
-    /**
-     * If necessary, toggles the user education view for the manage button. This is shown when the
-     * bubble stack is expanded for the first time.
-     *
-     * @param show whether the user education view should show or not.
-     */
-    void maybeShowManageEducation(boolean show) {
-        if (mManageEducationView == null) {
-            return;
-        }
-        if (show
-                && mShouldShowManageEducation
-                && mManageEducationView.getVisibility() != VISIBLE
-                && mIsExpanded
-                && mExpandedBubble.getExpandedView() != null) {
-            mManageEducationView.show(mExpandedBubble.getExpandedView(), mTempRect,
-                    () -> maybeShowManageEducation(false) /* run on click */);
-            Prefs.putBoolean(getContext(), HAS_SEEN_BUBBLES_MANAGE_EDUCATION, true);
-        } else if (!show
-                && mManageEducationView.getVisibility() == VISIBLE
-                && !mAnimatingManageEducationAway) {
-            mManageEducationView.animate()
-                    .alpha(0)
-                    .setDuration(mIsExpansionAnimating
-                            ? ANIMATE_STACK_USER_EDUCATION_DURATION_SHORT
-                            : ANIMATE_STACK_USER_EDUCATION_DURATION)
-                    .withEndAction(() -> {
-                        mAnimatingManageEducationAway = false;
-                        mShouldShowManageEducation = shouldShowManageEducation();
-                        mManageEducationView.setVisibility(GONE);
-                    });
-        }
-    }
-
     void showExpandedViewContents(int displayId) {
         if (mExpandedBubble != null
                 && mExpandedBubble.getExpandedView() != null
@@ -1801,7 +1684,9 @@
         cancelDelayedExpandCollapseSwitchAnimations();
 
         mIsExpanded = true;
-        hideStackUserEducation(true /* fromExpansion */);
+        if (mStackEduView != null) {
+            mStackEduView.hide(true /* fromExpansion */);
+        }
         beforeExpandedViewAnimation();
 
         mBubbleContainer.setActiveController(mExpandedAnimationController);
@@ -1809,7 +1694,9 @@
         updatePointerPosition();
         mExpandedAnimationController.expandFromStack(() -> {
             afterExpandedViewAnimation();
-            maybeShowManageEducation(true);
+            if (mIsExpanded && mExpandedBubble.getExpandedView() != null) {
+                maybeShowManageEdu();
+            }
         } /* after */);
 
         mExpandedViewContainer.setTranslationX(0);
@@ -1946,7 +1833,9 @@
                 .withEndActions(() -> {
                     final BubbleViewProvider previouslySelected = mExpandedBubble;
                     beforeExpandedViewAnimation();
-                    maybeShowManageEducation(false);
+                    if (mManageEduView != null) {
+                        mManageEduView.hide(false /* fromExpansion */);
+                    }
 
                     if (DEBUG_BUBBLE_STACK_VIEW) {
                         Log.d(TAG, "animateCollapse");
@@ -2114,8 +2003,8 @@
         // from any location.
         if (!mIsExpanded
                 || mShowingManage
-                || (mManageEducationView != null
-                    && mManageEducationView.getVisibility() == VISIBLE)) {
+                || (mManageEduView != null
+                    && mManageEduView.getVisibility() == VISIBLE)) {
             touchableRegion.setEmpty();
         }
     }
@@ -2299,7 +2188,7 @@
         if (flyoutMessage == null
                 || flyoutMessage.message == null
                 || !bubble.showFlyout()
-                || (mUserEducationView != null && mUserEducationView.getVisibility() == VISIBLE)
+                || (mStackEduView != null && mStackEduView.getVisibility() == VISIBLE)
                 || isExpanded()
                 || mIsExpansionAnimating
                 || mIsGestureInProgress
@@ -2408,7 +2297,7 @@
      * them.
      */
     public void getTouchableRegion(Rect outRect) {
-        if (mUserEducationView != null && mUserEducationView.getVisibility() == VISIBLE) {
+        if (mStackEduView != null && mStackEduView.getVisibility() == VISIBLE) {
             // When user education shows then capture all touches
             outRect.set(0, 0, getWidth(), getHeight());
             return;
@@ -2740,10 +2629,18 @@
                 .floatValue();
     }
 
+    public void setStackStartPosition(RelativeStackPosition position) {
+        mStackAnimationController.setStackStartPosition(position);
+    }
+
     public PointF getStackPosition() {
         return mStackAnimationController.getStackPosition();
     }
 
+    public RelativeStackPosition getRelativeStackPosition() {
+        return mStackAnimationController.getRelativeStackPosition();
+    }
+
     /**
      * Logs the bubble UI event.
      *
@@ -2772,18 +2669,6 @@
         return mExpandedBubble.getExpandedView().performBackPressIfNeeded();
     }
 
-    /** Whether the educational view should appear for bubbles. **/
-    private boolean shouldShowBubblesEducation() {
-        return BubbleDebugConfig.forceShowUserEducation(getContext())
-                || !Prefs.getBoolean(getContext(), HAS_SEEN_BUBBLES_EDUCATION, false);
-    }
-
-    /** Whether the educational view should appear for the expanded view "manage" button. **/
-    private boolean shouldShowManageEducation() {
-        return BubbleDebugConfig.forceShowUserEducation(getContext())
-                || !Prefs.getBoolean(getContext(), HAS_SEEN_BUBBLES_MANAGE_EDUCATION, false);
-    }
-
     /** For debugging only */
     List<Bubble> getBubblesOnScreen() {
         List<Bubble> bubbles = new ArrayList<>();
@@ -2797,4 +2682,47 @@
         }
         return bubbles;
     }
+
+    /**
+     * Representation of stack position that uses relative properties rather than absolute
+     * coordinates. This is used to maintain similar stack positions across configuration changes.
+     */
+    public static class RelativeStackPosition {
+        /** Whether to place the stack at the leftmost allowed position. */
+        private boolean mOnLeft;
+
+        /**
+         * How far down the vertically allowed region to place the stack. For example, if the stack
+         * allowed region is between y = 100 and y = 1100 and this is 0.2f, we'll place the stack at
+         * 100 + (0.2f * 1000) = 300.
+         */
+        private float mVerticalOffsetPercent;
+
+        public RelativeStackPosition(boolean onLeft, float verticalOffsetPercent) {
+            mOnLeft = onLeft;
+            mVerticalOffsetPercent = clampVerticalOffsetPercent(verticalOffsetPercent);
+        }
+
+        /** Constructs a relative position given a region and a point in that region. */
+        public RelativeStackPosition(PointF position, RectF region) {
+            mOnLeft = position.x < region.width() / 2;
+            mVerticalOffsetPercent =
+                    clampVerticalOffsetPercent((position.y - region.top) / region.height());
+        }
+
+        /** Ensures that the offset percent is between 0f and 1f. */
+        private float clampVerticalOffsetPercent(float offsetPercent) {
+            return Math.max(0f, Math.min(1f, offsetPercent));
+        }
+
+        /**
+         * Given an allowable stack position region, returns the point within that region
+         * represented by this relative position.
+         */
+        public PointF getAbsolutePositionInRegion(RectF region) {
+            return new PointF(
+                    mOnLeft ? region.left : region.right,
+                    region.top + mVerticalOffsetPercent * region.height());
+        }
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleViewInfoTask.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleViewInfoTask.java
index 1929fc4..5749169 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleViewInfoTask.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleViewInfoTask.java
@@ -103,11 +103,12 @@
 
     @Override
     protected void onPostExecute(BubbleViewInfo viewInfo) {
-        if (viewInfo != null) {
-            mBubble.setViewInfo(viewInfo);
-            if (mCallback != null && !isCancelled()) {
-                mCallback.onBubbleViewsReady(mBubble);
-            }
+        if (isCancelled() || viewInfo == null) {
+            return;
+        }
+        mBubble.setViewInfo(viewInfo);
+        if (mCallback != null) {
+            mCallback.onBubbleViewsReady(mBubble);
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/ManageEducationView.kt b/packages/SystemUI/src/com/android/systemui/bubbles/ManageEducationView.kt
index c58ab31..26a9773 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/ManageEducationView.kt
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/ManageEducationView.kt
@@ -18,52 +18,56 @@
 import android.content.Context
 import android.graphics.Color
 import android.graphics.Rect
-import android.util.AttributeSet
-import android.view.Gravity
+import android.view.LayoutInflater
 import android.view.View
 import android.widget.Button
 import android.widget.LinearLayout
 import android.widget.TextView
 import com.android.internal.util.ContrastColorUtil
 import com.android.systemui.Interpolators
+import com.android.systemui.Prefs
+import com.android.systemui.Prefs.Key.HAS_SEEN_BUBBLES_MANAGE_EDUCATION
 import com.android.systemui.R
 
 /**
- * Educational view to highlight the manage button that allows a user to configure the settings
+ * User education view to highlight the manage button that allows a user to configure the settings
  * for the bubble. Shown only the first time a user expands a bubble.
  */
-class ManageEducationView @JvmOverloads constructor(
-    context: Context?,
-    attrs: AttributeSet? = null,
-    defStyleAttr: Int = 0,
-    defStyleRes: Int = 0
-) : LinearLayout(context, attrs, defStyleAttr, defStyleRes) {
+class ManageEducationView constructor(context: Context) : LinearLayout(context) {
+
+    private val TAG = if (BubbleDebugConfig.TAG_WITH_CLASS_NAME) "BubbleManageEducationView"
+        else BubbleDebugConfig.TAG_BUBBLES
+
+    private val ANIMATE_DURATION : Long = 200
+    private val ANIMATE_DURATION_SHORT : Long = 40
 
     private val manageView by lazy { findViewById<View>(R.id.manage_education_view) }
     private val manageButton by lazy { findViewById<Button>(R.id.manage) }
     private val gotItButton by lazy { findViewById<Button>(R.id.got_it) }
     private val titleTextView by lazy { findViewById<TextView>(R.id.user_education_title) }
     private val descTextView by lazy { findViewById<TextView>(R.id.user_education_description) }
-    private var isInflated = false
+
+    private var isHiding = false
 
     init {
-        this.visibility = View.GONE
-        this.elevation = resources.getDimensionPixelSize(R.dimen.bubble_elevation).toFloat()
-        this.layoutDirection = View.LAYOUT_DIRECTION_LOCALE
+        LayoutInflater.from(context).inflate(R.layout.bubbles_manage_button_education, this);
+        visibility = View.GONE
+        elevation = resources.getDimensionPixelSize(R.dimen.bubble_elevation).toFloat()
+
+        // BubbleStackView forces LTR by default
+        // since most of Bubble UI direction depends on positioning by the user.
+        // This view actually lays out differently in RTL, so we set layout LOCALE here.
+        layoutDirection = View.LAYOUT_DIRECTION_LOCALE
     }
 
-    override fun setLayoutDirection(direction: Int) {
-        super.setLayoutDirection(direction)
-        // setLayoutDirection runs before onFinishInflate
-        // so skip if views haven't inflated; otherwise we'll get NPEs
-        if (!isInflated) return
-        setDirection()
+    override fun setLayoutDirection(layoutDirection: Int) {
+        super.setLayoutDirection(layoutDirection)
+        setDrawableDirection()
     }
 
     override fun onFinishInflate() {
         super.onFinishInflate()
-        isInflated = true
-        setDirection()
+        layoutDirection = resources.configuration.layoutDirection
         setTextColor()
     }
 
@@ -78,29 +82,35 @@
         descTextView.setTextColor(textColor)
     }
 
-    fun setDirection() {
+    private fun setDrawableDirection() {
         manageView.setBackgroundResource(
             if (resources.configuration.layoutDirection == View.LAYOUT_DIRECTION_RTL)
                 R.drawable.bubble_stack_user_education_bg_rtl
             else R.drawable.bubble_stack_user_education_bg)
-        titleTextView.gravity = Gravity.START
-        descTextView.gravity = Gravity.START
     }
 
-    fun show(expandedView: BubbleExpandedView, rect : Rect, hideMenu: Runnable) {
+    /**
+     * If necessary, toggles the user education view for the manage button. This is shown when the
+     * bubble stack is expanded for the first time.
+     *
+     * @param show whether the user education view should show or not.
+     */
+    fun show(expandedView: BubbleExpandedView, rect : Rect) {
+        if (visibility == VISIBLE) return
+
         alpha = 0f
         visibility = View.VISIBLE
         post {
             expandedView.getManageButtonBoundsOnScreen(rect)
-            with(hideMenu) {
-                manageButton
-                    .setOnClickListener {
-                        expandedView.findViewById<View>(R.id.settings_button).performClick()
-                        this.run()
-                    }
-                gotItButton.setOnClickListener { this.run() }
-                setOnClickListener { this.run() }
-            }
+
+            manageButton
+                .setOnClickListener {
+                    expandedView.findViewById<View>(R.id.settings_button).performClick()
+                    hide(true /* isStackExpanding */)
+                }
+            gotItButton.setOnClickListener { hide(true /* isStackExpanding */) }
+            setOnClickListener { hide(true /* isStackExpanding */) }
+
             with(manageView) {
                 translationX = 0f
                 val inset = resources.getDimensionPixelSize(
@@ -109,9 +119,27 @@
             }
             bringToFront()
             animate()
-                .setDuration(BubbleStackView.ANIMATE_STACK_USER_EDUCATION_DURATION.toLong())
+                .setDuration(ANIMATE_DURATION)
                 .setInterpolator(Interpolators.FAST_OUT_SLOW_IN)
                 .alpha(1f)
         }
+        setShouldShow(false)
+    }
+
+    fun hide(isStackExpanding: Boolean) {
+        if (visibility != VISIBLE || isHiding) return
+
+        animate()
+            .withStartAction { isHiding = true }
+            .alpha(0f)
+            .setDuration(if (isStackExpanding) ANIMATE_DURATION_SHORT else ANIMATE_DURATION)
+            .withEndAction {
+                isHiding = false
+                visibility = GONE
+            };
+    }
+
+    private fun setShouldShow(shouldShow: Boolean) {
+        Prefs.putBoolean(context, HAS_SEEN_BUBBLES_MANAGE_EDUCATION, !shouldShow)
     }
 }
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/StackEducationView.kt b/packages/SystemUI/src/com/android/systemui/bubbles/StackEducationView.kt
new file mode 100644
index 0000000..3e4c729
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/StackEducationView.kt
@@ -0,0 +1,134 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.systemui.bubbles
+
+import android.content.Context
+import android.graphics.Color
+import android.graphics.PointF
+import android.view.LayoutInflater
+import android.view.View
+import android.widget.LinearLayout
+import android.widget.TextView
+import com.android.internal.util.ContrastColorUtil
+import com.android.systemui.Interpolators
+import com.android.systemui.Prefs
+import com.android.systemui.Prefs.Key.HAS_SEEN_BUBBLES_EDUCATION
+import com.android.systemui.R
+
+/**
+ * User education view to highlight the collapsed stack of bubbles.
+ * Shown only the first time a user taps the stack.
+ */
+class StackEducationView constructor(context: Context) : LinearLayout(context){
+
+    private val TAG = if (BubbleDebugConfig.TAG_WITH_CLASS_NAME) "BubbleStackEducationView"
+        else BubbleDebugConfig.TAG_BUBBLES
+
+    private val ANIMATE_DURATION : Long = 200
+    private val ANIMATE_DURATION_SHORT : Long = 40
+
+    private val view by lazy { findViewById<View>(R.id.stack_education_layout) }
+    private val titleTextView by lazy { findViewById<TextView>(R.id.stack_education_title) }
+    private val descTextView by lazy { findViewById<TextView>(R.id.stack_education_description) }
+
+    private var isHiding = false
+
+    init {
+        LayoutInflater.from(context).inflate(R.layout.bubble_stack_user_education, this);
+
+        visibility = View.GONE
+        elevation = resources.getDimensionPixelSize(R.dimen.bubble_elevation).toFloat()
+
+        // BubbleStackView forces LTR by default
+        // since most of Bubble UI direction depends on positioning by the user.
+        // This view actually lays out differently in RTL, so we set layout LOCALE here.
+        layoutDirection = View.LAYOUT_DIRECTION_LOCALE
+    }
+
+    override fun setLayoutDirection(layoutDirection: Int) {
+        super.setLayoutDirection(layoutDirection)
+        setDrawableDirection()
+    }
+
+    override fun onFinishInflate() {
+        super.onFinishInflate()
+        layoutDirection = resources.configuration.layoutDirection
+        setTextColor()
+    }
+
+    private fun setTextColor() {
+        val ta = mContext.obtainStyledAttributes(intArrayOf(android.R.attr.colorAccent,
+            android.R.attr.textColorPrimaryInverse))
+        val bgColor = ta.getColor(0 /* index */, Color.BLACK)
+        var textColor = ta.getColor(1 /* index */, Color.WHITE)
+        ta.recycle()
+        textColor = ContrastColorUtil.ensureTextContrast(textColor, bgColor, true)
+        titleTextView.setTextColor(textColor)
+        descTextView.setTextColor(textColor)
+    }
+
+    private fun setDrawableDirection() {
+        view.setBackgroundResource(
+            if (resources.configuration.layoutDirection == View.LAYOUT_DIRECTION_LTR)
+                R.drawable.bubble_stack_user_education_bg
+            else R.drawable.bubble_stack_user_education_bg_rtl)
+    }
+
+    /**
+     * If necessary, shows the user education view for the bubble stack. This appears the first
+     * time a user taps on a bubble.
+     *
+     * @return true if user education was shown, false otherwise.
+     */
+    fun show(stackPosition: PointF) : Boolean{
+        if (visibility == VISIBLE) return false
+
+        setAlpha(0f)
+        setVisibility(View.VISIBLE)
+        post {
+            with(view) {
+                val bubbleSize = context.resources.getDimensionPixelSize(
+                    R.dimen.individual_bubble_size)
+                translationY = stackPosition.y + bubbleSize / 2 - getHeight() / 2
+            }
+            animate()
+                .setDuration(ANIMATE_DURATION)
+                .setInterpolator(Interpolators.FAST_OUT_SLOW_IN)
+                .alpha(1f)
+        }
+        setShouldShow(false)
+        return true
+    }
+
+    /**
+     * If necessary, hides the stack education view.
+     *
+     * @param fromExpansion if true this indicates the hide is happening due to the bubble being
+     *                      expanded, false if due to a touch outside of the bubble stack.
+     */
+    fun hide(fromExpansion: Boolean) {
+        if (visibility != VISIBLE || isHiding) return
+
+        animate()
+            .alpha(0f)
+            .setDuration(if (fromExpansion) ANIMATE_DURATION_SHORT else ANIMATE_DURATION)
+            .withEndAction { visibility = GONE }
+    }
+
+    private fun setShouldShow(shouldShow: Boolean) {
+        Prefs.putBoolean(context, HAS_SEEN_BUBBLES_EDUCATION, !shouldShow)
+    }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/animation/StackAnimationController.java b/packages/SystemUI/src/com/android/systemui/bubbles/animation/StackAnimationController.java
index b378469..e835ea2 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/animation/StackAnimationController.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/animation/StackAnimationController.java
@@ -35,6 +35,7 @@
 import androidx.dynamicanimation.animation.SpringForce;
 
 import com.android.systemui.R;
+import com.android.systemui.bubbles.BubbleStackView;
 import com.android.systemui.util.FloatingContentCoordinator;
 import com.android.systemui.util.animation.PhysicsAnimator;
 import com.android.systemui.util.magnetictarget.MagnetizedObject;
@@ -125,6 +126,9 @@
      */
     private Rect mAnimatingToBounds = new Rect();
 
+    /** Initial starting location for the stack. */
+    @Nullable private BubbleStackView.RelativeStackPosition mStackStartPosition;
+
     /** Whether or not the stack's start position has been set. */
     private boolean mStackMovedToStartPosition = false;
 
@@ -431,21 +435,6 @@
         return stackPos;
     }
 
-    /**
-     * Moves the stack in response to rotation. We keep it in the most similar position by keeping
-     * it on the same side, and positioning it the same percentage of the way down the screen
-     * (taking status bar/nav bar into account by using the allowable region's height).
-     */
-    public void moveStackToSimilarPositionAfterRotation(boolean wasOnLeft, float verticalPercent) {
-        final RectF allowablePos = getAllowableStackPositionRegion();
-        final float allowableRegionHeight = allowablePos.bottom - allowablePos.top;
-
-        final float x = wasOnLeft ? allowablePos.left : allowablePos.right;
-        final float y = (allowableRegionHeight * verticalPercent) + allowablePos.top;
-
-        setStackPosition(new PointF(x, y));
-    }
-
     /** Description of current animation controller state. */
     public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
         pw.println("StackAnimationController state:");
@@ -815,7 +804,7 @@
         } else {
             // When all children are removed ensure stack position is sane
             setStackPosition(mRestingStackPosition == null
-                    ? getDefaultStartPosition()
+                    ? getStartPosition()
                     : mRestingStackPosition);
 
             // Remove the stack from the coordinator since we don't have any bubbles and aren't
@@ -868,7 +857,7 @@
         mLayout.setVisibility(View.INVISIBLE);
         mLayout.post(() -> {
             setStackPosition(mRestingStackPosition == null
-                    ? getDefaultStartPosition()
+                    ? getStartPosition()
                     : mRestingStackPosition);
             mStackMovedToStartPosition = true;
             mLayout.setVisibility(View.VISIBLE);
@@ -938,15 +927,47 @@
         }
     }
 
-    /** Returns the default stack position, which is on the top left. */
-    public PointF getDefaultStartPosition() {
-        boolean isRtl = mLayout != null
-                && mLayout.getResources().getConfiguration().getLayoutDirection()
-                == View.LAYOUT_DIRECTION_RTL;
-        return new PointF(isRtl
-                        ? getAllowableStackPositionRegion().right
-                        : getAllowableStackPositionRegion().left,
-                getAllowableStackPositionRegion().top + mStackStartingVerticalOffset);
+    public void setStackPosition(BubbleStackView.RelativeStackPosition position) {
+        setStackPosition(position.getAbsolutePositionInRegion(getAllowableStackPositionRegion()));
+    }
+
+    public BubbleStackView.RelativeStackPosition getRelativeStackPosition() {
+        return new BubbleStackView.RelativeStackPosition(
+                mStackPosition, getAllowableStackPositionRegion());
+    }
+
+    /**
+     * Sets the starting position for the stack, where it will be located when the first bubble is
+     * added.
+     */
+    public void setStackStartPosition(BubbleStackView.RelativeStackPosition position) {
+        mStackStartPosition = position;
+    }
+
+    /**
+     * Returns the starting stack position. If {@link #setStackStartPosition} was called, this will
+     * return that position - otherwise, a reasonable default will be returned.
+     */
+    @Nullable public PointF getStartPosition() {
+        if (mLayout == null) {
+            return null;
+        }
+
+        if (mStackStartPosition == null) {
+            // Start on the left if we're in LTR, right otherwise.
+            final boolean startOnLeft =
+                    mLayout.getResources().getConfiguration().getLayoutDirection()
+                            != View.LAYOUT_DIRECTION_RTL;
+
+            final float startingVerticalOffset = mLayout.getResources().getDimensionPixelOffset(
+                    R.dimen.bubble_stack_starting_offset_y);
+
+            mStackStartPosition = new BubbleStackView.RelativeStackPosition(
+                    startOnLeft,
+                    startingVerticalOffset / getAllowableStackPositionRegion().height());
+        }
+
+        return mStackStartPosition.getAbsolutePositionInRegion(getAllowableStackPositionRegion());
     }
 
     private boolean isStackPositionSet() {
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/dagger/BubbleModule.java b/packages/SystemUI/src/com/android/systemui/bubbles/dagger/BubbleModule.java
index 10d301d..eecc41c 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/dagger/BubbleModule.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/dagger/BubbleModule.java
@@ -25,23 +25,22 @@
 import com.android.systemui.bubbles.BubbleController;
 import com.android.systemui.bubbles.BubbleData;
 import com.android.systemui.bubbles.BubbleDataRepository;
+import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dump.DumpManager;
 import com.android.systemui.model.SysUiState;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.statusbar.FeatureFlags;
 import com.android.systemui.statusbar.NotificationLockscreenUserManager;
+import com.android.systemui.statusbar.NotificationShadeWindowController;
 import com.android.systemui.statusbar.notification.NotificationEntryManager;
 import com.android.systemui.statusbar.notification.collection.NotifPipeline;
 import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProvider;
 import com.android.systemui.statusbar.phone.NotificationGroupManager;
-import com.android.systemui.statusbar.phone.NotificationShadeWindowController;
 import com.android.systemui.statusbar.phone.ShadeController;
 import com.android.systemui.statusbar.policy.ConfigurationController;
 import com.android.systemui.statusbar.policy.ZenModeController;
 import com.android.systemui.util.FloatingContentCoordinator;
 
-import javax.inject.Singleton;
-
 import dagger.Module;
 import dagger.Provides;
 
@@ -51,7 +50,7 @@
 
     /**
      */
-    @Singleton
+    @SysUISingleton
     @Provides
     static BubbleController newBubbleController(
             Context context,
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/storage/BubblePersistentRepository.kt b/packages/SystemUI/src/com/android/systemui/bubbles/storage/BubblePersistentRepository.kt
index 5b4d8c7..f447965 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/storage/BubblePersistentRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/storage/BubblePersistentRepository.kt
@@ -18,13 +18,13 @@
 import android.content.Context
 import android.util.AtomicFile
 import android.util.Log
+import com.android.systemui.dagger.SysUISingleton
 import java.io.File
 import java.io.FileOutputStream
 import java.io.IOException
 import javax.inject.Inject
-import javax.inject.Singleton
 
-@Singleton
+@SysUISingleton
 class BubblePersistentRepository @Inject constructor(
     context: Context
 ) {
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/storage/BubbleVolatileRepository.kt b/packages/SystemUI/src/com/android/systemui/bubbles/storage/BubbleVolatileRepository.kt
index 894970f..c6d5732 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/storage/BubbleVolatileRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/storage/BubbleVolatileRepository.kt
@@ -19,8 +19,8 @@
 import android.os.UserHandle
 import com.android.internal.annotations.VisibleForTesting
 import com.android.systemui.bubbles.ShortcutKey
+import com.android.systemui.dagger.SysUISingleton
 import javax.inject.Inject
-import javax.inject.Singleton
 
 private const val CAPACITY = 16
 
@@ -28,7 +28,7 @@
  * BubbleVolatileRepository holds the most updated snapshot of list of bubbles for in-memory
  * manipulation.
  */
-@Singleton
+@SysUISingleton
 class BubbleVolatileRepository @Inject constructor(
     private val launcherApps: LauncherApps
 ) {
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/FalsingManagerProxy.java b/packages/SystemUI/src/com/android/systemui/classifier/FalsingManagerProxy.java
index f35322b..83b6df3 100644
--- a/packages/SystemUI/src/com/android/systemui/classifier/FalsingManagerProxy.java
+++ b/packages/SystemUI/src/com/android/systemui/classifier/FalsingManagerProxy.java
@@ -31,6 +31,7 @@
 import com.android.systemui.Dumpable;
 import com.android.systemui.classifier.brightline.BrightLineFalsingManager;
 import com.android.systemui.classifier.brightline.FalsingDataProvider;
+import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.dagger.qualifiers.UiBackground;
 import com.android.systemui.dock.DockManager;
@@ -48,14 +49,13 @@
 import java.util.concurrent.Executor;
 
 import javax.inject.Inject;
-import javax.inject.Singleton;
 
 /**
  * Simple passthrough implementation of {@link FalsingManager} allowing plugins to swap in.
  *
  * {@link FalsingManagerImpl} is used when a Plugin is not loaded.
  */
-@Singleton
+@SysUISingleton
 public class FalsingManagerProxy implements FalsingManager, Dumpable {
 
     private static final String PROXIMITY_SENSOR_TAG = "FalsingManager";
diff --git a/packages/SystemUI/src/com/android/systemui/colorextraction/SysuiColorExtractor.java b/packages/SystemUI/src/com/android/systemui/colorextraction/SysuiColorExtractor.java
index 3ca1f59..5b33428 100644
--- a/packages/SystemUI/src/com/android/systemui/colorextraction/SysuiColorExtractor.java
+++ b/packages/SystemUI/src/com/android/systemui/colorextraction/SysuiColorExtractor.java
@@ -28,6 +28,7 @@
 import com.android.internal.colorextraction.types.Tonal;
 import com.android.keyguard.KeyguardUpdateMonitor;
 import com.android.systemui.Dumpable;
+import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.statusbar.policy.ConfigurationController;
 
 import java.io.FileDescriptor;
@@ -35,12 +36,11 @@
 import java.util.Arrays;
 
 import javax.inject.Inject;
-import javax.inject.Singleton;
 
 /**
  * ColorExtractor aware of wallpaper visibility
  */
-@Singleton
+@SysUISingleton
 public class SysuiColorExtractor extends ColorExtractor implements Dumpable,
         ConfigurationController.ConfigurationListener {
     private static final String TAG = "SysuiColorExtractor";
diff --git a/packages/SystemUI/src/com/android/systemui/controls/CustomIconCache.kt b/packages/SystemUI/src/com/android/systemui/controls/CustomIconCache.kt
new file mode 100644
index 0000000..ed60849
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/controls/CustomIconCache.kt
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.controls
+
+import android.content.ComponentName
+import android.graphics.drawable.Icon
+import androidx.annotation.GuardedBy
+import com.android.systemui.dagger.SysUISingleton
+import javax.inject.Inject
+
+/**
+ * Icon cache for custom icons sent with controls.
+ *
+ * It assumes that only one component can be current at the time, to minimize the number of icons
+ * stored at a given time.
+ */
+@SysUISingleton
+class CustomIconCache @Inject constructor() {
+
+    private var currentComponent: ComponentName? = null
+    @GuardedBy("cache")
+    private val cache: MutableMap<String, Icon> = LinkedHashMap()
+
+    /**
+     * Store an icon in the cache.
+     *
+     * If the icons currently stored do not correspond to the component to be stored, the cache is
+     * cleared first.
+     */
+    fun store(component: ComponentName, controlId: String, icon: Icon?) {
+        if (component != currentComponent) {
+            clear()
+            currentComponent = component
+        }
+        synchronized(cache) {
+            if (icon != null) {
+                cache.put(controlId, icon)
+            } else {
+                cache.remove(controlId)
+            }
+        }
+    }
+
+    /**
+     * Retrieves a custom icon stored in the cache.
+     *
+     * It will return null if the component requested is not the one whose icons are stored, or if
+     * there is no icon cached for that id.
+     */
+    fun retrieve(component: ComponentName, controlId: String): Icon? {
+        if (component != currentComponent) return null
+        return synchronized(cache) {
+            cache.get(controlId)
+        }
+    }
+
+    private fun clear() {
+        synchronized(cache) {
+            cache.clear()
+        }
+    }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsBindingControllerImpl.kt b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsBindingControllerImpl.kt
index aa3e193..658f46e 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsBindingControllerImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsBindingControllerImpl.kt
@@ -28,14 +28,14 @@
 import android.service.controls.actions.ControlAction
 import android.util.Log
 import com.android.internal.annotations.VisibleForTesting
+import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Background
 import com.android.systemui.util.concurrency.DelayableExecutor
 import dagger.Lazy
 import java.util.concurrent.atomic.AtomicBoolean
 import javax.inject.Inject
-import javax.inject.Singleton
 
-@Singleton
+@SysUISingleton
 @VisibleForTesting
 open class ControlsBindingControllerImpl @Inject constructor(
     private val context: Context,
diff --git a/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsControllerImpl.kt b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsControllerImpl.kt
index 40c8c6b..495872f 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsControllerImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsControllerImpl.kt
@@ -42,6 +42,7 @@
 import com.android.systemui.controls.ControlsServiceInfo
 import com.android.systemui.controls.management.ControlsListingController
 import com.android.systemui.controls.ui.ControlsUiController
+import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Background
 import com.android.systemui.dump.DumpManager
 import com.android.systemui.globalactions.GlobalActionsDialog
@@ -52,18 +53,17 @@
 import java.util.concurrent.TimeUnit
 import java.util.function.Consumer
 import javax.inject.Inject
-import javax.inject.Singleton
 
-@Singleton
+@SysUISingleton
 class ControlsControllerImpl @Inject constructor (
-    private val context: Context,
-    @Background private val executor: DelayableExecutor,
-    private val uiController: ControlsUiController,
-    private val bindingController: ControlsBindingController,
-    private val listingController: ControlsListingController,
-    private val broadcastDispatcher: BroadcastDispatcher,
-    optionalWrapper: Optional<ControlsFavoritePersistenceWrapper>,
-    dumpManager: DumpManager
+        private val context: Context,
+        @Background private val executor: DelayableExecutor,
+        private val uiController: ControlsUiController,
+        private val bindingController: ControlsBindingController,
+        private val listingController: ControlsListingController,
+        private val broadcastDispatcher: BroadcastDispatcher,
+        optionalWrapper: Optional<ControlsFavoritePersistenceWrapper>,
+        dumpManager: DumpManager
 ) : Dumpable, ControlsController {
 
     companion object {
diff --git a/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsFavoritePersistenceWrapper.kt b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsFavoritePersistenceWrapper.kt
index 1bda841..d930c98 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsFavoritePersistenceWrapper.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsFavoritePersistenceWrapper.kt
@@ -87,6 +87,10 @@
      * @param list a list of favorite controls. The list will be stored in the same order.
      */
     fun storeFavorites(structures: List<StructureInfo>) {
+        if (structures.isEmpty() && !file.exists()) {
+            // Do not create a new file to store nothing
+            return
+        }
         executor.execute {
             Log.d(TAG, "Saving data to file: $file")
             val atomicFile = AtomicFile(file)
diff --git a/packages/SystemUI/src/com/android/systemui/controls/dagger/ControlsComponent.kt b/packages/SystemUI/src/com/android/systemui/controls/dagger/ControlsComponent.kt
index 9a5b960..38a82f8 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/dagger/ControlsComponent.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/dagger/ControlsComponent.kt
@@ -19,10 +19,10 @@
 import com.android.systemui.controls.controller.ControlsController
 import com.android.systemui.controls.management.ControlsListingController
 import com.android.systemui.controls.ui.ControlsUiController
+import com.android.systemui.dagger.SysUISingleton
 import dagger.Lazy
 import java.util.Optional
 import javax.inject.Inject
-import javax.inject.Singleton
 
 /**
  * Pseudo-component to inject into classes outside `com.android.systemui.controls`.
@@ -30,7 +30,7 @@
  * If `featureEnabled` is false, all the optionals should be empty. The controllers will only be
  * instantiated if `featureEnabled` is true.
  */
-@Singleton
+@SysUISingleton
 class ControlsComponent @Inject constructor(
     @ControlsFeatureEnabled private val featureEnabled: Boolean,
     private val lazyControlsController: Lazy<ControlsController>,
diff --git a/packages/SystemUI/src/com/android/systemui/controls/dagger/ControlsModule.kt b/packages/SystemUI/src/com/android/systemui/controls/dagger/ControlsModule.kt
index 4760d29..fbdeb30 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/dagger/ControlsModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/dagger/ControlsModule.kt
@@ -33,13 +33,13 @@
 import com.android.systemui.controls.ui.ControlActionCoordinatorImpl
 import com.android.systemui.controls.ui.ControlsUiController
 import com.android.systemui.controls.ui.ControlsUiControllerImpl
+import com.android.systemui.dagger.SysUISingleton
 import dagger.Binds
 import dagger.BindsOptionalOf
 import dagger.Module
 import dagger.Provides
 import dagger.multibindings.ClassKey
 import dagger.multibindings.IntoMap
-import javax.inject.Singleton
 
 /**
  * Module for injecting classes in `com.android.systemui.controls`-
@@ -55,7 +55,7 @@
     companion object {
         @JvmStatic
         @Provides
-        @Singleton
+        @SysUISingleton
         @ControlsFeatureEnabled
         fun providesControlsFeatureEnabled(pm: PackageManager): Boolean {
             return pm.hasSystemFeature(PackageManager.FEATURE_CONTROLS)
diff --git a/packages/SystemUI/src/com/android/systemui/controls/management/ControlAdapter.kt b/packages/SystemUI/src/com/android/systemui/controls/management/ControlAdapter.kt
index c683a87..31830b9 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/management/ControlAdapter.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/management/ControlAdapter.kt
@@ -72,8 +72,13 @@
             TYPE_CONTROL -> {
                 ControlHolder(
                     layoutInflater.inflate(R.layout.controls_base_item, parent, false).apply {
-                        layoutParams.apply {
+                        (layoutParams as ViewGroup.MarginLayoutParams).apply {
                             width = ViewGroup.LayoutParams.MATCH_PARENT
+                            // Reset margins as they will be set through the decoration
+                            topMargin = 0
+                            bottomMargin = 0
+                            leftMargin = 0
+                            rightMargin = 0
                         }
                         elevation = this@ControlAdapter.elevation
                         background = parent.context.getDrawable(
@@ -386,7 +391,7 @@
         val type = parent.adapter?.getItemViewType(position)
         if (type == ControlAdapter.TYPE_CONTROL) {
             outRect.apply {
-                top = topMargin
+                top = topMargin * 2 // Use double margin, as we are not setting bottom
                 left = sideMargins
                 right = sideMargins
                 bottom = 0
diff --git a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsEditingActivity.kt b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsEditingActivity.kt
index ff40a8a..f68388d 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsEditingActivity.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsEditingActivity.kt
@@ -29,6 +29,7 @@
 import androidx.recyclerview.widget.RecyclerView
 import com.android.systemui.R
 import com.android.systemui.broadcast.BroadcastDispatcher
+import com.android.systemui.controls.CustomIconCache
 import com.android.systemui.controls.controller.ControlsControllerImpl
 import com.android.systemui.controls.controller.StructureInfo
 import com.android.systemui.globalactions.GlobalActionsComponent
@@ -42,7 +43,8 @@
 class ControlsEditingActivity @Inject constructor(
     private val controller: ControlsControllerImpl,
     broadcastDispatcher: BroadcastDispatcher,
-    private val globalActionsComponent: GlobalActionsComponent
+    private val globalActionsComponent: GlobalActionsComponent,
+    private val customIconCache: CustomIconCache
 ) : LifecycleActivity() {
 
     companion object {
@@ -170,7 +172,7 @@
 
     private fun setUpList() {
         val controls = controller.getFavoritesForStructure(component, structure)
-        model = FavoritesModel(component, controls, favoritesModelCallback)
+        model = FavoritesModel(customIconCache, component, controls, favoritesModelCallback)
         val elevation = resources.getFloat(R.dimen.control_card_elevation)
         val recyclerView = requireViewById<RecyclerView>(R.id.list)
         recyclerView.alpha = 0.0f
diff --git a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsListingControllerImpl.kt b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsListingControllerImpl.kt
index 1cd9712..0d4439f 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsListingControllerImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsListingControllerImpl.kt
@@ -27,11 +27,11 @@
 import com.android.settingslib.applications.ServiceListing
 import com.android.settingslib.widget.CandidateInfo
 import com.android.systemui.controls.ControlsServiceInfo
+import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Background
 import java.util.concurrent.Executor
 import java.util.concurrent.atomic.AtomicInteger
 import javax.inject.Inject
-import javax.inject.Singleton
 
 private fun createServiceListing(context: Context): ServiceListing {
     return ServiceListing.Builder(context).apply {
@@ -52,7 +52,7 @@
  * * Has an intent-filter responding to [ControlsProviderService.CONTROLS_ACTION]
  * * Has the bind permission `android.permission.BIND_CONTROLS`
  */
-@Singleton
+@SysUISingleton
 class ControlsListingControllerImpl @VisibleForTesting constructor(
     private val context: Context,
     @Background private val backgroundExecutor: Executor,
diff --git a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsModel.kt b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsModel.kt
index 4ef64a5..ad0e7a5 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsModel.kt
@@ -114,11 +114,27 @@
     val controlStatus: ControlStatus
 ) : ElementWrapper(), ControlInterface by controlStatus
 
+private fun nullIconGetter(_a: ComponentName, _b: String): Icon? = null
+
 data class ControlInfoWrapper(
     override val component: ComponentName,
     val controlInfo: ControlInfo,
     override var favorite: Boolean
 ) : ElementWrapper(), ControlInterface {
+
+    var customIconGetter: (ComponentName, String) -> Icon? = ::nullIconGetter
+        private set
+
+    // Separate constructor so the getter is not used in auto-generated methods
+    constructor(
+        component: ComponentName,
+        controlInfo: ControlInfo,
+        favorite: Boolean,
+        customIconGetter: (ComponentName, String) -> Icon?
+    ): this(component, controlInfo, favorite) {
+        this.customIconGetter = customIconGetter
+    }
+
     override val controlId: String
         get() = controlInfo.controlId
     override val title: CharSequence
@@ -128,8 +144,7 @@
     override val deviceType: Int
         get() = controlInfo.deviceType
     override val customIcon: Icon?
-        // Will need to address to support for edit activity
-        get() = null
+        get() = customIconGetter(component, controlId)
 }
 
 data class DividerWrapper(
diff --git a/packages/SystemUI/src/com/android/systemui/controls/management/FavoritesModel.kt b/packages/SystemUI/src/com/android/systemui/controls/management/FavoritesModel.kt
index 5242501..f9ce636 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/management/FavoritesModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/management/FavoritesModel.kt
@@ -21,6 +21,7 @@
 import androidx.recyclerview.widget.ItemTouchHelper
 import androidx.recyclerview.widget.RecyclerView
 import com.android.systemui.controls.ControlInterface
+import com.android.systemui.controls.CustomIconCache
 import com.android.systemui.controls.controller.ControlInfo
 import java.util.Collections
 
@@ -35,6 +36,7 @@
  * @property favoritesModelCallback callback to notify on first change and empty favorites
  */
 class FavoritesModel(
+    private val customIconCache: CustomIconCache,
     private val componentName: ComponentName,
     favorites: List<ControlInfo>,
     private val favoritesModelCallback: FavoritesModelCallback
@@ -83,7 +85,7 @@
         }
 
     override val elements: List<ElementWrapper> = favorites.map {
-        ControlInfoWrapper(componentName, it, true)
+        ControlInfoWrapper(componentName, it, true, customIconCache::retrieve)
     } + DividerWrapper()
 
     /**
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 22d6b6b..ab82225 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlActionCoordinatorImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlActionCoordinatorImpl.kt
@@ -16,30 +16,29 @@
 
 package com.android.systemui.controls.ui
 
+import android.annotation.MainThread
 import android.app.Dialog
 import android.content.Context
 import android.content.Intent
 import android.content.pm.PackageManager
 import android.content.pm.ResolveInfo
-import android.annotation.MainThread
-import android.os.Vibrator
 import android.os.VibrationEffect
+import android.os.Vibrator
 import android.service.controls.Control
 import android.service.controls.actions.BooleanAction
 import android.service.controls.actions.CommandAction
 import android.service.controls.actions.FloatAction
 import android.util.Log
 import android.view.HapticFeedbackConstants
+import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Main
 import com.android.systemui.globalactions.GlobalActionsComponent
 import com.android.systemui.plugins.ActivityStarter
 import com.android.systemui.statusbar.policy.KeyguardStateController
 import com.android.systemui.util.concurrency.DelayableExecutor
-
 import javax.inject.Inject
-import javax.inject.Singleton
 
-@Singleton
+@SysUISingleton
 class ControlActionCoordinatorImpl @Inject constructor(
     private val context: Context,
     private val bgExecutor: DelayableExecutor,
@@ -92,7 +91,7 @@
     override fun setValue(cvh: ControlViewHolder, templateId: String, newValue: Float) {
         bouncerOrRun(Action(cvh.cws.ci.controlId, {
             cvh.action(FloatAction(templateId, newValue))
-        }, true /* blockable */))
+        }, false /* blockable */))
     }
 
     override fun longPress(cvh: ControlViewHolder) {
diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt
index 1eb7e21..3710310 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt
@@ -44,6 +44,7 @@
 import android.widget.TextView
 import com.android.systemui.R
 import com.android.systemui.controls.ControlsServiceInfo
+import com.android.systemui.controls.CustomIconCache
 import com.android.systemui.controls.controller.ControlInfo
 import com.android.systemui.controls.controller.ControlsController
 import com.android.systemui.controls.controller.StructureInfo
@@ -51,6 +52,7 @@
 import com.android.systemui.controls.management.ControlsFavoritingActivity
 import com.android.systemui.controls.management.ControlsListingController
 import com.android.systemui.controls.management.ControlsProviderSelectorActivity
+import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Background
 import com.android.systemui.dagger.qualifiers.Main
 import com.android.systemui.globalactions.GlobalActionsPopupMenu
@@ -61,11 +63,10 @@
 import java.text.Collator
 import java.util.function.Consumer
 import javax.inject.Inject
-import javax.inject.Singleton
 
 private data class ControlKey(val componentName: ComponentName, val controlId: String)
 
-@Singleton
+@SysUISingleton
 class ControlsUiControllerImpl @Inject constructor (
     val controlsController: Lazy<ControlsController>,
     val context: Context,
@@ -75,7 +76,8 @@
     @Main val sharedPreferences: SharedPreferences,
     val controlActionCoordinator: ControlActionCoordinator,
     private val activityStarter: ActivityStarter,
-    private val shadeController: ShadeController
+    private val shadeController: ShadeController,
+    private val iconCache: CustomIconCache
 ) : ControlsUiController {
 
     companion object {
@@ -502,6 +504,7 @@
         controls.forEach { c ->
             controlsById.get(ControlKey(componentName, c.getControlId()))?.let {
                 Log.d(ControlsUiController.TAG, "onRefreshState() for id: " + c.getControlId())
+                iconCache.store(componentName, c.controlId, c.customIcon)
                 val cws = ControlWithState(componentName, it.ci, c)
                 val key = ControlKey(componentName, c.getControlId())
                 controlsById.put(key, cws)
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/ContextComponentResolver.java b/packages/SystemUI/src/com/android/systemui/dagger/ContextComponentResolver.java
index f91d795..b41915b 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/ContextComponentResolver.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/ContextComponentResolver.java
@@ -27,12 +27,11 @@
 
 import javax.inject.Inject;
 import javax.inject.Provider;
-import javax.inject.Singleton;
 
 /**
  * Used during Service and Activity instantiation to make them injectable.
  */
-@Singleton
+@SysUISingleton
 public class ContextComponentResolver implements ContextComponentHelper {
     private final Map<Class<?>, Provider<Activity>> mActivityCreators;
     private final Map<Class<?>, Provider<Service>> mServiceCreators;
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/DefaultServiceBinder.java b/packages/SystemUI/src/com/android/systemui/dagger/DefaultServiceBinder.java
index c95e81c..596e440 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/DefaultServiceBinder.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/DefaultServiceBinder.java
@@ -66,12 +66,6 @@
     @ClassKey(SystemUIAuxiliaryDumpService.class)
     public abstract Service bindSystemUIAuxiliaryDumpService(SystemUIAuxiliaryDumpService service);
 
-    /** */
-    @Binds
-    @IntoMap
-    @ClassKey(TakeScreenshotService.class)
-    public abstract Service bindTakeScreenshotService(TakeScreenshotService service);
-
     /** Inject into RecordingService */
     @Binds
     @IntoMap
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/DependencyProvider.java b/packages/SystemUI/src/com/android/systemui/dagger/DependencyProvider.java
index 3d31070..e5e3a1d 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/DependencyProvider.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/DependencyProvider.java
@@ -31,6 +31,7 @@
 import android.view.IWindowManager;
 import android.view.LayoutInflater;
 import android.view.WindowManager;
+import android.view.accessibility.AccessibilityManager;
 
 import com.android.internal.logging.MetricsLogger;
 import com.android.internal.logging.UiEventLogger;
@@ -40,6 +41,8 @@
 import com.android.keyguard.ViewMediatorCallback;
 import com.android.systemui.Prefs;
 import com.android.systemui.accessibility.ModeSwitchesController;
+import com.android.systemui.accessibility.SystemActions;
+import com.android.systemui.assist.AssistManager;
 import com.android.systemui.broadcast.BroadcastDispatcher;
 import com.android.systemui.broadcast.logging.BroadcastDispatcherLogger;
 import com.android.systemui.dagger.qualifiers.Background;
@@ -47,25 +50,37 @@
 import com.android.systemui.doze.AlwaysOnDisplayPolicy;
 import com.android.systemui.dump.DumpManager;
 import com.android.systemui.keyguard.KeyguardViewMediator;
+import com.android.systemui.model.SysUiState;
+import com.android.systemui.navigationbar.NavigationBarController;
+import com.android.systemui.navigationbar.NavigationModeController;
 import com.android.systemui.plugins.PluginInitializerImpl;
+import com.android.systemui.plugins.statusbar.StatusBarStateController;
+import com.android.systemui.recents.OverviewProxyService;
+import com.android.systemui.recents.Recents;
 import com.android.systemui.shared.plugins.PluginManager;
 import com.android.systemui.shared.plugins.PluginManagerImpl;
 import com.android.systemui.shared.system.ActivityManagerWrapper;
 import com.android.systemui.shared.system.DevicePolicyManagerWrapper;
+import com.android.systemui.stackdivider.SplitScreenController;
 import com.android.systemui.statusbar.CommandQueue;
-import com.android.systemui.statusbar.NavigationBarController;
+import com.android.systemui.statusbar.NotificationRemoteInputManager;
 import com.android.systemui.statusbar.phone.AutoHideController;
 import com.android.systemui.statusbar.phone.ConfigurationControllerImpl;
+import com.android.systemui.statusbar.phone.ShadeController;
+import com.android.systemui.statusbar.phone.StatusBar;
+import com.android.systemui.statusbar.policy.AccessibilityManagerWrapper;
 import com.android.systemui.statusbar.policy.ConfigurationController;
 import com.android.systemui.statusbar.policy.DataSaverController;
+import com.android.systemui.statusbar.policy.DeviceProvisionedController;
 import com.android.systemui.statusbar.policy.NetworkController;
 import com.android.systemui.util.leak.LeakDetector;
 
+import java.util.Optional;
 import java.util.concurrent.Executor;
 
 import javax.inject.Named;
-import javax.inject.Singleton;
 
+import dagger.Lazy;
 import dagger.Module;
 import dagger.Provides;
 
@@ -80,8 +95,9 @@
 @Module(includes = {NightDisplayListenerModule.class})
 public class DependencyProvider {
 
-    @Singleton
+    /** */
     @Provides
+    @SysUISingleton
     @Named(TIME_TICK_HANDLER_NAME)
     public Handler provideTimeTickHandler() {
         HandlerThread thread = new HandlerThread("TimeTick");
@@ -108,14 +124,16 @@
         return new Handler();
     }
 
-    @Singleton
+    /** */
     @Provides
+    @SysUISingleton
     public DataSaverController provideDataSaverController(NetworkController networkController) {
         return networkController.getDataSaverController();
     }
 
-    @Singleton
+    /** */
     @Provides
+    @SysUISingleton
     public DisplayMetrics provideDisplayMetrics(Context context, WindowManager windowManager) {
         DisplayMetrics displayMetrics = new DisplayMetrics();
         context.getDisplay().getMetrics(displayMetrics);
@@ -123,69 +141,116 @@
     }
 
     /** */
-    @Singleton
     @Provides
+    @SysUISingleton
     public INotificationManager provideINotificationManager() {
         return INotificationManager.Stub.asInterface(
                 ServiceManager.getService(Context.NOTIFICATION_SERVICE));
     }
 
     /** */
-    @Singleton
     @Provides
+    @SysUISingleton
     public LayoutInflater providerLayoutInflater(Context context) {
         return LayoutInflater.from(context);
     }
 
-    @Singleton
+    /** */
     @Provides
+    @SysUISingleton
     public LeakDetector provideLeakDetector() {
         return LeakDetector.create();
 
     }
 
-    @Singleton
+    /** */
     @Provides
+    @SysUISingleton
     public MetricsLogger provideMetricsLogger() {
         return new MetricsLogger();
     }
 
-    @Singleton
+    /** */
     @Provides
+    @SysUISingleton
     public PluginManager providePluginManager(Context context) {
         return new PluginManagerImpl(context, new PluginInitializerImpl());
     }
 
-    @Singleton
+    /** */
     @Provides
+    @SysUISingleton
     public NavigationBarController provideNavigationBarController(Context context,
-            @Main Handler mainHandler, CommandQueue commandQueue) {
-        return new NavigationBarController(context, mainHandler, commandQueue);
+            WindowManager windowManager,
+            Lazy<AssistManager> assistManagerLazy,
+            AccessibilityManager accessibilityManager,
+            AccessibilityManagerWrapper accessibilityManagerWrapper,
+            DeviceProvisionedController deviceProvisionedController,
+            MetricsLogger metricsLogger,
+            OverviewProxyService overviewProxyService,
+            NavigationModeController navigationModeController,
+            StatusBarStateController statusBarStateController,
+            SysUiState sysUiFlagsContainer,
+            BroadcastDispatcher broadcastDispatcher,
+            CommandQueue commandQueue,
+            Optional<SplitScreenController> splitScreenControllerOptional,
+            Optional<Recents> recentsOptional,
+            Lazy<StatusBar> statusBarLazy,
+            ShadeController shadeController,
+            NotificationRemoteInputManager notificationRemoteInputManager,
+            SystemActions systemActions,
+            @Main Handler mainHandler,
+            UiEventLogger uiEventLogger,
+            ConfigurationController configurationController) {
+        return new NavigationBarController(context,
+                windowManager,
+                assistManagerLazy,
+                accessibilityManager,
+                accessibilityManagerWrapper,
+                deviceProvisionedController,
+                metricsLogger,
+                overviewProxyService,
+                navigationModeController,
+                statusBarStateController,
+                sysUiFlagsContainer,
+                broadcastDispatcher,
+                commandQueue,
+                splitScreenControllerOptional,
+                recentsOptional,
+                statusBarLazy,
+                shadeController,
+                notificationRemoteInputManager,
+                systemActions,
+                mainHandler,
+                uiEventLogger,
+                configurationController);
     }
 
-    @Singleton
+    /** */
     @Provides
+    @SysUISingleton
     public ConfigurationController provideConfigurationController(Context context) {
         return new ConfigurationControllerImpl(context);
     }
 
     /** */
-    @Singleton
+    @SysUISingleton
     @Provides
     public AutoHideController provideAutoHideController(Context context,
             @Main Handler mainHandler, IWindowManager iWindowManager) {
         return new AutoHideController(context, mainHandler, iWindowManager);
     }
 
-    @Singleton
+    /** */
     @Provides
+    @SysUISingleton
     public ActivityManagerWrapper provideActivityManagerWrapper() {
         return ActivityManagerWrapper.getInstance();
     }
 
     /** Provides and initializes the {#link BroadcastDispatcher} for SystemUI */
-    @Singleton
     @Provides
+    @SysUISingleton
     public BroadcastDispatcher providesBroadcastDispatcher(
             Context context,
             @Background Looper backgroundLooper,
@@ -199,8 +264,9 @@
         return bD;
     }
 
-    @Singleton
+    /** */
     @Provides
+    @SysUISingleton
     public DevicePolicyManagerWrapper provideDevicePolicyManagerWrapper() {
         return DevicePolicyManagerWrapper.getInstance();
     }
@@ -230,24 +296,29 @@
     }
 
     /** */
-    @Singleton
     @Provides
+    public SystemActions providesSystemActions(Context context) {
+        return new SystemActions(context);
+    }
+
+    /** */
+    @Provides
+    @SysUISingleton
     public Choreographer providesChoreographer() {
         return Choreographer.getInstance();
     }
 
     /** Provides an instance of {@link com.android.internal.logging.UiEventLogger} */
-    @Singleton
     @Provides
+    @SysUISingleton
     static UiEventLogger provideUiEventLogger() {
         return new UiEventLoggerImpl();
     }
 
     /** */
-    @Singleton
     @Provides
+    @SysUISingleton
     public ModeSwitchesController providesModeSwitchesController(Context context) {
         return new ModeSwitchesController(context);
     }
-
 }
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/GlobalModule.java b/packages/SystemUI/src/com/android/systemui/dagger/GlobalModule.java
new file mode 100644
index 0000000..553655b
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/dagger/GlobalModule.java
@@ -0,0 +1,78 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.dagger;
+
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.hardware.display.AmbientDisplayConfiguration;
+import android.util.DisplayMetrics;
+import android.view.Choreographer;
+
+import com.android.systemui.Prefs;
+import com.android.systemui.dagger.qualifiers.Main;
+
+import javax.inject.Singleton;
+
+import dagger.Module;
+import dagger.Provides;
+
+/**
+ * Supplies globally scoped instances.
+ *
+ * Providers in this module will be accessible to both WMComponent and SysUIComponent scoped
+ * classes. They are in here because they are either needed globally or are inherently universal
+ * to the application.
+ *
+ * Note that just because a class might be used by both WM and SysUI does not necessarily mean that
+ * it should got into this module. If WM and SysUI might need the class for different purposes
+ * or different semantics, it may make sense to ask them to supply their own. Something like
+ * threading and concurrency provide a good example. Both components need
+ * Threads/Handlers/Executors, but they need separate instances of them in many cases.
+ *
+ * Please use discretion when adding things to the global scope.
+ */
+@Module
+public class GlobalModule {
+    /** */
+    @Provides
+    @Main
+    public SharedPreferences provideSharePreferences(Context context) {
+        return Prefs.get(context);
+    }
+
+    /** */
+    @Provides
+    public AmbientDisplayConfiguration provideAmbientDisplayConfiguration(Context context) {
+        return new AmbientDisplayConfiguration(context);
+    }
+
+    /** */
+    @Provides
+    @Singleton
+    public Choreographer providesChoreographer() {
+        return Choreographer.getInstance();
+    }
+
+    /** */
+    @Provides
+    @Singleton
+    public DisplayMetrics provideDisplayMetrics(Context context) {
+        DisplayMetrics displayMetrics = new DisplayMetrics();
+        context.getDisplay().getMetrics(displayMetrics);
+        return displayMetrics;
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/GlobalRootComponent.java b/packages/SystemUI/src/com/android/systemui/dagger/GlobalRootComponent.java
new file mode 100644
index 0000000..3d7c8ad4
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/dagger/GlobalRootComponent.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.dagger;
+
+import android.content.Context;
+
+import javax.inject.Singleton;
+
+import dagger.BindsInstance;
+import dagger.Component;
+
+/**
+ * Root component for Dagger injection.
+ */
+@Singleton
+@Component(modules = {
+        SysUISubcomponentModule.class,
+        WMModule.class})
+public interface GlobalRootComponent {
+
+    /**
+     * Builder for a GlobalRootComponent.
+     */
+    @Component.Builder
+    interface Builder {
+        @BindsInstance
+        Builder context(Context context);
+
+        GlobalRootComponent build();
+    }
+
+    /**
+     * Builder for a WMComponent.
+     */
+    WMComponent.Builder getWMComponentBuilder();
+
+    /**
+     * Builder for a SysuiComponent.
+     */
+    SysUIComponent.Builder getSysUIComponent();
+}
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIRootComponent.java b/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java
similarity index 60%
rename from packages/SystemUI/src/com/android/systemui/dagger/SystemUIRootComponent.java
rename to packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java
index 900c11f..b606201 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIRootComponent.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java
@@ -16,108 +16,79 @@
 
 package com.android.systemui.dagger;
 
-import static com.android.systemui.Dependency.ALLOW_NOTIFICATION_LONG_PRESS_NAME;
-
-import android.content.ContentProvider;
-import android.content.Context;
-
 import com.android.systemui.BootCompleteCacheImpl;
 import com.android.systemui.Dependency;
 import com.android.systemui.InitController;
 import com.android.systemui.SystemUIAppComponentFactory;
 import com.android.systemui.dump.DumpManager;
-import com.android.systemui.fragments.FragmentService;
 import com.android.systemui.keyguard.KeyguardSliceProvider;
-import com.android.systemui.onehanded.dagger.OneHandedModule;
-import com.android.systemui.pip.phone.PipMenuActivity;
 import com.android.systemui.pip.phone.dagger.PipModule;
 import com.android.systemui.statusbar.policy.ConfigurationController;
 import com.android.systemui.util.InjectionInflationController;
 
-import javax.inject.Named;
-import javax.inject.Singleton;
-
-import dagger.BindsInstance;
-import dagger.Component;
+import dagger.Subcomponent;
 
 /**
- * Root component for Dagger injection.
+ * Dagger Subcomponent for Core SysUI.
  */
-@Singleton
-@Component(modules = {
+@SysUISingleton
+@Subcomponent(modules = {
         DefaultComponentBinder.class,
         DependencyProvider.class,
         DependencyBinder.class,
-        OneHandedModule.class,
         PipModule.class,
         SystemServicesModule.class,
         SystemUIBinder.class,
         SystemUIModule.class,
         SystemUIDefaultModule.class})
-public interface SystemUIRootComponent {
+public interface SysUIComponent {
 
     /**
-     * Builder for a SystemUIRootComponent.
+     * Builder for a SysUIComponent.
      */
-    @Component.Builder
+    @Subcomponent.Builder
     interface Builder {
-        @BindsInstance
-        Builder context(Context context);
-
-        SystemUIRootComponent build();
+        SysUIComponent build();
     }
 
     /**
      * Provides a BootCompleteCache.
      */
-    @Singleton
+    @SysUISingleton
     BootCompleteCacheImpl provideBootCacheImpl();
 
     /**
      * Creates a ContextComponentHelper.
      */
-    @Singleton
+    @SysUISingleton
     ConfigurationController getConfigurationController();
 
     /**
      * Creates a ContextComponentHelper.
      */
-    @Singleton
+    @SysUISingleton
     ContextComponentHelper getContextComponentHelper();
 
     /**
      * Main dependency providing module.
      */
-    @Singleton
+    @SysUISingleton
     Dependency createDependency();
 
     /** */
-    @Singleton
+    @SysUISingleton
     DumpManager createDumpManager();
 
     /**
-     * FragmentCreator generates all Fragments that need injection.
-     */
-    @Singleton
-    FragmentService.FragmentCreator createFragmentCreator();
-
-
-    /**
      * Creates a InitController.
      */
-    @Singleton
+    @SysUISingleton
     InitController getInitController();
 
     /**
-     * ViewCreator generates all Views that need injection.
+     * ViewInstanceCreator generates all Views that need injection.
      */
-    InjectionInflationController.ViewCreator createViewCreator();
-
-    /**
-     * Whether notification long press is allowed.
-     */
-    @Named(ALLOW_NOTIFICATION_LONG_PRESS_NAME)
-    boolean allowNotificationLongPressName();
+    InjectionInflationController.ViewInstanceCreator.Factory createViewInstanceCreatorFactory();
 
     /**
      * Member injection into the supplied argument.
@@ -127,15 +98,5 @@
     /**
      * Member injection into the supplied argument.
      */
-    void inject(ContentProvider contentProvider);
-
-    /**
-     * Member injection into the supplied argument.
-     */
     void inject(KeyguardSliceProvider keyguardSliceProvider);
-
-    /**
-     * Member injection into the supplied argument.
-     */
-    void inject(PipMenuActivity pipMenuActivity);
 }
diff --git a/media/java/android/media/IStrategyPreferredDeviceDispatcher.aidl b/packages/SystemUI/src/com/android/systemui/dagger/SysUISingleton.java
similarity index 64%
copy from media/java/android/media/IStrategyPreferredDeviceDispatcher.aidl
copy to packages/SystemUI/src/com/android/systemui/dagger/SysUISingleton.java
index b1f99e6..a4b3342 100644
--- a/media/java/android/media/IStrategyPreferredDeviceDispatcher.aidl
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SysUISingleton.java
@@ -14,17 +14,20 @@
  * limitations under the License.
  */
 
-package android.media;
+package com.android.systemui.dagger;
 
-import android.media.AudioDeviceAttributes;
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.Retention;
+
+import javax.inject.Scope;
 
 /**
- * AIDL for AudioService to signal audio strategy-preferred device updates.
- *
- * {@hide}
+ * Scope annotation for singleton items within the SysUIComponent.
  */
-oneway interface IStrategyPreferredDeviceDispatcher {
-
-    void dispatchPrefDeviceChanged(int strategyId, in AudioDeviceAttributes device);
-
+@Documented
+@Retention(RUNTIME)
+@Scope
+public @interface SysUISingleton {
 }
diff --git a/tests/AutoVerify/app1/src/com/android/test/autoverify/MainActivity.java b/packages/SystemUI/src/com/android/systemui/dagger/SysUISubcomponentModule.java
similarity index 73%
copy from tests/AutoVerify/app1/src/com/android/test/autoverify/MainActivity.java
copy to packages/SystemUI/src/com/android/systemui/dagger/SysUISubcomponentModule.java
index 09ef472..aacc693 100644
--- a/tests/AutoVerify/app1/src/com/android/test/autoverify/MainActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SysUISubcomponentModule.java
@@ -13,3 +13,14 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
+
+package com.android.systemui.dagger;
+
+import dagger.Module;
+
+/**
+ * Dagger module for including the WMComponent.
+ */
+@Module(subcomponents = {SysUIComponent.class})
+public abstract class SysUISubcomponentModule {
+}
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemServicesModule.java b/packages/SystemUI/src/com/android/systemui/dagger/SystemServicesModule.java
index 251ce13..e7d2f125 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SystemServicesModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemServicesModule.java
@@ -41,8 +41,10 @@
 import android.hardware.SensorPrivacyManager;
 import android.hardware.display.ColorDisplayManager;
 import android.hardware.display.DisplayManager;
+import android.hardware.face.FaceManager;
 import android.media.AudioManager;
 import android.media.MediaRouter2Manager;
+import android.media.session.MediaSessionManager;
 import android.net.ConnectivityManager;
 import android.net.NetworkScoreManager;
 import android.net.wifi.WifiManager;
@@ -71,8 +73,6 @@
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.shared.system.PackageManagerWrapper;
 
-import javax.inject.Singleton;
-
 import dagger.Module;
 import dagger.Provides;
 
@@ -82,49 +82,49 @@
 @Module
 public class SystemServicesModule {
     @Provides
-    @Singleton
+    @SysUISingleton
     static AccessibilityManager provideAccessibilityManager(Context context) {
         return context.getSystemService(AccessibilityManager.class);
     }
 
     @Provides
-    @Singleton
+    @SysUISingleton
     static ActivityManager provideActivityManager(Context context) {
         return context.getSystemService(ActivityManager.class);
     }
 
-    @Singleton
+    @SysUISingleton
     @Provides
     static AlarmManager provideAlarmManager(Context context) {
         return context.getSystemService(AlarmManager.class);
     }
 
     @Provides
-    @Singleton
+    @SysUISingleton
     static AudioManager provideAudioManager(Context context) {
         return context.getSystemService(AudioManager.class);
     }
 
     @Provides
-    @Singleton
+    @SysUISingleton
     static ColorDisplayManager provideColorDisplayManager(Context context) {
         return context.getSystemService(ColorDisplayManager.class);
     }
 
     @Provides
-    @Singleton
+    @SysUISingleton
     static ConnectivityManager provideConnectivityManagager(Context context) {
         return context.getSystemService(ConnectivityManager.class);
     }
 
     @Provides
-    @Singleton
+    @SysUISingleton
     static ContentResolver provideContentResolver(Context context) {
         return context.getContentResolver();
     }
 
     @Provides
-    @Singleton
+    @SysUISingleton
     static DevicePolicyManager provideDevicePolicyManager(Context context) {
         return context.getSystemService(DevicePolicyManager.class);
     }
@@ -136,44 +136,52 @@
     }
 
     @Provides
-    @Singleton
+    @SysUISingleton
     static DisplayManager provideDisplayManager(Context context) {
         return context.getSystemService(DisplayManager.class);
     }
 
-    @Singleton
+    @SysUISingleton
     @Provides
     static IActivityManager provideIActivityManager() {
         return ActivityManager.getService();
     }
 
-    @Singleton
+    @SysUISingleton
     @Provides
     static IActivityTaskManager provideIActivityTaskManager() {
         return ActivityTaskManager.getService();
     }
 
     @Provides
-    @Singleton
+    @SysUISingleton
     static IBatteryStats provideIBatteryStats() {
         return IBatteryStats.Stub.asInterface(
                 ServiceManager.getService(BatteryStats.SERVICE_NAME));
     }
 
     @Provides
-    @Singleton
+    @SysUISingleton
     static IDreamManager provideIDreamManager() {
         return IDreamManager.Stub.asInterface(
                 ServiceManager.checkService(DreamService.DREAM_SERVICE));
     }
 
     @Provides
-    @Singleton
+    @SysUISingleton
+    @Nullable
+    static FaceManager provideFaceManager(Context context) {
+        return context.getSystemService(FaceManager.class);
+
+    }
+
+    @Provides
+    @SysUISingleton
     static IPackageManager provideIPackageManager() {
         return IPackageManager.Stub.asInterface(ServiceManager.getService("package"));
     }
 
-    @Singleton
+    @SysUISingleton
     @Provides
     static IStatusBarService provideIStatusBarService() {
         return IStatusBarService.Stub.asInterface(
@@ -187,32 +195,32 @@
                 ServiceManager.getService(Context.WALLPAPER_SERVICE));
     }
 
-    @Singleton
+    @SysUISingleton
     @Provides
     static IWindowManager provideIWindowManager() {
         return WindowManagerGlobal.getWindowManagerService();
     }
 
-    @Singleton
+    @SysUISingleton
     @Provides
     static KeyguardManager provideKeyguardManager(Context context) {
         return context.getSystemService(KeyguardManager.class);
     }
 
-    @Singleton
+    @SysUISingleton
     @Provides
     static LatencyTracker provideLatencyTracker(Context context) {
         return LatencyTracker.getInstance(context);
     }
 
-    @Singleton
+    @SysUISingleton
     @Provides
     static LauncherApps provideLauncherApps(Context context) {
         return context.getSystemService(LauncherApps.class);
     }
 
     @SuppressLint("MissingPermission")
-    @Singleton
+    @SysUISingleton
     @Provides
     @Nullable
     static LocalBluetoothManager provideLocalBluetoothController(Context context,
@@ -226,31 +234,36 @@
     }
 
     @Provides
-    @Singleton
+    static MediaSessionManager provideMediaSessionManager(Context context) {
+        return context.getSystemService(MediaSessionManager.class);
+    }
+
+    @Provides
+    @SysUISingleton
     static NetworkScoreManager provideNetworkScoreManager(Context context) {
         return context.getSystemService(NetworkScoreManager.class);
     }
 
-    @Singleton
+    @SysUISingleton
     @Provides
     static NotificationManager provideNotificationManager(Context context) {
         return context.getSystemService(NotificationManager.class);
     }
 
-    @Singleton
+    @SysUISingleton
     @Provides
     static PackageManager providePackageManager(Context context) {
         return context.getPackageManager();
     }
 
-    @Singleton
+    @SysUISingleton
     @Provides
     static PackageManagerWrapper providePackageManagerWrapper() {
         return PackageManagerWrapper.getInstance();
     }
 
     /** */
-    @Singleton
+    @SysUISingleton
     @Provides
     static PowerManager providePowerManager(Context context) {
         return context.getSystemService(PowerManager.class);
@@ -263,51 +276,51 @@
     }
 
     @Provides
-    @Singleton
+    @SysUISingleton
     static SensorManager providesSensorManager(Context context) {
         return context.getSystemService(SensorManager.class);
     }
 
-    @Singleton
+    @SysUISingleton
     @Provides
     static SensorPrivacyManager provideSensorPrivacyManager(Context context) {
         return context.getSystemService(SensorPrivacyManager.class);
     }
 
-    @Singleton
+    @SysUISingleton
     @Provides
     static ShortcutManager provideShortcutManager(Context context) {
         return context.getSystemService(ShortcutManager.class);
     }
 
     @Provides
-    @Singleton
+    @SysUISingleton
     @Nullable
     static TelecomManager provideTelecomManager(Context context) {
         return context.getSystemService(TelecomManager.class);
     }
 
     @Provides
-    @Singleton
+    @SysUISingleton
     static TelephonyManager provideTelephonyManager(Context context) {
         return context.getSystemService(TelephonyManager.class);
     }
 
     @Provides
-    @Singleton
+    @SysUISingleton
     static TrustManager provideTrustManager(Context context) {
         return context.getSystemService(TrustManager.class);
     }
 
     @Provides
-    @Singleton
+    @SysUISingleton
     @Nullable
     static Vibrator provideVibrator(Context context) {
         return context.getSystemService(Vibrator.class);
     }
 
     @Provides
-    @Singleton
+    @SysUISingleton
     static UserManager provideUserManager(Context context) {
         return context.getSystemService(UserManager.class);
     }
@@ -318,20 +331,20 @@
     }
 
     @Provides
-    @Singleton
+    @SysUISingleton
     @Nullable
     static WifiManager provideWifiManager(Context context) {
         return context.getSystemService(WifiManager.class);
     }
 
-    @Singleton
+    @SysUISingleton
     @Provides
     static WindowManager provideWindowManager(Context context) {
         return context.getSystemService(WindowManager.class);
     }
 
     @Provides
-    @Singleton
+    @SysUISingleton
     static RoleManager provideRoleManager(Context context) {
         return context.getSystemService(RoleManager.class);
     }
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIBinder.java b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIBinder.java
index 0aebb7e..9dfd9f8 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIBinder.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIBinder.java
@@ -34,7 +34,6 @@
 import com.android.systemui.recents.Recents;
 import com.android.systemui.recents.RecentsModule;
 import com.android.systemui.shortcut.ShortcutKeyDispatcher;
-import com.android.systemui.stackdivider.Divider;
 import com.android.systemui.statusbar.dagger.StatusBarModule;
 import com.android.systemui.statusbar.notification.InstantAppNotifier;
 import com.android.systemui.statusbar.phone.StatusBar;
@@ -43,6 +42,7 @@
 import com.android.systemui.toast.ToastUI;
 import com.android.systemui.util.leak.GarbageMonitor;
 import com.android.systemui.volume.VolumeUI;
+import com.android.systemui.wmshell.WMShell;
 
 import dagger.Binds;
 import dagger.Module;
@@ -61,12 +61,6 @@
     @ClassKey(AuthController.class)
     public abstract SystemUI bindAuthController(AuthController service);
 
-    /** Inject into Divider. */
-    @Binds
-    @IntoMap
-    @ClassKey(Divider.class)
-    public abstract SystemUI bindDivider(Divider sysui);
-
     /** Inject into GarbageMonitor.Service. */
     @Binds
     @IntoMap
@@ -187,4 +181,10 @@
     @IntoMap
     @ClassKey(WindowMagnification.class)
     public abstract SystemUI bindWindowMagnification(WindowMagnification sysui);
+
+    /** Inject into WMShell. */
+    @Binds
+    @IntoMap
+    @ClassKey(WMShell.class)
+    public abstract SystemUI bindWMShell(WMShell sysui);
 }
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIDefaultModule.java b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIDefaultModule.java
index 803e56d..a021114 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIDefaultModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIDefaultModule.java
@@ -29,6 +29,7 @@
 import com.android.systemui.broadcast.BroadcastDispatcher;
 import com.android.systemui.dagger.qualifiers.Background;
 import com.android.systemui.dagger.qualifiers.Main;
+import com.android.systemui.demomode.DemoModeController;
 import com.android.systemui.dock.DockManager;
 import com.android.systemui.dock.DockManagerImpl;
 import com.android.systemui.doze.DozeHost;
@@ -40,16 +41,17 @@
 import com.android.systemui.qs.tileimpl.QSFactoryImpl;
 import com.android.systemui.recents.Recents;
 import com.android.systemui.recents.RecentsImplementation;
-import com.android.systemui.stackdivider.DividerModule;
 import com.android.systemui.statusbar.CommandQueue;
 import com.android.systemui.statusbar.NotificationLockscreenUserManager;
 import com.android.systemui.statusbar.NotificationLockscreenUserManagerImpl;
+import com.android.systemui.statusbar.NotificationShadeWindowController;
 import com.android.systemui.statusbar.notification.NotificationEntryManager;
 import com.android.systemui.statusbar.phone.DozeServiceHost;
 import com.android.systemui.statusbar.phone.HeadsUpManagerPhone;
 import com.android.systemui.statusbar.phone.KeyguardBypassController;
 import com.android.systemui.statusbar.phone.KeyguardEnvironmentImpl;
 import com.android.systemui.statusbar.phone.NotificationGroupManager;
+import com.android.systemui.statusbar.phone.NotificationShadeWindowControllerImpl;
 import com.android.systemui.statusbar.phone.ShadeController;
 import com.android.systemui.statusbar.phone.ShadeControllerImpl;
 import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
@@ -59,10 +61,9 @@
 import com.android.systemui.statusbar.policy.DeviceProvisionedController;
 import com.android.systemui.statusbar.policy.DeviceProvisionedControllerImpl;
 import com.android.systemui.statusbar.policy.HeadsUpManager;
-import com.android.systemui.wmshell.WindowManagerShellModule;
+import com.android.systemui.wmshell.WMShellModule;
 
 import javax.inject.Named;
-import javax.inject.Singleton;
 
 import dagger.Binds;
 import dagger.Module;
@@ -72,10 +73,13 @@
  * A dagger module for injecting default implementations of components of System UI that may be
  * overridden by the System UI implementation.
  */
-@Module(includes = {DividerModule.class, QSModule.class, WindowManagerShellModule.class})
+@Module(includes = {
+            QSModule.class,
+            WMShellModule.class
+        })
 public abstract class SystemUIDefaultModule {
 
-    @Singleton
+    @SysUISingleton
     @Provides
     @Named(LEAK_REPORT_EMAIL_NAME)
     @Nullable
@@ -91,19 +95,29 @@
             NotificationLockscreenUserManagerImpl notificationLockscreenUserManager);
 
     @Provides
-    @Singleton
-    static BatteryController provideBatteryController(Context context,
-            EnhancedEstimates enhancedEstimates, PowerManager powerManager,
-            BroadcastDispatcher broadcastDispatcher, @Main Handler mainHandler,
+    @SysUISingleton
+    static BatteryController provideBatteryController(
+            Context context,
+            EnhancedEstimates enhancedEstimates,
+            PowerManager powerManager,
+            BroadcastDispatcher broadcastDispatcher,
+            DemoModeController demoModeController,
+            @Main Handler mainHandler,
             @Background Handler bgHandler) {
-        BatteryController bC = new BatteryControllerImpl(context, enhancedEstimates, powerManager,
-                broadcastDispatcher, mainHandler, bgHandler);
+        BatteryController bC = new BatteryControllerImpl(
+                context,
+                enhancedEstimates,
+                powerManager,
+                broadcastDispatcher,
+                demoModeController,
+                mainHandler,
+                bgHandler);
         bC.init();
         return bC;
     }
 
     @Binds
-    @Singleton
+    @SysUISingleton
     public abstract QSFactory bindQSFactory(QSFactoryImpl qsFactoryImpl);
 
     @Binds
@@ -116,14 +130,14 @@
     @Binds
     abstract ShadeController provideShadeController(ShadeControllerImpl shadeController);
 
-    @Singleton
+    @SysUISingleton
     @Provides
     @Named(ALLOW_NOTIFICATION_LONG_PRESS_NAME)
     static boolean provideAllowNotificationLongPress() {
         return true;
     }
 
-    @Singleton
+    @SysUISingleton
     @Provides
     static HeadsUpManagerPhone provideHeadsUpManagerPhone(
             Context context,
@@ -139,7 +153,7 @@
     abstract HeadsUpManager bindHeadsUpManagerPhone(HeadsUpManagerPhone headsUpManagerPhone);
 
     @Provides
-    @Singleton
+    @SysUISingleton
     static Recents provideRecents(Context context, RecentsImplementation recentsImplementation,
             CommandQueue commandQueue) {
         return new Recents(context, recentsImplementation, commandQueue);
@@ -154,5 +168,9 @@
             StatusBarKeyguardViewManager statusBarKeyguardViewManager);
 
     @Binds
+    abstract NotificationShadeWindowController bindNotificationShadeController(
+            NotificationShadeWindowControllerImpl notificationShadeWindowController);
+
+    @Binds
     abstract DozeHost provideDozeHost(DozeServiceHost dozeServiceHost);
 }
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
index fce545b..e985e3d 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
@@ -16,41 +16,33 @@
 
 package com.android.systemui.dagger;
 
-import android.annotation.Nullable;
-import android.content.Context;
-import android.content.pm.PackageManager;
-
-import com.android.keyguard.KeyguardUpdateMonitor;
 import com.android.systemui.BootCompleteCache;
 import com.android.systemui.BootCompleteCacheImpl;
 import com.android.systemui.assist.AssistModule;
+import com.android.systemui.demomode.dagger.DemoModeModule;
 import com.android.systemui.doze.dagger.DozeComponent;
-import com.android.systemui.dump.DumpManager;
+import com.android.systemui.fragments.FragmentService;
 import com.android.systemui.log.dagger.LogModule;
 import com.android.systemui.model.SysUiState;
-import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.recents.Recents;
+import com.android.systemui.screenshot.dagger.ScreenshotModule;
 import com.android.systemui.settings.dagger.SettingsModule;
-import com.android.systemui.stackdivider.Divider;
 import com.android.systemui.statusbar.CommandQueue;
 import com.android.systemui.statusbar.notification.collection.inflation.NotificationRowBinder;
 import com.android.systemui.statusbar.notification.collection.inflation.NotificationRowBinderImpl;
 import com.android.systemui.statusbar.notification.people.PeopleHubModule;
 import com.android.systemui.statusbar.notification.row.dagger.ExpandableNotificationRowComponent;
 import com.android.systemui.statusbar.notification.row.dagger.NotificationRowComponent;
-import com.android.systemui.statusbar.phone.KeyguardLiftController;
+import com.android.systemui.statusbar.notification.row.dagger.NotificationShelfComponent;
 import com.android.systemui.statusbar.phone.StatusBar;
 import com.android.systemui.statusbar.phone.dagger.StatusBarComponent;
 import com.android.systemui.statusbar.policy.HeadsUpManager;
 import com.android.systemui.util.concurrency.ConcurrencyModule;
-import com.android.systemui.util.sensors.AsyncSensorManager;
 import com.android.systemui.util.sensors.SensorModule;
 import com.android.systemui.util.settings.SettingsUtilModule;
 import com.android.systemui.util.time.SystemClock;
 import com.android.systemui.util.time.SystemClockImpl;
 
-import javax.inject.Singleton;
-
 import dagger.Binds;
 import dagger.BindsOptionalOf;
 import dagger.Module;
@@ -63,8 +55,10 @@
 @Module(includes = {
             AssistModule.class,
             ConcurrencyModule.class,
+            DemoModeModule.class,
             LogModule.class,
             PeopleHubModule.class,
+            ScreenshotModule.class,
             SensorModule.class,
             SettingsModule.class,
             SettingsUtilModule.class
@@ -72,7 +66,9 @@
         subcomponents = {StatusBarComponent.class,
                 NotificationRowComponent.class,
                 DozeComponent.class,
-                ExpandableNotificationRowComponent.class})
+                ExpandableNotificationRowComponent.class,
+                NotificationShelfComponent.class,
+                FragmentService.FragmentCreator.class})
 public abstract class SystemUIModule {
 
     @Binds
@@ -83,28 +79,12 @@
     public abstract ContextComponentHelper bindComponentHelper(
             ContextComponentResolver componentHelper);
 
-    @Singleton
-    @Provides
-    @Nullable
-    static KeyguardLiftController provideKeyguardLiftController(
-            Context context,
-            StatusBarStateController statusBarStateController,
-            AsyncSensorManager asyncSensorManager,
-            KeyguardUpdateMonitor keyguardUpdateMonitor,
-            DumpManager dumpManager) {
-        if (!context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_FACE)) {
-            return null;
-        }
-        return new KeyguardLiftController(statusBarStateController, asyncSensorManager,
-                keyguardUpdateMonitor, dumpManager);
-    }
-
     /** */
     @Binds
     public abstract NotificationRowBinder bindNotificationRowBinder(
             NotificationRowBinderImpl notificationRowBinder);
 
-    @Singleton
+    @SysUISingleton
     @Provides
     static SysUiState provideSysUiState() {
         return new SysUiState();
@@ -114,9 +94,6 @@
     abstract CommandQueue optionalCommandQueue();
 
     @BindsOptionalOf
-    abstract Divider optionalDivider();
-
-    @BindsOptionalOf
     abstract HeadsUpManager optionalHeadsUpManager();
 
     @BindsOptionalOf
@@ -125,7 +102,7 @@
     @BindsOptionalOf
     abstract StatusBar optionalStatusBar();
 
-    @Singleton
+    @SysUISingleton
     @Binds
     abstract SystemClock bindSystemClock(SystemClockImpl systemClock);
 }
diff --git a/services/core/java/com/android/server/protolog/common/BitmaskConversionException.java b/packages/SystemUI/src/com/android/systemui/dagger/WMComponent.java
similarity index 66%
copy from services/core/java/com/android/server/protolog/common/BitmaskConversionException.java
copy to packages/SystemUI/src/com/android/systemui/dagger/WMComponent.java
index 7bb27b2..929b61a 100644
--- a/services/core/java/com/android/server/protolog/common/BitmaskConversionException.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/WMComponent.java
@@ -14,13 +14,22 @@
  * limitations under the License.
  */
 
-package com.android.server.protolog.common;
+package com.android.systemui.dagger;
+
+import dagger.Subcomponent;
 
 /**
- * Error while converting a bitmask representing a list of LogDataTypes.
+ * Dagger Subcomponent for WindowManager.
  */
-public class BitmaskConversionException extends RuntimeException {
-    BitmaskConversionException(String msg) {
-        super(msg);
+@WMSingleton
+@Subcomponent(modules = {})
+public interface WMComponent {
+
+    /**
+     * Builder for a WMComponent.
+     */
+    @Subcomponent.Builder
+    interface Builder {
+        WMComponent build();
     }
 }
diff --git a/tests/AutoVerify/app1/src/com/android/test/autoverify/MainActivity.java b/packages/SystemUI/src/com/android/systemui/dagger/WMModule.java
similarity index 75%
copy from tests/AutoVerify/app1/src/com/android/test/autoverify/MainActivity.java
copy to packages/SystemUI/src/com/android/systemui/dagger/WMModule.java
index 09ef472..2894780 100644
--- a/tests/AutoVerify/app1/src/com/android/test/autoverify/MainActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/WMModule.java
@@ -13,3 +13,14 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
+
+package com.android.systemui.dagger;
+
+import dagger.Module;
+
+/**
+ * Dagger module for including the WMComponent.
+ */
+@Module(subcomponents = {WMComponent.class})
+public abstract class WMModule {
+}
diff --git a/media/java/android/media/IStrategyPreferredDeviceDispatcher.aidl b/packages/SystemUI/src/com/android/systemui/dagger/WMSingleton.java
similarity index 64%
copy from media/java/android/media/IStrategyPreferredDeviceDispatcher.aidl
copy to packages/SystemUI/src/com/android/systemui/dagger/WMSingleton.java
index b1f99e6..7292b9e 100644
--- a/media/java/android/media/IStrategyPreferredDeviceDispatcher.aidl
+++ b/packages/SystemUI/src/com/android/systemui/dagger/WMSingleton.java
@@ -14,17 +14,20 @@
  * limitations under the License.
  */
 
-package android.media;
+package com.android.systemui.dagger;
 
-import android.media.AudioDeviceAttributes;
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.Retention;
+
+import javax.inject.Scope;
 
 /**
- * AIDL for AudioService to signal audio strategy-preferred device updates.
- *
- * {@hide}
+ * Scope annotation for singleton items within the WMComponent.
  */
-oneway interface IStrategyPreferredDeviceDispatcher {
-
-    void dispatchPrefDeviceChanged(int strategyId, in AudioDeviceAttributes device);
-
+@Documented
+@Retention(RUNTIME)
+@Scope
+public @interface WMSingleton {
 }
diff --git a/packages/SystemUI/src/com/android/systemui/demomode/DemoMode.java b/packages/SystemUI/src/com/android/systemui/demomode/DemoMode.java
new file mode 100644
index 0000000..672ade2
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/demomode/DemoMode.java
@@ -0,0 +1,70 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.demomode;
+
+import com.google.android.collect.Lists;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Interface defining what it means to implement DemoMode. A DemoMode implementation should
+ * register with DemoModeController, providing a list of commands for wish to listen.
+ *
+ * If you only need to listen to commands, but *do not* care about demo mode state changes, you
+ * can implement DemoModeCommandReceiver instead
+ */
+public interface DemoMode extends DemoModeCommandReceiver {
+
+    List<String> NO_COMMANDS = new ArrayList<>();
+
+    /** Provide a set of commands to listen to, only acceptable values are the COMMAND_* keys */
+    default List<String> demoCommands() {
+        return NO_COMMANDS;
+    }
+
+    String ACTION_DEMO = "com.android.systemui.demo";
+
+    String EXTRA_COMMAND = "command";
+
+    /** Enter and exit are non-register-able; override started/finished to observe these states */
+    String COMMAND_ENTER = "enter";
+    String COMMAND_EXIT = "exit";
+
+    /** Observable commands to register a listener for  */
+    String COMMAND_CLOCK = "clock";
+    String COMMAND_BATTERY = "battery";
+    String COMMAND_NETWORK = "network";
+    String COMMAND_BARS = "bars";
+    String COMMAND_STATUS = "status";
+    String COMMAND_NOTIFICATIONS = "notifications";
+    String COMMAND_VOLUME = "volume";
+    String COMMAND_OPERATOR = "operator";
+
+    /** New keys need to be added here */
+    List<String> COMMANDS = Lists.newArrayList(
+            COMMAND_BARS,
+            COMMAND_BATTERY,
+            COMMAND_CLOCK,
+            COMMAND_NETWORK,
+            COMMAND_NOTIFICATIONS,
+            COMMAND_OPERATOR,
+            COMMAND_STATUS,
+            COMMAND_VOLUME
+    );
+}
+
diff --git a/packages/SystemUI/src/com/android/systemui/demomode/DemoModeAvailabilityTracker.kt b/packages/SystemUI/src/com/android/systemui/demomode/DemoModeAvailabilityTracker.kt
new file mode 100644
index 0000000..16f4150
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/demomode/DemoModeAvailabilityTracker.kt
@@ -0,0 +1,109 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.demomode
+
+import android.content.Context
+import android.database.ContentObserver
+import android.os.Handler
+import android.os.Looper
+import android.provider.Settings
+
+/**
+ * Class to track the availability of [DemoMode]. Use this class to track the availability and
+ * on/off state for DemoMode
+ *
+ * This class works by wrapping a content observer for the relevant keys related to DemoMode
+ * availability and current on/off state, and triggering callbacks.
+ */
+abstract class DemoModeAvailabilityTracker(val context: Context) {
+    var isInDemoMode = false
+    var isDemoModeAvailable = false
+
+    init {
+        isInDemoMode = checkIsDemoModeOn()
+        isDemoModeAvailable = checkIsDemoModeAllowed()
+    }
+
+    fun startTracking() {
+        val resolver = context.contentResolver
+        resolver.registerContentObserver(
+                Settings.Global.getUriFor(DEMO_MODE_ALLOWED), false, allowedObserver)
+        resolver.registerContentObserver(
+                Settings.Global.getUriFor(DEMO_MODE_ON), false, onObserver)
+    }
+
+    fun stopTracking() {
+        val resolver = context.contentResolver
+        resolver.unregisterContentObserver(allowedObserver)
+        resolver.unregisterContentObserver(onObserver)
+    }
+
+    abstract fun onDemoModeAvailabilityChanged()
+    abstract fun onDemoModeStarted()
+    abstract fun onDemoModeFinished()
+
+    private fun checkIsDemoModeAllowed(): Boolean {
+        return Settings.Global
+                .getInt(context.contentResolver, DEMO_MODE_ALLOWED, 0) != 0
+    }
+
+    private fun checkIsDemoModeOn(): Boolean {
+        return Settings.Global.getInt(context.contentResolver, DEMO_MODE_ON, 0) != 0
+    }
+
+    private val allowedObserver = object : ContentObserver(Handler(Looper.getMainLooper())) {
+        override fun onChange(selfChange: Boolean) {
+            val allowed = checkIsDemoModeAllowed()
+            if (DEBUG) {
+                android.util.Log.d(TAG, "onChange: DEMO_MODE_ALLOWED changed: $allowed")
+            }
+
+            if (isDemoModeAvailable == allowed) {
+                return
+            }
+
+            isDemoModeAvailable = allowed
+            onDemoModeAvailabilityChanged()
+        }
+    }
+
+    private val onObserver = object : ContentObserver(Handler(Looper.getMainLooper())) {
+        override fun onChange(selfChange: Boolean) {
+            val on = checkIsDemoModeOn()
+
+            if (DEBUG) {
+                android.util.Log.d(TAG, "onChange: DEMO_MODE_ON changed: $on")
+            }
+
+            if (isInDemoMode == on) {
+                return
+            }
+
+            isInDemoMode = on
+            if (on) {
+                onDemoModeStarted()
+            } else {
+                onDemoModeFinished()
+            }
+        }
+    }
+}
+
+private const val TAG = "DemoModeAvailabilityTracker"
+private const val DEMO_MODE_ALLOWED = "sysui_demo_allowed"
+private const val DEMO_MODE_ON = "sysui_tuner_demo_on"
+private const val DEBUG = false
diff --git a/packages/SystemUI/src/com/android/systemui/demomode/DemoModeCommandReceiver.java b/packages/SystemUI/src/com/android/systemui/demomode/DemoModeCommandReceiver.java
new file mode 100644
index 0000000..609536a
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/demomode/DemoModeCommandReceiver.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.demomode;
+
+import android.os.Bundle;
+
+/** Defines the basic DemoMode command interface  */
+public interface DemoModeCommandReceiver {
+    /** Override point to observe demo mode commands  */
+    void dispatchDemoCommand(String command, Bundle args);
+
+    /**
+     * Demo mode starts due to receiving [COMMAND_ENTER] or receiving any other demo mode command
+     * while [DEMO_MODE_ALLOWED] is true but demo mode is off
+     */
+    default void onDemoModeStarted() {}
+
+    /** Demo mode exited  */
+    default void onDemoModeFinished() {}
+
+}
diff --git a/packages/SystemUI/src/com/android/systemui/demomode/DemoModeController.kt b/packages/SystemUI/src/com/android/systemui/demomode/DemoModeController.kt
new file mode 100644
index 0000000..c76f556
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/demomode/DemoModeController.kt
@@ -0,0 +1,249 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.demomode
+
+import android.content.BroadcastReceiver
+import android.content.Context
+import android.content.Intent
+import android.content.IntentFilter
+import android.os.Bundle
+import android.os.UserHandle
+import android.util.Log
+import com.android.systemui.Dumpable
+import com.android.systemui.demomode.DemoMode.ACTION_DEMO
+import com.android.systemui.dump.DumpManager
+import com.android.systemui.statusbar.policy.CallbackController
+import com.android.systemui.util.Assert
+import com.android.systemui.util.settings.GlobalSettings
+import java.io.FileDescriptor
+import java.io.PrintWriter
+
+/**
+ * Handles system broadcasts for [DemoMode]
+ *
+ * Injected via [DemoModeModule]
+ */
+class DemoModeController constructor(
+    private val context: Context,
+    private val dumpManager: DumpManager,
+    private val globalSettings: GlobalSettings
+) : CallbackController<DemoMode>, Dumpable {
+
+    // Var updated when the availability tracker changes, or when we enter/exit demo mode in-process
+    var isInDemoMode = false
+
+    var isAvailable = false
+        get() = tracker.isDemoModeAvailable
+
+    private var initialized = false
+
+    private val receivers = mutableListOf<DemoMode>()
+    private val receiverMap: Map<String, MutableList<DemoMode>>
+
+    init {
+        val m = mutableMapOf<String, MutableList<DemoMode>>()
+        DemoMode.COMMANDS.map { command ->
+            m.put(command, mutableListOf())
+        }
+        receiverMap = m
+    }
+
+    fun initialize() {
+        if (initialized) {
+            throw IllegalStateException("Already initialized")
+        }
+
+        initialized = true
+
+        dumpManager.registerDumpable(TAG, this)
+
+        // Due to DemoModeFragment running in systemui:tuner process, we have to observe for
+        // content changes to know if the setting turned on or off
+        tracker.startTracking()
+
+        // TODO: We should probably exit demo mode if we booted up with it on
+        isInDemoMode = tracker.isInDemoMode
+
+        val demoFilter = IntentFilter()
+        demoFilter.addAction(ACTION_DEMO)
+        context.registerReceiverAsUser(broadcastReceiver, UserHandle.ALL, demoFilter,
+                android.Manifest.permission.DUMP, null)
+    }
+
+    override fun addCallback(listener: DemoMode) {
+        Assert.isMainThread()
+
+        // Register this listener for its commands
+        val commands = listener.demoCommands()
+
+        commands.forEach { command ->
+            if (!receiverMap.containsKey(command)) {
+                throw IllegalStateException("Command ($command) not recognized. " +
+                        "See DemoMode.java for valid commands")
+            }
+
+            receiverMap[command]!!.add(listener)
+        }
+
+        receivers.add(listener)
+        if (isInDemoMode) {
+            listener.onDemoModeStarted()
+        }
+    }
+
+    override fun removeCallback(listener: DemoMode) {
+        Assert.isMainThread()
+
+        listener.demoCommands().forEach { command ->
+            receiverMap[command]!!.remove(listener)
+        }
+
+        receivers.remove(listener)
+    }
+
+    private fun setIsDemoModeAllowed(enabled: Boolean) {
+        // Turn off demo mode if it was on
+        if (isInDemoMode && !enabled) {
+            requestFinishDemoMode()
+        }
+    }
+
+    private fun enterDemoMode() {
+        isInDemoMode = true
+        Assert.isMainThread()
+        receivers.forEach { r ->
+            r.onDemoModeStarted()
+        }
+    }
+
+    private fun exitDemoMode() {
+        isInDemoMode = false
+        Assert.isMainThread()
+        receivers.forEach { r ->
+            r.onDemoModeFinished()
+        }
+    }
+
+    fun dispatchDemoCommand(command: String, args: Bundle) {
+        Assert.isMainThread()
+
+        if (DEBUG) {
+            Log.d(TAG, "dispatchDemoCommand: $command, args=$args")
+        }
+
+        if (!isAvailable) {
+            return
+        }
+
+        if (command == DemoMode.COMMAND_ENTER) {
+            enterDemoMode()
+        } else if (command == DemoMode.COMMAND_EXIT) {
+            exitDemoMode()
+        } else if (!isInDemoMode) {
+            enterDemoMode()
+        }
+
+        // See? demo mode is easy now, you just notify the listeners when their command is called
+        receiverMap[command]!!.forEach { receiver ->
+            receiver.dispatchDemoCommand(command, args)
+        }
+    }
+
+    override fun dump(fd: FileDescriptor, pw: PrintWriter, args: Array<out String>) {
+        pw.println("DemoModeController state -")
+        pw.println("  isInDemoMode=$isInDemoMode")
+        pw.println("  isDemoModeAllowed=$isAvailable")
+        pw.print("  receivers=[")
+        receivers.forEach { recv ->
+            pw.print(" ${recv.javaClass.simpleName}")
+        }
+        pw.println(" ]")
+        pw.println("  receiverMap= [")
+        receiverMap.keys.forEach { command ->
+            pw.print("    $command : [")
+            val recvs = receiverMap[command]!!.map { receiver ->
+                receiver.javaClass.simpleName
+            }.joinToString(",")
+            pw.println("$recvs ]")
+        }
+    }
+
+    private val tracker = object : DemoModeAvailabilityTracker(context) {
+        override fun onDemoModeAvailabilityChanged() {
+            setIsDemoModeAllowed(isDemoModeAvailable)
+        }
+
+        override fun onDemoModeStarted() {
+            if (this@DemoModeController.isInDemoMode != isInDemoMode) {
+                enterDemoMode()
+            }
+        }
+
+        override fun onDemoModeFinished() {
+            if (this@DemoModeController.isInDemoMode != isInDemoMode) {
+                exitDemoMode()
+            }
+        }
+    }
+
+    private val broadcastReceiver = object : BroadcastReceiver() {
+        override fun onReceive(context: Context, intent: Intent) {
+            if (DEBUG) {
+                Log.v(TAG, "onReceive: $intent")
+            }
+
+            val action = intent.action
+            if (!ACTION_DEMO.equals(action)) {
+                return
+            }
+
+            val bundle = intent.extras ?: return
+            val command = bundle.getString("command", "").trim().toLowerCase()
+            if (command.length == 0) {
+                return
+            }
+
+            try {
+                dispatchDemoCommand(command, bundle)
+            } catch (t: Throwable) {
+                Log.w(TAG, "Error running demo command, intent=$intent $t")
+            }
+        }
+    }
+
+    fun requestSetDemoModeAllowed(allowed: Boolean) {
+        setGlobal(DEMO_MODE_ALLOWED, if (allowed) 1 else 0)
+    }
+
+    fun requestStartDemoMode() {
+        setGlobal(DEMO_MODE_ON, 1)
+    }
+
+    fun requestFinishDemoMode() {
+        setGlobal(DEMO_MODE_ON, 0)
+    }
+
+    private fun setGlobal(key: String, value: Int) {
+        globalSettings.putInt(key, value)
+    }
+}
+
+private const val TAG = "DemoModeController"
+private const val DEMO_MODE_ALLOWED = "sysui_demo_allowed"
+private const val DEMO_MODE_ON = "sysui_tuner_demo_on"
+
+private const val DEBUG = false
diff --git a/packages/SystemUI/src/com/android/systemui/demomode/dagger/DemoModeModule.java b/packages/SystemUI/src/com/android/systemui/demomode/dagger/DemoModeModule.java
new file mode 100644
index 0000000..de9affb
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/demomode/dagger/DemoModeModule.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.demomode.dagger;
+
+import android.content.Context;
+
+import com.android.systemui.dagger.SysUISingleton;
+import com.android.systemui.demomode.DemoModeController;
+import com.android.systemui.dump.DumpManager;
+import com.android.systemui.util.settings.GlobalSettings;
+
+import dagger.Module;
+import dagger.Provides;
+
+/**
+ * Dagger module providing {@link DemoModeController}
+ */
+@Module
+public abstract class DemoModeModule {
+    /** Provides DemoModeController */
+    @Provides
+    @SysUISingleton
+    static DemoModeController provideDemoModeController(
+            Context context,
+            DumpManager dumpManager,
+            GlobalSettings globalSettings) {
+        DemoModeController dmc = new DemoModeController(context, dumpManager, globalSettings);
+        dmc.initialize();
+        return /*run*/dmc;
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/dock/DockManagerImpl.java b/packages/SystemUI/src/com/android/systemui/dock/DockManagerImpl.java
index efc3905..8f39975 100644
--- a/packages/SystemUI/src/com/android/systemui/dock/DockManagerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/dock/DockManagerImpl.java
@@ -16,10 +16,11 @@
 
 package com.android.systemui.dock;
 
-import javax.inject.Inject;
-import javax.inject.Singleton;
+import com.android.systemui.dagger.SysUISingleton;
 
-@Singleton
+import javax.inject.Inject;
+
+@SysUISingleton
 public class DockManagerImpl implements DockManager {
 
     @Inject
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeLog.java b/packages/SystemUI/src/com/android/systemui/doze/DozeLog.java
index a311a45..99d2651 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeLog.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeLog.java
@@ -24,6 +24,7 @@
 import com.android.keyguard.KeyguardUpdateMonitor;
 import com.android.keyguard.KeyguardUpdateMonitorCallback;
 import com.android.systemui.Dumpable;
+import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dump.DumpManager;
 
 import java.io.FileDescriptor;
@@ -32,14 +33,13 @@
 import java.lang.annotation.RetentionPolicy;
 
 import javax.inject.Inject;
-import javax.inject.Singleton;
 
 /**
  * Logs doze events for debugging and triaging purposes. Logs are dumped in bugreports or on demand:
  *      adb shell dumpsys activity service com.android.systemui/.SystemUIService \
  *      dependency DumpController DozeLog,DozeStats
  */
-@Singleton
+@SysUISingleton
 public class DozeLog implements Dumpable {
     private final DozeLogger mLogger;
 
diff --git a/packages/SystemUI/src/com/android/systemui/doze/dagger/DozeComponent.java b/packages/SystemUI/src/com/android/systemui/doze/dagger/DozeComponent.java
index e925927..05050f9 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/dagger/DozeComponent.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/dagger/DozeComponent.java
@@ -31,7 +31,7 @@
     /** Simple Builder for {@link DozeComponent}. */
     @Subcomponent.Factory
     interface Builder {
-        DozeComponent build(@BindsInstance  DozeService dozeService);
+        DozeComponent build(@BindsInstance DozeMachine.Service dozeMachineService);
     }
 
     /** Supply a {@link DozeMachine}. */
diff --git a/packages/SystemUI/src/com/android/systemui/doze/dagger/DozeModule.java b/packages/SystemUI/src/com/android/systemui/doze/dagger/DozeModule.java
index a12e280..04f7c36 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/dagger/DozeModule.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/dagger/DozeModule.java
@@ -33,7 +33,6 @@
 import com.android.systemui.doze.DozeScreenState;
 import com.android.systemui.doze.DozeScreenStatePreventingAdapter;
 import com.android.systemui.doze.DozeSensors;
-import com.android.systemui.doze.DozeService;
 import com.android.systemui.doze.DozeSuspendScreenStatePreventingAdapter;
 import com.android.systemui.doze.DozeTriggers;
 import com.android.systemui.doze.DozeUi;
@@ -52,9 +51,9 @@
     @Provides
     @DozeScope
     @WrappedService
-    static DozeMachine.Service providesWrappedService(DozeService dozeService, DozeHost dozeHost,
-            DozeParameters dozeParameters) {
-        DozeMachine.Service wrappedService = dozeService;
+    static DozeMachine.Service providesWrappedService(DozeMachine.Service dozeMachineService,
+            DozeHost dozeHost, DozeParameters dozeParameters) {
+        DozeMachine.Service wrappedService = dozeMachineService;
         wrappedService = new DozeBrightnessHostForwarder(wrappedService, dozeHost);
         wrappedService = DozeScreenStatePreventingAdapter.wrapIfNeeded(
                 wrappedService, dozeParameters);
diff --git a/packages/SystemUI/src/com/android/systemui/dump/DumpManager.kt b/packages/SystemUI/src/com/android/systemui/dump/DumpManager.kt
index bbb7750..bfa4780 100644
--- a/packages/SystemUI/src/com/android/systemui/dump/DumpManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/dump/DumpManager.kt
@@ -18,11 +18,11 @@
 
 import android.util.ArrayMap
 import com.android.systemui.Dumpable
+import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.log.LogBuffer
 import java.io.FileDescriptor
 import java.io.PrintWriter
 import javax.inject.Inject
-import javax.inject.Singleton
 
 /**
  * Maintains a registry of things that should be dumped when a bug report is taken
@@ -33,7 +33,7 @@
  *
  * See [DumpHandler] for more information on how and when this information is dumped.
  */
-@Singleton
+@SysUISingleton
 class DumpManager @Inject constructor() {
     private val dumpables: MutableMap<String, RegisteredDumpable<Dumpable>> = ArrayMap()
     private val buffers: MutableMap<String, RegisteredDumpable<LogBuffer>> = ArrayMap()
diff --git a/packages/SystemUI/src/com/android/systemui/dump/LogBufferEulogizer.kt b/packages/SystemUI/src/com/android/systemui/dump/LogBufferEulogizer.kt
index 603cb67..0eab1af 100644
--- a/packages/SystemUI/src/com/android/systemui/dump/LogBufferEulogizer.kt
+++ b/packages/SystemUI/src/com/android/systemui/dump/LogBufferEulogizer.kt
@@ -18,6 +18,7 @@
 
 import android.content.Context
 import android.util.Log
+import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.log.LogBuffer
 import com.android.systemui.util.io.Files
 import com.android.systemui.util.time.SystemClock
@@ -33,7 +34,6 @@
 import java.util.Locale
 import java.util.concurrent.TimeUnit
 import javax.inject.Inject
-import javax.inject.Singleton
 
 /**
  * Dumps all [LogBuffer]s to a file
@@ -41,7 +41,7 @@
  * Intended for emergencies, i.e. we're about to crash. This file can then be read at a later date
  * (usually in a bug report).
  */
-@Singleton
+@SysUISingleton
 class LogBufferEulogizer(
     private val dumpManager: DumpManager,
     private val systemClock: SystemClock,
diff --git a/packages/SystemUI/src/com/android/systemui/fragments/FragmentService.java b/packages/SystemUI/src/com/android/systemui/fragments/FragmentService.java
index ae20076..0673879 100644
--- a/packages/SystemUI/src/com/android/systemui/fragments/FragmentService.java
+++ b/packages/SystemUI/src/com/android/systemui/fragments/FragmentService.java
@@ -21,9 +21,8 @@
 import android.view.View;
 
 import com.android.systemui.Dumpable;
-import com.android.systemui.dagger.SystemUIRootComponent;
+import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.qs.QSFragment;
-import com.android.systemui.statusbar.phone.NavigationBarFragment;
 import com.android.systemui.statusbar.policy.ConfigurationController;
 
 import java.io.FileDescriptor;
@@ -32,7 +31,6 @@
 import java.lang.reflect.Modifier;
 
 import javax.inject.Inject;
-import javax.inject.Singleton;
 
 import dagger.Subcomponent;
 
@@ -40,7 +38,7 @@
  * Holds a map of root views to FragmentHostStates and generates them as needed.
  * Also dispatches the configuration changes to all current FragmentHostStates.
  */
-@Singleton
+@SysUISingleton
 public class FragmentService implements Dumpable {
 
     private static final String TAG = "FragmentService";
@@ -61,9 +59,9 @@
             };
 
     @Inject
-    public FragmentService(SystemUIRootComponent rootComponent,
+    public FragmentService(FragmentCreator.Factory fragmentCreatorFactory,
             ConfigurationController configurationController) {
-        mFragmentCreator = rootComponent.createFragmentCreator();
+        mFragmentCreator = fragmentCreatorFactory.build();
         initInjectionMap();
         configurationController.addCallback(mConfigurationListener);
     }
@@ -121,10 +119,11 @@
      */
     @Subcomponent
     public interface FragmentCreator {
-        /**
-         * Inject a NavigationBarFragment.
-         */
-        NavigationBarFragment createNavigationBarFragment();
+        /** Factory for creating a FragmentCreator. */
+        @Subcomponent.Factory
+        interface Factory {
+            FragmentCreator build();
+        }
         /**
          * Inject a QSFragment.
          */
diff --git a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsComponent.java b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsComponent.java
index b29c5b0..86c8565 100644
--- a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsComponent.java
+++ b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsComponent.java
@@ -20,6 +20,7 @@
 
 import com.android.internal.statusbar.IStatusBarService;
 import com.android.systemui.SystemUI;
+import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.plugins.GlobalActions;
 import com.android.systemui.plugins.GlobalActions.GlobalActionsManager;
 import com.android.systemui.statusbar.CommandQueue;
@@ -30,12 +31,11 @@
 
 import javax.inject.Inject;
 import javax.inject.Provider;
-import javax.inject.Singleton;
 
 /**
  * Manages power menu plugins and communicates power menu actions to the StatusBar.
  */
-@Singleton
+@SysUISingleton
 public class GlobalActionsComponent extends SystemUI implements Callbacks, GlobalActionsManager {
 
     private final CommandQueue mCommandQueue;
diff --git a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java
index ef51abb..d213ac1 100644
--- a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java
@@ -132,7 +132,7 @@
 import com.android.systemui.plugins.GlobalActionsPanelPlugin;
 import com.android.systemui.settings.CurrentUserContextTracker;
 import com.android.systemui.statusbar.NotificationShadeDepthController;
-import com.android.systemui.statusbar.phone.NotificationShadeWindowController;
+import com.android.systemui.statusbar.NotificationShadeWindowController;
 import com.android.systemui.statusbar.policy.ConfigurationController;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
 import com.android.systemui.util.EmergencyDialerConstants;
@@ -148,6 +148,7 @@
 import java.util.concurrent.Executor;
 
 import javax.inject.Inject;
+import javax.inject.Provider;
 
 /**
  * Helper to show the global actions dialog.  Each item is an {@link Action} that may show depending
@@ -401,7 +402,7 @@
                         if (mDialog != null) {
                             if (!mDialog.isShowingControls() && shouldShowControls()) {
                                 mDialog.showControls(mControlsUiControllerOptional.get());
-                            } else if (shouldShowLockMessage()) {
+                            } else if (shouldShowLockMessage(mDialog)) {
                                 mDialog.showLockMessage();
                             }
                         }
@@ -698,19 +699,17 @@
         mPowerAdapter = new MyPowerOptionsAdapter();
 
         mDepthController.setShowingHomeControls(true);
-        GlobalActionsPanelPlugin.PanelViewController walletViewController =
-                getWalletViewController();
         ControlsUiController uiController = null;
         if (mControlsUiControllerOptional.isPresent() && shouldShowControls()) {
             uiController = mControlsUiControllerOptional.get();
         }
         ActionsDialog dialog = new ActionsDialog(mContext, mAdapter, mOverflowAdapter,
-                walletViewController, mDepthController, mSysuiColorExtractor,
+                this::getWalletViewController, mDepthController, mSysuiColorExtractor,
                 mStatusBarService, mNotificationShadeWindowController,
                 controlsAvailable(), uiController,
                 mSysUiState, this::onRotate, mKeyguardShowing, mPowerAdapter);
 
-        if (shouldShowLockMessage()) {
+        if (shouldShowLockMessage(dialog)) {
             dialog.showLockMessage();
         }
         dialog.setCanceledOnTouchOutside(false); // Handled by the custom class.
@@ -2124,7 +2123,8 @@
         private MultiListLayout mGlobalActionsLayout;
         private Drawable mBackgroundDrawable;
         private final SysuiColorExtractor mColorExtractor;
-        private final GlobalActionsPanelPlugin.PanelViewController mWalletViewController;
+        private final Provider<GlobalActionsPanelPlugin.PanelViewController> mWalletFactory;
+        @Nullable private GlobalActionsPanelPlugin.PanelViewController mWalletViewController;
         private boolean mKeyguardShowing;
         private boolean mShowing;
         private float mScrimAlpha;
@@ -2144,7 +2144,7 @@
         private TextView mLockMessage;
 
         ActionsDialog(Context context, MyAdapter adapter, MyOverflowAdapter overflowAdapter,
-                GlobalActionsPanelPlugin.PanelViewController walletViewController,
+                Provider<GlobalActionsPanelPlugin.PanelViewController> walletFactory,
                 NotificationShadeDepthController depthController,
                 SysuiColorExtractor sysuiColorExtractor, IStatusBarService statusBarService,
                 NotificationShadeWindowController notificationShadeWindowController,
@@ -2165,6 +2165,7 @@
             mSysUiState = sysuiState;
             mOnRotateCallback = onRotateCallback;
             mKeyguardShowing = keyguardShowing;
+            mWalletFactory = walletFactory;
 
             // Window initialization
             Window window = getWindow();
@@ -2187,7 +2188,6 @@
             window.getAttributes().setFitInsetsTypes(0 /* types */);
             setTitle(R.string.global_actions);
 
-            mWalletViewController = walletViewController;
             initializeLayout();
         }
 
@@ -2200,8 +2200,13 @@
             mControlsUiController.show(mControlsView, this::dismissForControlsActivity);
         }
 
+        private boolean isWalletViewAvailable() {
+            return mWalletViewController != null && mWalletViewController.getPanelContent() != null;
+        }
+
         private void initializeWalletView() {
-            if (mWalletViewController == null || mWalletViewController.getPanelContent() == null) {
+            mWalletViewController = mWalletFactory.get();
+            if (!isWalletViewAvailable()) {
                 return;
             }
 
@@ -2507,6 +2512,8 @@
         private void dismissWallet() {
             if (mWalletViewController != null) {
                 mWalletViewController.onDismissed();
+                // The wallet controller should not be re-used after being dismissed.
+                mWalletViewController = null;
             }
         }
 
@@ -2648,18 +2655,12 @@
                 && !mControlsServiceInfos.isEmpty();
     }
 
-    private boolean walletViewAvailable() {
-        GlobalActionsPanelPlugin.PanelViewController walletViewController =
-                getWalletViewController();
-        return walletViewController != null && walletViewController.getPanelContent() != null;
-    }
-
-    private boolean shouldShowLockMessage() {
+    private boolean shouldShowLockMessage(ActionsDialog dialog) {
         boolean isLockedAfterBoot = mLockPatternUtils.getStrongAuthForUser(getCurrentUser().id)
                 == STRONG_AUTH_REQUIRED_AFTER_BOOT;
         return !mKeyguardStateController.isUnlocked()
                 && (!mShowLockScreenCardsAndControls || isLockedAfterBoot)
-                && (controlsAvailable() || walletViewAvailable());
+                && (controlsAvailable() || dialog.isWalletViewAvailable());
     }
 
     private void onPowerMenuLockScreenSettingsChanged() {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/DismissCallbackRegistry.java b/packages/SystemUI/src/com/android/systemui/keyguard/DismissCallbackRegistry.java
index f6f3b99..d1bbc33 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/DismissCallbackRegistry.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/DismissCallbackRegistry.java
@@ -17,18 +17,18 @@
 package com.android.systemui.keyguard;
 
 import com.android.internal.policy.IKeyguardDismissCallback;
+import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dagger.qualifiers.UiBackground;
 
 import java.util.ArrayList;
 import java.util.concurrent.Executor;
 
 import javax.inject.Inject;
-import javax.inject.Singleton;
 
 /**
  * Registry holding the current set of {@link IKeyguardDismissCallback}s.
  */
-@Singleton
+@SysUISingleton
 public class DismissCallbackRegistry {
 
     private final ArrayList<DismissCallbackWrapper> mDismissCallbacks = new ArrayList<>();
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/FaceAuthScreenBrightnessController.kt b/packages/SystemUI/src/com/android/systemui/keyguard/FaceAuthScreenBrightnessController.kt
new file mode 100644
index 0000000..9dcc3bb
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/FaceAuthScreenBrightnessController.kt
@@ -0,0 +1,193 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard
+
+import android.animation.Animator
+import android.animation.AnimatorListenerAdapter
+import android.animation.ValueAnimator
+import android.content.res.Resources
+import android.database.ContentObserver
+import android.graphics.Bitmap
+import android.graphics.BitmapFactory
+import android.graphics.Color
+import android.graphics.drawable.ColorDrawable
+import android.hardware.biometrics.BiometricSourceType
+import android.os.Handler
+import android.provider.Settings.System.SCREEN_BRIGHTNESS_FLOAT
+import android.util.MathUtils
+import android.view.View
+import android.view.WindowManager.LayoutParams.BRIGHTNESS_OVERRIDE_NONE
+import android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
+import com.android.internal.annotations.VisibleForTesting
+import com.android.keyguard.KeyguardUpdateMonitor
+import com.android.keyguard.KeyguardUpdateMonitorCallback
+import com.android.systemui.Dumpable
+import com.android.systemui.R
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dump.DumpManager
+import com.android.systemui.statusbar.NotificationShadeWindowController
+import com.android.systemui.util.settings.GlobalSettings
+import com.android.systemui.util.settings.SystemSettings
+import java.io.FileDescriptor
+import java.io.PrintWriter
+import java.lang.Float.max
+import java.util.concurrent.TimeUnit
+
+val DEFAULT_ANIMATION_DURATION = TimeUnit.SECONDS.toMillis(4)
+val MAX_SCREEN_BRIGHTNESS = 100 // 0..100
+val MAX_SCRIM_OPACTY = 50 // 0..100
+val DEFAULT_USE_FACE_WALLPAPER = false
+
+/**
+ * This class is responsible for ramping up the display brightness (and white overlay) in order
+ * to mitigate low light conditions when running face auth without an IR camera.
+ */
+@SysUISingleton
+open class FaceAuthScreenBrightnessController(
+    private val notificationShadeWindowController: NotificationShadeWindowController,
+    private val keyguardUpdateMonitor: KeyguardUpdateMonitor,
+    private val resources: Resources,
+    private val globalSettings: GlobalSettings,
+    private val systemSettings: SystemSettings,
+    private val mainHandler: Handler,
+    private val dumpManager: DumpManager
+) : Dumpable {
+
+    private var userDefinedBrightness: Float = 1f
+    @VisibleForTesting
+    var useFaceAuthWallpaper = globalSettings
+            .getInt("sysui.use_face_auth_wallpaper", if (DEFAULT_USE_FACE_WALLPAPER) 1 else 0) == 1
+    private val brightnessAnimationDuration = globalSettings
+            .getLong("sysui.face_brightness_anim_duration", DEFAULT_ANIMATION_DURATION)
+    private val maxScreenBrightness = globalSettings
+            .getInt("sysui.face_max_brightness", MAX_SCREEN_BRIGHTNESS) / 100f
+    private val maxScrimOpacity = globalSettings
+            .getInt("sysui.face_max_scrim_opacity", MAX_SCRIM_OPACTY) / 100f
+    private val keyguardUpdateCallback = object : KeyguardUpdateMonitorCallback() {
+        override fun onBiometricRunningStateChanged(
+            running: Boolean,
+            biometricSourceType: BiometricSourceType?
+        ) {
+            if (biometricSourceType != BiometricSourceType.FACE) {
+                return
+            }
+            // TODO enable only when receiving a low-light error
+            overridingBrightness = running
+        }
+    }
+    private lateinit var whiteOverlay: View
+    private var brightnessAnimator: ValueAnimator? = null
+    private var overridingBrightness = false
+    set(value) {
+        if (field == value) {
+            return
+        }
+        field = value
+        brightnessAnimator?.cancel()
+
+        if (!value) {
+            notificationShadeWindowController.setFaceAuthDisplayBrightness(BRIGHTNESS_OVERRIDE_NONE)
+            if (whiteOverlay.alpha > 0) {
+                brightnessAnimator = createAnimator(whiteOverlay.alpha, 0f).apply {
+                    duration = 200
+                    addUpdateListener {
+                        whiteOverlay.alpha = it.animatedValue as Float
+                    }
+                    addListener(object : AnimatorListenerAdapter() {
+                        override fun onAnimationEnd(animation: Animator?) {
+                            whiteOverlay.visibility = View.INVISIBLE
+                            brightnessAnimator = null
+                        }
+                    })
+                    start()
+                }
+            }
+            return
+        }
+
+        val targetBrightness = max(maxScreenBrightness, userDefinedBrightness)
+        whiteOverlay.visibility = View.VISIBLE
+        brightnessAnimator = createAnimator(0f, 1f).apply {
+            duration = brightnessAnimationDuration
+            addUpdateListener {
+                val progress = it.animatedValue as Float
+                val brightnessProgress = MathUtils.constrainedMap(
+                        userDefinedBrightness, targetBrightness, 0f, 0.5f, progress)
+                val scrimProgress = MathUtils.constrainedMap(
+                        0f, maxScrimOpacity, 0.5f, 1f, progress)
+                notificationShadeWindowController.setFaceAuthDisplayBrightness(brightnessProgress)
+                whiteOverlay.alpha = scrimProgress
+            }
+            addListener(object : AnimatorListenerAdapter() {
+                override fun onAnimationEnd(animation: Animator?) {
+                    brightnessAnimator = null
+                }
+            })
+            start()
+        }
+    }
+
+    @VisibleForTesting
+    open fun createAnimator(start: Float, end: Float) = ValueAnimator.ofFloat(start, end)
+
+    /**
+     * Returns a bitmap that should be used by the lock screen as a wallpaper, if face auth requires
+     * a secure wallpaper.
+     */
+    var faceAuthWallpaper: Bitmap? = null
+    get() {
+        val user = KeyguardUpdateMonitor.getCurrentUser()
+        if (useFaceAuthWallpaper && keyguardUpdateMonitor.isFaceAuthEnabledForUser(user)) {
+            val options = BitmapFactory.Options().apply {
+                inScaled = false
+            }
+            return BitmapFactory.decodeResource(resources, R.drawable.face_auth_wallpaper, options)
+        }
+        return null
+    }
+    private set
+
+    fun attach(overlayView: View) {
+        whiteOverlay = overlayView
+        whiteOverlay.focusable = FLAG_NOT_FOCUSABLE
+        whiteOverlay.background = ColorDrawable(Color.WHITE)
+        whiteOverlay.isEnabled = false
+        whiteOverlay.alpha = 0f
+        whiteOverlay.visibility = View.INVISIBLE
+
+        dumpManager.registerDumpable(this.javaClass.name, this)
+        keyguardUpdateMonitor.registerCallback(keyguardUpdateCallback)
+        systemSettings.registerContentObserver(SCREEN_BRIGHTNESS_FLOAT,
+            object : ContentObserver(mainHandler) {
+                override fun onChange(selfChange: Boolean) {
+                    userDefinedBrightness = systemSettings.getFloat(SCREEN_BRIGHTNESS_FLOAT)
+                }
+            })
+        userDefinedBrightness = systemSettings.getFloat(SCREEN_BRIGHTNESS_FLOAT)
+    }
+
+    override fun dump(fd: FileDescriptor, pw: PrintWriter, args: Array<out String>) {
+        pw.apply {
+            println("overridingBrightness: $overridingBrightness")
+            println("useFaceAuthWallpaper: $useFaceAuthWallpaper")
+            println("brightnessAnimator: $brightnessAnimator")
+            println("brightnessAnimationDuration: $brightnessAnimationDuration")
+            println("maxScreenBrightness: $maxScreenBrightness")
+            println("userDefinedBrightness: $userDefinedBrightness")
+        }
+    }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardLifecyclesDispatcher.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardLifecyclesDispatcher.java
index 4a33590..f527775 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardLifecyclesDispatcher.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardLifecyclesDispatcher.java
@@ -19,13 +19,14 @@
 import android.os.Handler;
 import android.os.Message;
 
+import com.android.systemui.dagger.SysUISingleton;
+
 import javax.inject.Inject;
-import javax.inject.Singleton;
 
 /**
  * Dispatches the lifecycles keyguard gets from WindowManager on the main thread.
  */
-@Singleton
+@SysUISingleton
 public class KeyguardLifecyclesDispatcher {
 
     static final int SCREEN_TURNING_ON = 0;
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardSliceProvider.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardSliceProvider.java
index 3a37c0f..daef2c5 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardSliceProvider.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardSliceProvider.java
@@ -52,7 +52,6 @@
 import com.android.systemui.Dependency;
 import com.android.systemui.R;
 import com.android.systemui.SystemUIAppComponentFactory;
-import com.android.systemui.SystemUIFactory;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.statusbar.NotificationMediaManager;
 import com.android.systemui.statusbar.StatusBarState;
@@ -72,6 +71,9 @@
 
 /**
  * Simple Slice provider that shows the current date.
+ *
+ * Injection is handled by {@link SystemUIAppComponentFactory} +
+ * {@link com.android.systemui.dagger.GlobalRootComponent#inject(KeyguardSliceProvider)}.
  */
 public class KeyguardSliceProvider extends SliceProvider implements
         NextAlarmController.NextAlarmChangeCallback, ZenModeController.Callback,
@@ -298,7 +300,8 @@
     @Override
     public boolean onCreateSliceProvider() {
         mContextAvailableCallback.onContextAvailable(getContext());
-        inject();
+        mMediaWakeLock = new SettableWakeLock(WakeLock.createPartial(getContext(), "media"),
+                "media");
         synchronized (KeyguardSliceProvider.sInstanceLock) {
             KeyguardSliceProvider oldInstance = KeyguardSliceProvider.sInstance;
             if (oldInstance != null) {
@@ -319,13 +322,6 @@
     }
 
     @VisibleForTesting
-    protected void inject() {
-        SystemUIFactory.getInstance().getRootComponent().inject(this);
-        mMediaWakeLock = new SettableWakeLock(WakeLock.createPartial(getContext(), "media"),
-                "media");
-    }
-
-    @VisibleForTesting
     protected void onDestroy() {
         synchronized (KeyguardSliceProvider.sInstanceLock) {
             mNextAlarmController.removeCallback(this);
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
index 75f4809..6214a64 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
@@ -85,7 +85,6 @@
 import com.android.systemui.Dumpable;
 import com.android.systemui.R;
 import com.android.systemui.SystemUI;
-import com.android.systemui.SystemUIFactory;
 import com.android.systemui.broadcast.BroadcastDispatcher;
 import com.android.systemui.dagger.qualifiers.UiBackground;
 import com.android.systemui.dump.DumpManager;
@@ -94,7 +93,7 @@
 import com.android.systemui.shared.system.QuickStepContract;
 import com.android.systemui.statusbar.phone.BiometricUnlockController;
 import com.android.systemui.statusbar.phone.KeyguardBypassController;
-import com.android.systemui.statusbar.phone.NavigationModeController;
+import com.android.systemui.navigationbar.NavigationModeController;
 import com.android.systemui.statusbar.phone.NotificationPanelViewController;
 import com.android.systemui.statusbar.phone.StatusBar;
 import com.android.systemui.util.DeviceConfigProxy;
@@ -229,6 +228,7 @@
 
     /** TrustManager for letting it know when we change visibility */
     private final TrustManager mTrustManager;
+    private final InjectionInflationController mInjectionInflationController;
 
     /**
      * Used to keep the device awake while to ensure the keyguard finishes opening before
@@ -723,7 +723,8 @@
             @UiBackground Executor uiBgExecutor, PowerManager powerManager,
             TrustManager trustManager,
             DeviceConfigProxy deviceConfig,
-            NavigationModeController navigationModeController) {
+            NavigationModeController navigationModeController,
+            InjectionInflationController injectionInflationController) {
         super(context);
         mFalsingManager = falsingManager;
         mLockPatternUtils = lockPatternUtils;
@@ -734,6 +735,7 @@
         mUpdateMonitor = keyguardUpdateMonitor;
         mPM = powerManager;
         mTrustManager = trustManager;
+        mInjectionInflationController = injectionInflationController;
         dumpManager.registerDumpable(getClass().getName(), this);
         mDeviceConfig = deviceConfig;
         mShowHomeOverLockscreen = mDeviceConfig.getBoolean(
@@ -773,10 +775,8 @@
         mContext.registerReceiver(mDelayedLockBroadcastReceiver, delayedActionFilter,
                 SYSTEMUI_PERMISSION, null /* scheduler */);
 
-        InjectionInflationController injectionInflationController =
-                new InjectionInflationController(SystemUIFactory.getInstance().getRootComponent());
         mKeyguardDisplayManager = new KeyguardDisplayManager(mContext,
-                injectionInflationController);
+                mInjectionInflationController);
 
         mAlarmManager = (AlarmManager) mContext.getSystemService(Context.ALARM_SERVICE);
 
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ScreenLifecycle.java b/packages/SystemUI/src/com/android/systemui/keyguard/ScreenLifecycle.java
index 834bca5..084e84a 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ScreenLifecycle.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ScreenLifecycle.java
@@ -19,17 +19,17 @@
 import android.os.Trace;
 
 import com.android.systemui.Dumpable;
+import com.android.systemui.dagger.SysUISingleton;
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
 
 import javax.inject.Inject;
-import javax.inject.Singleton;
 
 /**
  * Tracks the screen lifecycle.
  */
-@Singleton
+@SysUISingleton
 public class ScreenLifecycle extends Lifecycle<ScreenLifecycle.Observer> implements Dumpable {
 
     public static final int SCREEN_OFF = 0;
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/WakefulnessLifecycle.java b/packages/SystemUI/src/com/android/systemui/keyguard/WakefulnessLifecycle.java
index d17f2f6..5161deb 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/WakefulnessLifecycle.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/WakefulnessLifecycle.java
@@ -20,6 +20,7 @@
 import android.os.Trace;
 
 import com.android.systemui.Dumpable;
+import com.android.systemui.dagger.SysUISingleton;
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
@@ -27,12 +28,11 @@
 import java.lang.annotation.RetentionPolicy;
 
 import javax.inject.Inject;
-import javax.inject.Singleton;
 
 /**
  * Tracks the wakefulness lifecycle.
  */
-@Singleton
+@SysUISingleton
 public class WakefulnessLifecycle extends Lifecycle<WakefulnessLifecycle.Observer> implements
         Dumpable {
 
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java b/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java
index 83c95b7..c9164f0 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java
@@ -16,27 +16,41 @@
 
 package com.android.systemui.keyguard.dagger;
 
+import android.annotation.Nullable;
 import android.app.trust.TrustManager;
 import android.content.Context;
+import android.content.pm.PackageManager;
+import android.content.res.Resources;
+import android.hardware.face.FaceManager;
+import android.os.Handler;
 import android.os.PowerManager;
 
 import com.android.internal.widget.LockPatternUtils;
 import com.android.keyguard.KeyguardUpdateMonitor;
 import com.android.keyguard.KeyguardViewController;
 import com.android.systemui.broadcast.BroadcastDispatcher;
+import com.android.systemui.dagger.SysUISingleton;
+import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.dagger.qualifiers.UiBackground;
 import com.android.systemui.dump.DumpManager;
 import com.android.systemui.keyguard.DismissCallbackRegistry;
+import com.android.systemui.keyguard.FaceAuthScreenBrightnessController;
 import com.android.systemui.keyguard.KeyguardViewMediator;
+import com.android.systemui.navigationbar.NavigationModeController;
 import com.android.systemui.plugins.FalsingManager;
-import com.android.systemui.statusbar.phone.NavigationModeController;
+import com.android.systemui.plugins.statusbar.StatusBarStateController;
+import com.android.systemui.statusbar.NotificationShadeWindowController;
+import com.android.systemui.statusbar.phone.KeyguardLiftController;
 import com.android.systemui.statusbar.phone.StatusBar;
 import com.android.systemui.util.DeviceConfigProxy;
+import com.android.systemui.util.InjectionInflationController;
+import com.android.systemui.util.sensors.AsyncSensorManager;
+import com.android.systemui.util.settings.GlobalSettings;
+import com.android.systemui.util.settings.SystemSettings;
 
+import java.util.Optional;
 import java.util.concurrent.Executor;
 
-import javax.inject.Singleton;
-
 import dagger.Lazy;
 import dagger.Module;
 import dagger.Provides;
@@ -50,7 +64,7 @@
      * Provides our instance of KeyguardViewMediator which is considered optional.
      */
     @Provides
-    @Singleton
+    @SysUISingleton
     public static KeyguardViewMediator newKeyguardViewMediator(
             Context context,
             FalsingManager falsingManager,
@@ -64,7 +78,8 @@
             TrustManager trustManager,
             @UiBackground Executor uiBgExecutor,
             DeviceConfigProxy deviceConfig,
-            NavigationModeController navigationModeController) {
+            NavigationModeController navigationModeController,
+            InjectionInflationController injectionInflationController) {
         return new KeyguardViewMediator(
                 context,
                 falsingManager,
@@ -78,6 +93,53 @@
                 powerManager,
                 trustManager,
                 deviceConfig,
-                navigationModeController);
+                navigationModeController,
+                injectionInflationController);
+    }
+
+    @SysUISingleton
+    @Provides
+    @Nullable
+    static KeyguardLiftController provideKeyguardLiftController(
+            Context context,
+            StatusBarStateController statusBarStateController,
+            AsyncSensorManager asyncSensorManager,
+            KeyguardUpdateMonitor keyguardUpdateMonitor,
+            DumpManager dumpManager) {
+        if (!context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_FACE)) {
+            return null;
+        }
+        return new KeyguardLiftController(statusBarStateController, asyncSensorManager,
+                keyguardUpdateMonitor, dumpManager);
+    }
+
+    @SysUISingleton
+    @Provides
+    static Optional<FaceAuthScreenBrightnessController> provideFaceAuthScreenBrightnessController(
+            Context context,
+            NotificationShadeWindowController notificationShadeWindowController,
+            @Main Resources resources,
+            Handler handler,
+            @Nullable FaceManager faceManager,
+            PackageManager packageManager,
+            KeyguardUpdateMonitor keyguardUpdateMonitor,
+            GlobalSettings globalSetting,
+            SystemSettings systemSettings,
+            DumpManager dumpManager) {
+        if (faceManager == null || !packageManager.hasSystemFeature(PackageManager.FEATURE_FACE)) {
+            return Optional.empty();
+        }
+
+        // Cameras that support "self illumination," via IR for example, don't need low light
+        // environment mitigation.
+        boolean needsLowLightMitigation = faceManager.getSensorProperties().stream()
+                .anyMatch((properties) -> !properties.supportsSelfIllumination);
+        if (!needsLowLightMitigation) {
+            return Optional.empty();
+        }
+
+        return Optional.of(new FaceAuthScreenBrightnessController(
+                notificationShadeWindowController, keyguardUpdateMonitor, resources,
+                globalSetting, systemSettings, handler, dumpManager));
     }
 }
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 16f76de..6db4086 100644
--- a/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java
@@ -20,6 +20,7 @@
 import android.os.Build;
 import android.os.Looper;
 
+import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.dump.DumpManager;
 import com.android.systemui.log.LogBuffer;
@@ -27,8 +28,6 @@
 import com.android.systemui.log.LogcatEchoTrackerDebug;
 import com.android.systemui.log.LogcatEchoTrackerProd;
 
-import javax.inject.Singleton;
-
 import dagger.Module;
 import dagger.Provides;
 
@@ -39,7 +38,7 @@
 public class LogModule {
     /** Provides a logging buffer for doze-related logs. */
     @Provides
-    @Singleton
+    @SysUISingleton
     @DozeLog
     public static LogBuffer provideDozeLogBuffer(
             LogcatEchoTracker bufferFilter,
@@ -51,7 +50,7 @@
 
     /** Provides a logging buffer for all logs related to the data layer of notifications. */
     @Provides
-    @Singleton
+    @SysUISingleton
     @NotificationLog
     public static LogBuffer provideNotificationsLogBuffer(
             LogcatEchoTracker bufferFilter,
@@ -63,7 +62,7 @@
 
     /** Provides a logging buffer for all logs related to managing notification sections. */
     @Provides
-    @Singleton
+    @SysUISingleton
     @NotificationSectionLog
     public static LogBuffer provideNotificationSectionLogBuffer(
             LogcatEchoTracker bufferFilter,
@@ -75,7 +74,7 @@
 
     /** Provides a logging buffer for all logs related to the data layer of notifications. */
     @Provides
-    @Singleton
+    @SysUISingleton
     @NotifInteractionLog
     public static LogBuffer provideNotifInteractionLogBuffer(
             LogcatEchoTracker echoTracker,
@@ -87,7 +86,7 @@
 
     /** Provides a logging buffer for all logs related to Quick Settings. */
     @Provides
-    @Singleton
+    @SysUISingleton
     @QSLog
     public static LogBuffer provideQuickSettingsLogBuffer(
             LogcatEchoTracker bufferFilter,
@@ -99,7 +98,7 @@
 
     /** Provides a logging buffer for {@link com.android.systemui.broadcast.BroadcastDispatcher} */
     @Provides
-    @Singleton
+    @SysUISingleton
     @BroadcastDispatcherLog
     public static LogBuffer provideBroadcastDispatcherLogBuffer(
             LogcatEchoTracker bufferFilter,
@@ -111,7 +110,7 @@
 
     /** Allows logging buffers to be tweaked via adb on debug builds but not on prod builds. */
     @Provides
-    @Singleton
+    @SysUISingleton
     public static LogcatEchoTracker provideLogcatEchoTracker(
             ContentResolver contentResolver,
             @Main Looper looper) {
diff --git a/packages/SystemUI/src/com/android/systemui/media/KeyguardMediaController.kt b/packages/SystemUI/src/com/android/systemui/media/KeyguardMediaController.kt
index f9c6307..094ece2 100644
--- a/packages/SystemUI/src/com/android/systemui/media/KeyguardMediaController.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/KeyguardMediaController.kt
@@ -17,6 +17,7 @@
 package com.android.systemui.media
 
 import android.view.View
+import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.plugins.statusbar.StatusBarStateController
 import com.android.systemui.statusbar.NotificationLockscreenUserManager
 import com.android.systemui.statusbar.StatusBarState
@@ -24,13 +25,12 @@
 import com.android.systemui.statusbar.notification.stack.MediaHeaderView
 import com.android.systemui.statusbar.phone.KeyguardBypassController
 import javax.inject.Inject
-import javax.inject.Singleton
 
 /**
  * A class that controls the media notifications on the lock screen, handles its visibility and
  * is responsible for the embedding of he media experience.
  */
-@Singleton
+@SysUISingleton
 class KeyguardMediaController @Inject constructor(
     private val mediaHost: MediaHost,
     private val bypassController: KeyguardBypassController,
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaCarouselController.kt b/packages/SystemUI/src/com/android/systemui/media/MediaCarouselController.kt
index de53168..a003d83 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaCarouselController.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaCarouselController.kt
@@ -3,7 +3,6 @@
 import android.content.Context
 import android.content.Intent
 import android.content.res.Configuration
-import android.graphics.Color
 import android.provider.Settings.ACTION_MEDIA_CONTROLS_SETTINGS
 import android.util.Log
 import android.util.MathUtils
@@ -11,20 +10,22 @@
 import android.view.View
 import android.view.ViewGroup
 import android.widget.LinearLayout
+import androidx.annotation.VisibleForTesting
 import com.android.systemui.R
+import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Main
 import com.android.systemui.plugins.ActivityStarter
 import com.android.systemui.plugins.FalsingManager
 import com.android.systemui.qs.PageIndicator
-import com.android.systemui.statusbar.notification.VisualStabilityManager
+import com.android.systemui.statusbar.notification.collection.legacy.VisualStabilityManager
 import com.android.systemui.statusbar.policy.ConfigurationController
 import com.android.systemui.util.Utils
 import com.android.systemui.util.animation.UniqueObjectHostView
 import com.android.systemui.util.animation.requiresRemeasuring
 import com.android.systemui.util.concurrency.DelayableExecutor
+import java.util.TreeMap
 import javax.inject.Inject
 import javax.inject.Provider
-import javax.inject.Singleton
 
 private const val TAG = "MediaCarouselController"
 private val settingsIntent = Intent().setAction(ACTION_MEDIA_CONTROLS_SETTINGS)
@@ -33,7 +34,7 @@
  * Class that is responsible for keeping the view carousel up to date.
  * This also handles changes in state and applies them to the media carousel like the expansion.
  */
-@Singleton
+@SysUISingleton
 class MediaCarouselController @Inject constructor(
     private val context: Context,
     private val mediaControlPanelFactory: Provider<MediaControlPanel>,
@@ -41,7 +42,7 @@
     private val mediaHostStatesManager: MediaHostStatesManager,
     private val activityStarter: ActivityStarter,
     @Main executor: DelayableExecutor,
-    mediaManager: MediaDataFilter,
+    mediaManager: MediaDataManager,
     configurationController: ConfigurationController,
     falsingManager: FalsingManager
 ) {
@@ -103,9 +104,7 @@
     private val mediaCarousel: MediaScrollView
     private val mediaCarouselScrollHandler: MediaCarouselScrollHandler
     val mediaFrame: ViewGroup
-    val mediaPlayers: MutableMap<String, MediaControlPanel> = mutableMapOf()
     private lateinit var settingsButton: View
-    private val mediaData: MutableMap<String, MediaData> = mutableMapOf()
     private val mediaContent: ViewGroup
     private val pageIndicator: PageIndicator
     private val visualStabilityCallback: VisualStabilityManager.Callback
@@ -123,7 +122,7 @@
         set(value) {
             if (field != value) {
                 field = value
-                for (player in mediaPlayers.values) {
+                for (player in MediaPlayerData.players()) {
                     player.setListening(field)
                 }
             }
@@ -151,11 +150,12 @@
         pageIndicator = mediaFrame.requireViewById(R.id.media_page_indicator)
         mediaCarouselScrollHandler = MediaCarouselScrollHandler(mediaCarousel, pageIndicator,
                 executor, mediaManager::onSwipeToDismiss, this::updatePageIndicatorLocation,
-                falsingManager)
+                this::closeGuts, falsingManager)
         isRtl = context.resources.configuration.layoutDirection == View.LAYOUT_DIRECTION_RTL
         inflateSettingsButton()
         mediaContent = mediaCarousel.requireViewById(R.id.media_carousel)
         configurationController.addCallback(configListener)
+        // TODO (b/162832756): remove visual stability manager when migrating to new pipeline
         visualStabilityCallback = VisualStabilityManager.Callback {
             if (needsReordering) {
                 needsReordering = false
@@ -168,20 +168,18 @@
                 true /* persistent */)
         mediaManager.addListener(object : MediaDataManager.Listener {
             override fun onMediaDataLoaded(key: String, oldKey: String?, data: MediaData) {
-                oldKey?.let { mediaData.remove(it) }
                 if (!data.active && !Utils.useMediaResumption(context)) {
                     // This view is inactive, let's remove this! This happens e.g when dismissing /
                     // timing out a view. We still have the data around because resumption could
                     // be on, but we should save the resources and release this.
+                    oldKey?.let { MediaPlayerData.removeMediaPlayer(it) }
                     onMediaDataRemoved(key)
                 } else {
-                    mediaData.put(key, data)
                     addOrUpdatePlayer(key, oldKey, data)
                 }
             }
 
             override fun onMediaDataRemoved(key: String) {
-                mediaData.remove(key)
                 removePlayer(key)
             }
         })
@@ -224,53 +222,36 @@
     }
 
     private fun reorderAllPlayers() {
-        for (mediaPlayer in mediaPlayers.values) {
-            val view = mediaPlayer.view?.player
-            if (mediaPlayer.isPlaying && mediaContent.indexOfChild(view) != 0) {
-                mediaContent.removeView(view)
-                mediaContent.addView(view, 0)
+        mediaContent.removeAllViews()
+        for (mediaPlayer in MediaPlayerData.players()) {
+            mediaPlayer.view?.let {
+                mediaContent.addView(it.player)
             }
         }
         mediaCarouselScrollHandler.onPlayersChanged()
     }
 
     private fun addOrUpdatePlayer(key: String, oldKey: String?, data: MediaData) {
-        // If the key was changed, update entry
-        val oldData = mediaPlayers[oldKey]
-        if (oldData != null) {
-            val oldData = mediaPlayers.remove(oldKey)
-            mediaPlayers.put(key, oldData!!)?.let {
-                Log.wtf(TAG, "new key $key already exists when migrating from $oldKey")
-            }
-        }
-        var existingPlayer = mediaPlayers[key]
+        val existingPlayer = MediaPlayerData.getMediaPlayer(key, oldKey)
         if (existingPlayer == null) {
-            existingPlayer = mediaControlPanelFactory.get()
-            existingPlayer.attach(PlayerViewHolder.create(LayoutInflater.from(context),
-                    mediaContent))
-            existingPlayer.mediaViewController.sizeChangedListener = this::updateCarouselDimensions
-            mediaPlayers[key] = existingPlayer
+            var newPlayer = mediaControlPanelFactory.get()
+            newPlayer.attach(PlayerViewHolder.create(LayoutInflater.from(context), mediaContent))
+            newPlayer.mediaViewController.sizeChangedListener = this::updateCarouselDimensions
+            MediaPlayerData.addMediaPlayer(key, data, newPlayer)
             val lp = LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
                     ViewGroup.LayoutParams.WRAP_CONTENT)
-            existingPlayer.view?.player?.setLayoutParams(lp)
-            existingPlayer.bind(data)
-            existingPlayer.setListening(currentlyExpanded)
-            updatePlayerToState(existingPlayer, noAnimation = true)
-            if (existingPlayer.isPlaying) {
-                mediaContent.addView(existingPlayer.view?.player, 0)
-            } else {
-                mediaContent.addView(existingPlayer.view?.player)
-            }
+            newPlayer.view?.player?.setLayoutParams(lp)
+            newPlayer.bind(data)
+            newPlayer.setListening(currentlyExpanded)
+            updatePlayerToState(newPlayer, noAnimation = true)
+            reorderAllPlayers()
         } else {
             existingPlayer.bind(data)
-            if (existingPlayer.isPlaying &&
-                    mediaContent.indexOfChild(existingPlayer.view?.player) != 0) {
-                if (visualStabilityManager.isReorderingAllowed) {
-                    mediaContent.removeView(existingPlayer.view?.player)
-                    mediaContent.addView(existingPlayer.view?.player, 0)
-                } else {
-                    needsReordering = true
-                }
+            MediaPlayerData.addMediaPlayer(key, data, existingPlayer)
+            if (visualStabilityManager.isReorderingAllowed) {
+                reorderAllPlayers()
+            } else {
+                needsReordering = true
             }
         }
         updatePageIndicator()
@@ -278,13 +259,13 @@
         mediaCarousel.requiresRemeasuring = true
         // Check postcondition: mediaContent should have the same number of children as there are
         // elements in mediaPlayers.
-        if (mediaPlayers.size != mediaContent.childCount) {
+        if (MediaPlayerData.players().size != mediaContent.childCount) {
             Log.wtf(TAG, "Size of players list and number of views in carousel are out of sync")
         }
     }
 
     private fun removePlayer(key: String) {
-        val removed = mediaPlayers.remove(key)
+        val removed = MediaPlayerData.removeMediaPlayer(key)
         removed?.apply {
             mediaCarouselScrollHandler.onPrePlayerRemoved(removed)
             mediaContent.removeView(removed.view?.player)
@@ -295,12 +276,7 @@
     }
 
     private fun recreatePlayers() {
-        // Note that this will scramble the order of players. Actively playing sessions will, at
-        // least, still be put in the front. If we want to maintain order, then more work is
-        // needed.
-        mediaData.forEach {
-            key, data ->
-            removePlayer(key)
+        MediaPlayerData.mediaData().forEach { (key, data) ->
             addOrUpdatePlayer(key = key, oldKey = null, data = data)
         }
     }
@@ -338,7 +314,7 @@
             currentStartLocation = startLocation
             currentEndLocation = endLocation
             currentTransitionProgress = progress
-            for (mediaPlayer in mediaPlayers.values) {
+            for (mediaPlayer in MediaPlayerData.players()) {
                 updatePlayerToState(mediaPlayer, immediately)
             }
             maybeResetSettingsCog()
@@ -387,7 +363,7 @@
     private fun updateCarouselDimensions() {
         var width = 0
         var height = 0
-        for (mediaPlayer in mediaPlayers.values) {
+        for (mediaPlayer in MediaPlayerData.players()) {
             val controller = mediaPlayer.mediaViewController
             // When transitioning the view to gone, the view gets smaller, but the translation
             // Doesn't, let's add the translation
@@ -449,7 +425,7 @@
             this.desiredLocation = desiredLocation
             this.desiredHostState = it
             currentlyExpanded = it.expansion > 0
-            for (mediaPlayer in mediaPlayers.values) {
+            for (mediaPlayer in MediaPlayerData.players()) {
                 if (animate) {
                     mediaPlayer.mediaViewController.animatePendingStateChange(
                             duration = duration,
@@ -470,6 +446,12 @@
         }
     }
 
+    fun closeGuts() {
+        MediaPlayerData.players().forEach {
+            it.closeGuts(true)
+        }
+    }
+
     /**
      * Update the size of the carousel, remeasuring it if necessary.
      */
@@ -492,3 +474,50 @@
         }
     }
 }
+
+@VisibleForTesting
+internal object MediaPlayerData {
+    private data class MediaSortKey(
+        val data: MediaData,
+        val updateTime: Long = 0,
+        val isPlaying: Boolean = false
+    )
+
+    private val comparator =
+        compareByDescending<MediaSortKey> { it.isPlaying }
+        .thenByDescending { it.data.isLocalSession }
+        .thenByDescending { !it.data.resumption }
+        .thenByDescending { it.updateTime }
+
+    private val mediaPlayers = TreeMap<MediaSortKey, MediaControlPanel>(comparator)
+    private val mediaData: MutableMap<String, MediaSortKey> = mutableMapOf()
+
+    fun addMediaPlayer(key: String, data: MediaData, player: MediaControlPanel) {
+        removeMediaPlayer(key)
+        val sortKey = MediaSortKey(data, System.currentTimeMillis(), player.isPlaying())
+        mediaData.put(key, sortKey)
+        mediaPlayers.put(sortKey, player)
+    }
+
+    fun getMediaPlayer(key: String, oldKey: String?): MediaControlPanel? {
+        // If the key was changed, update entry
+        oldKey?.let {
+            if (it != key) {
+                mediaData.remove(it)?.let { sortKey -> mediaData.put(key, sortKey) }
+            }
+        }
+        return mediaData.get(key)?.let { mediaPlayers.get(it) }
+    }
+
+    fun removeMediaPlayer(key: String) = mediaData.remove(key)?.let { mediaPlayers.remove(it) }
+
+    fun mediaData() = mediaData.entries.map { e -> Pair(e.key, e.value.data) }
+
+    fun players() = mediaPlayers.values
+
+    @VisibleForTesting
+    fun clear() {
+        mediaData.clear()
+        mediaPlayers.clear()
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaCarouselScrollHandler.kt b/packages/SystemUI/src/com/android/systemui/media/MediaCarouselScrollHandler.kt
index 3096908..77cac50 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaCarouselScrollHandler.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaCarouselScrollHandler.kt
@@ -56,6 +56,7 @@
     private val mainExecutor: DelayableExecutor,
     private val dismissCallback: () -> Unit,
     private var translationChangedListener: () -> Unit,
+    private val closeGuts: () -> Unit,
     private val falsingManager: FalsingManager
 ) {
     /**
@@ -452,6 +453,7 @@
         val nowScrolledIn = scrollIntoCurrentMedia != 0
         if (newIndex != activeMediaIndex || wasScrolledIn != nowScrolledIn) {
             activeMediaIndex = newIndex
+            closeGuts()
             updatePlayerVisibilities()
         }
         val relativeLocation = activeMediaIndex.toFloat() + if (playerWidthPlusPadding > 0)
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java b/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java
index 3fc162e..e55678dc 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java
@@ -16,6 +16,8 @@
 
 package com.android.systemui.media;
 
+import static android.provider.Settings.ACTION_MEDIA_CONTROLS_SETTINGS;
+
 import android.app.PendingIntent;
 import android.content.Context;
 import android.content.Intent;
@@ -45,6 +47,7 @@
 import com.android.systemui.R;
 import com.android.systemui.dagger.qualifiers.Background;
 import com.android.systemui.plugins.ActivityStarter;
+import com.android.systemui.statusbar.phone.KeyguardDismissUtil;
 import com.android.systemui.util.animation.TransitionLayout;
 
 import java.util.List;
@@ -52,6 +55,8 @@
 
 import javax.inject.Inject;
 
+import dagger.Lazy;
+
 /**
  * A view controller used for Media Playback.
  */
@@ -59,6 +64,8 @@
     private static final String TAG = "MediaControlPanel";
     private static final float DISABLED_ALPHA = 0.38f;
 
+    private static final Intent SETTINGS_INTENT = new Intent(ACTION_MEDIA_CONTROLS_SETTINGS);
+
     // Button IDs for QS controls
     static final int[] ACTION_IDS = {
             R.id.action0,
@@ -78,6 +85,8 @@
     private MediaViewController mMediaViewController;
     private MediaSession.Token mToken;
     private MediaController mController;
+    private KeyguardDismissUtil mKeyguardDismissUtil;
+    private Lazy<MediaDataManager> mMediaDataManagerLazy;
     private int mBackgroundColor;
     private int mAlbumArtSize;
     private int mAlbumArtRadius;
@@ -93,12 +102,15 @@
     @Inject
     public MediaControlPanel(Context context, @Background Executor backgroundExecutor,
             ActivityStarter activityStarter, MediaViewController mediaViewController,
-            SeekBarViewModel seekBarViewModel) {
+            SeekBarViewModel seekBarViewModel, Lazy<MediaDataManager> lazyMediaDataManager,
+            KeyguardDismissUtil keyguardDismissUtil) {
         mContext = context;
         mBackgroundExecutor = backgroundExecutor;
         mActivityStarter = activityStarter;
         mSeekBarViewModel = seekBarViewModel;
         mMediaViewController = mediaViewController;
+        mMediaDataManagerLazy = lazyMediaDataManager;
+        mKeyguardDismissUtil = keyguardDismissUtil;
         loadDimens();
 
         mViewOutlineProvider = new ViewOutlineProvider() {
@@ -174,6 +186,21 @@
         mSeekBarViewModel.getProgress().observeForever(mSeekBarObserver);
         mSeekBarViewModel.attachTouchHandlers(vh.getSeekBar());
         mMediaViewController.attach(player);
+
+        mViewHolder.getPlayer().setOnLongClickListener(v -> {
+            if (!mMediaViewController.isGutsVisible()) {
+                mMediaViewController.openGuts();
+                return true;
+            } else {
+                return false;
+            }
+        });
+        mViewHolder.getCancel().setOnClickListener(v -> {
+            closeGuts();
+        });
+        mViewHolder.getSettings().setOnClickListener(v -> {
+            mActivityStarter.startActivity(SETTINGS_INTENT, true /* dismissShade */);
+        });
     }
 
     /**
@@ -205,6 +232,7 @@
         PendingIntent clickIntent = data.getClickIntent();
         if (clickIntent != null) {
             mViewHolder.getPlayer().setOnClickListener(v -> {
+                if (mMediaViewController.isGutsVisible()) return;
                 mActivityStarter.postStartActivityDismissingKeyguard(clickIntent);
             });
         }
@@ -329,14 +357,38 @@
         final MediaController controller = getController();
         mBackgroundExecutor.execute(() -> mSeekBarViewModel.updateController(controller));
 
-        // Set up long press menu
-        // TODO: b/156036025 bring back media guts
+        // Dismiss
+        mViewHolder.getDismiss().setOnClickListener(v -> {
+            if (data.getNotificationKey() != null) {
+                closeGuts();
+                mKeyguardDismissUtil.executeWhenUnlocked(() -> {
+                    mMediaDataManagerLazy.get().dismissMediaData(data.getNotificationKey(),
+                            MediaViewController.GUTS_ANIMATION_DURATION + 100);
+                    return true;
+                }, /* requiresShadeOpen */ true);
+            } else {
+                Log.w(TAG, "Dismiss media with null notification. Token uid="
+                        + data.getToken().getUid());
+            }
+        });
 
         // TODO: We don't need to refresh this state constantly, only if the state actually changed
         // to something which might impact the measurement
         mMediaViewController.refreshState();
     }
 
+    /**
+     * Close the guts for this player.
+     * @param immediate {@code true} if it should be closed without animation
+     */
+    public void closeGuts(boolean immediate) {
+        mMediaViewController.closeGuts(immediate);
+    }
+
+    private void closeGuts() {
+        closeGuts(false);
+    }
+
     @UiThread
     private Drawable scaleDrawable(Icon icon) {
         if (icon == null) {
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaData.kt b/packages/SystemUI/src/com/android/systemui/media/MediaData.kt
index dafc52a..d6a0268 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaData.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaData.kt
@@ -82,6 +82,10 @@
      */
     var resumeAction: Runnable?,
     /**
+     * Local or remote playback
+     */
+    var isLocalSession: Boolean = true,
+    /**
      * Indicates that this player is a resumption player (ie. It only shows a play actions which
      * will start the app and start playing).
      */
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaDataCombineLatest.kt b/packages/SystemUI/src/com/android/systemui/media/MediaDataCombineLatest.kt
index d0642cc..aa3699e 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaDataCombineLatest.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaDataCombineLatest.kt
@@ -17,65 +17,48 @@
 package com.android.systemui.media
 
 import javax.inject.Inject
-import javax.inject.Singleton
 
 /**
- * Combines updates from [MediaDataManager] with [MediaDeviceManager].
+ * Combines [MediaDataManager.Listener] events with [MediaDeviceManager.Listener] events.
  */
-@Singleton
-class MediaDataCombineLatest @Inject constructor(
-    private val dataSource: MediaDataManager,
-    private val deviceSource: MediaDeviceManager
-) {
+class MediaDataCombineLatest @Inject constructor() : MediaDataManager.Listener,
+        MediaDeviceManager.Listener {
+
     private val listeners: MutableSet<MediaDataManager.Listener> = mutableSetOf()
     private val entries: MutableMap<String, Pair<MediaData?, MediaDeviceData?>> = mutableMapOf()
 
-    init {
-        dataSource.addListener(object : MediaDataManager.Listener {
-            override fun onMediaDataLoaded(key: String, oldKey: String?, data: MediaData) {
-                if (oldKey != null && oldKey != key && entries.contains(oldKey)) {
-                    entries[key] = data to entries.remove(oldKey)?.second
-                    update(key, oldKey)
-                } else {
-                    entries[key] = data to entries[key]?.second
-                    update(key, key)
-                }
-            }
-            override fun onMediaDataRemoved(key: String) {
-                remove(key)
-            }
-        })
-        deviceSource.addListener(object : MediaDeviceManager.Listener {
-            override fun onMediaDeviceChanged(
-                key: String,
-                oldKey: String?,
-                data: MediaDeviceData?
-            ) {
-                if (oldKey != null && oldKey != key && entries.contains(oldKey)) {
-                    entries[key] = entries.remove(oldKey)?.first to data
-                    update(key, oldKey)
-                } else {
-                    entries[key] = entries[key]?.first to data
-                    update(key, key)
-                }
-            }
-            override fun onKeyRemoved(key: String) {
-                remove(key)
-            }
-        })
+    override fun onMediaDataLoaded(key: String, oldKey: String?, data: MediaData) {
+        if (oldKey != null && oldKey != key && entries.contains(oldKey)) {
+            entries[key] = data to entries.remove(oldKey)?.second
+            update(key, oldKey)
+        } else {
+            entries[key] = data to entries[key]?.second
+            update(key, key)
+        }
     }
 
-    /**
-     * Get a map of all non-null data entries
-     */
-    fun getData(): Map<String, MediaData> {
-        return entries.filter {
-            (key, pair) -> pair.first != null && pair.second != null
-        }.mapValues {
-            (key, pair) -> pair.first!!.copy(device = pair.second)
+    override fun onMediaDataRemoved(key: String) {
+        remove(key)
+    }
+
+    override fun onMediaDeviceChanged(
+        key: String,
+        oldKey: String?,
+        data: MediaDeviceData?
+    ) {
+        if (oldKey != null && oldKey != key && entries.contains(oldKey)) {
+            entries[key] = entries.remove(oldKey)?.first to data
+            update(key, oldKey)
+        } else {
+            entries[key] = entries[key]?.first to data
+            update(key, key)
         }
     }
 
+    override fun onKeyRemoved(key: String) {
+        remove(key)
+    }
+
     /**
      * Add a listener for [MediaData] changes that has been combined with latest [MediaDeviceData].
      */
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaDataFilter.kt b/packages/SystemUI/src/com/android/systemui/media/MediaDataFilter.kt
index 24ca970..0664a41 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaDataFilter.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaDataFilter.kt
@@ -24,7 +24,6 @@
 import com.android.systemui.statusbar.NotificationLockscreenUserManager
 import java.util.concurrent.Executor
 import javax.inject.Inject
-import javax.inject.Singleton
 
 private const val TAG = "MediaDataFilter"
 private const val DEBUG = true
@@ -33,24 +32,24 @@
  * Filters data updates from [MediaDataCombineLatest] based on the current user ID, and handles user
  * switches (removing entries for the previous user, adding back entries for the current user)
  *
- * This is added downstream of [MediaDataManager] since we may still need to handle callbacks from
- * background users (e.g. timeouts) that UI classes should ignore.
- * Instead, UI classes should listen to this so they can stay in sync with the current user.
+ * This is added at the end of the pipeline since we may still need to handle callbacks from
+ * background users (e.g. timeouts).
  */
-@Singleton
 class MediaDataFilter @Inject constructor(
-    private val dataSource: MediaDataCombineLatest,
     private val broadcastDispatcher: BroadcastDispatcher,
     private val mediaResumeListener: MediaResumeListener,
-    private val mediaDataManager: MediaDataManager,
     private val lockscreenUserManager: NotificationLockscreenUserManager,
     @Main private val executor: Executor
 ) : MediaDataManager.Listener {
     private val userTracker: CurrentUserTracker
-    private val listeners: MutableSet<MediaDataManager.Listener> = mutableSetOf()
+    private val _listeners: MutableSet<MediaDataManager.Listener> = mutableSetOf()
+    internal val listeners: Set<MediaDataManager.Listener>
+        get() = _listeners.toSet()
+    internal lateinit var mediaDataManager: MediaDataManager
 
-    // The filtered mediaEntries, which will be a subset of all mediaEntries in MediaDataManager
-    private val mediaEntries: LinkedHashMap<String, MediaData> = LinkedHashMap()
+    private val allEntries: LinkedHashMap<String, MediaData> = LinkedHashMap()
+    // The filtered userEntries, which will be a subset of all userEntries in MediaDataManager
+    private val userEntries: LinkedHashMap<String, MediaData> = LinkedHashMap()
 
     init {
         userTracker = object : CurrentUserTracker(broadcastDispatcher) {
@@ -60,31 +59,34 @@
             }
         }
         userTracker.startTracking()
-        dataSource.addListener(this)
     }
 
     override fun onMediaDataLoaded(key: String, oldKey: String?, data: MediaData) {
+        if (oldKey != null && oldKey != key) {
+            allEntries.remove(oldKey)
+        }
+        allEntries.put(key, data)
+
         if (!lockscreenUserManager.isCurrentProfile(data.userId)) {
             return
         }
 
-        if (oldKey != null) {
-            mediaEntries.remove(oldKey)
+        if (oldKey != null && oldKey != key) {
+            userEntries.remove(oldKey)
         }
-        mediaEntries.put(key, data)
+        userEntries.put(key, data)
 
         // Notify listeners
-        val listenersCopy = listeners.toSet()
-        listenersCopy.forEach {
+        listeners.forEach {
             it.onMediaDataLoaded(key, oldKey, data)
         }
     }
 
     override fun onMediaDataRemoved(key: String) {
-        mediaEntries.remove(key)?.let {
+        allEntries.remove(key)
+        userEntries.remove(key)?.let {
             // Only notify listeners if something actually changed
-            val listenersCopy = listeners.toSet()
-            listenersCopy.forEach {
+            listeners.forEach {
                 it.onMediaDataRemoved(key)
             }
         }
@@ -93,11 +95,11 @@
     @VisibleForTesting
     internal fun handleUserSwitched(id: Int) {
         // If the user changes, remove all current MediaData objects and inform listeners
-        val listenersCopy = listeners.toSet()
-        val keyCopy = mediaEntries.keys.toMutableList()
+        val listenersCopy = listeners
+        val keyCopy = userEntries.keys.toMutableList()
         // Clear the list first, to make sure callbacks from listeners if we have any entries
         // are up to date
-        mediaEntries.clear()
+        userEntries.clear()
         keyCopy.forEach {
             if (DEBUG) Log.d(TAG, "Removing $it after user change")
             listenersCopy.forEach { listener ->
@@ -105,10 +107,10 @@
             }
         }
 
-        dataSource.getData().forEach { (key, data) ->
+        allEntries.forEach { (key, data) ->
             if (lockscreenUserManager.isCurrentProfile(data.userId)) {
                 if (DEBUG) Log.d(TAG, "Re-adding $key after user change")
-                mediaEntries.put(key, data)
+                userEntries.put(key, data)
                 listenersCopy.forEach { listener ->
                     listener.onMediaDataLoaded(key, null, data)
                 }
@@ -121,7 +123,7 @@
      */
     fun onSwipeToDismiss() {
         if (DEBUG) Log.d(TAG, "Media carousel swiped away")
-        val mediaKeys = mediaEntries.keys.toSet()
+        val mediaKeys = userEntries.keys.toSet()
         mediaKeys.forEach {
             mediaDataManager.setTimedOut(it, timedOut = true)
         }
@@ -130,7 +132,7 @@
     /**
      * Are there any media notifications active?
      */
-    fun hasActiveMedia() = mediaEntries.any { it.value.active }
+    fun hasActiveMedia() = userEntries.any { it.value.active }
 
     /**
      * Are there any media entries we should display?
@@ -138,7 +140,7 @@
      * If resumption is disabled, we only want to show active players
      */
     fun hasAnyMedia() = if (mediaResumeListener.isResumptionEnabled()) {
-        mediaEntries.isNotEmpty()
+        userEntries.isNotEmpty()
     } else {
         hasActiveMedia()
     }
@@ -146,10 +148,10 @@
     /**
      * Add a listener for filtered [MediaData] changes
      */
-    fun addListener(listener: MediaDataManager.Listener) = listeners.add(listener)
+    fun addListener(listener: MediaDataManager.Listener) = _listeners.add(listener)
 
     /**
      * Remove a listener that was registered with addListener
      */
-    fun removeListener(listener: MediaDataManager.Listener) = listeners.remove(listener)
-}
\ No newline at end of file
+    fun removeListener(listener: MediaDataManager.Listener) = _listeners.remove(listener)
+}
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt b/packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt
index d82150f2..686531a 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt
@@ -31,6 +31,7 @@
 import android.graphics.drawable.Icon
 import android.media.MediaDescription
 import android.media.MediaMetadata
+import android.media.session.MediaController
 import android.media.session.MediaSession
 import android.net.Uri
 import android.os.UserHandle
@@ -41,19 +42,21 @@
 import com.android.systemui.Dumpable
 import com.android.systemui.R
 import com.android.systemui.broadcast.BroadcastDispatcher
+import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Background
 import com.android.systemui.dagger.qualifiers.Main
 import com.android.systemui.dump.DumpManager
+import com.android.systemui.plugins.ActivityStarter
 import com.android.systemui.statusbar.notification.MediaNotificationProcessor
 import com.android.systemui.statusbar.notification.row.HybridGroupManager
 import com.android.systemui.util.Assert
 import com.android.systemui.util.Utils
+import com.android.systemui.util.concurrency.DelayableExecutor
 import java.io.FileDescriptor
 import java.io.IOException
 import java.io.PrintWriter
 import java.util.concurrent.Executor
 import javax.inject.Inject
-import javax.inject.Singleton
 
 // URI fields to try loading album art from
 private val ART_URIS = arrayOf(
@@ -86,36 +89,65 @@
 /**
  * A class that facilitates management and loading of Media Data, ready for binding.
  */
-@Singleton
+@SysUISingleton
 class MediaDataManager(
     private val context: Context,
     @Background private val backgroundExecutor: Executor,
-    @Main private val foregroundExecutor: Executor,
+    @Main private val foregroundExecutor: DelayableExecutor,
     private val mediaControllerFactory: MediaControllerFactory,
     private val broadcastDispatcher: BroadcastDispatcher,
     dumpManager: DumpManager,
     mediaTimeoutListener: MediaTimeoutListener,
     mediaResumeListener: MediaResumeListener,
+    mediaSessionBasedFilter: MediaSessionBasedFilter,
+    mediaDeviceManager: MediaDeviceManager,
+    mediaDataCombineLatest: MediaDataCombineLatest,
+    private val mediaDataFilter: MediaDataFilter,
+    private val activityStarter: ActivityStarter,
     private var useMediaResumption: Boolean,
     private val useQsMediaPlayer: Boolean
 ) : Dumpable {
 
-    private val listeners: MutableSet<Listener> = mutableSetOf()
+    // Internal listeners are part of the internal pipeline. External listeners (those registered
+    // with [MediaDeviceManager.addListener]) receive events after they have propagated through
+    // the internal pipeline.
+    // Another way to think of the distinction between internal and external listeners is the
+    // following. Internal listeners are listeners that MediaDataManager depends on, and external
+    // listeners are listeners that depend on MediaDataManager.
+    // TODO(b/159539991#comment5): Move internal listeners to separate package.
+    private val internalListeners: MutableSet<Listener> = mutableSetOf()
     private val mediaEntries: LinkedHashMap<String, MediaData> = LinkedHashMap()
+    internal var appsBlockedFromResume: MutableSet<String> = Utils.getBlockedMediaApps(context)
+        set(value) {
+            // Update list
+            appsBlockedFromResume.clear()
+            appsBlockedFromResume.addAll(value)
+
+            // Remove any existing resume players that are now blocked
+            appsBlockedFromResume.forEach {
+                removeAllForPackage(it)
+            }
+        }
 
     @Inject
     constructor(
         context: Context,
         @Background backgroundExecutor: Executor,
-        @Main foregroundExecutor: Executor,
+        @Main foregroundExecutor: DelayableExecutor,
         mediaControllerFactory: MediaControllerFactory,
         dumpManager: DumpManager,
         broadcastDispatcher: BroadcastDispatcher,
         mediaTimeoutListener: MediaTimeoutListener,
-        mediaResumeListener: MediaResumeListener
+        mediaResumeListener: MediaResumeListener,
+        mediaSessionBasedFilter: MediaSessionBasedFilter,
+        mediaDeviceManager: MediaDeviceManager,
+        mediaDataCombineLatest: MediaDataCombineLatest,
+        mediaDataFilter: MediaDataFilter,
+        activityStarter: ActivityStarter
     ) : this(context, backgroundExecutor, foregroundExecutor, mediaControllerFactory,
             broadcastDispatcher, dumpManager, mediaTimeoutListener, mediaResumeListener,
-            Utils.useMediaResumption(context), Utils.useQsMediaPlayer(context))
+            mediaSessionBasedFilter, mediaDeviceManager, mediaDataCombineLatest, mediaDataFilter,
+            activityStarter, Utils.useMediaResumption(context), Utils.useQsMediaPlayer(context))
 
     private val appChangeReceiver = object : BroadcastReceiver() {
         override fun onReceive(context: Context, intent: Intent) {
@@ -137,12 +169,26 @@
 
     init {
         dumpManager.registerDumpable(TAG, this)
+
+        // Initialize the internal processing pipeline. The listeners at the front of the pipeline
+        // are set as internal listeners so that they receive events. From there, events are
+        // propagated through the pipeline. The end of the pipeline is currently mediaDataFilter,
+        // so it is responsible for dispatching events to external listeners. To achieve this,
+        // external listeners that are registered with [MediaDataManager.addListener] are actually
+        // registered as listeners to mediaDataFilter.
+        addInternalListener(mediaTimeoutListener)
+        addInternalListener(mediaResumeListener)
+        addInternalListener(mediaSessionBasedFilter)
+        mediaSessionBasedFilter.addListener(mediaDeviceManager)
+        mediaSessionBasedFilter.addListener(mediaDataCombineLatest)
+        mediaDeviceManager.addListener(mediaDataCombineLatest)
+        mediaDataCombineLatest.addListener(mediaDataFilter)
+
+        // Set up links back into the pipeline for listeners that need to send events upstream.
         mediaTimeoutListener.timeoutCallback = { token: String, timedOut: Boolean ->
             setTimedOut(token, timedOut) }
-        addListener(mediaTimeoutListener)
-
         mediaResumeListener.setManager(this)
-        addListener(mediaResumeListener)
+        mediaDataFilter.mediaDataManager = this
 
         val suspendFilter = IntentFilter(Intent.ACTION_PACKAGES_SUSPENDED)
         broadcastDispatcher.registerReceiver(appChangeReceiver, suspendFilter, null, UserHandle.ALL)
@@ -180,13 +226,9 @@
 
     private fun removeAllForPackage(packageName: String) {
         Assert.isMainThread()
-        val listenersCopy = listeners.toSet()
         val toRemove = mediaEntries.filter { it.value.packageName == packageName }
         toRemove.forEach {
-            mediaEntries.remove(it.key)
-            listenersCopy.forEach { listener ->
-                listener.onMediaDataRemoved(it.key)
-            }
+            removeEntry(it.key)
         }
     }
 
@@ -246,12 +288,45 @@
     /**
      * Add a listener for changes in this class
      */
-    fun addListener(listener: Listener) = listeners.add(listener)
+    fun addListener(listener: Listener) {
+        // mediaDataFilter is the current end of the internal pipeline. Register external
+        // listeners as listeners to it.
+        mediaDataFilter.addListener(listener)
+    }
 
     /**
      * Remove a listener for changes in this class
      */
-    fun removeListener(listener: Listener) = listeners.remove(listener)
+    fun removeListener(listener: Listener) {
+        // Since mediaDataFilter is the current end of the internal pipelie, external listeners
+        // have been registered to it. So, they need to be removed from it too.
+        mediaDataFilter.removeListener(listener)
+    }
+
+    /**
+     * Add a listener for internal events.
+     */
+    private fun addInternalListener(listener: Listener) = internalListeners.add(listener)
+
+    /**
+     * Notify internal listeners of loaded event.
+     *
+     * External listeners registered with [addListener] will be notified after the event propagates
+     * through the internal listener pipeline.
+     */
+    private fun notifyMediaDataLoaded(key: String, oldKey: String?, info: MediaData) {
+        internalListeners.forEach { it.onMediaDataLoaded(key, oldKey, info) }
+    }
+
+    /**
+     * Notify internal listeners of removed event.
+     *
+     * External listeners registered with [addListener] will be notified after the event propagates
+     * through the internal listener pipeline.
+     */
+    private fun notifyMediaDataRemoved(key: String) {
+        internalListeners.forEach { it.onMediaDataRemoved(key) }
+    }
 
     /**
      * Called whenever the player has been paused or stopped for a while, or swiped from QQS.
@@ -269,6 +344,15 @@
         }
     }
 
+    private fun removeEntry(key: String) {
+        mediaEntries.remove(key)
+        notifyMediaDataRemoved(key)
+    }
+
+    fun dismissMediaData(key: String, delay: Long) {
+        foregroundExecutor.executeDelayed({ removeEntry(key) }, delay)
+    }
+
     private fun loadMediaDataInBgForResumption(
         userId: Int,
         desc: MediaDescription,
@@ -318,7 +402,8 @@
     ) {
         val token = sbn.notification.extras.getParcelable(Notification.EXTRA_MEDIA_SESSION)
                 as MediaSession.Token?
-        val metadata = mediaControllerFactory.create(token).metadata
+        val mediaController = mediaControllerFactory.create(token)
+        val metadata = mediaController.metadata
 
         // Foreground and Background colors computed from album art
         val notif: Notification = sbn.notification
@@ -393,10 +478,13 @@
                 }
                 val runnable = if (action.actionIntent != null) {
                     Runnable {
-                        try {
-                            action.actionIntent.send()
-                        } catch (e: PendingIntent.CanceledException) {
-                            Log.d(TAG, "Intent canceled", e)
+                        if (action.isAuthenticationRequired()) {
+                            activityStarter.dismissKeyguardThenExecute({
+                                var result = sendPendingIntent(action.actionIntent)
+                                result
+                            }, {}, true)
+                        } else {
+                            sendPendingIntent(action.actionIntent)
                         }
                     }
                 } else {
@@ -410,6 +498,9 @@
             }
         }
 
+        val isLocalSession = mediaController.playbackInfo?.playbackType ==
+            MediaController.PlaybackInfo.PLAYBACK_TYPE_LOCAL ?: true
+
         foregroundExecutor.execute {
             val resumeAction: Runnable? = mediaEntries[key]?.resumeAction
             val hasCheckedForResume = mediaEntries[key]?.hasCheckedForResume == true
@@ -417,8 +508,8 @@
             onMediaDataLoaded(key, oldKey, MediaData(sbn.normalizedUserId, true, bgColor, app,
                     smallIconDrawable, artist, song, artWorkIcon, actionIcons,
                     actionsToShowCollapsed, sbn.packageName, token, notif.contentIntent, null,
-                    active, resumeAction = resumeAction, notificationKey = key,
-                    hasCheckedForResume = hasCheckedForResume))
+                    active, resumeAction = resumeAction, isLocalSession = isLocalSession,
+                    notificationKey = key, hasCheckedForResume = hasCheckedForResume))
         }
     }
 
@@ -439,6 +530,15 @@
         return null
     }
 
+    private fun sendPendingIntent(intent: PendingIntent): Boolean {
+        return try {
+            intent.send()
+            true
+        } catch (e: PendingIntent.CanceledException) {
+            Log.d(TAG, "Intent canceled", e)
+            false
+        }
+    }
     /**
      * Load a bitmap from a URI
      * @param uri the uri to load
@@ -507,18 +607,16 @@
         if (mediaEntries.containsKey(key)) {
             // Otherwise this was removed already
             mediaEntries.put(key, data)
-            val listenersCopy = listeners.toSet()
-            listenersCopy.forEach {
-                it.onMediaDataLoaded(key, oldKey, data)
-            }
+            notifyMediaDataLoaded(key, oldKey, data)
         }
     }
 
     fun onNotificationRemoved(key: String) {
         Assert.isMainThread()
         val removed = mediaEntries.remove(key)
-        if (useMediaResumption && removed?.resumeAction != null) {
-            if (DEBUG) Log.d(TAG, "Not removing $key because resumable")
+        if (useMediaResumption && removed?.resumeAction != null &&
+                !isBlockedFromResume(removed?.packageName)) {
+            Log.d(TAG, "Not removing $key because resumable")
             // Move to resume key (aka package name) if that key doesn't already exist.
             val resumeAction = getResumeMediaAction(removed.resumeAction!!)
             val updated = removed.copy(token = null, actions = listOf(resumeAction),
@@ -526,34 +624,31 @@
             val pkg = removed?.packageName
             val migrate = mediaEntries.put(pkg, updated) == null
             // Notify listeners of "new" controls when migrating or removed and update when not
-            val listenersCopy = listeners.toSet()
             if (migrate) {
-                listenersCopy.forEach {
-                    it.onMediaDataLoaded(pkg, key, updated)
-                }
+                notifyMediaDataLoaded(pkg, key, updated)
             } else {
                 // Since packageName is used for the key of the resumption controls, it is
                 // possible that another notification has already been reused for the resumption
                 // controls of this package. In this case, rather than renaming this player as
                 // packageName, just remove it and then send a update to the existing resumption
                 // controls.
-                listenersCopy.forEach {
-                    it.onMediaDataRemoved(key)
-                }
-                listenersCopy.forEach {
-                    it.onMediaDataLoaded(pkg, pkg, updated)
-                }
+                notifyMediaDataRemoved(key)
+                notifyMediaDataLoaded(pkg, pkg, updated)
             }
             return
         }
         if (removed != null) {
-            val listenersCopy = listeners.toSet()
-            listenersCopy.forEach {
-                it.onMediaDataRemoved(key)
-            }
+            notifyMediaDataRemoved(key)
         }
     }
 
+    private fun isBlockedFromResume(packageName: String?): Boolean {
+        if (packageName == null) {
+            return true
+        }
+        return appsBlockedFromResume.contains(packageName)
+    }
+
     fun setMediaResumptionEnabled(isEnabled: Boolean) {
         if (useMediaResumption == isEnabled) {
             return
@@ -563,17 +658,31 @@
 
         if (!useMediaResumption) {
             // Remove any existing resume controls
-            val listenersCopy = listeners.toSet()
             val filtered = mediaEntries.filter { !it.value.active }
             filtered.forEach {
                 mediaEntries.remove(it.key)
-                listenersCopy.forEach { listener ->
-                    listener.onMediaDataRemoved(it.key)
-                }
+                notifyMediaDataRemoved(it.key)
             }
         }
     }
 
+    /**
+     * Invoked when the user has dismissed the media carousel
+     */
+    fun onSwipeToDismiss() = mediaDataFilter.onSwipeToDismiss()
+
+    /**
+     * Are there any media notifications active?
+     */
+    fun hasActiveMedia() = mediaDataFilter.hasActiveMedia()
+
+    /**
+     * Are there any media entries we should display?
+     * If resumption is enabled, this will include inactive players
+     * If resumption is disabled, we only want to show active players
+     */
+    fun hasAnyMedia() = mediaDataFilter.hasAnyMedia()
+
     interface Listener {
 
         /**
@@ -593,9 +702,11 @@
 
     override fun dump(fd: FileDescriptor, pw: PrintWriter, args: Array<out String>) {
         pw.apply {
-            println("listeners: $listeners")
+            println("internalListeners: $internalListeners")
+            println("externalListeners: ${mediaDataFilter.listeners}")
             println("mediaEntries: $mediaEntries")
             println("useMediaResumption: $useMediaResumption")
+            println("appsBlockedFromResume: $appsBlockedFromResume")
         }
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaDeviceManager.kt b/packages/SystemUI/src/com/android/systemui/media/MediaDeviceManager.kt
index ae7f66b..2bc908b 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaDeviceManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaDeviceManager.kt
@@ -24,34 +24,32 @@
 import androidx.annotation.WorkerThread
 import com.android.settingslib.media.LocalMediaManager
 import com.android.settingslib.media.MediaDevice
+import com.android.systemui.Dumpable
+import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Background
 import com.android.systemui.dagger.qualifiers.Main
-import com.android.systemui.Dumpable
 import com.android.systemui.dump.DumpManager
 import java.io.FileDescriptor
 import java.io.PrintWriter
 import java.util.concurrent.Executor
 import javax.inject.Inject
-import javax.inject.Singleton
 
 /**
  * Provides information about the route (ie. device) where playback is occurring.
  */
-@Singleton
 class MediaDeviceManager @Inject constructor(
     private val context: Context,
     private val localMediaManagerFactory: LocalMediaManagerFactory,
     private val mr2manager: MediaRouter2Manager,
     @Main private val fgExecutor: Executor,
     @Background private val bgExecutor: Executor,
-    private val mediaDataManager: MediaDataManager,
-    private val dumpManager: DumpManager
+    dumpManager: DumpManager
 ) : MediaDataManager.Listener, Dumpable {
+
     private val listeners: MutableSet<Listener> = mutableSetOf()
     private val entries: MutableMap<String, Entry> = mutableMapOf()
 
     init {
-        mediaDataManager.addListener(this)
         dumpManager.registerDumpable(javaClass.name, this)
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaHierarchyManager.kt b/packages/SystemUI/src/com/android/systemui/media/MediaHierarchyManager.kt
index fc33391..5475a00 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaHierarchyManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaHierarchyManager.kt
@@ -27,6 +27,7 @@
 import android.view.ViewGroup
 import android.view.ViewGroupOverlay
 import com.android.systemui.Interpolators
+import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.keyguard.WakefulnessLifecycle
 import com.android.systemui.plugins.statusbar.StatusBarStateController
 import com.android.systemui.statusbar.NotificationLockscreenUserManager
@@ -37,7 +38,6 @@
 import com.android.systemui.statusbar.policy.KeyguardStateController
 import com.android.systemui.util.animation.UniqueObjectHostView
 import javax.inject.Inject
-import javax.inject.Singleton
 
 /**
  * Similarly to isShown but also excludes views that have 0 alpha
@@ -65,7 +65,7 @@
  * This manager is responsible for placement of the unique media view between the different hosts
  * and animate the positions of the views to achieve seamless transitions.
  */
-@Singleton
+@SysUISingleton
 class MediaHierarchyManager @Inject constructor(
     private val context: Context,
     private val statusBarStateController: SysuiStatusBarStateController,
@@ -293,6 +293,13 @@
         return viewHost
     }
 
+    /**
+     * Close the guts in all players in [MediaCarouselController].
+     */
+    fun closeGuts() {
+        mediaCarouselController.closeGuts()
+    }
+
     private fun createUniqueObjectHost(): UniqueObjectHostView {
         val viewHost = UniqueObjectHostView(context)
         viewHost.addOnAttachStateChangeListener(object : View.OnAttachStateChangeListener {
@@ -382,6 +389,14 @@
         if (isCurrentlyInGuidedTransformation()) {
             return false
         }
+        // This is an invalid transition, and can happen when using the camera gesture from the
+        // lock screen. Disallow.
+        if (previousLocation == LOCATION_LOCKSCREEN &&
+            desiredLocation == LOCATION_QQS &&
+            statusbarState == StatusBarState.SHADE) {
+            return false
+        }
+
         if (currentLocation == LOCATION_QQS &&
                 previousLocation == LOCATION_LOCKSCREEN &&
                 (statusBarStateController.leaveOpenOnKeyguardHide() ||
@@ -597,8 +612,8 @@
             // When collapsing on the lockscreen, we want to remain in QS
             return LOCATION_QS
         }
-        if (location != LOCATION_LOCKSCREEN && desiredLocation == LOCATION_LOCKSCREEN
-                && !fullyAwake) {
+        if (location != LOCATION_LOCKSCREEN && desiredLocation == LOCATION_LOCKSCREEN &&
+                !fullyAwake) {
             // When unlocking from dozing / while waking up, the media shouldn't be transitioning
             // in an animated way. Let's keep it in the lockscreen until we're fully awake and
             // reattach it without an animation
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaHost.kt b/packages/SystemUI/src/com/android/systemui/media/MediaHost.kt
index 3598719..ce184aa 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaHost.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaHost.kt
@@ -14,7 +14,7 @@
 class MediaHost @Inject constructor(
     private val state: MediaHostStateHolder,
     private val mediaHierarchyManager: MediaHierarchyManager,
-    private val mediaDataFilter: MediaDataFilter,
+    private val mediaDataManager: MediaDataManager,
     private val mediaHostStatesManager: MediaHostStatesManager
 ) : MediaHostState by state {
     lateinit var hostView: UniqueObjectHostView
@@ -79,12 +79,12 @@
                 // be a delay until the views and the controllers are initialized, leaving us
                 // with either a blank view or the controllers not yet initialized and the
                 // measuring wrong
-                mediaDataFilter.addListener(listener)
+                mediaDataManager.addListener(listener)
                 updateViewVisibility()
             }
 
             override fun onViewDetachedFromWindow(v: View?) {
-                mediaDataFilter.removeListener(listener)
+                mediaDataManager.removeListener(listener)
             }
         })
 
@@ -113,9 +113,9 @@
 
     private fun updateViewVisibility() {
         visible = if (showsOnlyActiveMedia) {
-            mediaDataFilter.hasActiveMedia()
+            mediaDataManager.hasActiveMedia()
         } else {
-            mediaDataFilter.hasAnyMedia()
+            mediaDataManager.hasAnyMedia()
         }
         val newVisibility = if (visible) View.VISIBLE else View.GONE
         if (newVisibility != hostView.visibility) {
@@ -289,4 +289,4 @@
      * Get a copy of this view state, deepcopying all appropriate members
      */
     fun copy(): MediaHostState
-}
\ No newline at end of file
+}
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaHostStatesManager.kt b/packages/SystemUI/src/com/android/systemui/media/MediaHostStatesManager.kt
index d3954b7..ba7c167 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaHostStatesManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaHostStatesManager.kt
@@ -16,16 +16,16 @@
 
 package com.android.systemui.media
 
+import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.util.animation.MeasurementOutput
 import javax.inject.Inject
-import javax.inject.Singleton
 
 /**
  * A class responsible for managing all media host states of the various host locations and
  * coordinating the heights among different players. This class can be used to get the most up to
  * date state for any location.
  */
-@Singleton
+@SysUISingleton
 class MediaHostStatesManager @Inject constructor() {
 
     private val callbacks: MutableSet<Callback> = mutableSetOf()
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaResumeListener.kt b/packages/SystemUI/src/com/android/systemui/media/MediaResumeListener.kt
index 4ec746f..c00b5e9 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaResumeListener.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaResumeListener.kt
@@ -29,20 +29,20 @@
 import android.service.media.MediaBrowserService
 import android.util.Log
 import com.android.systemui.broadcast.BroadcastDispatcher
+import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Background
 import com.android.systemui.tuner.TunerService
 import com.android.systemui.util.Utils
 import java.util.concurrent.ConcurrentLinkedQueue
 import java.util.concurrent.Executor
 import javax.inject.Inject
-import javax.inject.Singleton
 
 private const val TAG = "MediaResumeListener"
 
 private const val MEDIA_PREFERENCES = "media_control_prefs"
 private const val MEDIA_PREFERENCE_KEY = "browser_components_"
 
-@Singleton
+@SysUISingleton
 class MediaResumeListener @Inject constructor(
     private val context: Context,
     private val broadcastDispatcher: BroadcastDispatcher,
@@ -52,6 +52,7 @@
 
     private var useMediaResumption: Boolean = Utils.useMediaResumption(context)
     private val resumeComponents: ConcurrentLinkedQueue<ComponentName> = ConcurrentLinkedQueue()
+    private var blockedApps: MutableSet<String> = Utils.getBlockedMediaApps(context)
 
     private lateinit var mediaDataManager: MediaDataManager
 
@@ -114,6 +115,14 @@
                 mediaDataManager.setMediaResumptionEnabled(useMediaResumption)
             }
         }, Settings.Secure.MEDIA_CONTROLS_RESUME)
+
+        // Listen to changes in which apps are allowed to persist
+        tunerService.addTunable(object : TunerService.Tunable {
+            override fun onTuningChanged(key: String?, newValue: String?) {
+                blockedApps = Utils.getBlockedMediaApps(context)
+                mediaDataManager.appsBlockedFromResume = blockedApps
+            }
+        }, Settings.Secure.MEDIA_CONTROLS_RESUME_BLOCKED)
     }
 
     fun isResumptionEnabled() = useMediaResumption
@@ -144,8 +153,10 @@
         }
 
         resumeComponents.forEach {
-            val browser = ResumeMediaBrowser(context, mediaBrowserCallback, it)
-            browser.findRecentMedia()
+            if (!blockedApps.contains(it.packageName)) {
+                val browser = ResumeMediaBrowser(context, mediaBrowserCallback, it)
+                browser.findRecentMedia()
+            }
         }
     }
 
@@ -154,7 +165,8 @@
             // If this had been started from a resume state, disconnect now that it's live
             mediaBrowser?.disconnect()
             // If we don't have a resume action, check if we haven't already
-            if (data.resumeAction == null && !data.hasCheckedForResume) {
+            if (data.resumeAction == null && !data.hasCheckedForResume &&
+                    !blockedApps.contains(data.packageName)) {
                 // TODO also check for a media button receiver intended for restarting (b/154127084)
                 Log.d(TAG, "Checking for service component for " + data.packageName)
                 val pm = context.packageManager
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaSessionBasedFilter.kt b/packages/SystemUI/src/com/android/systemui/media/MediaSessionBasedFilter.kt
new file mode 100644
index 0000000..f01713f
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaSessionBasedFilter.kt
@@ -0,0 +1,163 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.media
+
+import android.content.ComponentName
+import android.content.Context
+import android.media.session.MediaController
+import android.media.session.MediaController.PlaybackInfo
+import android.media.session.MediaSession
+import android.media.session.MediaSessionManager
+import android.util.Log
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.statusbar.phone.NotificationListenerWithPlugins
+import java.util.concurrent.Executor
+import javax.inject.Inject
+
+private const val TAG = "MediaSessionBasedFilter"
+
+/**
+ * Filters media loaded events for local media sessions while an app is casting.
+ *
+ * When an app is casting there can be one remote media sessions and potentially more local media
+ * sessions. In this situation, there should only be a media object for the remote session. To
+ * achieve this, update events for the local session need to be filtered.
+ */
+class MediaSessionBasedFilter @Inject constructor(
+    context: Context,
+    private val sessionManager: MediaSessionManager,
+    @Main private val foregroundExecutor: Executor,
+    @Background private val backgroundExecutor: Executor
+) : MediaDataManager.Listener {
+
+    private val listeners: MutableSet<MediaDataManager.Listener> = mutableSetOf()
+
+    // Keep track of MediaControllers for a given package to check if an app is casting and it
+    // filter loaded events for local sessions.
+    private val packageControllers: LinkedHashMap<String, MutableList<MediaController>> =
+            LinkedHashMap()
+
+    // Keep track of the key used for the session tokens. This information is used to know when
+    // dispatch a removed event so that a media object for a local session will be removed.
+    private val keyedTokens: MutableMap<String, MutableList<MediaSession.Token>> = mutableMapOf()
+
+    private val sessionListener = object : MediaSessionManager.OnActiveSessionsChangedListener {
+        override fun onActiveSessionsChanged(controllers: List<MediaController>) {
+            handleControllersChanged(controllers)
+        }
+    }
+
+    init {
+        backgroundExecutor.execute {
+            val name = ComponentName(context, NotificationListenerWithPlugins::class.java)
+            sessionManager.addOnActiveSessionsChangedListener(sessionListener, name)
+            handleControllersChanged(sessionManager.getActiveSessions(name))
+        }
+    }
+
+    /**
+     * Add a listener for filtered [MediaData] changes
+     */
+    fun addListener(listener: MediaDataManager.Listener) = listeners.add(listener)
+
+    /**
+     * Remove a listener that was registered with addListener
+     */
+    fun removeListener(listener: MediaDataManager.Listener) = listeners.remove(listener)
+
+    /**
+     * May filter loaded events by not passing them along to listeners.
+     *
+     * If an app has only one session with playback type PLAYBACK_TYPE_REMOTE, then assuming that
+     * the app is casting. Sometimes apps will send redundant updates to a local session with
+     * playback type PLAYBACK_TYPE_LOCAL. These updates should be filtered to improve the usability
+     * of the media controls.
+     */
+    override fun onMediaDataLoaded(key: String, oldKey: String?, info: MediaData) {
+        backgroundExecutor.execute {
+            val isMigration = oldKey != null && key != oldKey
+            if (isMigration) {
+                keyedTokens.remove(oldKey)?.let { removed -> keyedTokens.put(key, removed) }
+            }
+            if (info.token != null) {
+                keyedTokens.get(key)?.let {
+                    tokens ->
+                    tokens.add(info.token)
+                } ?: run {
+                    val tokens = mutableListOf(info.token)
+                    keyedTokens.put(key, tokens)
+                }
+            }
+            // Determine if an app is casting by checking if it has a session with playback type
+            // PLAYBACK_TYPE_REMOTE.
+            val remoteControllers = packageControllers.get(info.packageName)?.filter {
+                it.playbackInfo?.playbackType == PlaybackInfo.PLAYBACK_TYPE_REMOTE
+            }
+            // Limiting search to only apps with a single remote session.
+            val remote = if (remoteControllers?.size == 1) remoteControllers.firstOrNull() else null
+            if (isMigration || remote == null || remote.sessionToken == info.token) {
+                // Not filtering in this case. Passing the event along to listeners.
+                dispatchMediaDataLoaded(key, oldKey, info)
+            } else {
+                // Filtering this event because the app is casting and the loaded events is for a
+                // local session.
+                Log.d(TAG, "filtering key=$key local=${info.token} remote=${remote?.sessionToken}")
+                // If the local session uses a different notification key, then lets go a step
+                // farther and dismiss the media data so that media controls for the local session
+                // don't hang around while casting.
+                if (!keyedTokens.get(key)!!.contains(remote.sessionToken)) {
+                    dispatchMediaDataRemoved(key)
+                }
+            }
+        }
+    }
+
+    override fun onMediaDataRemoved(key: String) {
+        // Queue on background thread to ensure ordering of loaded and removed events is maintained.
+        backgroundExecutor.execute {
+            keyedTokens.remove(key)
+            dispatchMediaDataRemoved(key)
+        }
+    }
+
+    private fun dispatchMediaDataLoaded(key: String, oldKey: String?, info: MediaData) {
+        foregroundExecutor.execute {
+            listeners.toSet().forEach { it.onMediaDataLoaded(key, oldKey, info) }
+        }
+    }
+
+    private fun dispatchMediaDataRemoved(key: String) {
+        foregroundExecutor.execute {
+            listeners.toSet().forEach { it.onMediaDataRemoved(key) }
+        }
+    }
+
+    private fun handleControllersChanged(controllers: List<MediaController>) {
+        packageControllers.clear()
+        controllers.forEach {
+            controller ->
+            packageControllers.get(controller.packageName)?.let {
+                tokens ->
+                tokens.add(controller)
+            } ?: run {
+                val tokens = mutableListOf(controller)
+                packageControllers.put(controller.packageName, tokens)
+            }
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaTimeoutListener.kt b/packages/SystemUI/src/com/android/systemui/media/MediaTimeoutListener.kt
index 8662aac..6bd5274 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaTimeoutListener.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaTimeoutListener.kt
@@ -20,12 +20,12 @@
 import android.media.session.PlaybackState
 import android.os.SystemProperties
 import android.util.Log
+import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Main
 import com.android.systemui.statusbar.NotificationMediaManager.isPlayingState
 import com.android.systemui.util.concurrency.DelayableExecutor
 import java.util.concurrent.TimeUnit
 import javax.inject.Inject
-import javax.inject.Singleton
 
 private const val DEBUG = true
 private const val TAG = "MediaTimeout"
@@ -35,7 +35,7 @@
 /**
  * Controller responsible for keeping track of playback states and expiring inactive streams.
  */
-@Singleton
+@SysUISingleton
 class MediaTimeoutListener @Inject constructor(
     private val mediaControllerFactory: MediaControllerFactory,
     @Main private val mainExecutor: DelayableExecutor
@@ -54,32 +54,35 @@
         if (mediaListeners.containsKey(key)) {
             return
         }
-        // Having an old key means that we're migrating from/to resumption. We should invalidate
-        // the old listener and create a new one.
+        // Having an old key means that we're migrating from/to resumption. We should update
+        // the old listener to make sure that events will be dispatched to the new location.
         val migrating = oldKey != null && key != oldKey
         var wasPlaying = false
         if (migrating) {
-            if (mediaListeners.containsKey(oldKey)) {
-                val oldListener = mediaListeners.remove(oldKey)
-                wasPlaying = oldListener?.playing ?: false
-                oldListener?.destroy()
+            val reusedListener = mediaListeners.remove(oldKey)
+            if (reusedListener != null) {
+                wasPlaying = reusedListener.playing ?: false
                 if (DEBUG) Log.d(TAG, "migrating key $oldKey to $key, for resumption")
+                reusedListener.mediaData = data
+                reusedListener.key = key
+                mediaListeners[key] = reusedListener
+                if (wasPlaying != reusedListener.playing) {
+                    // If a player becomes active because of a migration, we'll need to broadcast
+                    // its state. Doing it now would lead to reentrant callbacks, so let's wait
+                    // until we're done.
+                    mainExecutor.execute {
+                        if (mediaListeners[key]?.playing == true) {
+                            if (DEBUG) Log.d(TAG, "deliver delayed playback state for $key")
+                            timeoutCallback.invoke(key, false /* timedOut */)
+                        }
+                    }
+                }
+                return
             } else {
                 Log.w(TAG, "Old key $oldKey for player $key doesn't exist. Continuing...")
             }
         }
         mediaListeners[key] = PlaybackStateListener(key, data)
-
-        // If a player becomes active because of a migration, we'll need to broadcast its state.
-        // Doing it now would lead to reentrant callbacks, so let's wait until we're done.
-        if (migrating && mediaListeners[key]?.playing != wasPlaying) {
-            mainExecutor.execute {
-                if (mediaListeners[key]?.playing == true) {
-                    if (DEBUG) Log.d(TAG, "deliver delayed playback state for $key")
-                    timeoutCallback.invoke(key, false /* timedOut */)
-                }
-            }
-        }
     }
 
     override fun onMediaDataRemoved(key: String) {
@@ -91,26 +94,34 @@
     }
 
     private inner class PlaybackStateListener(
-        private val key: String,
+        var key: String,
         data: MediaData
     ) : MediaController.Callback() {
 
         var timedOut = false
         var playing: Boolean? = null
 
+        var mediaData: MediaData = data
+            set(value) {
+                mediaController?.unregisterCallback(this)
+                field = value
+                mediaController = if (field.token != null) {
+                    mediaControllerFactory.create(field.token)
+                } else {
+                    null
+                }
+                mediaController?.registerCallback(this)
+                // Let's register the cancellations, but not dispatch events now.
+                // Timeouts didn't happen yet and reentrant events are troublesome.
+                processState(mediaController?.playbackState, dispatchEvents = false)
+            }
+
         // Resume controls may have null token
-        private val mediaController = if (data.token != null) {
-            mediaControllerFactory.create(data.token)
-        } else {
-            null
-        }
+        private var mediaController: MediaController? = null
         private var cancellation: Runnable? = null
 
         init {
-            mediaController?.registerCallback(this)
-            // Let's register the cancellations, but not dispatch events now.
-            // Timeouts didn't happen yet and reentrant events are troublesome.
-            processState(mediaController?.playbackState, dispatchEvents = false)
+            mediaData = data
         }
 
         fun destroy() {
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaViewController.kt b/packages/SystemUI/src/com/android/systemui/media/MediaViewController.kt
index 38817d7..92eeed4 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaViewController.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaViewController.kt
@@ -37,6 +37,11 @@
     private val mediaHostStatesManager: MediaHostStatesManager
 ) {
 
+    companion object {
+        @JvmField
+        val GUTS_ANIMATION_DURATION = 500L
+    }
+
     /**
      * A listener when the current dimensions of the player change
      */
@@ -169,6 +174,12 @@
      */
     val expandedLayout = ConstraintSet()
 
+    /**
+     * Whether the guts are visible for the associated player.
+     */
+    var isGutsVisible = false
+        private set
+
     init {
         collapsedLayout.load(context, R.xml.media_collapsed)
         expandedLayout.load(context, R.xml.media_expanded)
@@ -189,6 +200,37 @@
         configurationController.removeCallback(configurationListener)
     }
 
+    /**
+     * Show guts with an animated transition.
+     */
+    fun openGuts() {
+        if (isGutsVisible) return
+        isGutsVisible = true
+        animatePendingStateChange(GUTS_ANIMATION_DURATION, 0L)
+        setCurrentState(currentStartLocation,
+                currentEndLocation,
+                currentTransitionProgress,
+                applyImmediately = false)
+    }
+
+    /**
+     * Close the guts for the associated player.
+     *
+     * @param immediate if `false`, it will animate the transition.
+     */
+    @JvmOverloads
+    fun closeGuts(immediate: Boolean = false) {
+        if (!isGutsVisible) return
+        isGutsVisible = false
+        if (!immediate) {
+            animatePendingStateChange(GUTS_ANIMATION_DURATION, 0L)
+        }
+        setCurrentState(currentStartLocation,
+                currentEndLocation,
+                currentTransitionProgress,
+                applyImmediately = immediate)
+    }
+
     private fun ensureAllMeasurements() {
         val mediaStates = mediaHostStatesManager.mediaHostStates
         for (entry in mediaStates) {
@@ -203,6 +245,24 @@
             if (expansion > 0) expandedLayout else collapsedLayout
 
     /**
+     * Set the views to be showing/hidden based on the [isGutsVisible] for a given
+     * [TransitionViewState].
+     */
+    private fun setGutsViewState(viewState: TransitionViewState) {
+        PlayerViewHolder.controlsIds.forEach { id ->
+            viewState.widgetStates.get(id)?.let { state ->
+                // Make sure to use the unmodified state if guts are not visible
+                state.alpha = if (isGutsVisible) 0f else state.alpha
+                state.gone = if (isGutsVisible) true else state.gone
+            }
+        }
+        PlayerViewHolder.gutsIds.forEach { id ->
+            viewState.widgetStates.get(id)?.alpha = if (isGutsVisible) 1f else 0f
+            viewState.widgetStates.get(id)?.gone = !isGutsVisible
+        }
+    }
+
+    /**
      * Obtain a new viewState for a given media state. This usually returns a cached state, but if
      * it's not available, it will recreate one by measuring, which may be expensive.
      */
@@ -211,7 +271,7 @@
             return null
         }
         // Only a subset of the state is relevant to get a valid viewState. Let's get the cachekey
-        var cacheKey = getKey(state, tmpKey)
+        var cacheKey = getKey(state, isGutsVisible, tmpKey)
         val viewState = viewStates[cacheKey]
         if (viewState != null) {
             // we already have cached this measurement, let's continue
@@ -228,6 +288,7 @@
                         constraintSetForExpansion(state.expansion),
                         TransitionViewState())
 
+                setGutsViewState(result)
                 // We don't want to cache interpolated or null states as this could quickly fill up
                 // our cache. We only cache the start and the end states since the interpolation
                 // is cheap
@@ -252,11 +313,12 @@
         return result
     }
 
-    private fun getKey(state: MediaHostState, result: CacheKey): CacheKey {
+    private fun getKey(state: MediaHostState, guts: Boolean, result: CacheKey): CacheKey {
         result.apply {
             heightMeasureSpec = state.measurementInput?.heightMeasureSpec ?: 0
             widthMeasureSpec = state.measurementInput?.widthMeasureSpec ?: 0
             expansion = state.expansion
+            gutsVisible = guts
         }
         return result
     }
@@ -432,5 +494,6 @@
 private data class CacheKey(
     var widthMeasureSpec: Int = -1,
     var heightMeasureSpec: Int = -1,
-    var expansion: Float = 0.0f
+    var expansion: Float = 0.0f,
+    var gutsVisible: Boolean = false
 )
diff --git a/packages/SystemUI/src/com/android/systemui/media/PlayerViewHolder.kt b/packages/SystemUI/src/com/android/systemui/media/PlayerViewHolder.kt
index 600fdc2..11551ac 100644
--- a/packages/SystemUI/src/com/android/systemui/media/PlayerViewHolder.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/PlayerViewHolder.kt
@@ -23,6 +23,7 @@
 import android.widget.ImageView
 import android.widget.SeekBar
 import android.widget.TextView
+import androidx.constraintlayout.widget.ConstraintSet
 import com.android.systemui.R
 import com.android.systemui.util.animation.TransitionLayout
 
@@ -59,6 +60,11 @@
     val action3 = itemView.requireViewById<ImageButton>(R.id.action3)
     val action4 = itemView.requireViewById<ImageButton>(R.id.action4)
 
+    // Settings screen
+    val cancel = itemView.requireViewById<View>(R.id.cancel)
+    val dismiss = itemView.requireViewById<View>(R.id.dismiss)
+    val settings = itemView.requireViewById<View>(R.id.settings)
+
     init {
         (player.background as IlluminationDrawable).let {
             it.registerLightSource(seamless)
@@ -67,6 +73,9 @@
             it.registerLightSource(action2)
             it.registerLightSource(action3)
             it.registerLightSource(action4)
+            it.registerLightSource(cancel)
+            it.registerLightSource(dismiss)
+            it.registerLightSource(settings)
         }
     }
 
@@ -83,9 +92,6 @@
         }
     }
 
-    // Settings screen
-    val options = itemView.requireViewById<View>(R.id.qs_media_controls_options)
-
     companion object {
         /**
          * Creates a PlayerViewHolder.
@@ -105,5 +111,29 @@
                 progressTimes.layoutDirection = View.LAYOUT_DIRECTION_LTR
             }
         }
+
+        val controlsIds = setOf(
+                R.id.icon,
+                R.id.app_name,
+                R.id.album_art,
+                R.id.header_title,
+                R.id.header_artist,
+                R.id.media_seamless,
+                R.id.notification_media_progress_time,
+                R.id.media_progress_bar,
+                R.id.action0,
+                R.id.action1,
+                R.id.action2,
+                R.id.action3,
+                R.id.action4,
+                R.id.icon
+        )
+        val gutsIds = setOf(
+                R.id.media_text,
+                R.id.remove_text,
+                R.id.cancel,
+                R.id.dismiss,
+                R.id.settings
+        )
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/model/SysUiState.java b/packages/SystemUI/src/com/android/systemui/model/SysUiState.java
index ccf58ba..5dce093 100644
--- a/packages/SystemUI/src/com/android/systemui/model/SysUiState.java
+++ b/packages/SystemUI/src/com/android/systemui/model/SysUiState.java
@@ -22,6 +22,7 @@
 import android.util.Log;
 
 import com.android.systemui.Dumpable;
+import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.shared.system.QuickStepContract;
 
 import java.io.FileDescriptor;
@@ -29,13 +30,11 @@
 import java.util.ArrayList;
 import java.util.List;
 
-import javax.inject.Singleton;
-
 /**
  * Contains sysUi state flags and notifies registered
  * listeners whenever changes happen.
  */
-@Singleton
+@SysUISingleton
 public class SysUiState implements Dumpable {
 
     private static final String TAG = SysUiState.class.getSimpleName();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java
similarity index 83%
rename from packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java
rename to packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java
index 62970525..94a2bd9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java
@@ -1,18 +1,20 @@
 /*
- * Copyright (C) 2017 The Android Open Source Project
+ * 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
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
  *
  *      http://www.apache.org/licenses/LICENSE-2.0
  *
- * Unless required by applicable law or agreed to in writing, software distributed under the
- * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the specific language governing
- * permissions and limitations under the License.
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
  */
 
-package com.android.systemui.statusbar.phone;
+package com.android.systemui.navigationbar;
 
 import static android.app.StatusBarManager.NAVIGATION_HINT_BACK_ALT;
 import static android.app.StatusBarManager.NAVIGATION_HINT_IME_SHOWN;
@@ -21,6 +23,7 @@
 import static android.app.StatusBarManager.WindowType;
 import static android.app.StatusBarManager.WindowVisibleState;
 import static android.app.StatusBarManager.windowStateToString;
+import static android.view.Display.DEFAULT_DISPLAY;
 import static android.view.InsetsState.ITYPE_NAVIGATION_BAR;
 import static android.view.InsetsState.containsType;
 import static android.view.WindowInsetsController.APPEARANCE_LOW_PROFILE_BARS;
@@ -78,17 +81,16 @@
 import android.util.Log;
 import android.view.Display;
 import android.view.Gravity;
+import android.view.IWindowManager;
 import android.view.InsetsState.InternalInsetsType;
 import android.view.KeyEvent;
 import android.view.LayoutInflater;
 import android.view.MotionEvent;
 import android.view.Surface;
 import android.view.View;
-import android.view.ViewGroup;
 import android.view.ViewTreeObserver;
 import android.view.WindowInsetsController.Appearance;
 import android.view.WindowManager;
-import android.view.WindowManager.LayoutParams;
 import android.view.accessibility.AccessibilityEvent;
 import android.view.accessibility.AccessibilityManager;
 import android.view.accessibility.AccessibilityManager.AccessibilityServicesStateChangeListener;
@@ -102,52 +104,53 @@
 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
 import com.android.internal.util.LatencyTracker;
 import com.android.internal.view.AppearanceRegion;
+import com.android.systemui.Dependency;
 import com.android.systemui.R;
 import com.android.systemui.accessibility.SystemActions;
 import com.android.systemui.assist.AssistHandleViewController;
 import com.android.systemui.assist.AssistManager;
 import com.android.systemui.broadcast.BroadcastDispatcher;
 import com.android.systemui.dagger.qualifiers.Main;
-import com.android.systemui.fragments.FragmentHostManager;
-import com.android.systemui.fragments.FragmentHostManager.FragmentListener;
 import com.android.systemui.model.SysUiState;
+import com.android.systemui.navigationbar.buttons.ButtonDispatcher;
+import com.android.systemui.navigationbar.buttons.KeyButtonView;
+import com.android.systemui.navigationbar.buttons.RotationContextButton;
+import com.android.systemui.navigationbar.gestural.QuickswitchOrientedNavHandle;
+import com.android.systemui.plugins.DarkIconDispatcher;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.recents.OverviewProxyService;
 import com.android.systemui.recents.Recents;
 import com.android.systemui.shared.system.ActivityManagerWrapper;
 import com.android.systemui.shared.system.QuickStepContract;
-import com.android.systemui.stackdivider.Divider;
+import com.android.systemui.stackdivider.SplitScreenController;
 import com.android.systemui.statusbar.AutoHideUiElement;
 import com.android.systemui.statusbar.CommandQueue;
 import com.android.systemui.statusbar.CommandQueue.Callbacks;
-import com.android.systemui.statusbar.NavigationBarController;
 import com.android.systemui.statusbar.NotificationRemoteInputManager;
 import com.android.systemui.statusbar.StatusBarState;
 import com.android.systemui.statusbar.notification.stack.StackStateAnimator;
-import com.android.systemui.statusbar.phone.ContextualButton.ContextButtonListener;
+import com.android.systemui.statusbar.phone.AutoHideController;
+import com.android.systemui.statusbar.phone.BarTransitions;
+import com.android.systemui.statusbar.phone.LightBarController;
+import com.android.systemui.statusbar.phone.ShadeController;
+import com.android.systemui.statusbar.phone.StatusBar;
 import com.android.systemui.statusbar.policy.AccessibilityManagerWrapper;
+import com.android.systemui.statusbar.policy.BatteryController;
 import com.android.systemui.statusbar.policy.DeviceProvisionedController;
-import com.android.systemui.statusbar.policy.KeyButtonView;
-import com.android.systemui.util.LifecycleFragment;
 
-import java.io.FileDescriptor;
 import java.io.PrintWriter;
-import java.lang.ref.WeakReference;
 import java.util.List;
 import java.util.Locale;
 import java.util.Optional;
 import java.util.function.Consumer;
 
-import javax.inject.Inject;
-
 import dagger.Lazy;
 
 /**
- * Fragment containing the NavigationBarFragment. Contains logic for what happens
- * on clicks and view states of the nav bar.
+ * Contains logic for a navigation bar view.
  */
-public class NavigationBarFragment extends LifecycleFragment implements Callbacks,
-        NavigationModeController.ModeChangedListener, DisplayManager.DisplayListener {
+public class NavigationBar implements View.OnAttachStateChangeListener,
+        Callbacks, NavigationModeController.ModeChangedListener, DisplayManager.DisplayListener {
 
     public static final String TAG = "NavigationBar";
     private static final boolean DEBUG = false;
@@ -160,40 +163,47 @@
     private static final int LOCK_TO_APP_GESTURE_TOLERENCE = 200;
     private static final long AUTODIM_TIMEOUT_MS = 2250;
 
+    private final Context mContext;
+    private final WindowManager mWindowManager;
+    private final AccessibilityManager mAccessibilityManager;
     private final AccessibilityManagerWrapper mAccessibilityManagerWrapper;
-    protected final AssistManager mAssistManager;
-    private SysUiState mSysUiFlagsContainer;
-    private final MetricsLogger mMetricsLogger;
     private final DeviceProvisionedController mDeviceProvisionedController;
     private final StatusBarStateController mStatusBarStateController;
+    private final MetricsLogger mMetricsLogger;
+    private final Lazy<AssistManager> mAssistManagerLazy;
+    private final SysUiState mSysUiFlagsContainer;
+    private final Lazy<StatusBar> mStatusBarLazy;
+    private final ShadeController mShadeController;
+    private final NotificationRemoteInputManager mNotificationRemoteInputManager;
+    private final OverviewProxyService mOverviewProxyService;
     private final NavigationModeController mNavigationModeController;
+    private final BroadcastDispatcher mBroadcastDispatcher;
+    private final CommandQueue mCommandQueue;
+    private final Optional<SplitScreenController> mSplitScreenControllerOptional;
+    private final Optional<Recents> mRecentsOptional;
+    private final SystemActions mSystemActions;
+    private final Handler mHandler;
+    private final UiEventLogger mUiEventLogger;
 
-    protected NavigationBarView mNavigationBarView = null;
+    private Bundle mSavedState;
+    private NavigationBarView mNavigationBarView = null;
 
     private @WindowVisibleState int mNavigationBarWindowState = WINDOW_STATE_SHOWING;
 
     private int mNavigationIconHints = 0;
     private @TransitionMode int mNavigationBarMode;
-    private AccessibilityManager mAccessibilityManager;
     private ContentResolver mContentResolver;
     private boolean mAssistantAvailable;
 
     private int mDisabledFlags1;
     private int mDisabledFlags2;
-    private final Lazy<StatusBar> mStatusBarLazy;
-    private final ShadeController mShadeController;
-    private final NotificationRemoteInputManager mNotificationRemoteInputManager;
-    private final Divider mDivider;
-    private final Optional<Recents> mRecentsOptional;
-    private WindowManager mWindowManager;
-    private final CommandQueue mCommandQueue;
     private long mLastLockToAppLongPress;
-    private final SystemActions mSystemActions;
 
     private Locale mLocale;
     private int mLayoutDirection;
 
     private boolean mForceNavBarHandleOpaque;
+    private boolean mIsCurrentUserSetup;
 
     /** @see android.view.WindowInsetsController#setSystemBarsAppearance(int) */
     private @Appearance int mAppearance;
@@ -203,10 +213,6 @@
     private LightBarController mLightBarController;
     private AutoHideController mAutoHideController;
 
-    private OverviewProxyService mOverviewProxyService;
-
-    private final BroadcastDispatcher mBroadcastDispatcher;
-
     @VisibleForTesting
     public int mDisplayId;
     private boolean mIsOnDefaultDisplay;
@@ -227,7 +233,6 @@
     private int mStartingQuickSwitchRotation = -1;
     private int mCurrentRotation;
     private ViewTreeObserver.OnGlobalLayoutListener mOrientationHandleGlobalLayoutListener;
-    private UiEventLogger mUiEventLogger;
     private boolean mShowOrientedHandleForImmersiveMode;
 
     @com.android.internal.annotations.VisibleForTesting
@@ -252,7 +257,6 @@
     @Nullable
     private AssistHandleViewController mAssistHandlerViewController;
 
-    private final Handler mHandler;
 
     private final AutoHideUiElement mAutoHideUiElement = new AutoHideUiElement() {
         @Override
@@ -308,11 +312,15 @@
 
         @Override
         public void startAssistant(Bundle bundle) {
-            mAssistManager.startAssist(bundle);
+            mAssistManagerLazy.get().startAssist(bundle);
         }
 
         @Override
         public void onNavBarButtonAlphaChanged(float alpha, boolean animate) {
+            if (!mIsCurrentUserSetup) {
+                // If the current user is not yet setup, then don't update any button alphas
+                return;
+            }
             ButtonDispatcher buttonDispatcher = null;
             boolean forceVisible = false;
             if (QuickStepContract.isSwipeUpMode(mNavBarMode)) {
@@ -351,22 +359,13 @@
                 }
             };
 
-    private final ContextButtonListener mRotationButtonListener = (button, visible) -> {
-        if (visible) {
-            // If the button will actually become visible and the navbar is about to hide,
-            // tell the statusbar to keep it around for longer
-            mAutoHideController.touchAutoHide();
-            mNavigationBarView.notifyActiveTouchRegions();
-        }
-    };
-
     private final Runnable mAutoDim = () -> getBarTransitions().setAutoDim(true);
 
     private final ContentObserver mAssistContentObserver = new ContentObserver(
             new Handler(Looper.getMainLooper())) {
         @Override
         public void onChange(boolean selfChange, Uri uri) {
-            boolean available = mAssistManager
+            boolean available = mAssistManagerLazy.get()
                     .getAssistInfoForUser(UserHandle.USER_CURRENT) != null;
             if (mAssistantAvailable != available) {
                 sendAssistantAvailability(available);
@@ -377,71 +376,114 @@
 
     private final DeviceConfig.OnPropertiesChangedListener mOnPropertiesChangedListener =
             new DeviceConfig.OnPropertiesChangedListener() {
-        @Override
-        public void onPropertiesChanged(DeviceConfig.Properties properties) {
-            if (properties.getKeyset().contains(NAV_BAR_HANDLE_FORCE_OPAQUE)) {
-                mForceNavBarHandleOpaque = properties.getBoolean(
-                        NAV_BAR_HANDLE_FORCE_OPAQUE, /* defaultValue = */ true);
-            }
-        }
-    };
+                @Override
+                public void onPropertiesChanged(DeviceConfig.Properties properties) {
+                    if (properties.getKeyset().contains(NAV_BAR_HANDLE_FORCE_OPAQUE)) {
+                        mForceNavBarHandleOpaque = properties.getBoolean(
+                                NAV_BAR_HANDLE_FORCE_OPAQUE, /* defaultValue = */ true);
+                    }
+                }
+            };
 
-    @Inject
-    public NavigationBarFragment(AccessibilityManagerWrapper accessibilityManagerWrapper,
-            DeviceProvisionedController deviceProvisionedController, MetricsLogger metricsLogger,
-            AssistManager assistManager, OverviewProxyService overviewProxyService,
+    private final DeviceProvisionedController.DeviceProvisionedListener mUserSetupListener =
+            new DeviceProvisionedController.DeviceProvisionedListener() {
+                @Override
+                public void onUserSetupChanged() {
+                    mIsCurrentUserSetup = mDeviceProvisionedController.isCurrentUserSetup();
+                }
+            };
+
+    public NavigationBar(Context context,
+            WindowManager windowManager,
+            Lazy<AssistManager> assistManagerLazy,
+            AccessibilityManager accessibilityManager,
+            AccessibilityManagerWrapper accessibilityManagerWrapper,
+            DeviceProvisionedController deviceProvisionedController,
+            MetricsLogger metricsLogger,
+            OverviewProxyService overviewProxyService,
             NavigationModeController navigationModeController,
             StatusBarStateController statusBarStateController,
             SysUiState sysUiFlagsContainer,
             BroadcastDispatcher broadcastDispatcher,
-            CommandQueue commandQueue, Divider divider,
+            CommandQueue commandQueue,
+            Optional<SplitScreenController> splitScreenControllerOptional,
             Optional<Recents> recentsOptional, Lazy<StatusBar> statusBarLazy,
             ShadeController shadeController,
             NotificationRemoteInputManager notificationRemoteInputManager,
             SystemActions systemActions,
             @Main Handler mainHandler,
             UiEventLogger uiEventLogger) {
+        mContext = context;
+        mWindowManager = windowManager;
+        mAccessibilityManager = accessibilityManager;
         mAccessibilityManagerWrapper = accessibilityManagerWrapper;
         mDeviceProvisionedController = deviceProvisionedController;
         mStatusBarStateController = statusBarStateController;
         mMetricsLogger = metricsLogger;
-        mAssistManager = assistManager;
+        mAssistManagerLazy = assistManagerLazy;
         mSysUiFlagsContainer = sysUiFlagsContainer;
         mStatusBarLazy = statusBarLazy;
         mShadeController = shadeController;
         mNotificationRemoteInputManager = notificationRemoteInputManager;
-        mAssistantAvailable = mAssistManager.getAssistInfoForUser(UserHandle.USER_CURRENT) != null;
         mOverviewProxyService = overviewProxyService;
         mNavigationModeController = navigationModeController;
         mNavBarMode = navigationModeController.addListener(this);
         mBroadcastDispatcher = broadcastDispatcher;
         mCommandQueue = commandQueue;
-        mDivider = divider;
+        mSplitScreenControllerOptional = splitScreenControllerOptional;
         mRecentsOptional = recentsOptional;
         mSystemActions = systemActions;
         mHandler = mainHandler;
         mUiEventLogger = uiEventLogger;
     }
 
-    // ----- Fragment Lifecycle Callbacks -----
+    public View getView() {
+        return mNavigationBarView;
+    }
 
-    @Override
-    public void onCreate(@Nullable Bundle savedInstanceState) {
-        super.onCreate(savedInstanceState);
-        mCommandQueue.observe(getLifecycle(), this);
-        mWindowManager = getContext().getSystemService(WindowManager.class);
-        mAccessibilityManager = getContext().getSystemService(AccessibilityManager.class);
-        mContentResolver = getContext().getContentResolver();
+    public View createView(Bundle savedState) {
+        WindowManager.LayoutParams lp = new WindowManager.LayoutParams(
+                WindowManager.LayoutParams.MATCH_PARENT, WindowManager.LayoutParams.MATCH_PARENT,
+                WindowManager.LayoutParams.TYPE_NAVIGATION_BAR,
+                WindowManager.LayoutParams.FLAG_TOUCHABLE_WHEN_WAKING
+                        | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
+                        | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
+                        | WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH
+                        | WindowManager.LayoutParams.FLAG_SPLIT_TOUCH
+                        | WindowManager.LayoutParams.FLAG_SLIPPERY,
+                PixelFormat.TRANSLUCENT);
+        lp.token = new Binder();
+        lp.setTitle("NavigationBar" + mContext.getDisplayId());
+        lp.accessibilityTitle = mContext.getString(R.string.nav_bar);
+        lp.windowAnimations = 0;
+        lp.privateFlags |= WindowManager.LayoutParams.PRIVATE_FLAG_COLOR_SPACE_AGNOSTIC;
+
+        LayoutInflater layoutInflater = LayoutInflater.from(mContext);
+        NavigationBarFrame frame = (NavigationBarFrame) layoutInflater.inflate(
+                R.layout.navigation_bar_window, null);
+        View barView = layoutInflater.inflate(R.layout.navigation_bar, frame);
+        barView.addOnAttachStateChangeListener(this);
+
+        if (DEBUG) Log.v(TAG, "addNavigationBar: about to add " + barView);
+        mContext.getSystemService(WindowManager.class).addView(frame, lp);
+        mDisplayId = mContext.getDisplayId();
+        mIsOnDefaultDisplay = mDisplayId == DEFAULT_DISPLAY;
+
+        mCommandQueue.addCallback(this);
+        mAssistantAvailable = mAssistManagerLazy.get()
+                .getAssistInfoForUser(UserHandle.USER_CURRENT) != null;
+        mContentResolver = mContext.getContentResolver();
         mContentResolver.registerContentObserver(
                 Settings.Secure.getUriFor(Settings.Secure.ASSISTANT),
                 false /* notifyForDescendants */, mAssistContentObserver, UserHandle.USER_ALL);
 
-        if (savedInstanceState != null) {
-            mDisabledFlags1 = savedInstanceState.getInt(EXTRA_DISABLE_STATE, 0);
-            mDisabledFlags2 = savedInstanceState.getInt(EXTRA_DISABLE2_STATE, 0);
-            mAppearance = savedInstanceState.getInt(EXTRA_APPEARANCE, 0);
-            mTransientShown = savedInstanceState.getBoolean(EXTRA_TRANSIENT_STATE, false);
+        if (savedState != null) {
+            mDisabledFlags1 = savedState.getInt(EXTRA_DISABLE_STATE, 0);
+            mDisabledFlags2 = savedState.getInt(EXTRA_DISABLE2_STATE, 0);
+            mAppearance = savedState.getInt(EXTRA_APPEARANCE, 0);
+            mTransientShown = savedState.getBoolean(EXTRA_TRANSIENT_STATE, false);
         }
+        mSavedState = savedState;
         mAccessibilityManagerWrapper.addCallback(mAccessibilityListener);
 
         // Respect the latest disabled-flags.
@@ -453,44 +495,40 @@
                 /* defaultValue = */ true);
         DeviceConfig.addOnPropertiesChangedListener(
                 DeviceConfig.NAMESPACE_SYSTEMUI, mHandler::post, mOnPropertiesChangedListener);
+
+        mIsCurrentUserSetup = mDeviceProvisionedController.isCurrentUserSetup();
+        mDeviceProvisionedController.addCallback(mUserSetupListener);
+
+        return barView;
     }
 
-    @Override
-    public void onDestroy() {
-        super.onDestroy();
+    public void destroyView() {
+        mCommandQueue.removeCallback(this);
+        mContext.getSystemService(WindowManager.class).removeViewImmediate(
+                mNavigationBarView.getRootView());
         mNavigationModeController.removeListener(this);
+
         mAccessibilityManagerWrapper.removeCallback(mAccessibilityListener);
         mContentResolver.unregisterContentObserver(mAssistContentObserver);
+        mDeviceProvisionedController.removeCallback(mUserSetupListener);
 
         DeviceConfig.removeOnPropertiesChangedListener(mOnPropertiesChangedListener);
     }
 
     @Override
-    public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container,
-            Bundle savedInstanceState) {
-        return inflater.inflate(R.layout.navigation_bar, container, false);
-    }
-
-    @Override
-    public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
-        super.onViewCreated(view, savedInstanceState);
-        mNavigationBarView = (NavigationBarView) view;
-        final Display display = view.getDisplay();
-        // It may not have display when running unit test.
-        if (display != null) {
-            mDisplayId = display.getDisplayId();
-            mIsOnDefaultDisplay = mDisplayId == Display.DEFAULT_DISPLAY;
-        }
-
+    public void onViewAttachedToWindow(View v) {
+        final Display display = v.getDisplay();
+        mNavigationBarView = v.findViewById(R.id.navigation_bar_view);
         mNavigationBarView.setComponents(mStatusBarLazy.get().getPanelController());
         mNavigationBarView.setDisabledFlags(mDisabledFlags1);
         mNavigationBarView.setOnVerticalChangedListener(this::onVerticalChanged);
         mNavigationBarView.setOnTouchListener(this::onNavigationTouch);
-        if (savedInstanceState != null) {
-            mNavigationBarView.getLightTransitionsController().restoreState(savedInstanceState);
+        if (mSavedState != null) {
+            mNavigationBarView.getLightTransitionsController().restoreState(mSavedState);
         }
         mNavigationBarView.setNavigationIconHints(mNavigationIconHints);
         mNavigationBarView.setWindowVisible(isNavBarWindowVisible());
+        mSplitScreenControllerOptional.ifPresent(mNavigationBarView::registerDockedListener);
 
         prepareNavigationBarView();
         checkNavBarModes();
@@ -507,8 +545,6 @@
 
         // Currently there is no accelerometer sensor on non-default display.
         if (mIsOnDefaultDisplay) {
-            mNavigationBarView.getRotateSuggestionButton().setListener(mRotationButtonListener);
-
             final RotationButtonController rotationButtonController =
                     mNavigationBarView.getRotationButtonController();
             rotationButtonController.addRotationCallback(mRotationWatcher);
@@ -524,16 +560,37 @@
         setDisabled2Flags(mDisabledFlags2);
         if (mIsOnDefaultDisplay) {
             mAssistHandlerViewController =
-                new AssistHandleViewController(mHandler, mNavigationBarView);
+                    new AssistHandleViewController(mHandler, mNavigationBarView);
             getBarTransitions().addDarkIntensityListener(mAssistHandlerViewController);
         }
 
         initSecondaryHomeHandleForRotation();
+
+        // Unfortunately, we still need it because status bar needs LightBarController
+        // before notifications creation. We cannot directly use getLightBarController()
+        // from NavigationBarFragment directly.
+        LightBarController lightBarController = mIsOnDefaultDisplay
+                ? Dependency.get(LightBarController.class)
+                : new LightBarController(mContext,
+                        Dependency.get(DarkIconDispatcher.class),
+                        Dependency.get(BatteryController.class),
+                        Dependency.get(NavigationModeController.class));
+        setLightBarController(lightBarController);
+
+        // TODO(b/118592525): to support multi-display, we start to add something which is
+        //                    per-display, while others may be global. I think it's time to
+        //                    add a new class maybe named DisplayDependency to solve
+        //                    per-display Dependency problem.
+        AutoHideController autoHideController = mIsOnDefaultDisplay
+                ? Dependency.get(AutoHideController.class)
+                : new AutoHideController(mContext, mHandler,
+                        Dependency.get(IWindowManager.class));
+        setAutoHideController(autoHideController);
+        restoreAppearanceAndTransientState();
     }
 
     @Override
-    public void onDestroyView() {
-        super.onDestroyView();
+    public void onViewDetachedFromWindow(View v) {
         if (mNavigationBarView != null) {
             if (mIsOnDefaultDisplay) {
                 mNavigationBarView.getBarTransitions()
@@ -541,13 +598,13 @@
                 mAssistHandlerViewController = null;
             }
             mNavigationBarView.getBarTransitions().destroy();
-            mNavigationBarView.getLightTransitionsController().destroy(getContext());
+            mNavigationBarView.getLightTransitionsController().destroy(mContext);
         }
         mOverviewProxyService.removeCallback(mOverviewProxyListener);
         mBroadcastDispatcher.unregisterReceiver(mBroadcastReceiver);
         if (mOrientationHandle != null) {
             resetSecondaryHandle();
-            getContext().getSystemService(DisplayManager.class).unregisterDisplayListener(this);
+            mContext.getSystemService(DisplayManager.class).unregisterDisplayListener(this);
             getBarTransitions().removeDarkIntensityListener(mOrientationHandleIntensityListener);
             mWindowManager.removeView(mOrientationHandle);
             mOrientationHandle.getViewTreeObserver().removeOnGlobalLayoutListener(
@@ -558,9 +615,8 @@
         mOrientationHandle = null;
     }
 
-    @Override
+    // TODO: Remove this when we update nav bar recreation
     public void onSaveInstanceState(Bundle outState) {
-        super.onSaveInstanceState(outState);
         outState.putInt(EXTRA_DISABLE_STATE, mDisabledFlags1);
         outState.putInt(EXTRA_DISABLE2_STATE, mDisabledFlags2);
         outState.putInt(EXTRA_APPEARANCE, mAppearance);
@@ -570,10 +626,11 @@
         }
     }
 
-    @Override
+    /**
+     * Called when a non-reloading configuration change happens and we need to update.
+     */
     public void onConfigurationChanged(Configuration newConfig) {
-        super.onConfigurationChanged(newConfig);
-        final Locale locale = getContext().getResources().getConfiguration().locale;
+        final Locale locale = mContext.getResources().getConfiguration().locale;
         final int ld = TextUtils.getLayoutDirectionFromLocale(locale);
         if (!locale.equals(mLocale) || ld != mLayoutDirection) {
             if (DEBUG) {
@@ -593,10 +650,10 @@
             return;
         }
 
-        getContext().getSystemService(DisplayManager.class)
+        mContext.getSystemService(DisplayManager.class)
                 .registerDisplayListener(this, new Handler(Looper.getMainLooper()));
 
-        mOrientationHandle = new QuickswitchOrientedNavHandle(getContext());
+        mOrientationHandle = new QuickswitchOrientedNavHandle(mContext);
         mOrientationHandle.setId(R.id.secondary_home_handle);
 
         getBarTransitions().addDarkIntensityListener(mOrientationHandleIntensityListener);
@@ -608,7 +665,7 @@
                         | WindowManager.LayoutParams.FLAG_SPLIT_TOUCH
                         | WindowManager.LayoutParams.FLAG_SLIPPERY,
                 PixelFormat.TRANSLUCENT);
-        mOrientationParams.setTitle("SecondaryHomeHandle" + getContext().getDisplayId());
+        mOrientationParams.setTitle("SecondaryHomeHandle" + mContext.getDisplayId());
         mOrientationParams.privateFlags |= PRIVATE_FLAG_NO_MOVE_ANIMATION;
         mWindowManager.addView(mOrientationHandle, mOrientationParams);
         mOrientationHandle.setVisibility(View.GONE);
@@ -634,7 +691,8 @@
             return;
         }
 
-        if (mStartingQuickSwitchRotation == -1 || mDivider.isDividerVisible()) {
+        if (mStartingQuickSwitchRotation == -1 || mSplitScreenControllerOptional
+                .map(SplitScreenController::isDividerVisible).orElse(false)) {
             // Hide the secondary home handle if we are in multiwindow since apps in multiwindow
             // aren't allowed to set the display orientation
             resetSecondaryHandle();
@@ -697,23 +755,20 @@
         return delta;
     }
 
-    @Override
-    public void dump(String prefix, FileDescriptor fd, PrintWriter pw, String[] args) {
-        if (mNavigationBarView != null) {
-            pw.print("  mNavigationBarWindowState=");
-            pw.println(windowStateToString(mNavigationBarWindowState));
-            pw.print("  mNavigationBarMode=");
-            pw.println(BarTransitions.modeToString(mNavigationBarMode));
-            dumpBarTransitions(pw, "mNavigationBarView", mNavigationBarView.getBarTransitions());
-        }
+    public void dump(PrintWriter pw) {
+        pw.println("NavigationBar (displayId=" + mDisplayId + "):");
+        pw.println("  mStartingQuickSwitchRotation=" + mStartingQuickSwitchRotation);
+        pw.println("  mCurrentRotation=" + mCurrentRotation);
 
-        pw.print("  mStartingQuickSwitchRotation=" + mStartingQuickSwitchRotation);
-        pw.print("  mCurrentRotation=" + mCurrentRotation);
-        pw.print("  mNavigationBarView=");
-        if (mNavigationBarView == null) {
-            pw.println("null");
+        if (mNavigationBarView != null) {
+            pw.println("  mNavigationBarWindowState="
+                    + windowStateToString(mNavigationBarWindowState));
+            pw.println("  mNavigationBarMode="
+                    + BarTransitions.modeToString(mNavigationBarMode));
+            dumpBarTransitions(pw, "mNavigationBarView", mNavigationBarView.getBarTransitions());
+            mNavigationBarView.dump(pw);
         } else {
-            mNavigationBarView.dump(fd, pw, args);
+            pw.print("  mNavigationBarView=null");
         }
     }
 
@@ -802,15 +857,18 @@
         rotationButtonController.onRotationProposal(rotation, winRotation, isValid);
     }
 
-    /** Restores the appearance and the transient saved state to {@link NavigationBarFragment}. */
+    /** Restores the appearance and the transient saved state to {@link NavigationBar}. */
     public void restoreAppearanceAndTransientState() {
         final int barMode = barMode(mTransientShown, mAppearance);
         mNavigationBarMode = barMode;
         checkNavBarModes();
-        mAutoHideController.touchAutoHide();
-
-        mLightBarController.onNavigationBarAppearanceChanged(mAppearance, true /* nbModeChanged */,
-                barMode, false /* navbarColorManagedByIme */);
+        if (mAutoHideController != null) {
+            mAutoHideController.touchAutoHide();
+        }
+        if (mLightBarController != null) {
+            mLightBarController.onNavigationBarAppearanceChanged(mAppearance,
+                    true /* nbModeChanged */, barMode, false /* navbarColorManagedByIme */);
+        }
     }
 
     @Override
@@ -827,8 +885,10 @@
             }
             nbModeChanged = updateBarMode(barMode(mTransientShown, appearance));
         }
-        mLightBarController.onNavigationBarAppearanceChanged(appearance, nbModeChanged,
-                mNavigationBarMode, navbarColorManagedByIme);
+        if (mLightBarController != null) {
+            mLightBarController.onNavigationBarAppearanceChanged(appearance, nbModeChanged,
+                    mNavigationBarMode, navbarColorManagedByIme);
+        }
     }
 
     @Override
@@ -871,7 +931,7 @@
             mNavigationBarView.onTransientStateChanged(mTransientShown);
         }
         final int barMode = barMode(mTransientShown, mAppearance);
-        if (updateBarMode(barMode)) {
+        if (updateBarMode(barMode) && mLightBarController != null) {
             mLightBarController.onNavigationBarModeChanged(barMode);
         }
     }
@@ -885,7 +945,9 @@
             }
             mNavigationBarMode = barMode;
             checkNavBarModes();
-            mAutoHideController.touchAutoHide();
+            if (mAutoHideController != null) {
+                mAutoHideController.touchAutoHide();
+            }
             return true;
         }
         return false;
@@ -1021,7 +1083,7 @@
             case MotionEvent.ACTION_DOWN:
                 mHomeBlockedThisTouch = false;
                 TelecomManager telecomManager =
-                        getContext().getSystemService(TelecomManager.class);
+                        mContext.getSystemService(TelecomManager.class);
                 if (telecomManager != null && telecomManager.isRinging()) {
                     if (mStatusBarLazy.get().isKeyguardShowing()) {
                         Log.i(TAG, "Ignoring HOME; there's a ringing incoming call. " +
@@ -1044,7 +1106,9 @@
     }
 
     private boolean onNavigationTouch(View v, MotionEvent event) {
-        mAutoHideController.checkUserAutoHide(event);
+        if (mAutoHideController != null) {
+            mAutoHideController.checkUserAutoHide(event);
+        }
         return false;
     }
 
@@ -1059,10 +1123,10 @@
         }
         mMetricsLogger.action(MetricsEvent.ACTION_ASSIST_LONG_PRESS);
         mUiEventLogger.log(NavBarActionEvent.NAVBAR_ASSIST_LONGPRESS);
-        Bundle args  = new Bundle();
+        Bundle args = new Bundle();
         args.putInt(
                 AssistManager.INVOCATION_TYPE_KEY, AssistManager.INVOCATION_HOME_BUTTON_LONG_PRESS);
-        mAssistManager.startAssist(args);
+        mAssistManagerLazy.get().startAssist(args);
         mStatusBarLazy.get().awakenDreams();
 
         if (mNavigationBarView != null) {
@@ -1088,8 +1152,8 @@
     }
 
     private void onRecentsClick(View v) {
-        if (LatencyTracker.isEnabled(getContext())) {
-            LatencyTracker.getInstance(getContext()).onActionStart(
+        if (LatencyTracker.isEnabled(mContext)) {
+            LatencyTracker.getInstance(mContext).onActionStart(
                     LatencyTracker.ACTION_TOGGLE_RECENTS);
         }
         mStatusBarLazy.get().awakenDreams();
@@ -1183,11 +1247,13 @@
     }
 
     private boolean onLongPressRecents() {
-        if (mRecentsOptional.isPresent() || !ActivityTaskManager.supportsMultiWindow(getContext())
-                || !mDivider.getView().getSnapAlgorithm().isSplitScreenFeasible()
+        if (mRecentsOptional.isPresent() || !ActivityTaskManager.supportsMultiWindow(mContext)
                 || ActivityManager.isLowRamDeviceStatic()
                 // If we are connected to the overview service, then disable the recents button
-                || mOverviewProxyService.getProxy() != null) {
+                || mOverviewProxyService.getProxy() != null
+                || !mSplitScreenControllerOptional.map(splitScreen ->
+                splitScreen.getDividerView().getSnapAlgorithm().isSplitScreenFeasible())
+                .orElse(false)) {
             return false;
         }
 
@@ -1198,7 +1264,7 @@
     private void onAccessibilityClick(View v) {
         final Display display = v.getDisplay();
         mAccessibilityManager.notifyAccessibilityButtonClicked(
-                display != null ? display.getDisplayId() : Display.DEFAULT_DISPLAY);
+                display != null ? display.getDisplayId() : DEFAULT_DISPLAY);
     }
 
     private boolean onAccessibilityLongClick(View v) {
@@ -1206,7 +1272,7 @@
         intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
         final String chooserClassName = AccessibilityButtonChooserActivity.class.getName();
         intent.setClassName(CHOOSER_PACKAGE_NAME, chooserClassName);
-        v.getContext().startActivityAsUser(intent, UserHandle.CURRENT);
+        mContext.startActivityAsUser(intent, UserHandle.CURRENT);
         return true;
     }
 
@@ -1249,6 +1315,7 @@
 
     /**
      * Returns the system UI flags corresponding the the current accessibility button state
+     *
      * @param outFeedbackEnabled if non-null, sets it to true if accessibility feedback is enabled.
      */
     public int getA11yButtonState(@Nullable boolean[] outFeedbackEnabled) {
@@ -1303,7 +1370,10 @@
 
     public void setLightBarController(LightBarController lightBarController) {
         mLightBarController = lightBarController;
-        mLightBarController.setNavigationBar(mNavigationBarView.getLightTransitionsController());
+        if (mLightBarController != null) {
+            mLightBarController.setNavigationBar(
+                    mNavigationBarView.getLightTransitionsController());
+        }
     }
 
     /** Sets {@link AutoHideController} to the navigation bar. */
@@ -1312,6 +1382,7 @@
         if (mAutoHideController != null) {
             mAutoHideController.setNavigationBar(mAutoHideUiElement);
         }
+        mNavigationBarView.setAutoHideController(autoHideController);
     }
 
     private boolean isTransientShown() {
@@ -1348,17 +1419,6 @@
         if (!canShowSecondaryHandle()) {
             resetSecondaryHandle();
         }
-
-        // Workaround for b/132825155, for secondary users, we currently don't receive configuration
-        // changes on overlay package change since SystemUI runs for the system user. In this case,
-        // trigger a new configuration change to ensure that the nav bar is updated in the same way.
-        int userId = ActivityManagerWrapper.getInstance().getCurrentUserId();
-        if (userId != UserHandle.USER_SYSTEM) {
-            mHandler.post(() -> {
-                FragmentHostManager fragmentHost = FragmentHostManager.get(mNavigationBarView);
-                fragmentHost.reloadFragments();
-            });
-        }
     }
 
     public void disableAnimationsDuringHide(long delay) {
@@ -1409,7 +1469,7 @@
             return;
         }
 
-        int rotation = getContext().getResources().getConfiguration()
+        int rotation = mContext.getResources().getConfiguration()
                 .windowConfiguration.getRotation();
         if (rotation != mCurrentRotation) {
             mCurrentRotation = rotation;
@@ -1447,53 +1507,6 @@
         }
     };
 
-    public static View create(Context context, FragmentListener listener) {
-        WindowManager.LayoutParams lp = new WindowManager.LayoutParams(
-                LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT,
-                WindowManager.LayoutParams.TYPE_NAVIGATION_BAR,
-                WindowManager.LayoutParams.FLAG_TOUCHABLE_WHEN_WAKING
-                        | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
-                        | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
-                        | WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH
-                        | WindowManager.LayoutParams.FLAG_SPLIT_TOUCH
-                        | WindowManager.LayoutParams.FLAG_SLIPPERY,
-                PixelFormat.TRANSLUCENT);
-        lp.token = new Binder();
-        lp.setTitle("NavigationBar" + context.getDisplayId());
-        lp.accessibilityTitle = context.getString(R.string.nav_bar);
-        lp.windowAnimations = 0;
-        lp.privateFlags |= WindowManager.LayoutParams.PRIVATE_FLAG_COLOR_SPACE_AGNOSTIC;
-
-        View navigationBarView = LayoutInflater.from(context).inflate(
-                R.layout.navigation_bar_window, null);
-
-        if (DEBUG) Log.v(TAG, "addNavigationBar: about to add " + navigationBarView);
-        if (navigationBarView == null) return null;
-
-        navigationBarView.addOnAttachStateChangeListener(new View.OnAttachStateChangeListener() {
-            @Override
-            public void onViewAttachedToWindow(View v) {
-                final NavigationBarFragment fragment =
-                        FragmentHostManager.get(v).create(NavigationBarFragment.class);
-                final FragmentHostManager fragmentHost = FragmentHostManager.get(v);
-                fragmentHost.getFragmentManager().beginTransaction()
-                        .replace(R.id.navigation_bar_frame, fragment, TAG)
-                        .commit();
-                fragmentHost.addTagListener(TAG, listener);
-            }
-
-            @Override
-            public void onViewDetachedFromWindow(View v) {
-                final FragmentHostManager fragmentHost = FragmentHostManager.get(v);
-                fragmentHost.removeTagListener(TAG, listener);
-                FragmentHostManager.removeAndDestroy(v);
-                navigationBarView.removeOnAttachStateChangeListener(this);
-            }
-        });
-        context.getSystemService(WindowManager.class).addView(navigationBarView, lp);
-        return navigationBarView;
-    }
-
     @VisibleForTesting
     int getNavigationIconHints() {
         return mNavigationIconHints;
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarController.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarController.java
new file mode 100644
index 0000000..23ef71a
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarController.java
@@ -0,0 +1,396 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.navigationbar;
+
+import static android.view.Display.DEFAULT_DISPLAY;
+
+import android.content.Context;
+import android.content.pm.ActivityInfo;
+import android.content.res.Configuration;
+import android.hardware.display.DisplayManager;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.RemoteException;
+import android.os.UserHandle;
+import android.util.Log;
+import android.util.SparseArray;
+import android.view.Display;
+import android.view.IWindowManager;
+import android.view.View;
+import android.view.WindowManager;
+import android.view.WindowManagerGlobal;
+import android.view.accessibility.AccessibilityManager;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.logging.MetricsLogger;
+import com.android.internal.logging.UiEventLogger;
+import com.android.internal.statusbar.RegisterStatusBarResult;
+import com.android.settingslib.applications.InterestingConfigChanges;
+import com.android.systemui.Dumpable;
+import com.android.systemui.accessibility.SystemActions;
+import com.android.systemui.assist.AssistHandleViewController;
+import com.android.systemui.assist.AssistManager;
+import com.android.systemui.broadcast.BroadcastDispatcher;
+import com.android.systemui.dagger.SysUISingleton;
+import com.android.systemui.dagger.qualifiers.Main;
+import com.android.systemui.model.SysUiState;
+import com.android.systemui.plugins.statusbar.StatusBarStateController;
+import com.android.systemui.recents.OverviewProxyService;
+import com.android.systemui.recents.Recents;
+import com.android.systemui.shared.system.ActivityManagerWrapper;
+import com.android.systemui.stackdivider.SplitScreenController;
+import com.android.systemui.statusbar.CommandQueue;
+import com.android.systemui.statusbar.CommandQueue.Callbacks;
+import com.android.systemui.statusbar.NotificationRemoteInputManager;
+import com.android.systemui.statusbar.phone.BarTransitions.TransitionMode;
+import com.android.systemui.statusbar.phone.ShadeController;
+import com.android.systemui.statusbar.phone.StatusBar;
+import com.android.systemui.statusbar.policy.AccessibilityManagerWrapper;
+import com.android.systemui.statusbar.policy.ConfigurationController;
+import com.android.systemui.statusbar.policy.DeviceProvisionedController;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.util.Optional;
+
+import javax.inject.Inject;
+
+import dagger.Lazy;
+
+
+/** A controller to handle navigation bars. */
+@SysUISingleton
+public class NavigationBarController implements Callbacks,
+        ConfigurationController.ConfigurationListener,
+        NavigationModeController.ModeChangedListener, Dumpable {
+
+    private static final String TAG = NavigationBarController.class.getSimpleName();
+
+    private final Context mContext;
+    private final WindowManager mWindowManager;
+    private final Lazy<AssistManager> mAssistManagerLazy;
+    private final AccessibilityManager mAccessibilityManager;
+    private final AccessibilityManagerWrapper mAccessibilityManagerWrapper;
+    private final DeviceProvisionedController mDeviceProvisionedController;
+    private final MetricsLogger mMetricsLogger;
+    private final OverviewProxyService mOverviewProxyService;
+    private final NavigationModeController mNavigationModeController;
+    private final StatusBarStateController mStatusBarStateController;
+    private final SysUiState mSysUiFlagsContainer;
+    private final BroadcastDispatcher mBroadcastDispatcher;
+    private final CommandQueue mCommandQueue;
+    private final Optional<SplitScreenController> mSplitScreenControllerOptional;
+    private final Optional<Recents> mRecentsOptional;
+    private final Lazy<StatusBar> mStatusBarLazy;
+    private final ShadeController mShadeController;
+    private final NotificationRemoteInputManager mNotificationRemoteInputManager;
+    private final SystemActions mSystemActions;
+    private final UiEventLogger mUiEventLogger;
+    private final Handler mHandler;
+    private final DisplayManager mDisplayManager;
+
+    /** A displayId - nav bar maps. */
+    @VisibleForTesting
+    SparseArray<NavigationBar> mNavigationBars = new SparseArray<>();
+
+    // Tracks config changes that will actually recreate the nav bar
+    private final InterestingConfigChanges mConfigChanges = new InterestingConfigChanges(
+            ActivityInfo.CONFIG_FONT_SCALE | ActivityInfo.CONFIG_LOCALE
+                    | ActivityInfo.CONFIG_SCREEN_LAYOUT | ActivityInfo.CONFIG_ASSETS_PATHS
+                    | ActivityInfo.CONFIG_UI_MODE);
+
+    @Inject
+    public NavigationBarController(Context context,
+            WindowManager windowManager,
+            Lazy<AssistManager> assistManagerLazy,
+            AccessibilityManager accessibilityManager,
+            AccessibilityManagerWrapper accessibilityManagerWrapper,
+            DeviceProvisionedController deviceProvisionedController,
+            MetricsLogger metricsLogger,
+            OverviewProxyService overviewProxyService,
+            NavigationModeController navigationModeController,
+            StatusBarStateController statusBarStateController,
+            SysUiState sysUiFlagsContainer,
+            BroadcastDispatcher broadcastDispatcher,
+            CommandQueue commandQueue,
+            Optional<SplitScreenController> splitScreenControllerOptional,
+            Optional<Recents> recentsOptional,
+            Lazy<StatusBar> statusBarLazy,
+            ShadeController shadeController,
+            NotificationRemoteInputManager notificationRemoteInputManager,
+            SystemActions systemActions,
+            @Main Handler mainHandler,
+            UiEventLogger uiEventLogger,
+            ConfigurationController configurationController) {
+        mContext = context;
+        mWindowManager = windowManager;
+        mAssistManagerLazy = assistManagerLazy;
+        mAccessibilityManager = accessibilityManager;
+        mAccessibilityManagerWrapper = accessibilityManagerWrapper;
+        mDeviceProvisionedController = deviceProvisionedController;
+        mMetricsLogger = metricsLogger;
+        mOverviewProxyService = overviewProxyService;
+        mNavigationModeController = navigationModeController;
+        mStatusBarStateController = statusBarStateController;
+        mSysUiFlagsContainer = sysUiFlagsContainer;
+        mBroadcastDispatcher = broadcastDispatcher;
+        mCommandQueue = commandQueue;
+        mSplitScreenControllerOptional = splitScreenControllerOptional;
+        mRecentsOptional = recentsOptional;
+        mStatusBarLazy = statusBarLazy;
+        mShadeController = shadeController;
+        mNotificationRemoteInputManager = notificationRemoteInputManager;
+        mSystemActions = systemActions;
+        mUiEventLogger = uiEventLogger;
+        mHandler = mainHandler;
+        mDisplayManager = mContext.getSystemService(DisplayManager.class);
+        commandQueue.addCallback(this);
+        configurationController.addCallback(this);
+        mConfigChanges.applyNewConfig(mContext.getResources());
+    }
+
+    @Override
+    public void onConfigChanged(Configuration newConfig) {
+        if (mConfigChanges.applyNewConfig(mContext.getResources())) {
+            for (int i = 0; i < mNavigationBars.size(); i++) {
+                recreateNavigationBar(mNavigationBars.keyAt(i));
+            }
+        } else {
+            for (int i = 0; i < mNavigationBars.size(); i++) {
+                mNavigationBars.get(i).onConfigurationChanged(newConfig);
+            }
+        }
+    }
+
+    @Override
+    public void onNavigationModeChanged(int mode) {
+        // Workaround for b/132825155, for secondary users, we currently don't receive configuration
+        // changes on overlay package change since SystemUI runs for the system user. In this case,
+        // trigger a new configuration change to ensure that the nav bar is updated in the same way.
+        int userId = ActivityManagerWrapper.getInstance().getCurrentUserId();
+        if (userId != UserHandle.USER_SYSTEM) {
+            mHandler.post(() -> {
+                for (int i = 0; i < mNavigationBars.size(); i++) {
+                    recreateNavigationBar(mNavigationBars.keyAt(i));
+                }
+            });
+        }
+    }
+
+    @Override
+    public void onDisplayRemoved(int displayId) {
+        removeNavigationBar(displayId);
+    }
+
+    @Override
+    public void onDisplayReady(int displayId) {
+        Display display = mDisplayManager.getDisplay(displayId);
+        createNavigationBar(display, null /* savedState */, null /* result */);
+    }
+
+    /**
+     * Recreates the navigation bar for the given display.
+     */
+    private void recreateNavigationBar(int displayId) {
+        // TODO: Improve this flow so that we don't need to create a new nav bar but just
+        //       the view
+        Bundle savedState = new Bundle();
+        NavigationBar bar = mNavigationBars.get(displayId);
+        if (bar != null) {
+            bar.onSaveInstanceState(savedState);
+        }
+        removeNavigationBar(displayId);
+        createNavigationBar(mDisplayManager.getDisplay(displayId), savedState, null /* result */);
+    }
+
+    // TODO(b/117478341): I use {@code includeDefaultDisplay} to make this method compatible to
+    // CarStatusBar because they have their own nav bar. Think about a better way for it.
+    /**
+     * Creates navigation bars when car/status bar initializes.
+     *
+     * @param includeDefaultDisplay {@code true} to create navigation bar on default display.
+     */
+    public void createNavigationBars(final boolean includeDefaultDisplay,
+            RegisterStatusBarResult result) {
+        Display[] displays = mDisplayManager.getDisplays();
+        for (Display display : displays) {
+            if (includeDefaultDisplay || display.getDisplayId() != DEFAULT_DISPLAY) {
+                createNavigationBar(display, null /* savedState */, result);
+            }
+        }
+    }
+
+    /**
+     * Adds a navigation bar on default display or an external display if the display supports
+     * system decorations.
+     *
+     * @param display the display to add navigation bar on.
+     */
+    @VisibleForTesting
+    void createNavigationBar(Display display, Bundle savedState, RegisterStatusBarResult result) {
+        if (display == null) {
+            return;
+        }
+
+        final int displayId = display.getDisplayId();
+        final boolean isOnDefaultDisplay = displayId == DEFAULT_DISPLAY;
+        final IWindowManager wms = WindowManagerGlobal.getWindowManagerService();
+
+        try {
+            if (!wms.hasNavigationBar(displayId)) {
+                return;
+            }
+        } catch (RemoteException e) {
+            // Cannot get wms, just return with warning message.
+            Log.w(TAG, "Cannot get WindowManager.");
+            return;
+        }
+        final Context context = isOnDefaultDisplay
+                ? mContext
+                : mContext.createDisplayContext(display);
+        NavigationBar navBar = new NavigationBar(context,
+                mWindowManager,
+                mAssistManagerLazy,
+                mAccessibilityManager,
+                mAccessibilityManagerWrapper,
+                mDeviceProvisionedController,
+                mMetricsLogger,
+                mOverviewProxyService,
+                mNavigationModeController,
+                mStatusBarStateController,
+                mSysUiFlagsContainer,
+                mBroadcastDispatcher,
+                mCommandQueue,
+                mSplitScreenControllerOptional,
+                mRecentsOptional,
+                mStatusBarLazy,
+                mShadeController,
+                mNotificationRemoteInputManager,
+                mSystemActions,
+                mHandler,
+                mUiEventLogger);
+
+        View navigationBarView = navBar.createView(savedState);
+        navigationBarView.addOnAttachStateChangeListener(new View.OnAttachStateChangeListener() {
+            @Override
+            public void onViewAttachedToWindow(View v) {
+                mNavigationBars.put(displayId, navBar);
+
+                if (result != null) {
+                    navBar.setImeWindowStatus(display.getDisplayId(), result.mImeToken,
+                            result.mImeWindowVis, result.mImeBackDisposition,
+                            result.mShowImeSwitcher);
+                }
+            }
+
+            @Override
+            public void onViewDetachedFromWindow(View v) {
+                v.removeOnAttachStateChangeListener(this);
+            }
+        });
+    }
+
+    private void removeNavigationBar(int displayId) {
+        NavigationBar navBar = mNavigationBars.get(displayId);
+        if (navBar != null) {
+            navBar.setAutoHideController(/* autoHideController */ null);
+            navBar.destroyView();
+            mNavigationBars.remove(displayId);
+        }
+    }
+
+    /** @see NavigationBar#checkNavBarModes() */
+    public void checkNavBarModes(int displayId) {
+        NavigationBar navBar = mNavigationBars.get(displayId);
+        if (navBar != null) {
+            navBar.checkNavBarModes();
+        }
+    }
+
+    /** @see NavigationBar#finishBarAnimations() */
+    public void finishBarAnimations(int displayId) {
+        NavigationBar navBar = mNavigationBars.get(displayId);
+        if (navBar != null) {
+            navBar.finishBarAnimations();
+        }
+    }
+
+    /** @see NavigationBar#touchAutoDim() */
+    public void touchAutoDim(int displayId) {
+        NavigationBar navBar = mNavigationBars.get(displayId);
+        if (navBar != null) {
+            navBar.touchAutoDim();
+        }
+    }
+
+    /** @see NavigationBar#transitionTo(int, boolean) */
+    public void transitionTo(int displayId, @TransitionMode int barMode, boolean animate) {
+        NavigationBar navBar = mNavigationBars.get(displayId);
+        if (navBar != null) {
+            navBar.transitionTo(barMode, animate);
+        }
+    }
+
+    /** @see NavigationBar#disableAnimationsDuringHide(long) */
+    public void disableAnimationsDuringHide(int displayId, long delay) {
+        NavigationBar navBar = mNavigationBars.get(displayId);
+        if (navBar != null) {
+            navBar.disableAnimationsDuringHide(delay);
+        }
+    }
+
+    /** @return {@link NavigationBarView} on the default display. */
+    public @Nullable NavigationBarView getDefaultNavigationBarView() {
+        return getNavigationBarView(DEFAULT_DISPLAY);
+    }
+
+    /**
+     * @param displayId the ID of display which Navigation bar is on
+     * @return {@link NavigationBarView} on the display with {@code displayId}.
+     *         {@code null} if no navigation bar on that display.
+     */
+    public @Nullable NavigationBarView getNavigationBarView(int displayId) {
+        NavigationBar navBar = mNavigationBars.get(displayId);
+        return (navBar == null) ? null : (NavigationBarView) navBar.getView();
+    }
+
+    /** @return {@link NavigationBar} on the default display. */
+    @Nullable
+    public NavigationBar getDefaultNavigationBar() {
+        return mNavigationBars.get(DEFAULT_DISPLAY);
+    }
+
+    /** @return {@link AssistHandleViewController} (only on the default display). */
+    @Nullable
+    public AssistHandleViewController getAssistHandlerViewController() {
+        NavigationBar navBar = getDefaultNavigationBar();
+        return navBar == null ? null : navBar.getAssistHandlerViewController();
+    }
+
+    @Override
+    public void dump(@NonNull FileDescriptor fd, @NonNull PrintWriter pw, @NonNull String[] args) {
+        for (int i = 0; i < mNavigationBars.size(); i++) {
+            if (i > 0) {
+                pw.println();
+            }
+            mNavigationBars.get(i).dump(pw);
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFrame.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarFrame.java
similarity index 71%
rename from packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFrame.java
rename to packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarFrame.java
index 741f783..6c531d8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFrame.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarFrame.java
@@ -1,18 +1,20 @@
 /*
- * Copyright (C) 2017 The Android Open Source Project
+ * 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
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
  *
  *      http://www.apache.org/licenses/LICENSE-2.0
  *
- * Unless required by applicable law or agreed to in writing, software distributed under the
- * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the specific language governing
- * permissions and limitations under the License.
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
  */
 
-package com.android.systemui.statusbar.phone;
+package com.android.systemui.navigationbar;
 
 import static android.view.MotionEvent.ACTION_OUTSIDE;
 
@@ -24,7 +26,7 @@
 import android.view.MotionEvent;
 import android.widget.FrameLayout;
 
-import com.android.systemui.statusbar.policy.DeadZone;
+import com.android.systemui.navigationbar.buttons.DeadZone;
 
 public class NavigationBarFrame extends FrameLayout {
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarInflaterView.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarInflaterView.java
similarity index 95%
rename from packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarInflaterView.java
rename to packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarInflaterView.java
index 4337e20..2707460 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarInflaterView.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarInflaterView.java
@@ -1,18 +1,20 @@
 /*
- * Copyright (C) 2016 The Android Open Source Project
+ * 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
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
  *
  *      http://www.apache.org/licenses/LICENSE-2.0
  *
- * Unless required by applicable law or agreed to in writing, software distributed under the
- * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the specific language governing
- * permissions and limitations under the License.
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
  */
 
-package com.android.systemui.statusbar.phone;
+package com.android.systemui.navigationbar;
 
 import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
 import static android.view.WindowManagerPolicyConstants.NAV_BAR_MODE_3BUTTON;
@@ -35,10 +37,12 @@
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.systemui.Dependency;
 import com.android.systemui.R;
+import com.android.systemui.navigationbar.buttons.ButtonDispatcher;
+import com.android.systemui.navigationbar.buttons.KeyButtonView;
+import com.android.systemui.navigationbar.buttons.ReverseLinearLayout;
 import com.android.systemui.recents.OverviewProxyService;
 import com.android.systemui.shared.system.QuickStepContract;
-import com.android.systemui.statusbar.phone.ReverseLinearLayout.ReverseRelativeLayout;
-import com.android.systemui.statusbar.policy.KeyButtonView;
+import com.android.systemui.navigationbar.buttons.ReverseLinearLayout.ReverseRelativeLayout;
 
 import java.io.PrintWriter;
 import java.util.Objects;
@@ -472,8 +476,7 @@
     }
 
     public void dump(PrintWriter pw) {
-        pw.println("NavigationBarInflaterView {");
-        pw.println("      mCurrentLayout: " + mCurrentLayout);
-        pw.println("    }");
+        pw.println("NavigationBarInflaterView");
+        pw.println("  mCurrentLayout: " + mCurrentLayout);
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarTransitions.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarTransitions.java
similarity index 96%
rename from packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarTransitions.java
rename to packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarTransitions.java
index 1f50954..c0535b5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarTransitions.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarTransitions.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2013 The Android Open Source Project
+ * 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.
@@ -14,27 +14,27 @@
  * limitations under the License.
  */
 
-package com.android.systemui.statusbar.phone;
+package com.android.systemui.navigationbar;
 
 import static android.view.WindowManagerPolicyConstants.NAV_BAR_MODE_3BUTTON;
 
 import static com.android.systemui.util.Utils.isGesturalModeOnDefaultDisplay;
 
-import android.content.Context;
 import android.graphics.Rect;
 import android.os.Handler;
 import android.os.RemoteException;
-import android.os.ServiceManager;
 import android.util.SparseArray;
 import android.view.Display;
 import android.view.IWallpaperVisibilityListener;
 import android.view.IWindowManager;
 import android.view.View;
 
-import com.android.internal.statusbar.IStatusBarService;
 import com.android.systemui.Dependency;
 import com.android.systemui.R;
+import com.android.systemui.navigationbar.buttons.ButtonDispatcher;
 import com.android.systemui.statusbar.CommandQueue;
+import com.android.systemui.statusbar.phone.BarTransitions;
+import com.android.systemui.statusbar.phone.LightBarTransitionsController;
 
 import java.util.ArrayList;
 import java.util.List;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarView.java
similarity index 92%
rename from packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java
rename to packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarView.java
index 84512ac..7e3aa10 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarView.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2008 The Android Open Source Project
+ * 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.
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.systemui.statusbar.phone;
+package com.android.systemui.navigationbar;
 
 import static android.view.WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL;
 
@@ -70,6 +70,15 @@
 import com.android.systemui.R;
 import com.android.systemui.assist.AssistHandleViewController;
 import com.android.systemui.model.SysUiState;
+import com.android.systemui.navigationbar.buttons.ButtonDispatcher;
+import com.android.systemui.navigationbar.buttons.ContextualButton;
+import com.android.systemui.navigationbar.buttons.ContextualButtonGroup;
+import com.android.systemui.navigationbar.buttons.DeadZone;
+import com.android.systemui.navigationbar.buttons.KeyButtonDrawable;
+import com.android.systemui.navigationbar.buttons.RotationContextButton;
+import com.android.systemui.navigationbar.gestural.EdgeBackGestureHandler;
+import com.android.systemui.navigationbar.gestural.FloatingRotationButton;
+import com.android.systemui.navigationbar.gestural.RegionSamplingHelper;
 import com.android.systemui.recents.OverviewProxyService;
 import com.android.systemui.recents.Recents;
 import com.android.systemui.recents.RecentsOnboarding;
@@ -78,13 +87,13 @@
 import com.android.systemui.shared.system.QuickStepContract;
 import com.android.systemui.shared.system.SysUiStatsLog;
 import com.android.systemui.shared.system.WindowManagerWrapper;
-import com.android.systemui.stackdivider.Divider;
+import com.android.systemui.stackdivider.SplitScreenController;
 import com.android.systemui.statusbar.CommandQueue;
-import com.android.systemui.statusbar.NavigationBarController;
-import com.android.systemui.statusbar.policy.DeadZone;
-import com.android.systemui.statusbar.policy.KeyButtonDrawable;
+import com.android.systemui.statusbar.phone.AutoHideController;
+import com.android.systemui.statusbar.phone.LightBarTransitionsController;
+import com.android.systemui.statusbar.phone.NotificationPanelViewController;
+import com.android.systemui.statusbar.phone.StatusBar;
 
-import java.io.FileDescriptor;
 import java.io.PrintWriter;
 import java.util.function.Consumer;
 
@@ -131,6 +140,7 @@
     private boolean mDeadZoneConsuming = false;
     private final NavigationBarTransitions mBarTransitions;
     private final OverviewProxyService mOverviewProxyService;
+    private AutoHideController mAutoHideController;
 
     // performs manual animation in sync with layout transitions
     private final NavTransitionListener mTransitionListener = new NavTransitionListener();
@@ -166,7 +176,7 @@
      * When quickswitching between apps of different orientations, we draw a secondary home handle
      * in the position of the first app's orientation. This rect represents the region of that
      * home handle so we can apply the correct light/dark luma on that.
-     * @see {@link NavigationBarFragment#mOrientationHandle}
+     * @see {@link NavigationBar#mOrientationHandle}
      */
     @Nullable
     private Rect mOrientedHandleSamplingRegion;
@@ -276,6 +286,15 @@
         info.touchableRegion.setEmpty();
     };
 
+    private final Consumer<Boolean> mRotationButtonListener = (visible) -> {
+        if (visible) {
+            // If the button will actually become visible and the navbar is about to hide,
+            // tell the statusbar to keep it around for longer
+            mAutoHideController.touchAutoHide();
+        }
+        notifyActiveTouchRegions();
+    };
+
     public NavigationBarView(Context context, AttributeSet attrs) {
         super(context, attrs);
 
@@ -295,11 +314,12 @@
         // Set up the context group of buttons
         mContextualButtonGroup = new ContextualButtonGroup(R.id.menu_container);
         final ContextualButton imeSwitcherButton = new ContextualButton(R.id.ime_switcher,
-                R.drawable.ic_ime_switcher_default);
+                mLightContext, R.drawable.ic_ime_switcher_default);
         final RotationContextButton rotateSuggestionButton = new RotationContextButton(
-                R.id.rotate_suggestion, R.drawable.ic_sysbar_rotate_button_ccw_start_0);
+                R.id.rotate_suggestion, mLightContext,
+                R.drawable.ic_sysbar_rotate_button_ccw_start_0);
         final ContextualButton accessibilityButton =
-                new ContextualButton(R.id.accessibility_button,
+                new ContextualButton(R.id.accessibility_button, mLightContext,
                         R.drawable.ic_sysbar_accessibility_button);
         mContextualButtonGroup.addButton(imeSwitcherButton);
         if (!isGesturalMode) {
@@ -308,11 +328,13 @@
         mContextualButtonGroup.addButton(accessibilityButton);
 
         mOverviewProxyService = Dependency.get(OverviewProxyService.class);
-        mRecentsOnboarding = new RecentsOnboarding(context, mOverviewProxyService);
         mFloatingRotationButton = new FloatingRotationButton(context);
+        // TODO(165014649): Temporarily disable onboarding
+        // mRecentsOnboarding = new RecentsOnboarding(context, mOverviewProxyService);
         mRotationButtonController = new RotationButtonController(mLightContext,
                 mLightIconColor, mDarkIconColor,
-                isGesturalMode ? mFloatingRotationButton : rotateSuggestionButton);
+                isGesturalMode ? mFloatingRotationButton : rotateSuggestionButton,
+                mRotationButtonListener);
 
         mConfiguration = new Configuration();
         mTmpLastConfiguration = new Configuration();
@@ -360,6 +382,10 @@
                 });
     }
 
+    public void setAutoHideController(AutoHideController autoHideController) {
+        mAutoHideController = autoHideController;
+    }
+
     public NavigationBarTransitions getBarTransitions() {
         return mBarTransitions;
     }
@@ -763,7 +789,7 @@
             } else {
                 return;
             }
-            WindowManager wm = (WindowManager)getContext().getSystemService(Context.WINDOW_SERVICE);
+            WindowManager wm = getContext().getSystemService(WindowManager.class);
             wm.updateViewLayout((View) getParent(), lp);
         }
     }
@@ -851,7 +877,7 @@
         } else {
             lp.flags &= ~flags;
         }
-        WindowManager wm = (WindowManager) getContext().getSystemService(Context.WINDOW_SERVICE);
+        WindowManager wm = getContext().getSystemService(WindowManager.class);
         wm.updateViewLayout(navbarView, lp);
     }
 
@@ -860,7 +886,9 @@
         mNavBarMode = mode;
         mBarTransitions.onNavigationModeChanged(mNavBarMode);
         mEdgeBackGestureHandler.onNavigationModeChanged(mNavBarMode);
-        mRecentsOnboarding.onNavigationModeChanged(mNavBarMode);
+        if (mRecentsOnboarding != null) {
+            mRecentsOnboarding.onNavigationModeChanged(mNavBarMode);
+        }
 
         if (isGesturalMode(mNavBarMode)) {
             mRegionSamplingHelper.start(mSamplingBounds);
@@ -876,7 +904,9 @@
     }
 
     void hideRecentsOnboarding() {
-        mRecentsOnboarding.hide(true);
+        if (mRecentsOnboarding != null) {
+            mRecentsOnboarding.hide(true);
+        }
     }
 
     @Override
@@ -886,9 +916,6 @@
         mNavigationInflaterView.setButtonDispatchers(mButtonDispatchers);
 
         getImeSwitchButton().setOnClickListener(mImeSwitcherClickListener);
-
-        Divider divider = Dependency.get(Divider.class);
-        divider.registerInSplitScreenListener(mDockedListener);
         updateOrientationViews();
         reloadNavIcons();
     }
@@ -927,7 +954,9 @@
         super.onLayout(changed, left, top, right, bottom);
 
         notifyActiveTouchRegions();
-        mRecentsOnboarding.setNavBarHeight(getMeasuredHeight());
+        if (mRecentsOnboarding != null) {
+            mRecentsOnboarding.setNavBarHeight(getMeasuredHeight());
+        }
     }
 
     /**
@@ -938,7 +967,15 @@
         updateButtonLocation(getBackButton());
         updateButtonLocation(getHomeButton());
         updateButtonLocation(getRecentsButton());
-        updateButtonLocation(getRotateSuggestionButton());
+        updateButtonLocation(getImeSwitchButton());
+        updateButtonLocation(getAccessibilityButton());
+        if (mFloatingRotationButton.isVisible()) {
+            View floatingRotationView = mFloatingRotationButton.getCurrentView();
+            floatingRotationView.getBoundsOnScreen(mTmpBounds);
+            mActiveRegion.op(mTmpBounds, Op.UNION);
+        } else {
+            updateButtonLocation(getRotateSuggestionButton());
+        }
         mOverviewProxyService.onActiveNavBarRegionChanges(mActiveRegion);
     }
 
@@ -1081,7 +1118,9 @@
         boolean uiCarModeChanged = updateCarMode();
         updateIcons(mTmpLastConfiguration);
         updateRecentsIcon();
-        mRecentsOnboarding.onConfigurationChanged(mConfiguration);
+        if (mRecentsOnboarding != null) {
+            mRecentsOnboarding.onConfigurationChanged(mConfiguration);
+        }
         if (uiCarModeChanged || mTmpLastConfiguration.densityDpi != mConfiguration.densityDpi
                 || mTmpLastConfiguration.getLayoutDirection() != mConfiguration.getLayoutDirection()) {
             // If car mode or density changes, we need to reset the icons.
@@ -1139,6 +1178,9 @@
     @Override
     protected void onAttachedToWindow() {
         super.onAttachedToWindow();
+        // This needs to happen first as it can changed the enabled state which can affect whether
+        // the back button is visible
+        mEdgeBackGestureHandler.onNavBarAttached();
         requestApplyInsets();
         reorient();
         onNavigationModeChanged(mNavBarMode);
@@ -1147,7 +1189,6 @@
             mRotationButtonController.registerListeners();
         }
 
-        mEdgeBackGestureHandler.onNavBarAttached();
         getViewTreeObserver().addOnComputeInternalInsetsListener(mOnComputeInternalInsetsListener);
     }
 
@@ -1169,19 +1210,21 @@
     }
 
     private void setUpSwipeUpOnboarding(boolean connectedToOverviewProxy) {
-        if (connectedToOverviewProxy) {
-            mRecentsOnboarding.onConnectedToLauncher();
-        } else {
-            mRecentsOnboarding.onDisconnectedFromLauncher();
+        if (mRecentsOnboarding != null) {
+            if (connectedToOverviewProxy) {
+                mRecentsOnboarding.onConnectedToLauncher();
+            } else {
+                mRecentsOnboarding.onDisconnectedFromLauncher();
+            }
         }
     }
 
-    public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
-        pw.println("NavigationBarView {");
+    public void dump(PrintWriter pw) {
         final Rect r = new Rect();
         final Point size = new Point();
         getContextDisplay().getRealSize(size);
 
+        pw.println("NavigationBarView:");
         pw.println(String.format("      this: " + StatusBar.viewInfo(this)
                         + " " + visibilityToString(getVisibility())));
 
@@ -1204,21 +1247,23 @@
                         getLightTransitionsController().getCurrentDarkIntensity()));
 
         pw.println("      mOrientedHandleSamplingRegion: " + mOrientedHandleSamplingRegion);
+        pw.println("    mScreenOn: " + mScreenOn);
+
 
         dumpButton(pw, "back", getBackButton());
         dumpButton(pw, "home", getHomeButton());
         dumpButton(pw, "rcnt", getRecentsButton());
         dumpButton(pw, "rota", getRotateSuggestionButton());
         dumpButton(pw, "a11y", getAccessibilityButton());
-
-        pw.println("    }");
-        pw.println("    mScreenOn: " + mScreenOn);
+        dumpButton(pw, "ime", getImeSwitchButton());
 
         if (mNavigationInflaterView != null) {
             mNavigationInflaterView.dump(pw);
         }
         mContextualButtonGroup.dump(pw);
-        mRecentsOnboarding.dump(pw);
+        if (mRecentsOnboarding != null) {
+            mRecentsOnboarding.dump(pw);
+        }
         mRegionSamplingHelper.dump(pw);
         mEdgeBackGestureHandler.dump(pw);
     }
@@ -1251,6 +1296,10 @@
         return super.onApplyWindowInsets(insets);
     }
 
+    void registerDockedListener(SplitScreenController splitScreenController) {
+        splitScreenController.registerInSplitScreenListener(mDockedListener);
+    }
+
     private static void dumpButton(PrintWriter pw, String caption, ButtonDispatcher button) {
         pw.print("      " + caption + ": ");
         if (button == null) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationModeController.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationModeController.java
similarity index 98%
rename from packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationModeController.java
rename to packages/SystemUI/src/com/android/systemui/navigationbar/NavigationModeController.java
index c211de0..c704d32 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationModeController.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationModeController.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2019 The Android Open Source Project
+ * 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.
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.systemui.statusbar.phone;
+package com.android.systemui.navigationbar;
 
 import static android.content.Intent.ACTION_OVERLAY_CHANGED;
 import static android.view.WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL;
@@ -37,6 +37,7 @@
 import android.util.Log;
 
 import com.android.systemui.Dumpable;
+import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dagger.qualifiers.UiBackground;
 import com.android.systemui.shared.system.ActivityManagerWrapper;
 import com.android.systemui.statusbar.policy.ConfigurationController;
@@ -48,12 +49,11 @@
 import java.util.concurrent.Executor;
 
 import javax.inject.Inject;
-import javax.inject.Singleton;
 
 /**
  * Controller for tracking the current navigation bar mode.
  */
-@Singleton
+@SysUISingleton
 public class NavigationModeController implements Dumpable {
 
     private static final String TAG = NavigationModeController.class.getSimpleName();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/RotationButton.java b/packages/SystemUI/src/com/android/systemui/navigationbar/RotationButton.java
similarity index 80%
rename from packages/SystemUI/src/com/android/systemui/statusbar/phone/RotationButton.java
rename to packages/SystemUI/src/com/android/systemui/navigationbar/RotationButton.java
index 687efd3..e487858 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/RotationButton.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/RotationButton.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2019 The Android Open Source Project
+ * 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.
@@ -14,15 +14,18 @@
  * limitations under the License.
  */
 
-package com.android.systemui.statusbar.phone;
+package com.android.systemui.navigationbar;
 
 import android.view.View;
 
-import com.android.systemui.statusbar.policy.KeyButtonDrawable;
+import com.android.systemui.navigationbar.buttons.KeyButtonDrawable;
+
+import java.util.function.Consumer;
 
 /** Interface of a rotation button that interacts {@link RotationButtonController}. */
-interface RotationButton {
+public interface RotationButton {
     void setRotationButtonController(RotationButtonController rotationButtonController);
+    void setVisibilityChangedCallback(Consumer<Boolean> visibilityChangedCallback);
     View getCurrentView();
     boolean show();
     boolean hide();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/RotationButtonController.java b/packages/SystemUI/src/com/android/systemui/navigationbar/RotationButtonController.java
similarity index 97%
rename from packages/SystemUI/src/com/android/systemui/statusbar/phone/RotationButtonController.java
rename to packages/SystemUI/src/com/android/systemui/navigationbar/RotationButtonController.java
index f83cdd4..6cbf065 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/RotationButtonController.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/RotationButtonController.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2019 The Android Open Source Project
+ * 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.
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.systemui.statusbar.phone;
+package com.android.systemui.navigationbar;
 
 import static com.android.internal.view.RotationPolicy.NATURAL_ROTATION;
 
@@ -23,7 +23,6 @@
 import android.animation.ObjectAnimator;
 import android.annotation.ColorInt;
 import android.annotation.DrawableRes;
-import android.annotation.StyleRes;
 import android.app.StatusBarManager;
 import android.content.ContentResolver;
 import android.content.Context;
@@ -32,7 +31,6 @@
 import android.os.RemoteException;
 import android.provider.Settings;
 import android.util.Log;
-import android.view.ContextThemeWrapper;
 import android.view.IRotationWatcher.Stub;
 import android.view.MotionEvent;
 import android.view.Surface;
@@ -43,14 +41,13 @@
 import com.android.internal.logging.UiEvent;
 import com.android.internal.logging.UiEventLogger;
 import com.android.internal.logging.UiEventLoggerImpl;
-import com.android.settingslib.Utils;
 import com.android.systemui.Dependency;
 import com.android.systemui.Interpolators;
 import com.android.systemui.R;
+import com.android.systemui.navigationbar.buttons.KeyButtonDrawable;
 import com.android.systemui.shared.system.ActivityManagerWrapper;
 import com.android.systemui.shared.system.TaskStackChangeListener;
 import com.android.systemui.statusbar.policy.AccessibilityManagerWrapper;
-import com.android.systemui.statusbar.policy.KeyButtonDrawable;
 import com.android.systemui.statusbar.policy.RotationLockController;
 
 import java.util.Optional;
@@ -124,7 +121,8 @@
     }
 
     RotationButtonController(Context context, @ColorInt int lightIconColor,
-            @ColorInt int darkIconColor, RotationButton rotationButton) {
+            @ColorInt int darkIconColor, RotationButton rotationButton,
+            Consumer<Boolean> visibilityChangedCallback) {
         mContext = context;
         mLightIconColor = lightIconColor;
         mDarkIconColor = darkIconColor;
@@ -139,6 +137,7 @@
         mTaskStackListener = new TaskStackListenerImpl();
         mRotationButton.setOnClickListener(this::onRotateSuggestionClick);
         mRotationButton.setOnHoverListener(this::onRotateSuggestionHover);
+        mRotationButton.setVisibilityChangedCallback(visibilityChangedCallback);
     }
 
     void registerListeners() {
@@ -283,7 +282,6 @@
             return;
         }
 
-        // TODO: Remove styles?
         // Prepare to show the navbar icon by updating the icon style to change anim params
         mLastRotationSuggestion = rotation; // Remember rotation for click
         final boolean rotationCCW = isRotationAnimationCCW(windowRotation, rotation);
@@ -327,7 +325,7 @@
         }
     }
 
-    Context getContext() {
+    public Context getContext() {
         return mContext;
     }
 
@@ -335,15 +333,15 @@
         return mRotationButton;
     }
 
-    @DrawableRes int getIconResId() {
+    public @DrawableRes int getIconResId() {
         return mIconResId;
     }
 
-    @ColorInt int getLightIconColor() {
+    public @ColorInt int getLightIconColor() {
         return mLightIconColor;
     }
 
-    @ColorInt int getDarkIconColor() {
+    public @ColorInt int getDarkIconColor() {
         return mDarkIconColor;
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScreenPinningNotify.java b/packages/SystemUI/src/com/android/systemui/navigationbar/ScreenPinningNotify.java
similarity index 98%
rename from packages/SystemUI/src/com/android/systemui/statusbar/phone/ScreenPinningNotify.java
rename to packages/SystemUI/src/com/android/systemui/navigationbar/ScreenPinningNotify.java
index 071e00d..ac7baf5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScreenPinningNotify.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/ScreenPinningNotify.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.systemui.statusbar.phone;
+package com.android.systemui.navigationbar;
 
 import android.content.Context;
 import android.os.SystemClock;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ButtonDispatcher.java b/packages/SystemUI/src/com/android/systemui/navigationbar/buttons/ButtonDispatcher.java
similarity index 94%
rename from packages/SystemUI/src/com/android/systemui/statusbar/phone/ButtonDispatcher.java
rename to packages/SystemUI/src/com/android/systemui/navigationbar/buttons/ButtonDispatcher.java
index c273108..ade2923a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ButtonDispatcher.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/buttons/ButtonDispatcher.java
@@ -1,18 +1,20 @@
 /*
- * Copyright (C) 2016 The Android Open Source Project
+ * 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
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
  *
  *      http://www.apache.org/licenses/LICENSE-2.0
  *
- * Unless required by applicable law or agreed to in writing, software distributed under the
- * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the specific language governing
- * permissions and limitations under the License.
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
  */
 
-package com.android.systemui.statusbar.phone;
+package com.android.systemui.navigationbar.buttons;
 
 import static com.android.systemui.Interpolators.LINEAR;
 
@@ -24,7 +26,6 @@
 
 import com.android.systemui.Dependency;
 import com.android.systemui.assist.AssistManager;
-import com.android.systemui.statusbar.policy.KeyButtonDrawable;
 
 import java.util.ArrayList;
 
@@ -75,11 +76,11 @@
         mAssistManager = Dependency.get(AssistManager.class);
     }
 
-    void clear() {
+    public void clear() {
         mViews.clear();
     }
 
-    void addView(View view) {
+    public void addView(View view) {
         mViews.add(view);
         view.setOnClickListener(mClickListener);
         view.setOnTouchListener(mTouchListener);
@@ -337,6 +338,6 @@
     /**
      * Executes when button is detached from window.
      */
-    protected void onDestroy() {
+    public void onDestroy() {
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ButtonInterface.java b/packages/SystemUI/src/com/android/systemui/navigationbar/buttons/ButtonInterface.java
similarity index 86%
rename from packages/SystemUI/src/com/android/systemui/statusbar/phone/ButtonInterface.java
rename to packages/SystemUI/src/com/android/systemui/navigationbar/buttons/ButtonInterface.java
index 150a960..8d291dd 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ButtonInterface.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/buttons/ButtonInterface.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2019 The Android Open Source Project
+ * 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.
@@ -11,10 +11,10 @@
  * distributed under the License is distributed on an "AS IS" BASIS,
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  * See the License for the specific language governing permissions and
- * limitations under the License
+ * limitations under the License.
  */
 
-package com.android.systemui.statusbar.phone;
+package com.android.systemui.navigationbar.buttons;
 
 import android.annotation.Nullable;
 import android.graphics.drawable.Drawable;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ContextualButton.java b/packages/SystemUI/src/com/android/systemui/navigationbar/buttons/ContextualButton.java
similarity index 82%
rename from packages/SystemUI/src/com/android/systemui/statusbar/phone/ContextualButton.java
rename to packages/SystemUI/src/com/android/systemui/navigationbar/buttons/ContextualButton.java
index 53b369c..453e85a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ContextualButton.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/buttons/ContextualButton.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2018 The Android Open Source Project
+ * 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.
@@ -11,10 +11,10 @@
  * distributed under the License is distributed on an "AS IS" BASIS,
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  * See the License for the specific language governing permissions and
- * limitations under the License
+ * limitations under the License.
  */
 
-package com.android.systemui.statusbar.phone;
+package com.android.systemui.navigationbar.buttons;
 
 import android.annotation.DrawableRes;
 import android.annotation.IdRes;
@@ -22,9 +22,6 @@
 import android.content.Context;
 import android.view.View;
 
-import com.android.systemui.statusbar.policy.KeyButtonDrawable;
-import com.android.systemui.statusbar.policy.KeyButtonView;
-
 /**
  * Simple contextual button that is added to the {@link ContextualButtonGroup}. Extend if need extra
  * functionality.
@@ -34,6 +31,7 @@
     private ContextButtonListener mListener;
     private ContextualButtonGroup mGroup;
 
+    protected final Context mLightContext;
     protected final @DrawableRes int mIconResId;
 
     /**
@@ -42,8 +40,10 @@
       * @param buttonResId the button view from xml layout
       * @param iconResId icon resource to be used
       */
-    public ContextualButton(@IdRes int buttonResId, @DrawableRes int iconResId) {
+    public ContextualButton(@IdRes int buttonResId, Context lightContext,
+            @DrawableRes int iconResId) {
         super(buttonResId);
+        mLightContext = lightContext;
         mIconResId = iconResId;
     }
 
@@ -117,17 +117,8 @@
     }
 
     protected KeyButtonDrawable getNewDrawable(int lightIconColor, int darkIconColor) {
-        return KeyButtonDrawable.create(getContext().getApplicationContext(), lightIconColor,
-                darkIconColor, mIconResId, false /* shadow */, null /* ovalBackground */);
-    }
-
-    /**
-     * This context is from the view that could be stale after rotation or config change. To get
-     * correct resources use getApplicationContext() as well.
-     * @return current view context
-     */
-    protected Context getContext() {
-        return getCurrentView().getContext();
+        return KeyButtonDrawable.create(mLightContext, lightIconColor, darkIconColor, mIconResId,
+                false /* shadow */, null /* ovalBackground */);
     }
 
     public interface ContextButtonListener {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ContextualButtonGroup.java b/packages/SystemUI/src/com/android/systemui/navigationbar/buttons/ContextualButtonGroup.java
similarity index 90%
rename from packages/SystemUI/src/com/android/systemui/statusbar/phone/ContextualButtonGroup.java
rename to packages/SystemUI/src/com/android/systemui/navigationbar/buttons/ContextualButtonGroup.java
index c1017f4..50b638b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ContextualButtonGroup.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/buttons/ContextualButtonGroup.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2018 The Android Open Source Project
+ * 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.
@@ -11,10 +11,10 @@
  * distributed under the License is distributed on an "AS IS" BASIS,
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  * See the License for the specific language governing permissions and
- * limitations under the License
+ * limitations under the License.
  */
 
-package com.android.systemui.statusbar.phone;
+package com.android.systemui.navigationbar.buttons;
 
 import android.annotation.IdRes;
 import android.annotation.NonNull;
@@ -119,21 +119,20 @@
 
     public void dump(PrintWriter pw) {
         View view = getCurrentView();
-        pw.println("ContextualButtonGroup {");
-        pw.println("      getVisibleContextButton(): " + getVisibleContextButton());
-        pw.println("      isVisible(): " + isVisible());
-        pw.println("      attached(): " + (view != null && view.isAttachedToWindow()));
-        pw.println("      mButtonData [ ");
+        pw.println("ContextualButtonGroup");
+        pw.println("  getVisibleContextButton(): " + getVisibleContextButton());
+        pw.println("  isVisible(): " + isVisible());
+        pw.println("  attached(): " + (view != null && view.isAttachedToWindow()));
+        pw.println("  mButtonData [ ");
         for (int i = mButtonData.size() - 1; i >= 0; --i) {
             final ButtonData data = mButtonData.get(i);
             view = data.button.getCurrentView();
-            pw.println("            " + i + ": markedVisible=" + data.markedVisible
+            pw.println("    " + i + ": markedVisible=" + data.markedVisible
                     + " visible=" + data.button.getVisibility()
                     + " attached=" + (view != null && view.isAttachedToWindow())
                     + " alpha=" + data.button.getAlpha());
         }
-        pw.println("      ]");
-        pw.println("    }");
+        pw.println("  ]");
     }
 
     private int getContextButtonIndex(@IdRes int buttonResId) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeadZone.java b/packages/SystemUI/src/com/android/systemui/navigationbar/buttons/DeadZone.java
similarity index 96%
rename from packages/SystemUI/src/com/android/systemui/statusbar/policy/DeadZone.java
rename to packages/SystemUI/src/com/android/systemui/navigationbar/buttons/DeadZone.java
index 12d0617..7e5b554 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeadZone.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/buttons/DeadZone.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2012 The Android Open Source Project
+ * 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.
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.systemui.statusbar.policy;
+package com.android.systemui.navigationbar.buttons;
 
 import android.animation.ObjectAnimator;
 import android.content.res.Resources;
@@ -26,8 +26,8 @@
 
 import com.android.systemui.Dependency;
 import com.android.systemui.R;
-import com.android.systemui.statusbar.NavigationBarController;
-import com.android.systemui.statusbar.phone.NavigationBarView;
+import com.android.systemui.navigationbar.NavigationBarController;
+import com.android.systemui.navigationbar.NavigationBarView;
 
 /**
  * The "dead zone" consumes unintentional taps along the top edge of the navigation bar.
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyButtonDrawable.java b/packages/SystemUI/src/com/android/systemui/navigationbar/buttons/KeyButtonDrawable.java
similarity index 98%
rename from packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyButtonDrawable.java
rename to packages/SystemUI/src/com/android/systemui/navigationbar/buttons/KeyButtonDrawable.java
index 7559388..fc20169 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyButtonDrawable.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/buttons/KeyButtonDrawable.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2016 The Android Open Source Project
+ * 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.
@@ -11,10 +11,10 @@
  * distributed under the License is distributed on an "AS IS" BASIS,
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  * See the License for the specific language governing permissions and
- * limitations under the License
+ * limitations under the License.
  */
 
-package com.android.systemui.statusbar.policy;
+package com.android.systemui.navigationbar.buttons;
 
 import android.animation.ArgbEvaluator;
 import android.annotation.ColorInt;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyButtonRipple.java b/packages/SystemUI/src/com/android/systemui/navigationbar/buttons/KeyButtonRipple.java
similarity index 98%
rename from packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyButtonRipple.java
rename to packages/SystemUI/src/com/android/systemui/navigationbar/buttons/KeyButtonRipple.java
index 2d8784d..72cd4f1 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyButtonRipple.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/buttons/KeyButtonRipple.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2014 The Android Open Source Project
+ * 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.
@@ -11,10 +11,10 @@
  * distributed under the License is distributed on an "AS IS" BASIS,
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  * See the License for the specific language governing permissions and
- * limitations under the License
+ * limitations under the License.
  */
 
-package com.android.systemui.statusbar.policy;
+package com.android.systemui.navigationbar.buttons;
 
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyButtonView.java b/packages/SystemUI/src/com/android/systemui/navigationbar/buttons/KeyButtonView.java
similarity index 98%
rename from packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyButtonView.java
rename to packages/SystemUI/src/com/android/systemui/navigationbar/buttons/KeyButtonView.java
index 8d7ecd0..d6b8316 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyButtonView.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/buttons/KeyButtonView.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2008 The Android Open Source Project
+ * 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.
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.systemui.statusbar.policy;
+package com.android.systemui.navigationbar.buttons;
 
 import static android.view.Display.INVALID_DISPLAY;
 import static android.view.KeyEvent.KEYCODE_UNKNOWN;
@@ -61,7 +61,6 @@
 import com.android.systemui.bubbles.BubbleController;
 import com.android.systemui.recents.OverviewProxyService;
 import com.android.systemui.shared.system.QuickStepContract;
-import com.android.systemui.statusbar.phone.ButtonInterface;
 
 public class KeyButtonView extends ImageView implements ButtonInterface {
     private static final String TAG = KeyButtonView.class.getSimpleName();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NearestTouchFrame.java b/packages/SystemUI/src/com/android/systemui/navigationbar/buttons/NearestTouchFrame.java
similarity index 88%
rename from packages/SystemUI/src/com/android/systemui/statusbar/phone/NearestTouchFrame.java
rename to packages/SystemUI/src/com/android/systemui/navigationbar/buttons/NearestTouchFrame.java
index d022808..88c8fea 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NearestTouchFrame.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/buttons/NearestTouchFrame.java
@@ -1,18 +1,20 @@
 /*
- * Copyright (C) 2017 The Android Open Source Project
+ * 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
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
  *
  *      http://www.apache.org/licenses/LICENSE-2.0
  *
- * Unless required by applicable law or agreed to in writing, software distributed under the
- * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the specific language governing
- * permissions and limitations under the License.
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
  */
 
-package com.android.systemui.statusbar.phone;
+package com.android.systemui.navigationbar.buttons;
 
 import android.content.Context;
 import android.content.res.Configuration;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ReverseLinearLayout.java b/packages/SystemUI/src/com/android/systemui/navigationbar/buttons/ReverseLinearLayout.java
similarity index 98%
rename from packages/SystemUI/src/com/android/systemui/statusbar/phone/ReverseLinearLayout.java
rename to packages/SystemUI/src/com/android/systemui/navigationbar/buttons/ReverseLinearLayout.java
index d3ec187..f1e1366 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ReverseLinearLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/buttons/ReverseLinearLayout.java
@@ -12,7 +12,7 @@
  * permissions and limitations under the License.
  */
 
-package com.android.systemui.statusbar.phone;
+package com.android.systemui.navigationbar.buttons;
 
 import android.annotation.Nullable;
 import android.content.Context;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/RotationContextButton.java b/packages/SystemUI/src/com/android/systemui/navigationbar/buttons/RotationContextButton.java
similarity index 63%
rename from packages/SystemUI/src/com/android/systemui/statusbar/phone/RotationContextButton.java
rename to packages/SystemUI/src/com/android/systemui/navigationbar/buttons/RotationContextButton.java
index d7e95e4..6a97a33 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/RotationContextButton.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/buttons/RotationContextButton.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2018 The Android Open Source Project
+ * 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.
@@ -11,16 +11,22 @@
  * distributed under the License is distributed on an "AS IS" BASIS,
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  * See the License for the specific language governing permissions and
- * limitations under the License
+ * limitations under the License.
  */
 
-package com.android.systemui.statusbar.phone;
+package com.android.systemui.navigationbar.buttons;
 
 import android.annotation.DrawableRes;
 import android.annotation.IdRes;
+import android.content.Context;
 import android.view.View;
 
-import com.android.systemui.statusbar.policy.KeyButtonDrawable;
+import com.android.systemui.navigationbar.RotationButton;
+import com.android.systemui.navigationbar.RotationButtonController;
+import com.android.systemui.navigationbar.buttons.ContextualButton;
+import com.android.systemui.navigationbar.buttons.KeyButtonDrawable;
+
+import java.util.function.Consumer;
 
 /** Containing logic for the rotation button in nav bar. */
 public class RotationContextButton extends ContextualButton implements RotationButton {
@@ -28,8 +34,12 @@
 
     private RotationButtonController mRotationButtonController;
 
-    public RotationContextButton(@IdRes int buttonResId, @DrawableRes int iconResId) {
-        super(buttonResId, iconResId);
+    /**
+     * @param lightContext the context to use to load the icon resource
+     */
+    public RotationContextButton(@IdRes int buttonResId, Context lightContext,
+            @DrawableRes int iconResId) {
+        super(buttonResId, lightContext, iconResId);
     }
 
     @Override
@@ -38,6 +48,18 @@
     }
 
     @Override
+    public void setVisibilityChangedCallback(Consumer<Boolean> visibilityChangedCallback) {
+        setListener(new ContextButtonListener() {
+            @Override
+            public void onVisibilityChanged(ContextualButton button, boolean visible) {
+                if (visibilityChangedCallback != null) {
+                    visibilityChangedCallback.accept(visible);
+                }
+            }
+        });
+    }
+
+    @Override
     public void setVisibility(int visibility) {
         super.setVisibility(visibility);
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/EdgeBackGestureHandler.java b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java
similarity index 98%
rename from packages/SystemUI/src/com/android/systemui/statusbar/phone/EdgeBackGestureHandler.java
rename to packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java
index 9606318..a1b55c4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/EdgeBackGestureHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java
@@ -1,5 +1,5 @@
-/**
- * Copyright (C) 2019 The Android Open Source Project
+/*
+ * 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.
@@ -13,7 +13,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package com.android.systemui.statusbar.phone;
+package com.android.systemui.navigationbar.gestural;
 
 import static android.view.Display.INVALID_DISPLAY;
 
@@ -59,6 +59,8 @@
 import com.android.systemui.broadcast.BroadcastDispatcher;
 import com.android.systemui.bubbles.BubbleController;
 import com.android.systemui.model.SysUiState;
+import com.android.systemui.navigationbar.NavigationBarView;
+import com.android.systemui.navigationbar.NavigationModeController;
 import com.android.systemui.plugins.NavigationEdgeBackPlugin;
 import com.android.systemui.plugins.PluginListener;
 import com.android.systemui.recents.OverviewProxyService;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/FloatingRotationButton.java b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/FloatingRotationButton.java
similarity index 81%
rename from packages/SystemUI/src/com/android/systemui/statusbar/phone/FloatingRotationButton.java
rename to packages/SystemUI/src/com/android/systemui/navigationbar/gestural/FloatingRotationButton.java
index 687f5f1..61118c5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/FloatingRotationButton.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/FloatingRotationButton.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2019 The Android Open Source Project
+ * 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.
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.systemui.statusbar.phone;
+package com.android.systemui.navigationbar.gestural;
 
 import android.content.Context;
 import android.content.res.Resources;
@@ -27,8 +27,12 @@
 import android.view.WindowManager;
 
 import com.android.systemui.R;
-import com.android.systemui.statusbar.policy.KeyButtonDrawable;
-import com.android.systemui.statusbar.policy.KeyButtonView;
+import com.android.systemui.navigationbar.RotationButton;
+import com.android.systemui.navigationbar.RotationButtonController;
+import com.android.systemui.navigationbar.buttons.KeyButtonDrawable;
+import com.android.systemui.navigationbar.buttons.KeyButtonView;
+
+import java.util.function.Consumer;
 
 /** Containing logic for the rotation button on the physical left bottom corner of the screen. */
 public class FloatingRotationButton implements RotationButton {
@@ -45,8 +49,9 @@
     private boolean mCanShow = true;
 
     private RotationButtonController mRotationButtonController;
+    private Consumer<Boolean> mVisibilityChangedCallback;
 
-    FloatingRotationButton(Context context) {
+    public FloatingRotationButton(Context context) {
         mContext = context;
         mWindowManager = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE);
         mKeyButtonView = (KeyButtonView) LayoutInflater.from(mContext).inflate(
@@ -67,6 +72,11 @@
     }
 
     @Override
+    public void setVisibilityChangedCallback(Consumer<Boolean> visibilityChangedCallback) {
+        mVisibilityChangedCallback = visibilityChangedCallback;
+    }
+
+    @Override
     public View getCurrentView() {
         return mKeyButtonView;
     }
@@ -105,6 +115,16 @@
             mKeyButtonDrawable.resetAnimation();
             mKeyButtonDrawable.startAnimation();
         }
+        mKeyButtonView.addOnLayoutChangeListener(new View.OnLayoutChangeListener() {
+            @Override
+            public void onLayoutChange(View view, int i, int i1, int i2, int i3, int i4, int i5,
+                    int i6, int i7) {
+                if (mIsShowing && mVisibilityChangedCallback != null) {
+                    mVisibilityChangedCallback.accept(true);
+                }
+                mKeyButtonView.removeOnLayoutChangeListener(this);
+            }
+        });
         return true;
     }
 
@@ -115,6 +135,9 @@
         }
         mWindowManager.removeViewImmediate(mKeyButtonView);
         mIsShowing = false;
+        if (mVisibilityChangedCallback != null) {
+            mVisibilityChangedCallback.accept(false);
+        }
         return true;
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarEdgePanel.java b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/NavigationBarEdgePanel.java
similarity index 99%
rename from packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarEdgePanel.java
rename to packages/SystemUI/src/com/android/systemui/navigationbar/gestural/NavigationBarEdgePanel.java
index 2357309..284f41a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarEdgePanel.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/NavigationBarEdgePanel.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2019 The Android Open Source Project
+ * 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.
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.systemui.statusbar.phone;
+package com.android.systemui.navigationbar.gestural;
 
 import android.animation.ValueAnimator;
 import android.content.Context;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationHandle.java b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/NavigationHandle.java
similarity index 95%
rename from packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationHandle.java
rename to packages/SystemUI/src/com/android/systemui/navigationbar/gestural/NavigationHandle.java
index b874795..33e6aa4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationHandle.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/NavigationHandle.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2019 The Android Open Source Project
+ * 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.
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.systemui.statusbar.phone;
+package com.android.systemui.navigationbar.gestural;
 
 import android.animation.ArgbEvaluator;
 import android.annotation.ColorInt;
@@ -29,6 +29,7 @@
 
 import com.android.settingslib.Utils;
 import com.android.systemui.R;
+import com.android.systemui.navigationbar.buttons.ButtonInterface;
 
 public class NavigationHandle extends View implements ButtonInterface {
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickswitchOrientedNavHandle.java b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/QuickswitchOrientedNavHandle.java
similarity index 91%
rename from packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickswitchOrientedNavHandle.java
rename to packages/SystemUI/src/com/android/systemui/navigationbar/gestural/QuickswitchOrientedNavHandle.java
index fe74677..71c8a2c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickswitchOrientedNavHandle.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/QuickswitchOrientedNavHandle.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2019 The Android Open Source Project
+ * 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.
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.systemui.statusbar.phone;
+package com.android.systemui.navigationbar.gestural;
 
 import android.content.Context;
 import android.graphics.Canvas;
@@ -34,7 +34,7 @@
         mWidth = context.getResources().getDimensionPixelSize(R.dimen.navigation_home_handle_width);
     }
 
-    void setDeltaRotation(@Surface.Rotation int rotation) {
+    public void setDeltaRotation(@Surface.Rotation int rotation) {
         mDeltaRotation = rotation;
     }
 
@@ -43,7 +43,7 @@
         canvas.drawRoundRect(computeHomeHandleBounds(), mRadius, mRadius, mPaint);
     }
 
-    RectF computeHomeHandleBounds() {
+    public RectF computeHomeHandleBounds() {
         int left;
         int top;
         int bottom;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/RegionSamplingHelper.java b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/RegionSamplingHelper.java
similarity index 96%
rename from packages/SystemUI/src/com/android/systemui/statusbar/phone/RegionSamplingHelper.java
rename to packages/SystemUI/src/com/android/systemui/navigationbar/gestural/RegionSamplingHelper.java
index 3c8aa86..70117eb 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/RegionSamplingHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/RegionSamplingHelper.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2019 The Android Open Source Project
+ * 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.
@@ -11,10 +11,10 @@
  * distributed under the License is distributed on an "AS IS" BASIS,
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  * See the License for the specific language governing permissions and
- * limitations under the License
+ * limitations under the License.
  */
 
-package com.android.systemui.statusbar.phone;
+package com.android.systemui.navigationbar.gestural;
 
 import static android.view.Display.DEFAULT_DISPLAY;
 
@@ -108,7 +108,7 @@
         }
     }
 
-    void start(Rect initialSamplingBounds) {
+    public void start(Rect initialSamplingBounds) {
         if (!mCallback.isSamplingEnabled()) {
             return;
         }
@@ -122,12 +122,12 @@
         updateSamplingListener();
     }
 
-    void stop() {
+    public void stop() {
         mSamplingEnabled = false;
         updateSamplingListener();
     }
 
-    void stopAndDestroy() {
+    public void stopAndDestroy() {
         stop();
         mSamplingListener.destroy();
         mIsDestroyed = true;
@@ -220,12 +220,12 @@
         }
     }
 
-    void setWindowVisible(boolean visible) {
+    public void setWindowVisible(boolean visible) {
         mWindowVisible = visible;
         updateSamplingListener();
     }
 
-    void dump(PrintWriter pw) {
+    public void dump(PrintWriter pw) {
         pw.println("RegionSamplingHelper:");
         pw.println("  sampleView isAttached: " + mSampledView.isAttachedToWindow());
         pw.println("  sampleView isScValid: " + (mSampledView.isAttachedToWindow()
diff --git a/packages/SystemUI/src/com/android/systemui/onehanded/OneHandedAnimationController.java b/packages/SystemUI/src/com/android/systemui/onehanded/OneHandedAnimationController.java
index 2b07ac3..9be1b5a 100644
--- a/packages/SystemUI/src/com/android/systemui/onehanded/OneHandedAnimationController.java
+++ b/packages/SystemUI/src/com/android/systemui/onehanded/OneHandedAnimationController.java
@@ -19,6 +19,7 @@
 import android.animation.Animator;
 import android.animation.ValueAnimator;
 import android.annotation.IntDef;
+import android.content.Context;
 import android.graphics.Rect;
 import android.view.SurfaceControl;
 import android.view.animation.Interpolator;
@@ -32,8 +33,6 @@
 import java.util.HashMap;
 import java.util.List;
 
-import javax.inject.Inject;
-
 /**
  * Controller class of OneHanded animations (both from and to OneHanded mode).
  */
@@ -62,10 +61,8 @@
     /**
      * Constructor of OneHandedAnimationController
      */
-    @Inject
-    public OneHandedAnimationController(
-            OneHandedSurfaceTransactionHelper surfaceTransactionHelper) {
-        mSurfaceTransactionHelper = surfaceTransactionHelper;
+    public OneHandedAnimationController(Context context) {
+        mSurfaceTransactionHelper = new OneHandedSurfaceTransactionHelper(context);
         mOvershootInterpolator = new OvershootInterpolator();
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/onehanded/OneHandedManagerImpl.java b/packages/SystemUI/src/com/android/systemui/onehanded/OneHandedController.java
similarity index 63%
rename from packages/SystemUI/src/com/android/systemui/onehanded/OneHandedManagerImpl.java
rename to packages/SystemUI/src/com/android/systemui/onehanded/OneHandedController.java
index 51e5875..bb59449 100644
--- a/packages/SystemUI/src/com/android/systemui/onehanded/OneHandedManagerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/onehanded/OneHandedController.java
@@ -24,15 +24,19 @@
 import android.content.Context;
 import android.graphics.Point;
 import android.graphics.Rect;
+import android.os.SystemProperties;
+import android.view.KeyEvent;
 
 import androidx.annotation.NonNull;
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.systemui.Dumpable;
-import com.android.systemui.R;
+import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.model.SysUiState;
+import com.android.systemui.navigationbar.NavigationModeController;
 import com.android.systemui.shared.system.ActivityManagerWrapper;
 import com.android.systemui.shared.system.TaskStackChangeListener;
+import com.android.systemui.statusbar.CommandQueue;
 import com.android.wm.shell.common.DisplayChangeController;
 import com.android.wm.shell.common.DisplayController;
 
@@ -40,19 +44,22 @@
 import java.io.PrintWriter;
 
 import javax.inject.Inject;
-import javax.inject.Singleton;
 
 /**
  * Manages and manipulates the one handed states, transitions, and gesture for phones.
  */
-@Singleton
-public class OneHandedManagerImpl implements OneHandedManager, Dumpable {
+@SysUISingleton
+public class OneHandedController implements Dumpable {
     private static final String TAG = "OneHandedManager";
+    private static final String ONE_HANDED_MODE_OFFSET_PERCENTAGE =
+            "persist.debug.one_handed_offset_percentage";
 
     private boolean mIsOneHandedEnabled;
+    private boolean mIsSwipeToNotificationEnabled;
     private boolean mTaskChangeToExit;
     private float mOffSetFraction;
 
+    private final CommandQueue mCommandQueue;
     private final DisplayController mDisplayController;
     private final OneHandedGestureHandler mGestureHandler;
     private final OneHandedTimeoutHandler mTimeoutHandler;
@@ -60,11 +67,7 @@
     private final OneHandedTutorialHandler mTutorialHandler;
     private final SysUiState mSysUiFlagContainer;
 
-    private Context mContext;
     private OneHandedDisplayAreaOrganizer mDisplayAreaOrganizer;
-    private OneHandedGestureHandler.OneHandedGestureEventCallback mGestureEventCallback;
-    private OneHandedTouchHandler.OneHandedTouchEventCallback mTouchEventCallback;
-    private OneHandedTransitionCallback mTransitionCallback;
 
     /**
      * Handler for system task stack changes, exit when user lunch new task or bring task to front
@@ -92,7 +95,6 @@
     /**
      * Handle rotation based on OnDisplayChangingListener callback
      */
-    @VisibleForTesting
     private final DisplayChangeController.OnDisplayChangingListener mRotationController =
             (display, fromRotation, toRotation, wct) -> {
                 if (mDisplayAreaOrganizer != null) {
@@ -104,22 +106,56 @@
      * Constructor of OneHandedManager
      */
     @Inject
-    public OneHandedManagerImpl(Context context,
+    public OneHandedController(Context context,
+            CommandQueue commandQueue,
+            DisplayController displayController,
+            NavigationModeController navigationModeController,
+            SysUiState sysUiState) {
+        mCommandQueue = commandQueue;
+        mDisplayController = displayController;
+        mDisplayController.addDisplayChangingController(mRotationController);
+        mSysUiFlagContainer = sysUiState;
+        mOffSetFraction = SystemProperties.getInt(ONE_HANDED_MODE_OFFSET_PERCENTAGE, 50) / 100.0f;
+
+        mIsOneHandedEnabled = OneHandedSettingsUtil.getSettingsOneHandedModeEnabled(
+                context.getContentResolver());
+        mIsSwipeToNotificationEnabled = OneHandedSettingsUtil.getSettingsSwipeToNotificationEnabled(
+                context.getContentResolver());
+        mTimeoutHandler = OneHandedTimeoutHandler.get();
+        mTouchHandler = new OneHandedTouchHandler();
+        mTutorialHandler = new OneHandedTutorialHandler(context);
+        mDisplayAreaOrganizer = new OneHandedDisplayAreaOrganizer(context, displayController,
+                new OneHandedAnimationController(context), mTutorialHandler);
+        mGestureHandler = new OneHandedGestureHandler(
+                context, displayController, navigationModeController);
+        updateOneHandedEnabled();
+        setupGestures();
+    }
+
+    /**
+     * Constructor of OneHandedManager for testing
+     */
+    // TODO(b/161980408): Should remove extra constructor.
+    @VisibleForTesting
+    OneHandedController(Context context,
+            CommandQueue commandQueue,
             DisplayController displayController,
             OneHandedDisplayAreaOrganizer displayAreaOrganizer,
             OneHandedTouchHandler touchHandler,
             OneHandedTutorialHandler tutorialHandler,
             OneHandedGestureHandler gestureHandler,
             SysUiState sysUiState) {
-        mContext = context;
+        mCommandQueue = commandQueue;
         mDisplayAreaOrganizer = displayAreaOrganizer;
         mDisplayController = displayController;
         mDisplayController.addDisplayChangingController(mRotationController);
         mSysUiFlagContainer = sysUiState;
-        mOffSetFraction =
-                context.getResources().getFraction(R.fraction.config_one_handed_offset, 1, 1);
+        mOffSetFraction = SystemProperties.getInt(ONE_HANDED_MODE_OFFSET_PERCENTAGE, 50) / 100.0f;
+
         mIsOneHandedEnabled = OneHandedSettingsUtil.getSettingsOneHandedModeEnabled(
                 context.getContentResolver());
+        mIsSwipeToNotificationEnabled = OneHandedSettingsUtil.getSettingsSwipeToNotificationEnabled(
+                context.getContentResolver());
         mTimeoutHandler = OneHandedTimeoutHandler.get();
         mTouchHandler = touchHandler;
         mTutorialHandler = tutorialHandler;
@@ -148,11 +184,18 @@
     }
 
     /**
-     * Start one handed mode
+     * Sets whether to enable swipe bottom to notification gesture when user update settings.
      */
-    @Override
+    public void setSwipeToNotificationEnabled(boolean enabled) {
+        mIsSwipeToNotificationEnabled = enabled;
+        updateOneHandedEnabled();
+    }
+
+    /**
+     * Enters one handed mode.
+     */
     public void startOneHanded() {
-        if (!mDisplayAreaOrganizer.isInOneHanded() && mIsOneHandedEnabled) {
+        if (!mDisplayAreaOrganizer.isInOneHanded()) {
             final int yOffSet = Math.round(getDisplaySize().y * mOffSetFraction);
             mDisplayAreaOrganizer.scheduleOffset(0, yOffSet);
             mTimeoutHandler.resetTimer();
@@ -160,9 +203,8 @@
     }
 
     /**
-     * Stop one handed mode
+     * Exits one handed mode.
      */
-    @Override
     public void stopOneHanded() {
         if (mDisplayAreaOrganizer.isInOneHanded()) {
             mDisplayAreaOrganizer.scheduleOffset(0, 0);
@@ -171,53 +213,45 @@
     }
 
     private void setupGestures() {
-        mTouchEventCallback = new OneHandedTouchHandler.OneHandedTouchEventCallback() {
-            @Override
-            public boolean onStart() {
-                boolean result = false;
-                if (!mDisplayAreaOrganizer.isInOneHanded()) {
-                    startOneHanded();
-                    result = true;
-                }
-                return result;
-            }
+        mTouchHandler.registerTouchEventListener(
+                new OneHandedTouchHandler.OneHandedTouchEventCallback() {
+                    @Override
+                    public void onStart() {
+                        if (mIsOneHandedEnabled) {
+                            startOneHanded();
+                        }
+                    }
 
-            @Override
-            public boolean onStop() {
-                boolean result = false;
-                if (mDisplayAreaOrganizer.isInOneHanded()) {
-                    stopOneHanded();
-                    result = true;
-                }
-                return result;
-            }
-        };
-        mTouchHandler.registerTouchEventListener(mTouchEventCallback);
+                    @Override
+                    public void onStop() {
+                        if (mIsOneHandedEnabled) {
+                            stopOneHanded();
+                        }
+                    }
+                });
 
-        mGestureEventCallback = new OneHandedGestureHandler.OneHandedGestureEventCallback() {
-            @Override
-            public boolean onStart() {
-                boolean result = false;
-                if (!mDisplayAreaOrganizer.isInOneHanded()) {
-                    startOneHanded();
-                    result = true;
-                }
-                return result;
-            }
+        mGestureHandler.setGestureEventListener(
+                new OneHandedGestureHandler.OneHandedGestureEventCallback() {
+                    @Override
+                    public void onStart() {
+                        if (mIsOneHandedEnabled) {
+                            startOneHanded();
+                        } else if (mIsSwipeToNotificationEnabled) {
+                            mCommandQueue.handleSystemKey(KeyEvent.KEYCODE_SYSTEM_NAVIGATION_DOWN);
+                        }
+                    }
 
-            @Override
-            public boolean onStop() {
-                boolean result = false;
-                if (mDisplayAreaOrganizer.isInOneHanded()) {
-                    stopOneHanded();
-                    result = true;
-                }
-                return result;
-            }
-        };
-        mGestureHandler.setGestureEventListener(mGestureEventCallback);
+                    @Override
+                    public void onStop() {
+                        if (mIsOneHandedEnabled) {
+                            stopOneHanded();
+                        } else if (mIsSwipeToNotificationEnabled) {
+                            mCommandQueue.handleSystemKey(KeyEvent.KEYCODE_SYSTEM_NAVIGATION_UP);
+                        }
+                    }
+                });
 
-        mTransitionCallback = new OneHandedTransitionCallback() {
+        mDisplayAreaOrganizer.registerTransitionCallback(new OneHandedTransitionCallback() {
             @Override
             public void onStartFinished(Rect bounds) {
                 mSysUiFlagContainer.setFlag(SYSUI_STATE_ONE_HANDED_ACTIVE,
@@ -229,8 +263,8 @@
                 mSysUiFlagContainer.setFlag(SYSUI_STATE_ONE_HANDED_ACTIVE,
                         false).commitUpdate(DEFAULT_DISPLAY);
             }
-        };
-        mDisplayAreaOrganizer.registerTransitionCallback(mTransitionCallback);
+        });
+
         mDisplayAreaOrganizer.registerTransitionCallback(mTouchHandler);
         mDisplayAreaOrganizer.registerTransitionCallback(mGestureHandler);
         mDisplayAreaOrganizer.registerTransitionCallback(mTutorialHandler);
@@ -264,7 +298,7 @@
             ActivityManagerWrapper.getInstance().registerTaskStackListener(mTaskStackListener);
         }
         mTouchHandler.onOneHandedEnabled(mIsOneHandedEnabled);
-        mGestureHandler.onOneHandedEnabled(mIsOneHandedEnabled);
+        mGestureHandler.onOneHandedEnabled(mIsOneHandedEnabled || mIsSwipeToNotificationEnabled);
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/systemui/onehanded/OneHandedDisplayAreaOrganizer.java b/packages/SystemUI/src/com/android/systemui/onehanded/OneHandedDisplayAreaOrganizer.java
index 8550959..ad9f7ea 100644
--- a/packages/SystemUI/src/com/android/systemui/onehanded/OneHandedDisplayAreaOrganizer.java
+++ b/packages/SystemUI/src/com/android/systemui/onehanded/OneHandedDisplayAreaOrganizer.java
@@ -26,6 +26,7 @@
 import android.graphics.Rect;
 import android.os.Handler;
 import android.os.Looper;
+import android.os.SystemProperties;
 import android.util.Log;
 import android.view.SurfaceControl;
 import android.window.DisplayAreaInfo;
@@ -47,8 +48,6 @@
 import java.util.List;
 import java.util.Objects;
 
-import javax.inject.Inject;
-
 /**
  * Manages OneHanded display areas such as offset.
  *
@@ -61,6 +60,8 @@
  */
 public class OneHandedDisplayAreaOrganizer extends DisplayAreaOrganizer implements Dumpable {
     private static final String TAG = "OneHandedDisplayAreaOrganizer";
+    private static final String ONE_HANDED_MODE_TRANSLATE_ANIMATION_DURATION =
+            "persist.debug.one_handed_translate_animation_duration";
 
     @VisibleForTesting
     static final int MSG_RESET_IMMEDIATE = 1;
@@ -146,7 +147,6 @@
     /**
      * Constructor of OneHandedDisplayAreaOrganizer
      */
-    @Inject
     public OneHandedDisplayAreaOrganizer(Context context,
             DisplayController displayController,
             OneHandedAnimationController animationController,
@@ -156,8 +156,8 @@
         mDisplayController = displayController;
         mDefaultDisplayBounds.set(getDisplayBounds());
         mLastVisualDisplayBounds.set(getDisplayBounds());
-        mEnterExitAnimationDurationMs = context.getResources().getInteger(
-                com.android.systemui.R.integer.config_one_handed_translate_animation_duration);
+        mEnterExitAnimationDurationMs =
+                SystemProperties.getInt(ONE_HANDED_MODE_TRANSLATE_ANIMATION_DURATION, 300);
         mSurfaceControlTransactionFactory = SurfaceControl.Transaction::new;
         mTutorialHandler = tutorialHandler;
     }
diff --git a/packages/SystemUI/src/com/android/systemui/onehanded/OneHandedGestureHandler.java b/packages/SystemUI/src/com/android/systemui/onehanded/OneHandedGestureHandler.java
index ded3861..f3be699 100644
--- a/packages/SystemUI/src/com/android/systemui/onehanded/OneHandedGestureHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/onehanded/OneHandedGestureHandler.java
@@ -40,18 +40,14 @@
 import androidx.annotation.VisibleForTesting;
 
 import com.android.systemui.R;
-import com.android.systemui.statusbar.phone.NavigationModeController;
+import com.android.systemui.navigationbar.NavigationModeController;
 import com.android.wm.shell.common.DisplayChangeController;
 import com.android.wm.shell.common.DisplayController;
 
-import javax.inject.Inject;
-import javax.inject.Singleton;
-
 /**
  * The class manage swipe up and down gesture for 3-Button mode navigation,
  * others(e.g, 2-button, full gesture mode) are handled by Launcher quick steps.
  */
-@Singleton
 public class OneHandedGestureHandler implements OneHandedTransitionCallback,
         NavigationModeController.ModeChangedListener,
         DisplayChangeController.OnDisplayChangingListener {
@@ -91,7 +87,6 @@
      * @param displayController        {@link DisplayController}
      * @param navigationModeController {@link NavigationModeController}
      */
-    @Inject
     public OneHandedGestureHandler(Context context, DisplayController displayController,
             NavigationModeController navigationModeController) {
         mDisplayController = displayController;
@@ -108,7 +103,7 @@
     }
 
     /**
-     * Notified by {@link OneHandedManager}, when user update settings of Enabled or Disabled
+     * Notified by {@link OneHandedController}, when user update settings of Enabled or Disabled
      *
      * @param isEnabled is one handed settings enabled or not
      */
@@ -159,7 +154,7 @@
                     } else {
                         float distance = (float) Math.hypot(mLastPos.x - mDownPos.x,
                                 mLastPos.y - mDownPos.y);
-                        if (distance > mDragDistThreshold && mPassedSlop) {
+                        if (distance > mDragDistThreshold) {
                             mGestureEventCallback.onStop();
                         }
                     }
@@ -269,17 +264,17 @@
     }
 
     /**
-     * The touch(gesture) events to notify {@link OneHandedManager} start or stop one handed
+     * The touch(gesture) events to notify {@link OneHandedController} start or stop one handed
      */
     public interface OneHandedGestureEventCallback {
         /**
-         * Handle the start event event, and return whether the event was consumed.
+         * Handles the start gesture.
          */
-        boolean onStart();
+        void onStart();
 
         /**
-         * Handle the exit event event, and return whether the event was consumed.
+         * Handles the exit gesture.
          */
-        boolean onStop();
+        void onStop();
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/onehanded/OneHandedManager.java b/packages/SystemUI/src/com/android/systemui/onehanded/OneHandedManager.java
deleted file mode 100644
index 90187a2..0000000
--- a/packages/SystemUI/src/com/android/systemui/onehanded/OneHandedManager.java
+++ /dev/null
@@ -1,44 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.onehanded;
-
-/**
- * The base class of OneHandedManager
- */
-public interface OneHandedManager {
-
-    /**
-     * Set one handed enabled or disabled
-     */
-    default void setOneHandedEnabled(boolean enabled) {}
-
-    /**
-     * Set task stack changed to exit
-     */
-    default void setTaskChangeToExit(boolean enabled) {}
-
-    /**
-     * Exit one handed mode
-     */
-    default void stopOneHanded() {}
-
-    /**
-     * Trigger one handed mode
-     */
-    default void startOneHanded() {}
-
-}
diff --git a/packages/SystemUI/src/com/android/systemui/onehanded/OneHandedSettingsUtil.java b/packages/SystemUI/src/com/android/systemui/onehanded/OneHandedSettingsUtil.java
index 9b232cd..0598f32 100644
--- a/packages/SystemUI/src/com/android/systemui/onehanded/OneHandedSettingsUtil.java
+++ b/packages/SystemUI/src/com/android/systemui/onehanded/OneHandedSettingsUtil.java
@@ -22,19 +22,16 @@
 import android.net.Uri;
 import android.provider.Settings;
 
-import com.android.internal.annotations.VisibleForTesting;
+import com.android.systemui.dagger.SysUISingleton;
 
 import java.io.PrintWriter;
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 
-import javax.inject.Inject;
-import javax.inject.Singleton;
-
 /**
  * APIs for querying or updating one handed settings .
  */
-@Singleton
+@SysUISingleton
 public final class OneHandedSettingsUtil {
     private static final String TAG = "OneHandedSettingsUtil";
 
@@ -65,11 +62,6 @@
      */
     public static final int ONE_HANDED_TIMEOUT_LONG_IN_SECONDS = 12;
 
-    @VisibleForTesting
-    @Inject
-    OneHandedSettingsUtil() {
-    }
-
     /**
      * Register one handed preference settings observer
      *
@@ -132,6 +124,14 @@
                 Settings.Secure.ONE_HANDED_MODE_TIMEOUT, ONE_HANDED_TIMEOUT_MEDIUM_IN_SECONDS);
     }
 
+    /**
+     * Returns whether swipe bottom to notification gesture enabled or not.
+     */
+    public static boolean getSettingsSwipeToNotificationEnabled(ContentResolver resolver) {
+        return Settings.Secure.getInt(resolver,
+                Settings.Secure.SWIPE_BOTTOM_TO_NOTIFICATION_ENABLED, 0) == 1;
+    }
+
     protected static void dump(PrintWriter pw, String prefix, ContentResolver resolver) {
         final String innerPrefix = prefix + "  ";
         pw.println(prefix + TAG);
@@ -142,4 +142,6 @@
         pw.print(innerPrefix + "tapsAppToExit=");
         pw.println(getSettingsTapsAppToExit(resolver));
     }
+
+    private OneHandedSettingsUtil() {}
 }
diff --git a/packages/SystemUI/src/com/android/systemui/onehanded/OneHandedSurfaceTransactionHelper.java b/packages/SystemUI/src/com/android/systemui/onehanded/OneHandedSurfaceTransactionHelper.java
index 5933eb3..bc4a9b4 100644
--- a/packages/SystemUI/src/com/android/systemui/onehanded/OneHandedSurfaceTransactionHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/onehanded/OneHandedSurfaceTransactionHelper.java
@@ -23,8 +23,6 @@
 
 import com.android.systemui.R;
 
-import javax.inject.Inject;
-
 /**
  * Abstracts the common operations on {@link SurfaceControl.Transaction} for OneHanded transition.
  */
@@ -32,7 +30,6 @@
     private final boolean mEnableCornerRadius;
     private final float mCornerRadius;
 
-    @Inject
     public OneHandedSurfaceTransactionHelper(Context context) {
         final Resources res = context.getResources();
         mCornerRadius = res.getDimension(com.android.internal.R.dimen.rounded_corner_radius);
diff --git a/packages/SystemUI/src/com/android/systemui/onehanded/OneHandedTimeoutHandler.java b/packages/SystemUI/src/com/android/systemui/onehanded/OneHandedTimeoutHandler.java
index 194b24f..6bed304 100644
--- a/packages/SystemUI/src/com/android/systemui/onehanded/OneHandedTimeoutHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/onehanded/OneHandedTimeoutHandler.java
@@ -33,12 +33,9 @@
 import java.util.List;
 import java.util.concurrent.TimeUnit;
 
-import javax.inject.Singleton;
-
 /**
  * Timeout handler for stop one handed mode operations.
  */
-@Singleton
 public class OneHandedTimeoutHandler implements Dumpable {
     private static final String TAG = "OneHandedTimeoutHandler";
     private static boolean sIsDragging = false;
diff --git a/packages/SystemUI/src/com/android/systemui/onehanded/OneHandedTouchHandler.java b/packages/SystemUI/src/com/android/systemui/onehanded/OneHandedTouchHandler.java
index d616a3a..8265da6 100644
--- a/packages/SystemUI/src/com/android/systemui/onehanded/OneHandedTouchHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/onehanded/OneHandedTouchHandler.java
@@ -35,15 +35,11 @@
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
 
-import javax.inject.Inject;
-import javax.inject.Singleton;
-
 /**
  * Manages all the touch handling for One Handed on the Phone, including user tap outside region
  * to exit, reset timer when user is in one-handed mode.
  * Refer {@link OneHandedGestureHandler} to see start and stop one handed gesture
  */
-@Singleton
 public class OneHandedTouchHandler implements OneHandedTransitionCallback, Dumpable {
     private static final String TAG = "OneHandedTouchHandler";
     private final Rect mLastUpdatedBounds = new Rect();
@@ -61,14 +57,13 @@
     private boolean mIsOnStopTransitioning;
     private boolean mIsInOutsideRegion;
 
-    @Inject
     public OneHandedTouchHandler() {
         mTimeoutHandler = OneHandedTimeoutHandler.get();
         updateIsEnabled();
     }
 
     /**
-     * Notified by {@link OneHandedManagerImpl}, when user update settings of Enabled or Disabled
+     * Notified by {@link OneHandedController}, when user update settings of Enabled or Disabled
      *
      * @param isEnabled is one handed settings enabled or not
      */
@@ -171,17 +166,17 @@
     }
 
     /**
-     * The touch(gesture) events to notify {@link OneHandedManager} start or stop one handed
+     * The touch(gesture) events to notify {@link OneHandedController} start or stop one handed
      */
     public interface OneHandedTouchEventCallback {
         /**
-         * Handle the start event event, and return whether the event was consumed.
+         * Handle the start event.
          */
-        boolean onStart();
+        void onStart();
 
         /**
-         * Handle the exit event event, and return whether the event was consumed.
+         * Handle the exit event.
          */
-        boolean onStop();
+        void onStop();
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/onehanded/OneHandedTutorialHandler.java b/packages/SystemUI/src/com/android/systemui/onehanded/OneHandedTutorialHandler.java
index 8a67da5..0354c72 100644
--- a/packages/SystemUI/src/com/android/systemui/onehanded/OneHandedTutorialHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/onehanded/OneHandedTutorialHandler.java
@@ -16,11 +16,14 @@
 
 package com.android.systemui.onehanded;
 
+import android.content.ContentResolver;
 import android.content.Context;
 import android.graphics.PixelFormat;
 import android.graphics.Point;
 import android.graphics.Rect;
 import android.os.Handler;
+import android.os.SystemProperties;
+import android.provider.Settings;
 import android.view.Gravity;
 import android.view.LayoutInflater;
 import android.view.View;
@@ -36,24 +39,25 @@
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
 
-import javax.inject.Inject;
-import javax.inject.Singleton;
-
 /**
  * Manages the user tutorial handling for One Handed operations, including animations synchronized
  * with one-handed translation.
  * Refer {@link OneHandedGestureHandler} and {@link OneHandedTouchHandler} to see start and stop
  * one handed gesture
  */
-@Singleton
 public class OneHandedTutorialHandler implements OneHandedTransitionCallback, Dumpable {
     private static final String TAG = "OneHandedTutorialHandler";
+    private static final String ONE_HANDED_MODE_OFFSET_PERCENTAGE =
+            "persist.debug.one_handed_offset_percentage";
+    private static final int MAX_TUTORIAL_SHOW_COUNT = 2;
     private final Rect mLastUpdatedBounds = new Rect();
     private final WindowManager mWindowManager;
 
     private View mTutorialView;
     private Point mDisplaySize = new Point();
     private Handler mUpdateHandler;
+    private ContentResolver mContentResolver;
+    private boolean mCanShowTutorial;
 
     /**
      * Container of the tutorial panel showing at outside region when one handed starting
@@ -68,23 +72,31 @@
         }
     };
 
-    @Inject
     public OneHandedTutorialHandler(Context context) {
         context.getDisplay().getRealSize(mDisplaySize);
+        mContentResolver = context.getContentResolver();
         mUpdateHandler = new Handler();
         mWindowManager = context.getSystemService(WindowManager.class);
         mTargetViewContainer = new FrameLayout(context);
         mTargetViewContainer.setClipChildren(false);
-        mTutorialAreaHeight = Math.round(mDisplaySize.y * context.getResources().getFraction(
-                R.fraction.config_one_handed_offset, 1, 1));
+        mTutorialAreaHeight = Math.round(mDisplaySize.y
+                * (SystemProperties.getInt(ONE_HANDED_MODE_OFFSET_PERCENTAGE, 50) / 100.0f));
         mTutorialView = LayoutInflater.from(context).inflate(R.xml.one_handed_tutorial, null);
         mTargetViewContainer.addView(mTutorialView);
-        createOrUpdateTutorialTarget();
+        mCanShowTutorial = (Settings.Secure.getInt(mContentResolver,
+                Settings.Secure.ONE_HANDED_TUTORIAL_SHOW_COUNT, 0) >= MAX_TUTORIAL_SHOW_COUNT)
+                ? false : true;
+        if (mCanShowTutorial) {
+            createOrUpdateTutorialTarget();
+        }
     }
 
     @Override
     public void onStartFinished(Rect bounds) {
-        mUpdateHandler.post(() -> updateFinished(View.VISIBLE, 0f));
+        mUpdateHandler.post(() -> {
+            updateFinished(View.VISIBLE, 0f);
+            updateTutorialCount();
+        });
     }
 
     @Override
@@ -94,10 +106,23 @@
     }
 
     private void updateFinished(int visible, float finalPosition) {
+        if (!canShowTutorial()) {
+            return;
+        }
+
         mTargetViewContainer.setVisibility(visible);
         mTargetViewContainer.setTranslationY(finalPosition);
     }
 
+    private void updateTutorialCount() {
+        int showCount = Settings.Secure.getInt(mContentResolver,
+                Settings.Secure.ONE_HANDED_TUTORIAL_SHOW_COUNT, 0);
+        showCount = Math.min(MAX_TUTORIAL_SHOW_COUNT, showCount + 1);
+        mCanShowTutorial = showCount < MAX_TUTORIAL_SHOW_COUNT;
+        Settings.Secure.putInt(mContentResolver,
+                Settings.Secure.ONE_HANDED_TUTORIAL_SHOW_COUNT, showCount);
+    }
+
     /**
      * Adds the tutorial target view to the WindowManager and update its layout, so it's ready
      * to be animated in.
@@ -133,9 +158,7 @@
         final WindowManager.LayoutParams lp = new WindowManager.LayoutParams(
                 mDisplaySize.x, mTutorialAreaHeight, 0, 0,
                 WindowManager.LayoutParams.TYPE_NAVIGATION_BAR_PANEL,
-                WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN
-                        | WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE
-                        | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE,
+                WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN,
                 PixelFormat.TRANSLUCENT);
         lp.gravity = Gravity.TOP | Gravity.LEFT;
         lp.privateFlags |= WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS;
@@ -153,7 +176,19 @@
         pw.println(mLastUpdatedBounds);
     }
 
+    private boolean canShowTutorial() {
+        if (!mCanShowTutorial) {
+            mTargetViewContainer.setVisibility(View.GONE);
+            return false;
+        }
+
+        return true;
+    }
+
     private void onAnimationUpdate(float value) {
+        if (!canShowTutorial()) {
+            return;
+        }
         mTargetViewContainer.setVisibility(View.VISIBLE);
         mTargetViewContainer.setTransitionGroup(true);
         mTargetViewContainer.setTranslationY(value - mTargetViewContainer.getHeight());
diff --git a/packages/SystemUI/src/com/android/systemui/onehanded/OneHandedUI.java b/packages/SystemUI/src/com/android/systemui/onehanded/OneHandedUI.java
index 9239435..3348a06 100644
--- a/packages/SystemUI/src/com/android/systemui/onehanded/OneHandedUI.java
+++ b/packages/SystemUI/src/com/android/systemui/onehanded/OneHandedUI.java
@@ -40,7 +40,7 @@
 import com.android.systemui.Dependency;
 import com.android.systemui.Dumpable;
 import com.android.systemui.SystemUI;
-import com.android.systemui.dump.DumpManager;
+import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.keyguard.ScreenLifecycle;
 import com.android.systemui.statusbar.CommandQueue;
 
@@ -48,23 +48,21 @@
 import java.io.PrintWriter;
 
 import javax.inject.Inject;
-import javax.inject.Singleton;
 
 /**
  * A service that controls UI of the one handed mode function.
  */
-@Singleton
+@SysUISingleton
 public class OneHandedUI extends SystemUI implements CommandQueue.Callbacks, Dumpable {
     private static final String TAG = "OneHandedUI";
     private static final String ONE_HANDED_MODE_GESTURAL_OVERLAY =
             "com.android.internal.systemui.onehanded.gestural";
     private static final String SUPPORT_ONE_HANDED_MODE = "ro.support_one_handed_mode";
 
-    private final OneHandedManagerImpl mOneHandedManager;
+    private final OneHandedController mOneHandedController;
     private final CommandQueue mCommandQueue;
     private final Handler mMainHandler = new Handler(Looper.getMainLooper());
     private final IOverlayManager mOverlayManager;
-    private final OneHandedSettingsUtil mSettingUtil;
     private final OneHandedTimeoutHandler mTimeoutHandler;
     private final ScreenLifecycle mScreenLifecycle;
 
@@ -76,11 +74,14 @@
             OneHandedEvents.writeEvent(enabled
                     ? OneHandedEvents.EVENT_ONE_HANDED_SETTINGS_ENABLED_ON
                     : OneHandedEvents.EVENT_ONE_HANDED_SETTINGS_ENABLED_OFF);
-            if (mOneHandedManager != null) {
-                mOneHandedManager.setOneHandedEnabled(enabled);
+            if (mOneHandedController != null) {
+                mOneHandedController.setOneHandedEnabled(enabled);
             }
 
-            setEnabledGesturalOverlay(enabled);
+            // Also checks swipe to notification settings since they all need gesture overlay.
+            setEnabledGesturalOverlay(
+                    enabled || OneHandedSettingsUtil.getSettingsSwipeToNotificationEnabled(
+                            mContext.getContentResolver()));
         }
     };
 
@@ -124,35 +125,49 @@
                     ? OneHandedEvents.EVENT_ONE_HANDED_SETTINGS_APP_TAPS_EXIT_ON
                     : OneHandedEvents.EVENT_ONE_HANDED_SETTINGS_APP_TAPS_EXIT_OFF);
 
-            if (mOneHandedManager != null) {
-                mOneHandedManager.setTaskChangeToExit(enabled);
+            if (mOneHandedController != null) {
+                mOneHandedController.setTaskChangeToExit(enabled);
             }
         }
     };
 
+    private final ContentObserver mSwipeToNotificationEnabledObserver =
+            new ContentObserver(mMainHandler) {
+                @Override
+                public void onChange(boolean selfChange) {
+                    final boolean enabled =
+                            OneHandedSettingsUtil.getSettingsSwipeToNotificationEnabled(
+                                    mContext.getContentResolver());
+                    if (mOneHandedController != null) {
+                        mOneHandedController.setSwipeToNotificationEnabled(enabled);
+                    }
+
+                    // Also checks one handed mode settings since they all need gesture overlay.
+                    setEnabledGesturalOverlay(
+                            enabled || OneHandedSettingsUtil.getSettingsOneHandedModeEnabled(
+                                    mContext.getContentResolver()));
+                }
+            };
+
     @Inject
     public OneHandedUI(Context context,
             CommandQueue commandQueue,
-            OneHandedManagerImpl oneHandedManager,
-            DumpManager dumpManager,
-            OneHandedSettingsUtil settingsUtil,
+            OneHandedController oneHandedController,
             ScreenLifecycle screenLifecycle) {
         super(context);
 
         if (!SystemProperties.getBoolean(SUPPORT_ONE_HANDED_MODE, false)) {
             Log.i(TAG, "Device config SUPPORT_ONE_HANDED_MODE off");
             mCommandQueue = null;
-            mOneHandedManager = null;
+            mOneHandedController = null;
             mOverlayManager = null;
-            mSettingUtil = null;
             mTimeoutHandler = null;
             mScreenLifecycle = null;
             return;
         }
 
         mCommandQueue = commandQueue;
-        mOneHandedManager = oneHandedManager;
-        mSettingUtil = settingsUtil;
+        mOneHandedController = oneHandedController;
         mTimeoutHandler = OneHandedTimeoutHandler.get();
         mScreenLifecycle = screenLifecycle;
         mOverlayManager = IOverlayManager.Stub.asInterface(
@@ -233,21 +248,26 @@
     }
 
     private void setupSettingObservers() {
-        mSettingUtil.registerSettingsKeyObserver(Settings.Secure.ONE_HANDED_MODE_ENABLED,
+        OneHandedSettingsUtil.registerSettingsKeyObserver(Settings.Secure.ONE_HANDED_MODE_ENABLED,
                 mContext.getContentResolver(), mEnabledObserver);
-        mSettingUtil.registerSettingsKeyObserver(Settings.Secure.ONE_HANDED_MODE_TIMEOUT,
+        OneHandedSettingsUtil.registerSettingsKeyObserver(Settings.Secure.ONE_HANDED_MODE_TIMEOUT,
                 mContext.getContentResolver(), mTimeoutObserver);
-        mSettingUtil.registerSettingsKeyObserver(Settings.Secure.TAPS_APP_TO_EXIT,
+        OneHandedSettingsUtil.registerSettingsKeyObserver(Settings.Secure.TAPS_APP_TO_EXIT,
                 mContext.getContentResolver(), mTaskChangeExitObserver);
+        OneHandedSettingsUtil.registerSettingsKeyObserver(
+                Settings.Secure.SWIPE_BOTTOM_TO_NOTIFICATION_ENABLED,
+                mContext.getContentResolver(), mSwipeToNotificationEnabledObserver);
     }
 
     private void updateSettings() {
-        mOneHandedManager.setOneHandedEnabled(
-                mSettingUtil.getSettingsOneHandedModeEnabled(mContext.getContentResolver()));
-        mTimeoutHandler.setTimeout(
-                mSettingUtil.getSettingsOneHandedModeTimeout(mContext.getContentResolver()));
-        mOneHandedManager.setTaskChangeToExit(
-                mSettingUtil.getSettingsTapsAppToExit(mContext.getContentResolver()));
+        mOneHandedController.setOneHandedEnabled(OneHandedSettingsUtil
+                .getSettingsOneHandedModeEnabled(mContext.getContentResolver()));
+        mTimeoutHandler.setTimeout(OneHandedSettingsUtil
+                .getSettingsOneHandedModeTimeout(mContext.getContentResolver()));
+        mOneHandedController.setTaskChangeToExit(OneHandedSettingsUtil
+                .getSettingsTapsAppToExit(mContext.getContentResolver()));
+        mOneHandedController.setSwipeToNotificationEnabled(OneHandedSettingsUtil
+                .getSettingsSwipeToNotificationEnabled(mContext.getContentResolver()));
     }
 
     @Override
@@ -275,7 +295,7 @@
      * Trigger one handed more
      */
     public void startOneHanded() {
-        mOneHandedManager.startOneHanded();
+        mOneHandedController.startOneHanded();
         OneHandedEvents.writeEvent(OneHandedEvents.EVENT_ONE_HANDED_TRIGGER_GESTURE_IN);
     }
 
@@ -283,7 +303,7 @@
      * Dismiss one handed more
      */
     public void stopOneHanded() {
-        mOneHandedManager.stopOneHanded();
+        mOneHandedController.stopOneHanded();
         OneHandedEvents.writeEvent(OneHandedEvents.EVENT_ONE_HANDED_TRIGGER_GESTURE_OUT);
     }
 
@@ -294,17 +314,15 @@
         final String innerPrefix = "  ";
         pw.println(TAG + "one handed states: ");
 
-        if (mOneHandedManager != null) {
-            ((OneHandedManagerImpl) mOneHandedManager).dump(fd, pw, args);
+        if (mOneHandedController != null) {
+            mOneHandedController.dump(fd, pw, args);
         }
 
         if (mTimeoutHandler != null) {
             mTimeoutHandler.dump(fd, pw, args);
         }
 
-        if (mSettingUtil != null) {
-            mSettingUtil.dump(pw, innerPrefix, mContext.getContentResolver());
-        }
+        OneHandedSettingsUtil.dump(pw, innerPrefix, mContext.getContentResolver());
 
         if (mOverlayManager != null) {
             OverlayInfo info = null;
diff --git a/packages/SystemUI/src/com/android/systemui/onehanded/dagger/OneHandedModule.java b/packages/SystemUI/src/com/android/systemui/onehanded/dagger/OneHandedModule.java
deleted file mode 100644
index fe5fa2b..0000000
--- a/packages/SystemUI/src/com/android/systemui/onehanded/dagger/OneHandedModule.java
+++ /dev/null
@@ -1,36 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.onehanded.dagger;
-
-import com.android.systemui.onehanded.OneHandedManager;
-import com.android.systemui.onehanded.OneHandedManagerImpl;
-
-import dagger.Binds;
-import dagger.Module;
-
-/**
- * Dagger Module for One handed.
- */
-@Module
-public abstract class OneHandedModule {
-
-    /** Binds OneHandedManager as the default. */
-    @Binds
-    public abstract OneHandedManager provideOneHandedManager(
-            OneHandedManagerImpl oneHandedManagerImpl);
-
-}
diff --git a/packages/SystemUI/src/com/android/systemui/pip/PipAnimationController.java b/packages/SystemUI/src/com/android/systemui/pip/PipAnimationController.java
index 7201931..fd8ca80 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/PipAnimationController.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/PipAnimationController.java
@@ -31,8 +31,6 @@
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 
-import javax.inject.Inject;
-
 /**
  * Controller class of PiP animations (both from and to PiP mode).
  */
@@ -53,16 +51,16 @@
     public static final int TRANSITION_DIRECTION_NONE = 0;
     public static final int TRANSITION_DIRECTION_SAME = 1;
     public static final int TRANSITION_DIRECTION_TO_PIP = 2;
-    public static final int TRANSITION_DIRECTION_TO_FULLSCREEN = 3;
-    public static final int TRANSITION_DIRECTION_TO_SPLIT_SCREEN = 4;
+    public static final int TRANSITION_DIRECTION_LEAVE_PIP = 3;
+    public static final int TRANSITION_DIRECTION_LEAVE_PIP_TO_SPLIT_SCREEN = 4;
     public static final int TRANSITION_DIRECTION_REMOVE_STACK = 5;
 
     @IntDef(prefix = { "TRANSITION_DIRECTION_" }, value = {
             TRANSITION_DIRECTION_NONE,
             TRANSITION_DIRECTION_SAME,
             TRANSITION_DIRECTION_TO_PIP,
-            TRANSITION_DIRECTION_TO_FULLSCREEN,
-            TRANSITION_DIRECTION_TO_SPLIT_SCREEN,
+            TRANSITION_DIRECTION_LEAVE_PIP,
+            TRANSITION_DIRECTION_LEAVE_PIP_TO_SPLIT_SCREEN,
             TRANSITION_DIRECTION_REMOVE_STACK
     })
     @Retention(RetentionPolicy.SOURCE)
@@ -73,8 +71,8 @@
     }
 
     public static boolean isOutPipDirection(@TransitionDirection int direction) {
-        return direction == TRANSITION_DIRECTION_TO_FULLSCREEN
-                || direction == TRANSITION_DIRECTION_TO_SPLIT_SCREEN;
+        return direction == TRANSITION_DIRECTION_LEAVE_PIP
+                || direction == TRANSITION_DIRECTION_LEAVE_PIP_TO_SPLIT_SCREEN;
     }
 
     private final PipSurfaceTransactionHelper mSurfaceTransactionHelper;
@@ -88,7 +86,6 @@
                 return handler;
             });
 
-    @Inject
     PipAnimationController(PipSurfaceTransactionHelper helper) {
         mSurfaceTransactionHelper = helper;
     }
diff --git a/packages/SystemUI/src/com/android/systemui/pip/PipBoundsHandler.java b/packages/SystemUI/src/com/android/systemui/pip/PipBoundsHandler.java
index df3aead..b464e8ad 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/PipBoundsHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/PipBoundsHandler.java
@@ -39,28 +39,23 @@
 import android.view.Gravity;
 import android.window.WindowContainerTransaction;
 
-import com.android.wm.shell.common.DisplayController;
+import com.android.systemui.dagger.SysUISingleton;
 import com.android.wm.shell.common.DisplayLayout;
 
 import java.io.PrintWriter;
 
-import javax.inject.Inject;
-import javax.inject.Singleton;
-
 /**
  * Handles bounds calculation for PIP on Phone and other form factors, it keeps tracking variant
  * state changes originated from Window Manager and is the source of truth for PiP window bounds.
  */
-@Singleton
+@SysUISingleton
 public class PipBoundsHandler {
 
     private static final String TAG = PipBoundsHandler.class.getSimpleName();
     private static final float INVALID_SNAP_FRACTION = -1f;
 
-    private final Context mContext;
     private final PipSnapAlgorithm mSnapAlgorithm;
     private final DisplayInfo mDisplayInfo = new DisplayInfo();
-    private final DisplayController mDisplayController;
     private DisplayLayout mDisplayLayout;
 
     private ComponentName mLastPipComponentName;
@@ -82,25 +77,10 @@
     private boolean mIsShelfShowing;
     private int mShelfHeight;
 
-    private final DisplayController.OnDisplaysChangedListener mDisplaysChangedListener =
-            new DisplayController.OnDisplaysChangedListener() {
-        @Override
-        public void onDisplayAdded(int displayId) {
-            if (displayId == mContext.getDisplayId()) {
-                mDisplayLayout.set(mDisplayController.getDisplayLayout(displayId));
-            }
-        }
-    };
-
-    @Inject
-    public PipBoundsHandler(Context context, PipSnapAlgorithm pipSnapAlgorithm,
-            DisplayController displayController) {
-        mContext = context;
-        mSnapAlgorithm = pipSnapAlgorithm;
+    public PipBoundsHandler(Context context) {
+        mSnapAlgorithm = new PipSnapAlgorithm(context);
         mDisplayLayout = new DisplayLayout();
-        mDisplayController = displayController;
-        mDisplayController.addDisplayWindowListener(mDisplaysChangedListener);
-        reloadResources();
+        reloadResources(context);
         // Initialize the aspect ratio to the default aspect ratio.  Don't do this in reload
         // resources as it would clobber mAspectRatio when entering PiP from fullscreen which
         // triggers a configuration change and the resources to be reloaded.
@@ -110,8 +90,8 @@
     /**
      * TODO: move the resources to SysUI package.
      */
-    private void reloadResources() {
-        final Resources res = mContext.getResources();
+    private void reloadResources(Context context) {
+        final Resources res = context.getResources();
         mDefaultAspectRatio = res.getFloat(
                 com.android.internal.R.dimen.config_pictureInPictureDefaultAspectRatio);
         mDefaultStackGravity = res.getInteger(
@@ -133,6 +113,19 @@
                 com.android.internal.R.dimen.config_pictureInPictureMaxAspectRatio);
     }
 
+    /**
+     * Sets or update latest {@link DisplayLayout} when new display added or rotation callbacks
+     * from {@link DisplayController.OnDisplaysChangedListener}
+     * @param newDisplayLayout latest {@link DisplayLayout}
+     */
+    public void setDisplayLayout(DisplayLayout newDisplayLayout) {
+        mDisplayLayout.set(newDisplayLayout);
+    }
+
+    /**
+     * Update the Min edge size for {@link PipSnapAlgorithm} to calculate corresponding bounds
+     * @param minEdgeSize
+     */
     public void setMinEdgeSize(int minEdgeSize) {
         mCurrentMinSize = minEdgeSize;
     }
@@ -217,6 +210,14 @@
         return mReentrySnapFraction != INVALID_SNAP_FRACTION;
     }
 
+    /**
+     * The {@link PipSnapAlgorithm} is couple on display bounds
+     * @return {@link PipSnapAlgorithm}.
+     */
+    public PipSnapAlgorithm getSnapAlgorithm() {
+        return mSnapAlgorithm;
+    }
+
     public Rect getDisplayBounds() {
         return new Rect(0, 0, mDisplayInfo.logicalWidth, mDisplayInfo.logicalHeight);
     }
@@ -237,8 +238,8 @@
     /**
      * Responds to IPinnedStackListener on configuration change.
      */
-    public void onConfigurationChanged() {
-        reloadResources();
+    public void onConfigurationChanged(Context context) {
+        reloadResources(context);
     }
 
     /**
@@ -300,10 +301,10 @@
      * aren't in PIP because the rotation layout is used to calculate the proper insets for the
      * next enter animation into PIP.
      */
-    public void onDisplayRotationChangedNotInPip(int toRotation) {
+    public void onDisplayRotationChangedNotInPip(Context context, int toRotation) {
         // Update the display layout, note that we have to do this on every rotation even if we
         // aren't in PIP since we need to update the display layout to get the right resources
-        mDisplayLayout.rotateTo(mContext.getResources(), toRotation);
+        mDisplayLayout.rotateTo(context.getResources(), toRotation);
 
         // Populate the new {@link #mDisplayInfo}.
         // The {@link DisplayInfo} queried from DisplayManager would be the one before rotation,
@@ -319,7 +320,8 @@
      *
      * @return {@code true} if internal {@link DisplayInfo} is rotated, {@code false} otherwise.
      */
-    public boolean onDisplayRotationChanged(Rect outBounds, Rect oldBounds, Rect outInsetBounds,
+    public boolean onDisplayRotationChanged(Context context, Rect outBounds, Rect oldBounds,
+            Rect outInsetBounds,
             int displayId, int fromRotation, int toRotation, WindowContainerTransaction t) {
         // Bail early if the event is not sent to current {@link #mDisplayInfo}
         if ((displayId != mDisplayInfo.displayId) || (fromRotation == toRotation)) {
@@ -342,7 +344,7 @@
         final float snapFraction = getSnapFraction(postChangeStackBounds);
 
         // Update the display layout
-        mDisplayLayout.rotateTo(mContext.getResources(), toRotation);
+        mDisplayLayout.rotateTo(context.getResources(), toRotation);
 
         // Populate the new {@link #mDisplayInfo}.
         // The {@link DisplayInfo} queried from DisplayManager would be the one before rotation,
@@ -546,5 +548,6 @@
         pw.println(innerPrefix + "mImeHeight=" + mImeHeight);
         pw.println(innerPrefix + "mIsShelfShowing=" + mIsShelfShowing);
         pw.println(innerPrefix + "mShelfHeight=" + mShelfHeight);
+        pw.println(innerPrefix + "mSnapAlgorithm" + mSnapAlgorithm);
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/pip/PipSnapAlgorithm.java b/packages/SystemUI/src/com/android/systemui/pip/PipSnapAlgorithm.java
index a9b32d9..5d23e42 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/PipSnapAlgorithm.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/PipSnapAlgorithm.java
@@ -22,24 +22,18 @@
 import android.graphics.Rect;
 import android.util.Size;
 
-import javax.inject.Inject;
-
 /**
  * Calculates the snap targets and the snap position for the PIP given a position and a velocity.
  * All bounds are relative to the display top/left.
  */
 public class PipSnapAlgorithm {
 
-    private final Context mContext;
-
     private final float mDefaultSizePercent;
     private final float mMinAspectRatioForMinSize;
     private final float mMaxAspectRatioForMinSize;
 
-    @Inject
     public PipSnapAlgorithm(Context context) {
         Resources res = context.getResources();
-        mContext = context;
         mDefaultSizePercent = res.getFloat(
                 com.android.internal.R.dimen.config_pictureInPictureDefaultSizePercent);
         mMaxAspectRatioForMinSize = res.getFloat(
diff --git a/packages/SystemUI/src/com/android/systemui/pip/PipSurfaceTransactionHelper.java b/packages/SystemUI/src/com/android/systemui/pip/PipSurfaceTransactionHelper.java
index 2c7ec48..3e98169 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/PipSurfaceTransactionHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/PipSurfaceTransactionHelper.java
@@ -23,16 +23,14 @@
 import android.graphics.RectF;
 import android.view.SurfaceControl;
 
+import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.statusbar.policy.ConfigurationController;
 import com.android.wm.shell.R;
 
-import javax.inject.Inject;
-import javax.inject.Singleton;
-
 /**
  * Abstracts the common operations on {@link SurfaceControl.Transaction} for PiP transition.
  */
-@Singleton
+@SysUISingleton
 public class PipSurfaceTransactionHelper implements ConfigurationController.ConfigurationListener {
 
     private final Context mContext;
@@ -46,7 +44,6 @@
     private final RectF mTmpDestinationRectF = new RectF();
     private final Rect mTmpDestinationRect = new Rect();
 
-    @Inject
     public PipSurfaceTransactionHelper(Context context, ConfigurationController configController) {
         final Resources res = context.getResources();
         mContext = context;
diff --git a/packages/SystemUI/src/com/android/systemui/pip/PipTaskOrganizer.java b/packages/SystemUI/src/com/android/systemui/pip/PipTaskOrganizer.java
index 312d6d6..e558be2 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/PipTaskOrganizer.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/PipTaskOrganizer.java
@@ -23,12 +23,12 @@
 
 import static com.android.systemui.pip.PipAnimationController.ANIM_TYPE_ALPHA;
 import static com.android.systemui.pip.PipAnimationController.ANIM_TYPE_BOUNDS;
+import static com.android.systemui.pip.PipAnimationController.TRANSITION_DIRECTION_LEAVE_PIP;
+import static com.android.systemui.pip.PipAnimationController.TRANSITION_DIRECTION_LEAVE_PIP_TO_SPLIT_SCREEN;
 import static com.android.systemui.pip.PipAnimationController.TRANSITION_DIRECTION_NONE;
 import static com.android.systemui.pip.PipAnimationController.TRANSITION_DIRECTION_REMOVE_STACK;
 import static com.android.systemui.pip.PipAnimationController.TRANSITION_DIRECTION_SAME;
-import static com.android.systemui.pip.PipAnimationController.TRANSITION_DIRECTION_TO_FULLSCREEN;
 import static com.android.systemui.pip.PipAnimationController.TRANSITION_DIRECTION_TO_PIP;
-import static com.android.systemui.pip.PipAnimationController.TRANSITION_DIRECTION_TO_SPLIT_SCREEN;
 import static com.android.systemui.pip.PipAnimationController.isInPipDirection;
 import static com.android.systemui.pip.PipAnimationController.isOutPipDirection;
 
@@ -56,9 +56,11 @@
 import android.window.WindowOrganizer;
 
 import com.android.internal.os.SomeArgs;
+import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.pip.phone.PipUpdateThread;
-import com.android.systemui.stackdivider.Divider;
+import com.android.systemui.stackdivider.SplitScreenController;
 import com.android.wm.shell.R;
+import com.android.wm.shell.ShellTaskOrganizer;
 import com.android.wm.shell.common.DisplayController;
 
 import java.io.PrintWriter;
@@ -67,11 +69,9 @@
 import java.util.List;
 import java.util.Map;
 import java.util.Objects;
+import java.util.Optional;
 import java.util.function.Consumer;
 
-import javax.inject.Inject;
-import javax.inject.Singleton;
-
 /**
  * Manages PiP tasks such as resize and offset.
  *
@@ -83,8 +83,8 @@
  * This class is also responsible for general resize/offset PiP operations within SysUI component,
  * see also {@link com.android.systemui.pip.phone.PipMotionHelper}.
  */
-@Singleton
-public class PipTaskOrganizer extends TaskOrganizer implements
+@SysUISingleton
+public class PipTaskOrganizer extends TaskOrganizer implements ShellTaskOrganizer.TaskListener,
         DisplayController.OnDisplaysChangedListener {
     private static final String TAG = PipTaskOrganizer.class.getSimpleName();
     private static final boolean DEBUG = false;
@@ -99,12 +99,14 @@
     private final Handler mUpdateHandler;
     private final PipBoundsHandler mPipBoundsHandler;
     private final PipAnimationController mPipAnimationController;
+    private final PipUiEventLogger mPipUiEventLoggerLogger;
     private final List<PipTransitionCallback> mPipTransitionCallbacks = new ArrayList<>();
     private final Rect mLastReportedBounds = new Rect();
     private final int mEnterExitAnimationDuration;
     private final PipSurfaceTransactionHelper mSurfaceTransactionHelper;
     private final Map<IBinder, Configuration> mInitialState = new HashMap<>();
-    private final Divider mSplitDivider;
+    private final Optional<SplitScreenController> mSplitScreenControllerOptional;
+    protected final ShellTaskOrganizer mTaskOrganizer;
 
     // These callbacks are called on the update thread
     private final PipAnimationController.PipAnimationCallback mPipAnimationCallback =
@@ -204,21 +206,24 @@
      */
     private boolean mShouldDeferEnteringPip;
 
-    @Inject
     public PipTaskOrganizer(Context context, @NonNull PipBoundsHandler boundsHandler,
             @NonNull PipSurfaceTransactionHelper surfaceTransactionHelper,
-            @Nullable Divider divider,
+            Optional<SplitScreenController> splitScreenControllerOptional,
             @NonNull DisplayController displayController,
-            @NonNull PipAnimationController pipAnimationController) {
+            @NonNull PipUiEventLogger pipUiEventLogger,
+            @NonNull ShellTaskOrganizer shellTaskOrganizer) {
         mMainHandler = new Handler(Looper.getMainLooper());
         mUpdateHandler = new Handler(PipUpdateThread.get().getLooper(), mUpdateCallbacks);
         mPipBoundsHandler = boundsHandler;
         mEnterExitAnimationDuration = context.getResources()
                 .getInteger(R.integer.config_pipResizeAnimationDuration);
         mSurfaceTransactionHelper = surfaceTransactionHelper;
-        mPipAnimationController = pipAnimationController;
+        mPipAnimationController = new PipAnimationController(mSurfaceTransactionHelper);
+        mPipUiEventLoggerLogger = pipUiEventLogger;
         mSurfaceControlTransactionFactory = SurfaceControl.Transaction::new;
-        mSplitDivider = divider;
+        mSplitScreenControllerOptional = splitScreenControllerOptional;
+        mTaskOrganizer = shellTaskOrganizer;
+        mTaskOrganizer.addListener(this, WINDOWING_MODE_PINNED);
         displayController.addDisplayWindowListener(this);
     }
 
@@ -279,14 +284,16 @@
             return;
         }
 
+        mPipUiEventLoggerLogger.log(
+                PipUiEventLogger.PipUiEventEnum.PICTURE_IN_PICTURE_EXPAND_TO_FULLSCREEN);
         final Configuration initialConfig = mInitialState.remove(mToken.asBinder());
         final boolean orientationDiffers = initialConfig.windowConfiguration.getRotation()
                 != mPipBoundsHandler.getDisplayRotation();
         final WindowContainerTransaction wct = new WindowContainerTransaction();
         final Rect destinationBounds = initialConfig.windowConfiguration.getBounds();
         final int direction = syncWithSplitScreenBounds(destinationBounds)
-                ? TRANSITION_DIRECTION_TO_SPLIT_SCREEN
-                : TRANSITION_DIRECTION_TO_FULLSCREEN;
+                ? TRANSITION_DIRECTION_LEAVE_PIP_TO_SPLIT_SCREEN
+                : TRANSITION_DIRECTION_LEAVE_PIP;
         if (orientationDiffers) {
             // Send started callback though animation is ignored.
             sendOnPipTransitionStarted(direction);
@@ -303,12 +310,15 @@
             mSurfaceTransactionHelper.scale(tx, mLeash, destinationBounds,
                     mLastReportedBounds);
             tx.setWindowCrop(mLeash, destinationBounds.width(), destinationBounds.height());
-            wct.setActivityWindowingMode(mToken, direction == TRANSITION_DIRECTION_TO_SPLIT_SCREEN
+            // We set to fullscreen here for now, but later it will be set to UNDEFINED for
+            // the proper windowing mode to take place. See #applyWindowingModeChangeOnExit.
+            wct.setActivityWindowingMode(mToken,
+                    direction == TRANSITION_DIRECTION_LEAVE_PIP_TO_SPLIT_SCREEN
                     ? WINDOWING_MODE_SPLIT_SCREEN_SECONDARY
                     : WINDOWING_MODE_FULLSCREEN);
             wct.setBounds(mToken, destinationBounds);
             wct.setBoundsChangeTransaction(mToken, tx);
-            applySyncTransaction(wct, new WindowContainerTransactionCallback() {
+            mTaskOrganizer.applySyncTransaction(wct, new WindowContainerTransactionCallback() {
                 @Override
                 public void onTransactionReady(int id, SurfaceControl.Transaction t) {
                     t.apply();
@@ -327,9 +337,11 @@
         wct.setWindowingMode(mToken, getOutPipWindowingMode());
         // Simply reset the activity mode set prior to the animation running.
         wct.setActivityWindowingMode(mToken, WINDOWING_MODE_UNDEFINED);
-        if (mSplitDivider != null && direction == TRANSITION_DIRECTION_TO_SPLIT_SCREEN) {
-            wct.reparent(mToken, mSplitDivider.getSecondaryRoot(), true /* onTop */);
-        }
+        mSplitScreenControllerOptional.ifPresent(splitScreen -> {
+            if (direction == TRANSITION_DIRECTION_LEAVE_PIP_TO_SPLIT_SCREEN) {
+                wct.reparent(mToken, splitScreen.getSecondaryRoot(), true /* onTop */);
+            }
+        });
     }
 
     /**
@@ -378,6 +390,9 @@
         mInitialState.put(mToken.asBinder(), new Configuration(mTaskInfo.configuration));
         mPictureInPictureParams = mTaskInfo.pictureInPictureParams;
 
+        mPipUiEventLoggerLogger.setTaskInfo(mTaskInfo);
+        mPipUiEventLoggerLogger.log(PipUiEventLogger.PipUiEventEnum.PICTURE_IN_PICTURE_ENTER);
+
         if (mShouldDeferEnteringPip) {
             if (DEBUG) Log.d(TAG, "Defer entering PiP animation, fixed rotation is ongoing");
             // if deferred, hide the surface till fixed rotation is completed
@@ -435,7 +450,7 @@
         wct.setActivityWindowingMode(mToken, WINDOWING_MODE_UNDEFINED);
         wct.setBounds(mToken, destinationBounds);
         wct.scheduleFinishEnterPip(mToken, destinationBounds);
-        applySyncTransaction(wct, new WindowContainerTransactionCallback() {
+        mTaskOrganizer.applySyncTransaction(wct, new WindowContainerTransactionCallback() {
             @Override
             public void onTransactionReady(int id, SurfaceControl.Transaction t) {
                 t.apply();
@@ -451,10 +466,11 @@
 
     private void sendOnPipTransitionStarted(
             @PipAnimationController.TransitionDirection int direction) {
+        final Rect pipBounds = new Rect(mLastReportedBounds);
         runOnMainHandler(() -> {
             for (int i = mPipTransitionCallbacks.size() - 1; i >= 0; i--) {
                 final PipTransitionCallback callback = mPipTransitionCallbacks.get(i);
-                callback.onPipTransitionStarted(mTaskInfo.baseActivity, direction);
+                callback.onPipTransitionStarted(mTaskInfo.baseActivity, direction, pipBounds);
             }
         });
     }
@@ -510,6 +526,7 @@
         mPictureInPictureParams = null;
         mInPip = false;
         mExitingPip = false;
+        mPipUiEventLoggerLogger.setTaskInfo(null);
     }
 
     @Override
@@ -530,11 +547,6 @@
     }
 
     @Override
-    public void onBackPressedOnTaskRoot(ActivityManager.RunningTaskInfo taskInfo) {
-        // Do nothing
-    }
-
-    @Override
     public void onFixedRotationStarted(int displayId, int newRotation) {
         mShouldDeferEnteringPip = true;
     }
@@ -625,7 +637,7 @@
      * {@link PictureInPictureParams} would affect the bounds.
      */
     private boolean applyPictureInPictureParams(@NonNull PictureInPictureParams params) {
-        final boolean changed = (mPictureInPictureParams == null) ? true : !Objects.equals(
+        final boolean changed = (mPictureInPictureParams == null) || !Objects.equals(
                 mPictureInPictureParams.getAspectRatioRational(), params.getAspectRatioRational());
         if (changed) {
             mPictureInPictureParams = params;
@@ -842,7 +854,7 @@
         } else if (isOutPipDirection(direction)) {
             // If we are animating to fullscreen, then we need to reset the override bounds
             // on the task to ensure that the task "matches" the parent's bounds.
-            taskBounds = (direction == TRANSITION_DIRECTION_TO_FULLSCREEN)
+            taskBounds = (direction == TRANSITION_DIRECTION_LEAVE_PIP)
                     ? null : destinationBounds;
             applyWindowingModeChangeOnExit(wct, direction);
         } else {
@@ -925,20 +937,22 @@
     }
 
     /**
-     * Sync with {@link #mSplitDivider} on destination bounds if PiP is going to split screen.
+     * Sync with {@link SplitScreenController} on destination bounds if PiP is going to split
+     * screen.
      *
      * @param destinationBoundsOut contain the updated destination bounds if applicable
      * @return {@code true} if destinationBounds is altered for split screen
      */
     private boolean syncWithSplitScreenBounds(Rect destinationBoundsOut) {
-        if (mSplitDivider == null || !mSplitDivider.isDividerVisible()) {
-            // bail early if system is not in split screen mode
+        if (!mSplitScreenControllerOptional.isPresent()) {
+            // fail early if system is not in split screen mode
             return false;
         }
+
         // PiP window will go to split-secondary mode instead of fullscreen, populates the
         // split screen bounds here.
-        destinationBoundsOut.set(
-                mSplitDivider.getView().getNonMinimizedSplitScreenSecondaryBounds());
+        destinationBoundsOut.set(mSplitScreenControllerOptional.get().getDividerView()
+                .getNonMinimizedSplitScreenSecondaryBounds());
         return true;
     }
 
@@ -970,7 +984,7 @@
         /**
          * Callback when the pip transition is started.
          */
-        void onPipTransitionStarted(ComponentName activity, int direction);
+        void onPipTransitionStarted(ComponentName activity, int direction, Rect pipBounds);
 
         /**
          * Callback when the pip transition is finished.
diff --git a/packages/SystemUI/src/com/android/systemui/pip/PipUI.java b/packages/SystemUI/src/com/android/systemui/pip/PipUI.java
index 4fb675e..2cd1e20 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/PipUI.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/PipUI.java
@@ -25,6 +25,7 @@
 import android.os.UserManager;
 
 import com.android.systemui.SystemUI;
+import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.shared.recents.IPinnedStackAnimationListener;
 import com.android.systemui.statusbar.CommandQueue;
 
@@ -32,12 +33,11 @@
 import java.io.PrintWriter;
 
 import javax.inject.Inject;
-import javax.inject.Singleton;
 
 /**
  * Controls the picture-in-picture window.
  */
-@Singleton
+@SysUISingleton
 public class PipUI extends SystemUI implements CommandQueue.Callbacks {
 
     private final CommandQueue mCommandQueue;
diff --git a/packages/SystemUI/src/com/android/systemui/pip/PipUiEventLogger.java b/packages/SystemUI/src/com/android/systemui/pip/PipUiEventLogger.java
new file mode 100644
index 0000000..8bcaa8a
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/pip/PipUiEventLogger.java
@@ -0,0 +1,93 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.pip;
+
+import android.app.TaskInfo;
+
+import com.android.internal.logging.UiEvent;
+import com.android.internal.logging.UiEventLogger;
+import com.android.systemui.dagger.SysUISingleton;
+
+/**
+ * Helper class that ends PiP log to UiEvent, see also go/uievent
+ */
+@SysUISingleton
+public class PipUiEventLogger {
+
+    private final UiEventLogger mUiEventLogger;
+
+    private TaskInfo mTaskInfo;
+
+    public PipUiEventLogger(UiEventLogger uiEventLogger) {
+        mUiEventLogger = uiEventLogger;
+    }
+
+    public void setTaskInfo(TaskInfo taskInfo) {
+        mTaskInfo = taskInfo;
+    }
+
+    /**
+     * Sends log via UiEvent, reference go/uievent for how to debug locally
+     */
+    public void log(PipUiEventEnum event) {
+        if (mTaskInfo == null) {
+            return;
+        }
+        mUiEventLogger.log(event, mTaskInfo.userId, mTaskInfo.topActivity.getPackageName());
+    }
+
+    /**
+     * Enums for logging the PiP events to UiEvent
+     */
+    public enum PipUiEventEnum implements UiEventLogger.UiEventEnum {
+        @UiEvent(doc = "Activity enters picture-in-picture mode")
+        PICTURE_IN_PICTURE_ENTER(603),
+
+        @UiEvent(doc = "Expands from picture-in-picture to fullscreen")
+        PICTURE_IN_PICTURE_EXPAND_TO_FULLSCREEN(604),
+
+        @UiEvent(doc = "Removes picture-in-picture by tap close button")
+        PICTURE_IN_PICTURE_TAP_TO_REMOVE(605),
+
+        @UiEvent(doc = "Removes picture-in-picture by drag to dismiss area")
+        PICTURE_IN_PICTURE_DRAG_TO_REMOVE(606),
+
+        @UiEvent(doc = "Shows picture-in-picture menu")
+        PICTURE_IN_PICTURE_SHOW_MENU(607),
+
+        @UiEvent(doc = "Hides picture-in-picture menu")
+        PICTURE_IN_PICTURE_HIDE_MENU(608),
+
+        @UiEvent(doc = "Changes the aspect ratio of picture-in-picture window. This is inherited"
+                + " from previous Tron-based logging and currently not in use.")
+        PICTURE_IN_PICTURE_CHANGE_ASPECT_RATIO(609),
+
+        @UiEvent(doc = "User resize of the picture-in-picture window")
+        PICTURE_IN_PICTURE_RESIZE(610);
+
+        private final int mId;
+
+        PipUiEventEnum(int id) {
+            mId = id;
+        }
+
+        @Override
+        public int getId() {
+            return mId;
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipAccessibilityInteractionConnection.java b/packages/SystemUI/src/com/android/systemui/pip/phone/PipAccessibilityInteractionConnection.java
index c715398..a133189 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipAccessibilityInteractionConnection.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipAccessibilityInteractionConnection.java
@@ -131,7 +131,7 @@
                         result = true;
                         break;
                     case AccessibilityNodeInfo.ACTION_EXPAND:
-                        mMotionHelper.expandPipToFullscreen();
+                        mMotionHelper.expandLeavePip();
                         result = true;
                         break;
                     default:
diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipManager.java b/packages/SystemUI/src/com/android/systemui/pip/phone/PipManager.java
index 582cd04..6aec144 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipManager.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipManager.java
@@ -42,32 +42,37 @@
 import com.android.systemui.Dependency;
 import com.android.systemui.UiOffloadThread;
 import com.android.systemui.broadcast.BroadcastDispatcher;
+import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.model.SysUiState;
 import com.android.systemui.pip.BasePipManager;
 import com.android.systemui.pip.PipBoundsHandler;
-import com.android.systemui.pip.PipSnapAlgorithm;
+import com.android.systemui.pip.PipSurfaceTransactionHelper;
 import com.android.systemui.pip.PipTaskOrganizer;
+import com.android.systemui.pip.PipUiEventLogger;
+import com.android.systemui.pip.phone.dagger.PipMenuActivityClass;
 import com.android.systemui.shared.recents.IPinnedStackAnimationListener;
 import com.android.systemui.shared.system.ActivityManagerWrapper;
 import com.android.systemui.shared.system.InputConsumerController;
 import com.android.systemui.shared.system.PinnedStackListenerForwarder.PinnedStackListener;
 import com.android.systemui.shared.system.TaskStackChangeListener;
 import com.android.systemui.shared.system.WindowManagerWrapper;
+import com.android.systemui.stackdivider.SplitScreenController;
 import com.android.systemui.statusbar.policy.ConfigurationController;
 import com.android.systemui.util.DeviceConfigProxy;
 import com.android.systemui.util.FloatingContentCoordinator;
+import com.android.wm.shell.ShellTaskOrganizer;
 import com.android.wm.shell.common.DisplayChangeController;
 import com.android.wm.shell.common.DisplayController;
 
 import java.io.PrintWriter;
+import java.util.Optional;
 
 import javax.inject.Inject;
-import javax.inject.Singleton;
 
 /**
  * Manages the picture-in-picture (PIP) UI and states for Phones.
  */
-@Singleton
+@SysUISingleton
 public class PipManager implements BasePipManager, PipTaskOrganizer.PipTransitionCallback {
     private static final String TAG = "PipManager";
 
@@ -80,15 +85,17 @@
     private final Rect mTmpNormalBounds = new Rect();
     protected final Rect mReentryBounds = new Rect();
 
-    private PipBoundsHandler mPipBoundsHandler;
+    private DisplayController mDisplayController;
     private InputConsumerController mInputConsumerController;
+    private PipAppOpsListener mAppOpsListener;
     private PipMediaController mMediaController;
     private PipTouchHandler mTouchHandler;
     private PipTaskOrganizer mPipTaskOrganizer;
-    private PipAppOpsListener mAppOpsListener;
+    private PipSurfaceTransactionHelper mPipSurfaceTransactionHelper;
     private IPinnedStackAnimationListener mPinnedStackAnimationRecentsListener;
     private boolean mIsInFixedRotation;
 
+    protected PipBoundsHandler mPipBoundsHandler;
     protected PipMenuActivityController mMenuController;
 
     /**
@@ -99,15 +106,16 @@
         if (!mPipTaskOrganizer.isInPip() || mPipTaskOrganizer.isDeferringEnterPipAnimation()) {
             // Skip if we aren't in PIP or haven't actually entered PIP yet. We still need to update
             // the display layout in the bounds handler in this case.
-            mPipBoundsHandler.onDisplayRotationChangedNotInPip(toRotation);
+            mPipBoundsHandler.onDisplayRotationChangedNotInPip(mContext, toRotation);
             return;
         }
         // If there is an animation running (ie. from a shelf offset), then ensure that we calculate
         // the bounds for the next orientation using the destination bounds of the animation
         // TODO: Techincally this should account for movement animation bounds as well
         Rect currentBounds = mPipTaskOrganizer.getCurrentOrAnimatingBounds();
-        final boolean changed = mPipBoundsHandler.onDisplayRotationChanged(mTmpNormalBounds,
-                currentBounds, mTmpInsetBounds, displayId, fromRotation, toRotation, t);
+        final boolean changed = mPipBoundsHandler.onDisplayRotationChanged(mContext,
+                mTmpNormalBounds, currentBounds, mTmpInsetBounds, displayId, fromRotation,
+                toRotation, t);
         if (changed) {
             // If the pip was in the offset zone earlier, adjust the new bounds to the bottom of the
             // movement bounds
@@ -133,16 +141,22 @@
 
     private DisplayController.OnDisplaysChangedListener mFixedRotationListener =
             new DisplayController.OnDisplaysChangedListener() {
-        @Override
-        public void onFixedRotationStarted(int displayId, int newRotation) {
-            mIsInFixedRotation = true;
-        }
+                @Override
+                public void onFixedRotationStarted(int displayId, int newRotation) {
+                    mIsInFixedRotation = true;
+                }
 
-        @Override
-        public void onFixedRotationFinished(int displayId) {
-            mIsInFixedRotation = false;
-        }
-    };
+                @Override
+                public void onFixedRotationFinished(int displayId) {
+                    mIsInFixedRotation = false;
+                }
+
+                @Override
+                public void onDisplayAdded(int displayId) {
+                    mPipBoundsHandler.setDisplayLayout(
+                            mDisplayController.getDisplayLayout(displayId));
+                }
+            };
 
     /**
      * Handler for system task stack changes.
@@ -181,7 +195,7 @@
                     != WINDOWING_MODE_PINNED) {
                 return;
             }
-            mTouchHandler.getMotionHelper().expandPipToFullscreen(clearedTask /* skipAnimation */);
+            mTouchHandler.getMotionHelper().expandLeavePip(clearedTask /* skipAnimation */);
         }
     };
 
@@ -226,12 +240,15 @@
 
         @Override
         public void onConfigurationChanged() {
-            mHandler.post(() -> mPipBoundsHandler.onConfigurationChanged());
+            mHandler.post(() -> mPipBoundsHandler.onConfigurationChanged(mContext));
         }
 
         @Override
         public void onAspectRatioChanged(float aspectRatio) {
-            mHandler.post(() -> mPipBoundsHandler.onAspectRatioChanged(aspectRatio));
+            mHandler.post(() -> {
+                mPipBoundsHandler.onAspectRatioChanged(aspectRatio);
+                mTouchHandler.onAspectRatioChanged();
+            });
         }
     }
 
@@ -250,14 +267,15 @@
 
     @Inject
     public PipManager(Context context, BroadcastDispatcher broadcastDispatcher,
-            DisplayController displayController,
-            FloatingContentCoordinator floatingContentCoordinator,
+            @PipMenuActivityClass Class<?> pipMenuActivityClass,
+            ConfigurationController configController,
             DeviceConfigProxy deviceConfig,
-            PipBoundsHandler pipBoundsHandler,
-            PipSnapAlgorithm pipSnapAlgorithm,
-            PipTaskOrganizer pipTaskOrganizer,
+            DisplayController displayController,
+            Optional<SplitScreenController> splitScreenControllerOptional,
+            FloatingContentCoordinator floatingContentCoordinator,
             SysUiState sysUiState,
-            ConfigurationController configController) {
+            PipUiEventLogger pipUiEventLogger,
+            ShellTaskOrganizer shellTaskOrganizer) {
         mContext = context;
         mActivityManager = ActivityManager.getService();
 
@@ -269,16 +287,20 @@
         }
         ActivityManagerWrapper.getInstance().registerTaskStackListener(mTaskStackListener);
 
-        mPipBoundsHandler = pipBoundsHandler;
-        mPipTaskOrganizer = pipTaskOrganizer;
+        mDisplayController = displayController;
+        mPipBoundsHandler = new PipBoundsHandler(mContext);
+        mPipSurfaceTransactionHelper = new PipSurfaceTransactionHelper(context, configController);
+        mPipTaskOrganizer = new PipTaskOrganizer(mContext, mPipBoundsHandler,
+                mPipSurfaceTransactionHelper, splitScreenControllerOptional, mDisplayController,
+                pipUiEventLogger, shellTaskOrganizer);
         mPipTaskOrganizer.registerPipTransitionCallback(this);
         mInputConsumerController = InputConsumerController.getPipInputConsumer();
         mMediaController = new PipMediaController(context, mActivityManager, broadcastDispatcher);
-        mMenuController = new PipMenuActivityController(context, mMediaController,
-                mInputConsumerController);
+        mMenuController = new PipMenuActivityController(context, pipMenuActivityClass,
+                mMediaController, mInputConsumerController);
         mTouchHandler = new PipTouchHandler(context, mActivityManager,
                 mMenuController, mInputConsumerController, mPipBoundsHandler, mPipTaskOrganizer,
-                floatingContentCoordinator, deviceConfig, pipSnapAlgorithm, sysUiState);
+                floatingContentCoordinator, deviceConfig, sysUiState, pipUiEventLogger);
         mAppOpsListener = new PipAppOpsListener(context, mActivityManager,
                 mTouchHandler.getMotionHelper());
         displayController.addDisplayChangingController(mRotationController);
@@ -293,7 +315,6 @@
         configController.addCallback(mOverlayChangedListener);
 
         try {
-            mPipTaskOrganizer.registerOrganizer(WINDOWING_MODE_PINNED);
             ActivityManager.StackInfo stackInfo = ActivityTaskManager.getService().getStackInfo(
                     WINDOWING_MODE_PINNED, ACTIVITY_TYPE_UNDEFINED);
             if (stackInfo != null) {
@@ -318,7 +339,7 @@
      */
     @Override
     public void expandPip() {
-        mTouchHandler.getMotionHelper().expandPipToFullscreen(false /* skipAnimation */);
+        mTouchHandler.getMotionHelper().expandLeavePip(false /* skipAnimation */);
     }
 
     /**
@@ -371,10 +392,10 @@
     }
 
     @Override
-    public void onPipTransitionStarted(ComponentName activity, int direction) {
+    public void onPipTransitionStarted(ComponentName activity, int direction, Rect pipBounds) {
         if (isOutPipDirection(direction)) {
             // Exiting PIP, save the reentry bounds to restore to when re-entering.
-            updateReentryBounds();
+            updateReentryBounds(pipBounds);
             mPipBoundsHandler.onSaveReentryBounds(activity, mReentryBounds);
         }
         // Disable touches while the animation is running
@@ -391,15 +412,8 @@
     /**
      * Update the bounds used to save the re-entry size and snap fraction when exiting PIP.
      */
-    public void updateReentryBounds() {
-        // On phones, the expansion animation that happens on pip tap before restoring
-        // to fullscreen makes it so that the last reported bounds are the expanded
-        // bounds. We want to restore to the unexpanded bounds when re-entering pip,
-        // so we use the bounds before expansion (normal) instead of the reported
-        // bounds.
-        Rect reentryBounds = mTouchHandler.getNormalBounds();
-        // Apply the snap fraction of the current bounds to the normal bounds.
-        final Rect bounds = mPipTaskOrganizer.getLastReportedBounds();
+    public void updateReentryBounds(Rect bounds) {
+        final Rect reentryBounds = mTouchHandler.getUserResizeBounds();
         float snapFraction = mPipBoundsHandler.getSnapFraction(bounds);
         mPipBoundsHandler.applySnapFraction(reentryBounds, snapFraction);
         mReentryBounds.set(reentryBounds);
diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuActivity.java b/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuActivity.java
index d6f3e16..1b1b2de 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuActivity.java
@@ -76,17 +76,15 @@
 import android.widget.LinearLayout;
 
 import com.android.systemui.Interpolators;
-import com.android.systemui.SystemUIFactory;
 import com.android.wm.shell.R;
 
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.List;
 
-import javax.inject.Inject;
-
 /**
  * Translucent activity that gets started on top of a task in PIP to allow the user to control it.
+ * TODO(b/150319024): PipMenuActivity will move to a Window
  */
 public class PipMenuActivity extends Activity {
 
@@ -126,19 +124,11 @@
     private final List<RemoteAction> mActions = new ArrayList<>();
 
     private AccessibilityManager mAccessibilityManager;
-    private View mViewRoot;
     private Drawable mBackgroundDrawable;
     private View mMenuContainer;
     private LinearLayout mActionsGroup;
-    private View mSettingsButton;
-    private View mDismissButton;
-    private View mResizeHandle;
-    private View mTopEndContainer;
     private int mBetweenActionPaddingLand;
 
-    @Inject
-    PipMenuIconsAlgorithm mPipMenuIconsAlgorithm;
-
     private AnimatorSet mMenuContainerAnimator;
 
     private ValueAnimator.AnimatorUpdateListener mMenuBgUpdateListener =
@@ -193,6 +183,9 @@
                     break;
                 }
                 case MESSAGE_MENU_EXPANDED : {
+                    if (mMenuContainerAnimator == null) {
+                        return;
+                    }
                     mMenuContainerAnimator.setStartDelay(MENU_SHOW_ON_EXPAND_START_DELAY);
                     mMenuContainerAnimator.start();
                     break;
@@ -202,6 +195,9 @@
                     break;
                 }
                 case MESSAGE_UPDATE_MENU_LAYOUT: {
+                    if (mPipMenuIconsAlgorithm == null) {
+                        return;
+                    }
                     final Rect bounds = (Rect) msg.obj;
                     mPipMenuIconsAlgorithm.onBoundsChanged(bounds);
                     break;
@@ -214,6 +210,13 @@
 
     private final Runnable mFinishRunnable = this::hideMenu;
 
+    protected View mViewRoot;
+    protected View mSettingsButton;
+    protected View mDismissButton;
+    protected View mResizeHandle;
+    protected View mTopEndContainer;
+    protected PipMenuIconsAlgorithm mPipMenuIconsAlgorithm;
+
     @Override
     protected void onCreate(@Nullable Bundle savedInstanceState) {
         // Set the flags to allow us to watch for outside touches and also hide the menu and start
@@ -222,8 +225,6 @@
 
         super.onCreate(savedInstanceState);
 
-        SystemUIFactory.getInstance().getRootComponent().inject(this);
-
         setContentView(R.layout.pip_menu_activity);
 
         mAccessibilityManager = getSystemService(AccessibilityManager.class);
@@ -254,7 +255,7 @@
         mActionsGroup = findViewById(R.id.actions_group);
         mBetweenActionPaddingLand = getResources().getDimensionPixelSize(
                 R.dimen.pip_between_action_padding_land);
-
+        mPipMenuIconsAlgorithm = new PipMenuIconsAlgorithm(this.getApplicationContext());
         mPipMenuIconsAlgorithm.bindViews((ViewGroup) mViewRoot, (ViewGroup) mTopEndContainer,
                 mResizeHandle, mSettingsButton, mDismissButton);
         updateFromIntent(getIntent());
diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuActivityController.java b/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuActivityController.java
index 267c5ea..383f6b3 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuActivityController.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuActivityController.java
@@ -110,6 +110,8 @@
         void onPipShowMenu();
     }
 
+    /** TODO(b/150319024): PipMenuActivity will move to a Window */
+    private Class<?> mPipMenuActivityClass;
     private Context mContext;
     private PipMediaController mMediaController;
     private InputConsumerController mInputConsumerController;
@@ -185,11 +187,13 @@
         }
     };
 
-    public PipMenuActivityController(Context context,
-            PipMediaController mediaController, InputConsumerController inputConsumerController) {
+    public PipMenuActivityController(Context context, Class<?> pipMenuActivityClass,
+            PipMediaController mediaController, InputConsumerController inputConsumerController
+    ) {
         mContext = context;
         mMediaController = mediaController;
         mInputConsumerController = inputConsumerController;
+        mPipMenuActivityClass = pipMenuActivityClass;
     }
 
     public boolean isMenuActivityVisible() {
@@ -454,7 +458,7 @@
                     WINDOWING_MODE_PINNED, ACTIVITY_TYPE_UNDEFINED);
             if (pinnedStackInfo != null && pinnedStackInfo.taskIds != null &&
                     pinnedStackInfo.taskIds.length > 0) {
-                Intent intent = new Intent(mContext, PipMenuActivity.class);
+                Intent intent = new Intent(mContext, mPipMenuActivityClass);
                 intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
                 intent.putExtra(EXTRA_CONTROLLER_MESSENGER, mMessenger);
                 intent.putExtra(EXTRA_ACTIONS, resolveMenuActions());
diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuIconsAlgorithm.java b/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuIconsAlgorithm.java
index 69a04d8..6cfed07 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuIconsAlgorithm.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuIconsAlgorithm.java
@@ -24,8 +24,6 @@
 import android.view.ViewGroup;
 import android.widget.FrameLayout;
 
-import javax.inject.Inject;
-
 /**
  * Helper class to calculate and place the menu icons on the PIP Menu.
  */
@@ -40,8 +38,7 @@
     protected View mSettingsButton;
     protected View mDismissButton;
 
-    @Inject
-    public PipMenuIconsAlgorithm(Context context) {
+    protected PipMenuIconsAlgorithm(Context context) {
     }
 
     /**
@@ -56,7 +53,6 @@
         mDismissButton = dismissButton;
     }
 
-
     /**
      * Updates the position of the drag handle based on where the PIP window is on the screen.
      */
diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipMotionHelper.java b/packages/SystemUI/src/com/android/systemui/pip/phone/PipMotionHelper.java
index ca3ef24..19138fd 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipMotionHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipMotionHelper.java
@@ -54,7 +54,7 @@
 
     private static final int SHRINK_STACK_FROM_MENU_DURATION = 250;
     private static final int EXPAND_STACK_TO_MENU_DURATION = 250;
-    private static final int EXPAND_STACK_TO_FULLSCREEN_DURATION = 300;
+    private static final int LEAVE_PIP_DURATION = 300;
     private static final int SHIFT_DURATION = 300;
 
     /** Friction to use for PIP when it moves via physics fling animations. */
@@ -154,7 +154,7 @@
     private final PipTaskOrganizer.PipTransitionCallback mPipTransitionCallback =
             new PipTaskOrganizer.PipTransitionCallback() {
         @Override
-        public void onPipTransitionStarted(ComponentName activity, int direction) {}
+        public void onPipTransitionStarted(ComponentName activity, int direction, Rect pipBounds) {}
 
         @Override
         public void onPipTransitionFinished(ComponentName activity, int direction) {
@@ -304,16 +304,18 @@
     }
 
     /**
-     * Resizes the pinned stack back to fullscreen.
+     * Resizes the pinned stack back to unknown windowing mode, which could be freeform or
+     *      * fullscreen depending on the display area's windowing mode.
      */
-    void expandPipToFullscreen() {
-        expandPipToFullscreen(false /* skipAnimation */);
+    void expandLeavePip() {
+        expandLeavePip(false /* skipAnimation */);
     }
 
     /**
-     * Resizes the pinned stack back to fullscreen.
+     * Resizes the pinned stack back to unknown windowing mode, which could be freeform or
+     * fullscreen depending on the display area's windowing mode.
      */
-    void expandPipToFullscreen(boolean skipAnimation) {
+    void expandLeavePip(boolean skipAnimation) {
         if (DEBUG) {
             Log.d(TAG, "exitPip: skipAnimation=" + skipAnimation
                     + " callers=\n" + Debug.getCallers(5, "    "));
@@ -323,7 +325,7 @@
         mPipTaskOrganizer.getUpdateHandler().post(() -> {
             mPipTaskOrganizer.exitPip(skipAnimation
                     ? 0
-                    : EXPAND_STACK_TO_FULLSCREEN_DURATION);
+                    : LEAVE_PIP_DURATION);
         });
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipResizeGestureHandler.java b/packages/SystemUI/src/com/android/systemui/pip/phone/PipResizeGestureHandler.java
index d884fa9..2800bb9 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipResizeGestureHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipResizeGestureHandler.java
@@ -52,6 +52,7 @@
 import com.android.systemui.model.SysUiState;
 import com.android.systemui.pip.PipBoundsHandler;
 import com.android.systemui.pip.PipTaskOrganizer;
+import com.android.systemui.pip.PipUiEventLogger;
 import com.android.systemui.util.DeviceConfigProxy;
 import com.android.wm.shell.R;
 
@@ -88,6 +89,7 @@
     private final Point mMaxSize = new Point();
     private final Point mMinSize = new Point();
     private final Rect mLastResizeBounds = new Rect();
+    private final Rect mUserResizeBounds = new Rect();
     private final Rect mLastDownBounds = new Rect();
     private final Rect mDragCornerSize = new Rect();
     private final Rect mTmpTopLeftCorner = new Rect();
@@ -109,13 +111,15 @@
     private InputMonitor mInputMonitor;
     private InputEventReceiver mInputEventReceiver;
     private PipTaskOrganizer mPipTaskOrganizer;
+    private PipUiEventLogger mPipUiEventLogger;
 
     private int mCtrlType;
 
     public PipResizeGestureHandler(Context context, PipBoundsHandler pipBoundsHandler,
             PipMotionHelper motionHelper, DeviceConfigProxy deviceConfig,
             PipTaskOrganizer pipTaskOrganizer, Function<Rect, Rect> movementBoundsSupplier,
-            Runnable updateMovementBoundsRunnable, SysUiState sysUiState) {
+            Runnable updateMovementBoundsRunnable, SysUiState sysUiState,
+            PipUiEventLogger pipUiEventLogger) {
         mContext = context;
         mDisplayId = context.getDisplayId();
         mMainExecutor = context.getMainExecutor();
@@ -125,6 +129,7 @@
         mMovementBoundsSupplier = movementBoundsSupplier;
         mUpdateMovementBoundsRunnable = updateMovementBoundsRunnable;
         mSysUiState = sysUiState;
+        mPipUiEventLogger = pipUiEventLogger;
 
         context.getDisplay().getRealSize(mMaxSize);
         reloadResources();
@@ -181,6 +186,7 @@
 
     void onActivityUnpinned() {
         mIsAttached = false;
+        mUserResizeBounds.setEmpty();
         updateIsEnabled();
     }
 
@@ -329,6 +335,7 @@
                 case MotionEvent.ACTION_UP:
                 case MotionEvent.ACTION_CANCEL:
                     if (!mLastResizeBounds.isEmpty()) {
+                        mUserResizeBounds.set(mLastResizeBounds);
                         mPipTaskOrganizer.scheduleFinishResizePip(mLastResizeBounds,
                                 (Rect bounds) -> {
                                     new Handler(Looper.getMainLooper()).post(() -> {
@@ -337,6 +344,8 @@
                                         resetState();
                                     });
                                 });
+                        mPipUiEventLogger.log(
+                                PipUiEventLogger.PipUiEventEnum.PICTURE_IN_PICTURE_RESIZE);
                     } else {
                         resetState();
                     }
@@ -351,6 +360,18 @@
         mThresholdCrossed = false;
     }
 
+    void setUserResizeBounds(Rect bounds) {
+        mUserResizeBounds.set(bounds);
+    }
+
+    void invalidateUserResizeBounds() {
+        mUserResizeBounds.setEmpty();
+    }
+
+    Rect getUserResizeBounds() {
+        return mUserResizeBounds;
+    }
+
     void updateMaxSize(int maxX, int maxY) {
         mMaxSize.set(maxX, maxY);
     }
diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchHandler.java b/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchHandler.java
index 5434b62..1b84c14 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchHandler.java
@@ -34,7 +34,6 @@
 import android.os.Handler;
 import android.os.RemoteException;
 import android.util.Log;
-import android.util.Pair;
 import android.util.Size;
 import android.view.Gravity;
 import android.view.IPinnedStackController;
@@ -55,13 +54,12 @@
 import androidx.dynamicanimation.animation.SpringForce;
 
 import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.os.logging.MetricsLoggerWrapper;
 import com.android.systemui.R;
 import com.android.systemui.model.SysUiState;
 import com.android.systemui.pip.PipAnimationController;
 import com.android.systemui.pip.PipBoundsHandler;
-import com.android.systemui.pip.PipSnapAlgorithm;
 import com.android.systemui.pip.PipTaskOrganizer;
+import com.android.systemui.pip.PipUiEventLogger;
 import com.android.systemui.shared.system.InputConsumerController;
 import com.android.systemui.util.DeviceConfigProxy;
 import com.android.systemui.util.DismissCircleView;
@@ -94,11 +92,12 @@
     private final WindowManager mWindowManager;
     private final IActivityManager mActivityManager;
     private final PipBoundsHandler mPipBoundsHandler;
+    private final PipUiEventLogger mPipUiEventLogger;
+
     private PipResizeGestureHandler mPipResizeGestureHandler;
     private IPinnedStackController mPinnedStackController;
 
     private final PipMenuActivityController mMenuController;
-    private final PipSnapAlgorithm mSnapAlgorithm;
     private final AccessibilityManager mAccessibilityManager;
     private boolean mShowPipMenuOnAnimationEnd = false;
 
@@ -132,9 +131,6 @@
 
     // The current movement bounds
     private Rect mMovementBounds = new Rect();
-    // The current resized bounds, changed by user resize.
-    // This is used during expand/un-expand to save/restore the user's resized size.
-    @VisibleForTesting Rect mResizedBounds = new Rect();
 
     // The reference inset bounds, used to determine the dismiss fraction
     private Rect mInsetBounds = new Rect();
@@ -193,16 +189,12 @@
 
         @Override
         public void onPipExpand() {
-            mMotionHelper.expandPipToFullscreen();
+            mMotionHelper.expandLeavePip();
         }
 
         @Override
         public void onPipDismiss() {
-            Pair<ComponentName, Integer> topPipActivity = PipUtils.getTopPipActivity(mContext,
-                    mActivityManager);
-            if (topPipActivity.first != null) {
-                MetricsLoggerWrapper.logPictureInPictureDismissByTap(mContext, topPipActivity);
-            }
+            mPipUiEventLogger.log(PipUiEventLogger.PipUiEventEnum.PICTURE_IN_PICTURE_TAP_TO_REMOVE);
             mTouchState.removeDoubleTapTimeoutCallback();
             mMotionHelper.dismissPip();
         }
@@ -222,23 +214,23 @@
             PipTaskOrganizer pipTaskOrganizer,
             FloatingContentCoordinator floatingContentCoordinator,
             DeviceConfigProxy deviceConfig,
-            PipSnapAlgorithm pipSnapAlgorithm,
-            SysUiState sysUiState) {
+            SysUiState sysUiState,
+            PipUiEventLogger pipUiEventLogger) {
         // Initialize the Pip input consumer
         mContext = context;
         mActivityManager = activityManager;
         mAccessibilityManager = context.getSystemService(AccessibilityManager.class);
+        mPipBoundsHandler = pipBoundsHandler;
         mWindowManager = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE);
         mMenuController = menuController;
         mMenuController.addListener(new PipMenuListener());
-        mSnapAlgorithm = pipSnapAlgorithm;
         mGesture = new DefaultPipTouchGesture();
         mMotionHelper = new PipMotionHelper(mContext, pipTaskOrganizer, mMenuController,
-                mSnapAlgorithm, floatingContentCoordinator);
+                mPipBoundsHandler.getSnapAlgorithm(), floatingContentCoordinator);
         mPipResizeGestureHandler =
                 new PipResizeGestureHandler(context, pipBoundsHandler, mMotionHelper,
                         deviceConfig, pipTaskOrganizer, this::getMovementBounds,
-                        this::updateMovementBounds, sysUiState);
+                        this::updateMovementBounds, sysUiState, pipUiEventLogger);
         mTouchState = new PipTouchState(ViewConfiguration.get(context), mHandler,
                 () -> mMenuController.showMenuWithDelay(MENU_STATE_FULL, mMotionHelper.getBounds(),
                         true /* allowMenuTimeout */, willResizeMenu(), shouldShowResizeHandle()),
@@ -253,11 +245,12 @@
         inputConsumerController.setInputListener(this::handleTouchEvent);
         inputConsumerController.setRegistrationListener(this::onRegistrationChanged);
 
-        mPipBoundsHandler = pipBoundsHandler;
         mFloatingContentCoordinator = floatingContentCoordinator;
         mConnection = new PipAccessibilityInteractionConnection(mContext, mMotionHelper,
-                pipTaskOrganizer, pipSnapAlgorithm, this::onAccessibilityShowMenu,
-                this::updateMovementBounds, mHandler);
+                pipTaskOrganizer, mPipBoundsHandler.getSnapAlgorithm(),
+                this::onAccessibilityShowMenu, this::updateMovementBounds, mHandler);
+
+        mPipUiEventLogger = pipUiEventLogger;
 
         mTargetView = new DismissCircleView(context);
         mTargetViewContainer = new FrameLayout(context);
@@ -307,11 +300,8 @@
                     hideDismissTarget();
                 });
 
-                Pair<ComponentName, Integer> topPipActivity = PipUtils.getTopPipActivity(mContext,
-                        mActivityManager);
-                if (topPipActivity.first != null) {
-                    MetricsLoggerWrapper.logPictureInPictureDismissByDrag(mContext, topPipActivity);
-                }
+                mPipUiEventLogger.log(
+                        PipUiEventLogger.PipUiEventEnum.PICTURE_IN_PICTURE_DRAG_TO_REMOVE);
             }
         });
 
@@ -383,7 +373,6 @@
 
             mFloatingContentCoordinator.onContentRemoved(mMotionHelper);
         }
-        mResizedBounds.setEmpty();
         mPipResizeGestureHandler.onActivityUnpinned();
     }
 
@@ -393,9 +382,8 @@
         mMotionHelper.synchronizePinnedStackBounds();
         updateMovementBounds();
         if (direction == TRANSITION_DIRECTION_TO_PIP) {
-            // updates mResizedBounds only if it's an entering PiP animation
-            // mResized should be otherwise updated in setMenuState.
-            mResizedBounds.set(mMotionHelper.getBounds());
+            // Set the initial bounds as the user resize bounds.
+            mPipResizeGestureHandler.setUserResizeBounds(mMotionHelper.getBounds());
         }
 
         if (mShowPipMenuOnAnimationEnd) {
@@ -427,15 +415,29 @@
 
     public void adjustBoundsForRotation(Rect outBounds, Rect curBounds, Rect insetBounds) {
         final Rect toMovementBounds = new Rect();
-        mSnapAlgorithm.getMovementBounds(outBounds, insetBounds, toMovementBounds, 0);
+        mPipBoundsHandler.getSnapAlgorithm().getMovementBounds(outBounds, insetBounds,
+                toMovementBounds, 0);
         final int prevBottom = mMovementBounds.bottom - mMovementBoundsExtraOffsets;
         if ((prevBottom - mBottomOffsetBufferPx) <= curBounds.top) {
             outBounds.offsetTo(outBounds.left, toMovementBounds.bottom);
         }
     }
 
+    /**
+     * Responds to IPinnedStackListener on resetting aspect ratio for the pinned window.
+     */
+    public void onAspectRatioChanged() {
+        mPipResizeGestureHandler.invalidateUserResizeBounds();
+    }
+
     public void onMovementBoundsChanged(Rect insetBounds, Rect normalBounds, Rect curBounds,
             boolean fromImeAdjustment, boolean fromShelfAdjustment, int displayRotation) {
+        // Set the user resized bounds equal to the new normal bounds in case they were
+        // invalidated (e.g. by an aspect ratio change).
+        if (mPipResizeGestureHandler.getUserResizeBounds().isEmpty()) {
+            mPipResizeGestureHandler.setUserResizeBounds(normalBounds);
+        }
+
         final int bottomOffset = mIsImeShowing ? mImeHeight : 0;
         final boolean fromDisplayRotationChanged = (mDisplayRotation != displayRotation);
         if (fromDisplayRotationChanged) {
@@ -445,26 +447,26 @@
         // Re-calculate the expanded bounds
         mNormalBounds.set(normalBounds);
         Rect normalMovementBounds = new Rect();
-        mSnapAlgorithm.getMovementBounds(mNormalBounds, insetBounds, normalMovementBounds,
-                bottomOffset);
+        mPipBoundsHandler.getSnapAlgorithm().getMovementBounds(mNormalBounds, insetBounds,
+                normalMovementBounds, bottomOffset);
 
         if (mMovementBounds.isEmpty()) {
             // mMovementBounds is not initialized yet and a clean movement bounds without
             // bottom offset shall be used later in this function.
-            mSnapAlgorithm.getMovementBounds(curBounds, insetBounds, mMovementBounds,
-                    0 /* bottomOffset */);
+            mPipBoundsHandler.getSnapAlgorithm().getMovementBounds(curBounds, insetBounds,
+                    mMovementBounds, 0 /* bottomOffset */);
         }
 
         // Calculate the expanded size
         float aspectRatio = (float) normalBounds.width() / normalBounds.height();
         Point displaySize = new Point();
         mContext.getDisplay().getRealSize(displaySize);
-        Size expandedSize = mSnapAlgorithm.getSizeForAspectRatio(aspectRatio,
+        Size expandedSize = mPipBoundsHandler.getSnapAlgorithm().getSizeForAspectRatio(aspectRatio,
                 mExpandedShortestEdgeSize, displaySize.x, displaySize.y);
         mExpandedBounds.set(0, 0, expandedSize.getWidth(), expandedSize.getHeight());
         Rect expandedMovementBounds = new Rect();
-        mSnapAlgorithm.getMovementBounds(mExpandedBounds, insetBounds, expandedMovementBounds,
-                bottomOffset);
+        mPipBoundsHandler.getSnapAlgorithm().getMovementBounds(mExpandedBounds, insetBounds,
+                expandedMovementBounds, bottomOffset);
 
         mPipResizeGestureHandler.updateMinSize(mNormalBounds.width(), mNormalBounds.height());
         mPipResizeGestureHandler.updateMaxSize(mExpandedBounds.width(), mExpandedBounds.height());
@@ -484,7 +486,7 @@
             } else {
                 final boolean isExpanded = mMenuState == MENU_STATE_FULL && willResizeMenu();
                 final Rect toMovementBounds = new Rect();
-                mSnapAlgorithm.getMovementBounds(curBounds, insetBounds,
+                mPipBoundsHandler.getSnapAlgorithm().getMovementBounds(curBounds, insetBounds,
                         toMovementBounds, mIsImeShowing ? mImeHeight : 0);
                 final int prevBottom = mMovementBounds.bottom - mMovementBoundsExtraOffsets;
                 // This is to handle landscape fullscreen IMEs, don't apply the extra offset in this
@@ -495,8 +497,8 @@
 
                 if (isExpanded) {
                     curBounds.set(mExpandedBounds);
-                    mSnapAlgorithm.applySnapFraction(curBounds, toMovementBounds,
-                            mSavedSnapFraction);
+                    mPipBoundsHandler.getSnapAlgorithm().applySnapFraction(curBounds,
+                            toMovementBounds, mSavedSnapFraction);
                 }
 
                 if (prevBottom < toBottom) {
@@ -603,7 +605,7 @@
                 .spring(DynamicAnimation.TRANSLATION_Y,
                         mTargetViewContainer.getHeight(),
                         mTargetSpringConfig)
-                .withEndActions(() ->  mTargetViewContainer.setVisibility(View.GONE))
+                .withEndActions(() -> mTargetViewContainer.setVisibility(View.GONE))
                 .start();
 
         ((TransitionDrawable) mTargetViewContainer.getBackground()).reverseTransition(
@@ -808,9 +810,7 @@
             // Save the current snap fraction and if we do not drag or move the PiP, then
             // we store back to this snap fraction.  Otherwise, we'll reset the snap
             // fraction and snap to the closest edge.
-            // Also save the current resized bounds so when the menu disappears, we can restore it.
             if (resize) {
-                mResizedBounds.set(mMotionHelper.getBounds());
                 Rect expandedBounds = new Rect(mExpandedBounds);
                 mSavedSnapFraction = mMotionHelper.animateToExpandedState(expandedBounds,
                         mMovementBounds, mExpandedMovementBounds, callback);
@@ -839,10 +839,10 @@
                 }
 
                 if (mDeferResizeToNormalBoundsUntilRotation == -1) {
-                    Rect restoreBounds = new Rect(mResizedBounds);
+                    Rect restoreBounds = new Rect(getUserResizeBounds());
                     Rect restoredMovementBounds = new Rect();
-                    mSnapAlgorithm.getMovementBounds(restoreBounds, mInsetBounds,
-                            restoredMovementBounds, mIsImeShowing ? mImeHeight : 0);
+                    mPipBoundsHandler.getSnapAlgorithm().getMovementBounds(restoreBounds,
+                            mInsetBounds, restoredMovementBounds, mIsImeShowing ? mImeHeight : 0);
                     mMotionHelper.animateToUnexpandedState(restoreBounds, mSavedSnapFraction,
                             restoredMovementBounds, mMovementBounds, false /* immediate */);
                     mSavedSnapFraction = -1f;
@@ -856,8 +856,10 @@
         // If pip menu has dismissed, we should register the A11y ActionReplacingConnection for pip
         // as well, or it can't handle a11y focus and pip menu can't perform any action.
         onRegistrationChanged(menuState == MENU_STATE_NONE);
-        if (menuState != MENU_STATE_CLOSE) {
-            MetricsLoggerWrapper.logPictureInPictureMenuVisible(mContext, menuState == MENU_STATE_FULL);
+        if (menuState == MENU_STATE_NONE) {
+            mPipUiEventLogger.log(PipUiEventLogger.PipUiEventEnum.PICTURE_IN_PICTURE_HIDE_MENU);
+        } else if (menuState == MENU_STATE_FULL) {
+            mPipUiEventLogger.log(PipUiEventLogger.PipUiEventEnum.PICTURE_IN_PICTURE_SHOW_MENU);
         }
     }
 
@@ -890,6 +892,10 @@
         return mNormalBounds;
     }
 
+    Rect getUserResizeBounds() {
+        return mPipResizeGestureHandler.getUserResizeBounds();
+    }
+
     /**
      * Gesture controlling normal movement of the PIP.
      */
@@ -991,7 +997,7 @@
                 // Expand to fullscreen if this is a double tap
                 // the PiP should be frozen until the transition ends
                 setTouchEnabled(false);
-                mMotionHelper.expandPipToFullscreen();
+                mMotionHelper.expandLeavePip();
             } else if (mMenuState != MENU_STATE_FULL) {
                 if (!mTouchState.isWaitingForDoubleTap()) {
                     // User has stalled long enough for this not to be a drag or a double tap, just
@@ -1016,25 +1022,25 @@
                 mMenuController.hideMenu();
             }
         }
-    };
+    }
 
     /**
      * Updates the current movement bounds based on whether the menu is currently visible and
      * resized.
      */
     private void updateMovementBounds() {
-        mSnapAlgorithm.getMovementBounds(mMotionHelper.getBounds(), mInsetBounds,
-                mMovementBounds, mIsImeShowing ? mImeHeight : 0);
+        mPipBoundsHandler.getSnapAlgorithm().getMovementBounds(mMotionHelper.getBounds(),
+                mInsetBounds, mMovementBounds, mIsImeShowing ? mImeHeight : 0);
         mMotionHelper.setCurrentMovementBounds(mMovementBounds);
 
         boolean isMenuExpanded = mMenuState == MENU_STATE_FULL;
         mPipBoundsHandler.setMinEdgeSize(
-                isMenuExpanded  && willResizeMenu() ? mExpandedShortestEdgeSize : 0);
+                isMenuExpanded && willResizeMenu() ? mExpandedShortestEdgeSize : 0);
     }
 
     private Rect getMovementBounds(Rect curBounds) {
         Rect movementBounds = new Rect();
-        mSnapAlgorithm.getMovementBounds(curBounds, mInsetBounds,
+        mPipBoundsHandler.getSnapAlgorithm().getMovementBounds(curBounds, mInsetBounds,
                 movementBounds, mIsImeShowing ? mImeHeight : 0);
         return movementBounds;
     }
@@ -1066,6 +1072,7 @@
         pw.println(innerPrefix + "mSavedSnapFraction=" + mSavedSnapFraction);
         pw.println(innerPrefix + "mEnableDragToEdgeDismiss=" + mEnableDismissDragToEdge);
         pw.println(innerPrefix + "mMovementBoundsExtraOffsets=" + mMovementBoundsExtraOffsets);
+        mPipBoundsHandler.dump(pw, innerPrefix);
         mTouchState.dump(pw, innerPrefix);
         mMotionHelper.dump(pw, innerPrefix);
         if (mPipResizeGestureHandler != null) {
diff --git a/media/java/android/media/IStrategyPreferredDeviceDispatcher.aidl b/packages/SystemUI/src/com/android/systemui/pip/phone/dagger/PipMenuActivityClass.java
similarity index 66%
copy from media/java/android/media/IStrategyPreferredDeviceDispatcher.aidl
copy to packages/SystemUI/src/com/android/systemui/pip/phone/dagger/PipMenuActivityClass.java
index b1f99e6..114c30e 100644
--- a/media/java/android/media/IStrategyPreferredDeviceDispatcher.aidl
+++ b/packages/SystemUI/src/com/android/systemui/pip/phone/dagger/PipMenuActivityClass.java
@@ -14,17 +14,17 @@
  * limitations under the License.
  */
 
-package android.media;
+package com.android.systemui.pip.phone.dagger;
 
-import android.media.AudioDeviceAttributes;
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
 
-/**
- * AIDL for AudioService to signal audio strategy-preferred device updates.
- *
- * {@hide}
- */
-oneway interface IStrategyPreferredDeviceDispatcher {
+import java.lang.annotation.Documented;
+import java.lang.annotation.Retention;
 
-    void dispatchPrefDeviceChanged(int strategyId, in AudioDeviceAttributes device);
+import javax.inject.Qualifier;
 
+@Qualifier
+@Documented
+@Retention(RUNTIME)
+public @interface PipMenuActivityClass {
 }
diff --git a/packages/SystemUI/src/com/android/systemui/pip/tv/PipManager.java b/packages/SystemUI/src/com/android/systemui/pip/tv/PipManager.java
index 2138f09..5d8e630 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/tv/PipManager.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/tv/PipManager.java
@@ -20,6 +20,7 @@
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
 import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
 
+import android.annotation.NonNull;
 import android.app.ActivityManager.RunningTaskInfo;
 import android.app.ActivityManager.StackInfo;
 import android.app.ActivityTaskManager;
@@ -49,26 +50,31 @@
 import com.android.systemui.R;
 import com.android.systemui.UiOffloadThread;
 import com.android.systemui.broadcast.BroadcastDispatcher;
+import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.pip.BasePipManager;
 import com.android.systemui.pip.PipBoundsHandler;
 import com.android.systemui.pip.PipSurfaceTransactionHelper;
 import com.android.systemui.pip.PipTaskOrganizer;
+import com.android.systemui.pip.PipUiEventLogger;
 import com.android.systemui.shared.system.ActivityManagerWrapper;
 import com.android.systemui.shared.system.PinnedStackListenerForwarder.PinnedStackListener;
 import com.android.systemui.shared.system.TaskStackChangeListener;
 import com.android.systemui.shared.system.WindowManagerWrapper;
-import com.android.systemui.stackdivider.Divider;
+import com.android.systemui.stackdivider.SplitScreenController;
+import com.android.systemui.statusbar.policy.ConfigurationController;
+import com.android.wm.shell.ShellTaskOrganizer;
+import com.android.wm.shell.common.DisplayController;
 
 import java.util.ArrayList;
 import java.util.List;
+import java.util.Optional;
 
 import javax.inject.Inject;
-import javax.inject.Singleton;
 
 /**
  * Manages the picture-in-picture (PIP) UI and states.
  */
-@Singleton
+@SysUISingleton
 public class PipManager implements BasePipManager, PipTaskOrganizer.PipTransitionCallback {
     private static final String TAG = "PipManager";
     static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
@@ -111,6 +117,7 @@
     private Context mContext;
     private PipBoundsHandler mPipBoundsHandler;
     private PipTaskOrganizer mPipTaskOrganizer;
+    private PipSurfaceTransactionHelper mPipSurfaceTransactionHelper;
     private IActivityTaskManager mActivityTaskManager;
     private MediaSessionManager mMediaSessionManager;
     private int mState = STATE_NO_PIP;
@@ -186,20 +193,23 @@
     private class PipManagerPinnedStackListener extends PinnedStackListener {
         @Override
         public void onImeVisibilityChanged(boolean imeVisible, int imeHeight) {
-            if (mState == STATE_PIP) {
-                if (mImeVisible != imeVisible) {
-                    if (imeVisible) {
-                        // Save the IME height adjustment, and offset to not occlude the IME
-                        mPipBounds.offset(0, -imeHeight);
-                        mImeHeightAdjustment = imeHeight;
-                    } else {
-                        // Apply the inverse adjustment when the IME is hidden
-                        mPipBounds.offset(0, mImeHeightAdjustment);
+            mHandler.post(() -> {
+                mPipBoundsHandler.onImeVisibilityChanged(imeVisible, imeHeight);
+                if (mState == STATE_PIP) {
+                    if (mImeVisible != imeVisible) {
+                        if (imeVisible) {
+                            // Save the IME height adjustment, and offset to not occlude the IME
+                            mPipBounds.offset(0, -imeHeight);
+                            mImeHeightAdjustment = imeHeight;
+                        } else {
+                            // Apply the inverse adjustment when the IME is hidden
+                            mPipBounds.offset(0, mImeHeightAdjustment);
+                        }
+                        mImeVisible = imeVisible;
+                        resizePinnedStack(STATE_PIP);
                     }
-                    mImeVisible = imeVisible;
-                    resizePinnedStack(STATE_PIP);
                 }
-            }
+            });
         }
 
         @Override
@@ -226,17 +236,18 @@
 
     @Inject
     public PipManager(Context context, BroadcastDispatcher broadcastDispatcher,
-            PipBoundsHandler pipBoundsHandler,
-            PipTaskOrganizer pipTaskOrganizer,
-            PipSurfaceTransactionHelper surfaceTransactionHelper,
-            Divider divider) {
+            ConfigurationController configController,
+            DisplayController displayController,
+            Optional<SplitScreenController> splitScreenControllerOptional,
+            @NonNull PipUiEventLogger pipUiEventLogger,
+            ShellTaskOrganizer shellTaskOrganizer) {
         if (mInitialized) {
             return;
         }
 
         mInitialized = true;
         mContext = context;
-        mPipBoundsHandler = pipBoundsHandler;
+        mPipBoundsHandler = new PipBoundsHandler(mContext);
         // Ensure that we have the display info in case we get calls to update the bounds before the
         // listener calls back
         final DisplayInfo displayInfo = new DisplayInfo();
@@ -245,7 +256,10 @@
 
         mResizeAnimationDuration = context.getResources()
                 .getInteger(R.integer.config_pipResizeAnimationDuration);
-        mPipTaskOrganizer = pipTaskOrganizer;
+        mPipSurfaceTransactionHelper = new PipSurfaceTransactionHelper(context, configController);
+        mPipTaskOrganizer = new PipTaskOrganizer(mContext, mPipBoundsHandler,
+                mPipSurfaceTransactionHelper, splitScreenControllerOptional, displayController,
+                pipUiEventLogger, shellTaskOrganizer);
         mPipTaskOrganizer.registerPipTransitionCallback(this);
         mActivityTaskManager = ActivityTaskManager.getService();
         ActivityManagerWrapper.getInstance().registerTaskStackListener(mTaskStackListener);
@@ -264,7 +278,6 @@
 
         try {
             WindowManagerWrapper.getInstance().addPinnedStackListener(mPinnedStackListener);
-            mPipTaskOrganizer.registerOrganizer(WINDOWING_MODE_PINNED);
         } catch (RemoteException | UnsupportedOperationException e) {
             Log.e(TAG, "Failed to register pinned stack listener", e);
         }
@@ -663,7 +676,7 @@
     };
 
     @Override
-    public void onPipTransitionStarted(ComponentName activity, int direction) { }
+    public void onPipTransitionStarted(ComponentName activity, int direction, Rect pipBounds) { }
 
     @Override
     public void onPipTransitionFinished(ComponentName activity, int direction) {
diff --git a/packages/SystemUI/src/com/android/systemui/plugins/PluginDependencyProvider.java b/packages/SystemUI/src/com/android/systemui/plugins/PluginDependencyProvider.java
index 6949531..ad1e21d 100644
--- a/packages/SystemUI/src/com/android/systemui/plugins/PluginDependencyProvider.java
+++ b/packages/SystemUI/src/com/android/systemui/plugins/PluginDependencyProvider.java
@@ -17,15 +17,15 @@
 import android.util.ArrayMap;
 
 import com.android.systemui.Dependency;
+import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.plugins.PluginDependency.DependencyProvider;
 import com.android.systemui.shared.plugins.PluginManager;
 
 import javax.inject.Inject;
-import javax.inject.Singleton;
 
 /**
  */
-@Singleton
+@SysUISingleton
 public class PluginDependencyProvider extends DependencyProvider {
 
     private final ArrayMap<Class<?>, Object> mDependencies = new ArrayMap<>();
diff --git a/packages/SystemUI/src/com/android/systemui/power/EnhancedEstimatesImpl.java b/packages/SystemUI/src/com/android/systemui/power/EnhancedEstimatesImpl.java
index 7d54c21..90da891 100644
--- a/packages/SystemUI/src/com/android/systemui/power/EnhancedEstimatesImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/power/EnhancedEstimatesImpl.java
@@ -2,11 +2,11 @@
 
 import com.android.settingslib.fuelgauge.Estimate;
 import com.android.settingslib.fuelgauge.EstimateKt;
+import com.android.systemui.dagger.SysUISingleton;
 
 import javax.inject.Inject;
-import javax.inject.Singleton;
 
-@Singleton
+@SysUISingleton
 public class EnhancedEstimatesImpl implements EnhancedEstimates {
 
     @Inject
diff --git a/packages/SystemUI/src/com/android/systemui/power/PowerNotificationWarnings.java b/packages/SystemUI/src/com/android/systemui/power/PowerNotificationWarnings.java
index 6abbbbe..a27e9ac 100644
--- a/packages/SystemUI/src/com/android/systemui/power/PowerNotificationWarnings.java
+++ b/packages/SystemUI/src/com/android/systemui/power/PowerNotificationWarnings.java
@@ -59,6 +59,7 @@
 import com.android.systemui.Dependency;
 import com.android.systemui.R;
 import com.android.systemui.SystemUI;
+import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.plugins.ActivityStarter;
 import com.android.systemui.statusbar.phone.SystemUIDialog;
 import com.android.systemui.util.NotificationChannels;
@@ -70,11 +71,10 @@
 import java.util.Objects;
 
 import javax.inject.Inject;
-import javax.inject.Singleton;
 
 /**
  */
-@Singleton
+@SysUISingleton
 public class PowerNotificationWarnings implements PowerUI.WarningsUI {
 
     private static final String TAG = PowerUI.TAG + ".Notification";
diff --git a/packages/SystemUI/src/com/android/systemui/power/PowerUI.java b/packages/SystemUI/src/com/android/systemui/power/PowerUI.java
index 66804be..a888305 100644
--- a/packages/SystemUI/src/com/android/systemui/power/PowerUI.java
+++ b/packages/SystemUI/src/com/android/systemui/power/PowerUI.java
@@ -46,6 +46,7 @@
 import com.android.systemui.R;
 import com.android.systemui.SystemUI;
 import com.android.systemui.broadcast.BroadcastDispatcher;
+import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.statusbar.CommandQueue;
 import com.android.systemui.statusbar.phone.StatusBar;
 
@@ -56,11 +57,10 @@
 import java.util.concurrent.Future;
 
 import javax.inject.Inject;
-import javax.inject.Singleton;
 
 import dagger.Lazy;
 
-@Singleton
+@SysUISingleton
 public class PowerUI extends SystemUI implements CommandQueue.Callbacks {
 
     static final String TAG = "PowerUI";
diff --git a/packages/SystemUI/src/com/android/systemui/privacy/PrivacyItemController.kt b/packages/SystemUI/src/com/android/systemui/privacy/PrivacyItemController.kt
index d5a14f7..e57478e 100644
--- a/packages/SystemUI/src/com/android/systemui/privacy/PrivacyItemController.kt
+++ b/packages/SystemUI/src/com/android/systemui/privacy/PrivacyItemController.kt
@@ -31,6 +31,7 @@
 import com.android.systemui.appops.AppOpItem
 import com.android.systemui.appops.AppOpsController
 import com.android.systemui.broadcast.BroadcastDispatcher
+import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Background
 import com.android.systemui.dagger.qualifiers.Main
 import com.android.systemui.dump.DumpManager
@@ -41,11 +42,9 @@
 import java.lang.ref.WeakReference
 import java.util.concurrent.Executor
 import javax.inject.Inject
-import javax.inject.Singleton
 
-@Singleton
+@SysUISingleton
 class PrivacyItemController @Inject constructor(
-    context: Context,
     private val appOpsController: AppOpsController,
     @Main uiExecutor: DelayableExecutor,
     @Background private val bgExecutor: Executor,
@@ -57,16 +56,22 @@
 
     @VisibleForTesting
     internal companion object {
-        val OPS = intArrayOf(AppOpsManager.OP_CAMERA,
-                AppOpsManager.OP_RECORD_AUDIO,
+        val OPS_MIC_CAMERA = intArrayOf(AppOpsManager.OP_CAMERA,
+                AppOpsManager.OP_PHONE_CALL_CAMERA, AppOpsManager.OP_RECORD_AUDIO,
+                AppOpsManager.OP_PHONE_CALL_MICROPHONE)
+        val OPS_LOCATION = intArrayOf(
                 AppOpsManager.OP_COARSE_LOCATION,
                 AppOpsManager.OP_FINE_LOCATION)
+        val OPS = OPS_MIC_CAMERA + OPS_LOCATION
         val intentFilter = IntentFilter().apply {
             addAction(Intent.ACTION_USER_SWITCHED)
             addAction(Intent.ACTION_MANAGED_PROFILE_AVAILABLE)
             addAction(Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE)
         }
         const val TAG = "PrivacyItemController"
+        private const val ALL_INDICATORS =
+                SystemUiDeviceConfigFlags.PROPERTY_PERMISSIONS_HUB_ENABLED
+        private const val MIC_CAMERA = SystemUiDeviceConfigFlags.PROPERTY_MIC_CAMERA_ENABLED
     }
 
     @VisibleForTesting
@@ -74,9 +79,14 @@
         @Synchronized get() = field.toList() // Returns a shallow copy of the list
         @Synchronized set
 
-    private fun isPermissionsHubEnabled(): Boolean {
+    private fun isAllIndicatorsEnabled(): Boolean {
         return deviceConfigProxy.getBoolean(DeviceConfig.NAMESPACE_PRIVACY,
-                SystemUiDeviceConfigFlags.PROPERTY_PERMISSIONS_HUB_ENABLED, false)
+                ALL_INDICATORS, false)
+    }
+
+    private fun isMicCameraEnabled(): Boolean {
+        return deviceConfigProxy.getBoolean(DeviceConfig.NAMESPACE_PRIVACY,
+                MIC_CAMERA, false)
     }
 
     private var currentUserIds = emptyList<Int>()
@@ -94,23 +104,28 @@
         uiExecutor.execute(notifyChanges)
     }
 
-    var indicatorsAvailable = isPermissionsHubEnabled()
+    var allIndicatorsAvailable = isAllIndicatorsEnabled()
         private set
-    @VisibleForTesting
-    internal val devicePropertiesChangedListener =
+    var micCameraAvailable = isMicCameraEnabled()
+        private set
+
+    private val devicePropertiesChangedListener =
             object : DeviceConfig.OnPropertiesChangedListener {
         override fun onPropertiesChanged(properties: DeviceConfig.Properties) {
             if (DeviceConfig.NAMESPACE_PRIVACY.equals(properties.getNamespace()) &&
-                    properties.getKeyset().contains(
-                    SystemUiDeviceConfigFlags.PROPERTY_PERMISSIONS_HUB_ENABLED)) {
-                val flag = properties.getBoolean(
-                        SystemUiDeviceConfigFlags.PROPERTY_PERMISSIONS_HUB_ENABLED, false)
-                if (indicatorsAvailable != flag) {
-                    // This is happening already in the UI executor, so we can iterate in the
-                    indicatorsAvailable = flag
-                    callbacks.forEach { it.get()?.onFlagChanged(flag) }
+                    (properties.keyset.contains(ALL_INDICATORS) ||
+                    properties.keyset.contains(MIC_CAMERA))) {
+
+                // Running on the ui executor so can iterate on callbacks
+                if (properties.keyset.contains(ALL_INDICATORS)) {
+                    allIndicatorsAvailable = properties.getBoolean(ALL_INDICATORS, false)
+                    callbacks.forEach { it.get()?.onFlagAllChanged(allIndicatorsAvailable) }
                 }
 
+                if (properties.keyset.contains(MIC_CAMERA)) {
+                    micCameraAvailable = properties.getBoolean(MIC_CAMERA, false)
+                    callbacks.forEach { it.get()?.onFlagMicCameraChanged(micCameraAvailable) }
+                }
                 internalUiExecutor.updateListeningState()
             }
         }
@@ -123,6 +138,10 @@
             packageName: String,
             active: Boolean
         ) {
+            // Check if we care about this code right now
+            if (!allIndicatorsAvailable && code in OPS_LOCATION) {
+                return
+            }
             val userId = UserHandle.getUserId(uid)
             if (userId in currentUserIds) {
                 update(false)
@@ -166,13 +185,16 @@
     }
 
     /**
-     * Updates listening status based on whether there are callbacks and the indicators are enabled
+     * Updates listening status based on whether there are callbacks and the indicators are enabled.
+     *
+     * Always listen to all OPS so we don't have to figure out what we should be listening to. We
+     * still have to filter anyway. Updates are filtered in the callback.
      *
      * This is only called from private (add/remove)Callback and from the config listener, all in
      * main thread.
      */
     private fun setListeningState() {
-        val listen = !callbacks.isEmpty() and indicatorsAvailable
+        val listen = !callbacks.isEmpty() and (allIndicatorsAvailable || micCameraAvailable)
         if (listening == listen) return
         listening = listen
         if (listening) {
@@ -227,20 +249,27 @@
 
     private fun toPrivacyItem(appOpItem: AppOpItem): PrivacyItem? {
         val type: PrivacyType = when (appOpItem.code) {
+            AppOpsManager.OP_PHONE_CALL_CAMERA,
             AppOpsManager.OP_CAMERA -> PrivacyType.TYPE_CAMERA
-            AppOpsManager.OP_COARSE_LOCATION -> PrivacyType.TYPE_LOCATION
+            AppOpsManager.OP_COARSE_LOCATION,
             AppOpsManager.OP_FINE_LOCATION -> PrivacyType.TYPE_LOCATION
+            AppOpsManager.OP_PHONE_CALL_MICROPHONE,
             AppOpsManager.OP_RECORD_AUDIO -> PrivacyType.TYPE_MICROPHONE
             else -> return null
         }
+        if (type == PrivacyType.TYPE_LOCATION && !allIndicatorsAvailable) return null
         val app = PrivacyApplication(appOpItem.packageName, appOpItem.uid)
         return PrivacyItem(type, app)
     }
 
     interface Callback {
         fun onPrivacyItemsChanged(privacyItems: List<PrivacyItem>)
+
         @JvmDefault
-        fun onFlagChanged(flag: Boolean) {}
+        fun onFlagAllChanged(flag: Boolean) {}
+
+        @JvmDefault
+        fun onFlagMicCameraChanged(flag: Boolean) {}
     }
 
     internal inner class Receiver : BroadcastReceiver() {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/PageIndicator.java b/packages/SystemUI/src/com/android/systemui/qs/PageIndicator.java
index f8655cc..52a2cec 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/PageIndicator.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/PageIndicator.java
@@ -3,7 +3,9 @@
 import android.content.Context;
 import android.content.res.ColorStateList;
 import android.content.res.TypedArray;
+import android.graphics.drawable.Animatable2;
 import android.graphics.drawable.AnimatedVectorDrawable;
+import android.graphics.drawable.Drawable;
 import android.util.AttributeSet;
 import android.util.Log;
 import android.view.View;
@@ -44,6 +46,24 @@
     private int mPosition = -1;
     private boolean mAnimating;
 
+    private final Animatable2.AnimationCallback mAnimationCallback =
+            new Animatable2.AnimationCallback() {
+
+                @Override
+                public void onAnimationEnd(Drawable drawable) {
+                    super.onAnimationEnd(drawable);
+                    if (DEBUG) Log.d(TAG, "onAnimationEnd - queued: " + mQueuedPositions.size());
+                    if (drawable instanceof AnimatedVectorDrawable) {
+                        ((AnimatedVectorDrawable) drawable).unregisterAnimationCallback(
+                                mAnimationCallback);
+                    }
+                    mAnimating = false;
+                    if (mQueuedPositions.size() != 0) {
+                        setPosition(mQueuedPositions.remove(0));
+                    }
+                }
+            };
+
     public PageIndicator(Context context, AttributeSet attrs) {
         super(context, attrs);
 
@@ -197,10 +217,8 @@
         final AnimatedVectorDrawable avd = (AnimatedVectorDrawable) getContext().getDrawable(res);
         imageView.setImageDrawable(avd);
         avd.forceAnimationOnUI();
+        avd.registerAnimationCallback(mAnimationCallback);
         avd.start();
-        // TODO: Figure out how to user an AVD animation callback instead, which doesn't
-        // seem to be working right now...
-        postDelayed(mAnimationDone, ANIMATION_DURATION);
     }
 
     private int getTransition(boolean fromB, boolean isMajorAState, boolean isMajor) {
@@ -264,15 +282,4 @@
             getChildAt(i).layout(left, 0, mPageIndicatorWidth + left, mPageIndicatorHeight);
         }
     }
-
-    private final Runnable mAnimationDone = new Runnable() {
-        @Override
-        public void run() {
-            if (DEBUG) Log.d(TAG, "onAnimationEnd - queued: " + mQueuedPositions.size());
-            mAnimating = false;
-            if (mQueuedPositions.size() != 0) {
-                setPosition(mQueuedPositions.remove(0));
-            }
-        }
-    };
 }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/PagedTileLayout.java b/packages/SystemUI/src/com/android/systemui/qs/PagedTileLayout.java
index 560998b..22c735d 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/PagedTileLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/PagedTileLayout.java
@@ -185,7 +185,9 @@
             fakeDragBy(getScrollX() - mScroller.getCurrX());
         } else if (isFakeDragging()) {
             endFakeDrag();
-            mBounceAnimatorSet.start();
+            if (mBounceAnimatorSet != null) {
+                mBounceAnimatorSet.start();
+            }
             setOffscreenPageLimit(1);
         }
         super.computeScroll();
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java b/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java
index 3b16a4e..9a63a56 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java
@@ -37,6 +37,7 @@
 import com.android.systemui.Dumpable;
 import com.android.systemui.R;
 import com.android.systemui.broadcast.BroadcastDispatcher;
+import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dagger.qualifiers.Background;
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.dump.DumpManager;
@@ -69,10 +70,9 @@
 
 import javax.inject.Inject;
 import javax.inject.Provider;
-import javax.inject.Singleton;
 
 /** Platform implementation of the quick settings tile host **/
-@Singleton
+@SysUISingleton
 public class QSTileHost implements QSHost, Tunable, PluginListener<QSFactory>, Dumpable {
     private static final String TAG = "QSTileHost";
     private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java b/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java
index 2dc82dd..2e258d5 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java
@@ -151,7 +151,8 @@
     private Space mSpace;
     private BatteryMeterView mBatteryRemainingIcon;
     private RingerModeTracker mRingerModeTracker;
-    private boolean mPermissionsHubEnabled;
+    private boolean mAllIndicatorsEnabled;
+    private boolean mMicCameraIndicatorsEnabled;
 
     private PrivacyItemController mPrivacyItemController;
     private final UiEventLogger mUiEventLogger;
@@ -178,13 +179,26 @@
         }
 
         @Override
-        public void onFlagChanged(boolean flag) {
-            if (mPermissionsHubEnabled != flag) {
-                StatusIconContainer iconContainer = requireViewById(R.id.statusIcons);
-                iconContainer.setIgnoredSlots(getIgnoredIconSlots());
-                setChipVisibility(!mPrivacyChip.getPrivacyList().isEmpty());
+        public void onFlagAllChanged(boolean flag) {
+            if (mAllIndicatorsEnabled != flag) {
+                mAllIndicatorsEnabled = flag;
+                update();
             }
         }
+
+        @Override
+        public void onFlagMicCameraChanged(boolean flag) {
+            if (mMicCameraIndicatorsEnabled != flag) {
+                mMicCameraIndicatorsEnabled = flag;
+                update();
+            }
+        }
+
+        private void update() {
+            StatusIconContainer iconContainer = requireViewById(R.id.statusIcons);
+            iconContainer.setIgnoredSlots(getIgnoredIconSlots());
+            setChipVisibility(!mPrivacyChip.getPrivacyList().isEmpty());
+        }
     };
 
     @Inject
@@ -267,7 +281,8 @@
         mRingerModeTextView.setSelected(true);
         mNextAlarmTextView.setSelected(true);
 
-        mPermissionsHubEnabled = mPrivacyItemController.getIndicatorsAvailable();
+        mAllIndicatorsEnabled = mPrivacyItemController.getAllIndicatorsAvailable();
+        mMicCameraIndicatorsEnabled = mPrivacyItemController.getMicCameraAvailable();
     }
 
     public QuickQSPanel getHeaderQsPanel() {
@@ -276,13 +291,15 @@
 
     private List<String> getIgnoredIconSlots() {
         ArrayList<String> ignored = new ArrayList<>();
-        ignored.add(mContext.getResources().getString(
-                com.android.internal.R.string.status_bar_camera));
-        ignored.add(mContext.getResources().getString(
-                com.android.internal.R.string.status_bar_microphone));
-        if (mPermissionsHubEnabled) {
+        if (getChipEnabled()) {
             ignored.add(mContext.getResources().getString(
-                    com.android.internal.R.string.status_bar_location));
+                    com.android.internal.R.string.status_bar_camera));
+            ignored.add(mContext.getResources().getString(
+                    com.android.internal.R.string.status_bar_microphone));
+            if (mAllIndicatorsEnabled) {
+                ignored.add(mContext.getResources().getString(
+                        com.android.internal.R.string.status_bar_location));
+            }
         }
 
         return ignored;
@@ -300,7 +317,7 @@
     }
 
     private void setChipVisibility(boolean chipVisible) {
-        if (chipVisible && mPermissionsHubEnabled) {
+        if (chipVisible && getChipEnabled()) {
             mPrivacyChip.setVisibility(View.VISIBLE);
             // Makes sure that the chip is logged as viewed at most once each time QS is opened
             // mListening makes sure that the callback didn't return after the user closed QS
@@ -607,7 +624,8 @@
             mAlarmController.addCallback(this);
             mLifecycle.setCurrentState(Lifecycle.State.RESUMED);
             // Get the most up to date info
-            mPermissionsHubEnabled = mPrivacyItemController.getIndicatorsAvailable();
+            mAllIndicatorsEnabled = mPrivacyItemController.getAllIndicatorsAvailable();
+            mMicCameraIndicatorsEnabled = mPrivacyItemController.getMicCameraAvailable();
             mPrivacyItemController.addCallback(mPICCallback);
         } else {
             mZenController.removeCallback(this);
@@ -747,4 +765,8 @@
             updateHeaderTextContainerAlphaAnimator();
         }
     }
+
+    private boolean getChipEnabled() {
+        return mMicCameraIndicatorsEnabled || mAllIndicatorsEnabled;
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSFactoryImpl.java b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSFactoryImpl.java
index 69a6fe1..8e33496 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSFactoryImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSFactoryImpl.java
@@ -20,6 +20,7 @@
 import android.view.ContextThemeWrapper;
 
 import com.android.systemui.R;
+import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.plugins.qs.QSFactory;
 import com.android.systemui.plugins.qs.QSIconView;
 import com.android.systemui.plugins.qs.QSTile;
@@ -49,11 +50,10 @@
 
 import javax.inject.Inject;
 import javax.inject.Provider;
-import javax.inject.Singleton;
 
 import dagger.Lazy;
 
-@Singleton
+@SysUISingleton
 public class QSFactoryImpl implements QSFactory {
 
     private static final String TAG = "QSFactory";
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java
index 8c485a6..255513a 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java
@@ -14,6 +14,7 @@
 
 package com.android.systemui.qs.tileimpl;
 
+import static androidx.lifecycle.Lifecycle.State.CREATED;
 import static androidx.lifecycle.Lifecycle.State.DESTROYED;
 import static androidx.lifecycle.Lifecycle.State.RESUMED;
 import static androidx.lifecycle.Lifecycle.State.STARTED;
@@ -173,6 +174,7 @@
 
         mState = newTileState();
         mTmpState = newTileState();
+        mUiHandler.post(() -> mLifecycle.setCurrentState(CREATED));
     }
 
     protected final void resetStates() {
@@ -451,15 +453,24 @@
         if (listening) {
             if (mListeners.add(listener) && mListeners.size() == 1) {
                 if (DEBUG) Log.d(TAG, "handleSetListening true");
-                mLifecycle.setCurrentState(RESUMED);
                 handleSetListening(listening);
-                refreshState(); // Ensure we get at least one refresh after listening.
+                mUiHandler.post(() -> {
+                    // This tile has been destroyed, the state should not change anymore and we
+                    // should not refresh it anymore.
+                    if (mLifecycle.getCurrentState().equals(DESTROYED)) return;
+                    mLifecycle.setCurrentState(RESUMED);
+                    refreshState(); // Ensure we get at least one refresh after listening.
+                });
             }
         } else {
             if (mListeners.remove(listener) && mListeners.size() == 0) {
                 if (DEBUG) Log.d(TAG, "handleSetListening false");
-                mLifecycle.setCurrentState(STARTED);
                 handleSetListening(listening);
+                mUiHandler.post(() -> {
+                    // This tile has been destroyed, the state should not change anymore.
+                    if (mLifecycle.getCurrentState().equals(DESTROYED)) return;
+                    mLifecycle.setCurrentState(STARTED);
+                });
             }
         }
         updateIsFullQs();
@@ -486,11 +497,14 @@
         mQSLogger.logTileDestroyed(mTileSpec, "Handle destroy");
         if (mListeners.size() != 0) {
             handleSetListening(false);
+            mListeners.clear();
         }
         mCallbacks.clear();
         mHandler.removeCallbacksAndMessages(null);
         // This will force it to be removed from all controllers that may have it registered.
-        mLifecycle.setCurrentState(DESTROYED);
+        mUiHandler.post(() -> {
+            mLifecycle.setCurrentState(DESTROYED);
+        });
     }
 
     protected void checkIfRestrictionEnforcedByAdminOnly(State state, String userRestriction) {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/BatterySaverTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/BatterySaverTile.java
index eb794a8..c64fc50 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/BatterySaverTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/BatterySaverTile.java
@@ -130,6 +130,7 @@
                 : mPowerSave ? Tile.STATE_ACTIVE : Tile.STATE_INACTIVE;
         state.icon = mIcon;
         state.label = mContext.getString(R.string.battery_detail_switch_title);
+        state.secondaryLabel = "";
         state.contentDescription = state.label;
         state.value = mPowerSave;
         state.expandedAccessibilityClassName = Switch.class.getName();
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/NightDisplayTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/NightDisplayTile.java
index 3264429..d8548de 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/NightDisplayTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/NightDisplayTile.java
@@ -67,7 +67,7 @@
     private static final String PATTERN_HOUR_MINUTE = "h:mm a";
     private static final String PATTERN_HOUR_NINUTE_24 = "HH:mm";
 
-    private final ColorDisplayManager mManager;
+    private ColorDisplayManager mManager;
     private final LocationController mLocationController;
     private final NightDisplayListenerModule.Builder mNightDisplayListenerBuilder;
     private NightDisplayListener mListener;
@@ -126,6 +126,8 @@
             mListener.setCallback(null);
         }
 
+        mManager = getHost().getUserContext().getSystemService(ColorDisplayManager.class);
+
         // Make a new controller for the new user.
         mListener = mNightDisplayListenerBuilder.setUser(newUserId).build();
         if (mIsListening) {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/UiModeNightTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/UiModeNightTile.java
index 07b841f..78975a47 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/UiModeNightTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/UiModeNightTile.java
@@ -58,10 +58,9 @@
     public static DateTimeFormatter formatter = DateTimeFormatter.ofPattern("hh:mm a");
     private final Icon mIcon = ResourceIcon.get(
             com.android.internal.R.drawable.ic_qs_ui_mode_night);
-    private final UiModeManager mUiModeManager;
+    private UiModeManager mUiModeManager;
     private final BatteryController mBatteryController;
     private final LocationController mLocationController;
-
     @Inject
     public UiModeNightTile(
             QSHost host,
@@ -78,7 +77,7 @@
         super(host, backgroundLooper, mainHandler, metricsLogger, statusBarStateController,
                 activityStarter, qsLogger);
         mBatteryController = batteryController;
-        mUiModeManager = mContext.getSystemService(UiModeManager.class);
+        mUiModeManager = host.getUserContext().getSystemService(UiModeManager.class);
         mLocationController = locationController;
         configurationController.observe(getLifecycle(), this);
         batteryController.observe(getLifecycle(), this);
diff --git a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyRecentsImpl.java b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyRecentsImpl.java
index f89185e..3decb96 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyRecentsImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyRecentsImpl.java
@@ -35,28 +35,28 @@
 
 import com.android.systemui.Dependency;
 import com.android.systemui.R;
+import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.shared.recents.IOverviewProxy;
 import com.android.systemui.shared.system.ActivityManagerWrapper;
-import com.android.systemui.stackdivider.Divider;
+import com.android.systemui.stackdivider.SplitScreenController;
 import com.android.systemui.statusbar.phone.StatusBar;
 
 import java.util.Optional;
 
 import javax.inject.Inject;
-import javax.inject.Singleton;
 
 import dagger.Lazy;
 
 /**
  * An implementation of the Recents interface which proxies to the OverviewProxyService.
  */
-@Singleton
+@SysUISingleton
 public class OverviewProxyRecentsImpl implements RecentsImplementation {
 
     private final static String TAG = "OverviewProxyRecentsImpl";
     @Nullable
     private final Lazy<StatusBar> mStatusBarLazy;
-    private final Optional<Divider> mDividerOptional;
+    private final Optional<SplitScreenController> mSplitScreenControllerOptional;
 
     private Context mContext;
     private Handler mHandler;
@@ -66,9 +66,9 @@
     @SuppressWarnings("OptionalUsedAsFieldOrParameterType")
     @Inject
     public OverviewProxyRecentsImpl(Optional<Lazy<StatusBar>> statusBarLazy,
-            Optional<Divider> dividerOptional) {
+            Optional<SplitScreenController> splitScreenControllerOptional) {
         mStatusBarLazy = statusBarLazy.orElse(null);
-        mDividerOptional = dividerOptional;
+        mSplitScreenControllerOptional = splitScreenControllerOptional;
     }
 
     @Override
@@ -163,12 +163,12 @@
             if (runningTask.supportsSplitScreenMultiWindow) {
                 if (ActivityManagerWrapper.getInstance().setTaskWindowingModeSplitScreenPrimary(
                         runningTask.id, stackCreateMode, initialBounds)) {
-                    mDividerOptional.ifPresent(Divider::onDockedTopTask);
-
-                    // The overview service is handling split screen, so just skip the wait for the
-                    // first draw and notify the divider to start animating now
-                    mDividerOptional.ifPresent(Divider::onRecentsDrawn);
-
+                    mSplitScreenControllerOptional.ifPresent(splitScreen -> {
+                        splitScreen.onDockedTopTask();
+                        // The overview service is handling split screen, so just skip the wait
+                        // for the first draw and notify the divider to start animating now
+                        splitScreen.onRecentsDrawn();
+                    });
                     return true;
                 }
             } else {
diff --git a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
index d03082e..304dc93 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
@@ -61,12 +61,19 @@
 import android.view.Surface;
 import android.view.accessibility.AccessibilityManager;
 
+import androidx.annotation.NonNull;
+
 import com.android.internal.accessibility.dialog.AccessibilityButtonChooserActivity;
 import com.android.internal.policy.ScreenDecorationsUtils;
 import com.android.internal.util.ScreenshotHelper;
 import com.android.systemui.Dumpable;
 import com.android.systemui.broadcast.BroadcastDispatcher;
+import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.model.SysUiState;
+import com.android.systemui.navigationbar.NavigationBar;
+import com.android.systemui.navigationbar.NavigationBarController;
+import com.android.systemui.navigationbar.NavigationBarView;
+import com.android.systemui.navigationbar.NavigationModeController;
 import com.android.systemui.onehanded.OneHandedUI;
 import com.android.systemui.pip.PipAnimationController;
 import com.android.systemui.pip.PipUI;
@@ -79,13 +86,9 @@
 import com.android.systemui.shared.system.ActivityManagerWrapper;
 import com.android.systemui.shared.system.InputMonitorCompat;
 import com.android.systemui.shared.system.QuickStepContract;
-import com.android.systemui.stackdivider.Divider;
+import com.android.systemui.stackdivider.SplitScreenController;
 import com.android.systemui.statusbar.CommandQueue;
-import com.android.systemui.statusbar.NavigationBarController;
-import com.android.systemui.statusbar.phone.NavigationBarFragment;
-import com.android.systemui.statusbar.phone.NavigationBarView;
-import com.android.systemui.statusbar.phone.NavigationModeController;
-import com.android.systemui.statusbar.phone.NotificationShadeWindowController;
+import com.android.systemui.statusbar.NotificationShadeWindowController;
 import com.android.systemui.statusbar.phone.StatusBar;
 import com.android.systemui.statusbar.phone.StatusBarWindowCallback;
 import com.android.systemui.statusbar.policy.CallbackController;
@@ -97,14 +100,13 @@
 import java.util.Optional;
 
 import javax.inject.Inject;
-import javax.inject.Singleton;
 
 import dagger.Lazy;
 
 /**
  * Class to send information from overview to launcher with a binder.
  */
-@Singleton
+@SysUISingleton
 public class OverviewProxyService extends CurrentUserTracker implements
         CallbackController<OverviewProxyListener>, NavigationModeController.ModeChangedListener,
         Dumpable {
@@ -121,10 +123,10 @@
     private final Context mContext;
     private final PipUI mPipUI;
     private final Optional<Lazy<StatusBar>> mStatusBarOptionalLazy;
-    private final Optional<Divider> mDividerOptional;
+    private final Optional<SplitScreenController> mSplitScreenControllerOptional;
     private SysUiState mSysUiState;
     private final Handler mHandler;
-    private final NavigationBarController mNavBarController;
+    private final Lazy<NavigationBarController> mNavBarControllerLazy;
     private final NotificationShadeWindowController mStatusBarWinController;
     private final Runnable mConnectionRunnable = this::internalConnectToCurrentUser;
     private final ComponentName mRecentsComponentName;
@@ -230,7 +232,9 @@
             }
             long token = Binder.clearCallingIdentity();
             try {
-                mDividerOptional.ifPresent(Divider::onDockedFirstAnimationFrame);
+                mSplitScreenControllerOptional.ifPresent(splitScreen -> {
+                    splitScreen.onDockedFirstAnimationFrame();
+                });
             } finally {
                 Binder.restoreCallingIdentity(token);
             }
@@ -260,8 +264,8 @@
             }
             long token = Binder.clearCallingIdentity();
             try {
-                return mDividerOptional.map(
-                        divider -> divider.getView().getNonMinimizedSplitScreenSecondaryBounds())
+                return mSplitScreenControllerOptional.map(splitScreen ->
+                        splitScreen.getDividerView().getNonMinimizedSplitScreenSecondaryBounds())
                         .orElse(null);
             } finally {
                 Binder.restoreCallingIdentity(token);
@@ -397,10 +401,8 @@
 
         @Override
         public void setSplitScreenMinimized(boolean minimized) {
-            Divider divider = mDividerOptional.get();
-            if (divider != null) {
-                divider.setMinimized(minimized);
-            }
+            mSplitScreenControllerOptional.ifPresent(
+                    splitScreen -> splitScreen.setMinimized(minimized));
         }
 
         @Override
@@ -598,9 +600,9 @@
     @SuppressWarnings("OptionalUsedAsFieldOrParameterType")
     @Inject
     public OverviewProxyService(Context context, CommandQueue commandQueue,
-            NavigationBarController navBarController, NavigationModeController navModeController,
+            Lazy<NavigationBarController> navBarControllerLazy, NavigationModeController navModeController,
             NotificationShadeWindowController statusBarWinController, SysUiState sysUiState,
-            PipUI pipUI, Optional<Divider> dividerOptional,
+            PipUI pipUI, Optional<SplitScreenController> splitScreenControllerOptional,
             Optional<Lazy<StatusBar>> statusBarOptionalLazy, OneHandedUI oneHandedUI,
             BroadcastDispatcher broadcastDispatcher) {
         super(broadcastDispatcher);
@@ -608,10 +610,10 @@
         mPipUI = pipUI;
         mStatusBarOptionalLazy = statusBarOptionalLazy;
         mHandler = new Handler();
-        mNavBarController = navBarController;
+        mNavBarControllerLazy = navBarControllerLazy;
         mStatusBarWinController = statusBarWinController;
         mConnectionBackoffAttempts = 0;
-        mDividerOptional = dividerOptional;
+        mSplitScreenControllerOptional = splitScreenControllerOptional;
         mRecentsComponentName = ComponentName.unflattenFromString(context.getString(
                 com.android.internal.R.string.config_recentsComponentName));
         mQuickStepIntent = new Intent(ACTION_QUICKSTEP)
@@ -677,10 +679,10 @@
     }
 
     private void updateSystemUiStateFlags() {
-        final NavigationBarFragment navBarFragment =
-                mNavBarController.getDefaultNavigationBarFragment();
+        final NavigationBar navBarFragment =
+                mNavBarControllerLazy.get().getDefaultNavigationBar();
         final NavigationBarView navBarView =
-                mNavBarController.getNavigationBarView(mContext.getDisplayId());
+                mNavBarControllerLazy.get().getNavigationBarView(mContext.getDisplayId());
         if (SysUiState.DEBUG) {
             Log.d(TAG_OPS, "Updating sysui state flags: navBarFragment=" + navBarFragment
                     + " navBarView=" + navBarView);
@@ -753,10 +755,8 @@
         startConnectionToCurrentUser();
 
         // Clean up the minimized state if launcher dies
-        Divider divider = mDividerOptional.get();
-        if (divider != null) {
-            divider.setMinimized(false);
-        }
+        mSplitScreenControllerOptional.ifPresent(
+                splitScreen -> splitScreen.setMinimized(false));
     }
 
     public void startConnectionToCurrentUser() {
@@ -808,7 +808,7 @@
     }
 
     @Override
-    public void addCallback(OverviewProxyListener listener) {
+    public void addCallback(@NonNull OverviewProxyListener listener) {
         if (!mConnectionCallbacks.contains(listener)) {
             mConnectionCallbacks.add(listener);
         }
@@ -817,7 +817,7 @@
     }
 
     @Override
-    public void removeCallback(OverviewProxyListener listener) {
+    public void removeCallback(@NonNull OverviewProxyListener listener) {
         mConnectionCallbacks.remove(listener);
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsImplementation.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsImplementation.java
index 1d29ac6..a641730 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/RecentsImplementation.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsImplementation.java
@@ -35,7 +35,6 @@
     default void showRecentApps(boolean triggeredFromAltTab) {}
     default void hideRecentApps(boolean triggeredFromAltTab, boolean triggeredFromHomeKey) {}
     default void toggleRecentApps() {}
-    default void growRecents() {}
     default boolean splitPrimaryTask(int stackCreateMode, Rect initialBounds,
             int metricsDockAction) {
         return false;
diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsOnboarding.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsOnboarding.java
index 3b3d9dd..9c5a3de 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/RecentsOnboarding.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsOnboarding.java
@@ -466,23 +466,22 @@
     }
 
     public void dump(PrintWriter pw) {
-        pw.println("RecentsOnboarding {");
-        pw.println("      mTaskListenerRegistered: " + mTaskListenerRegistered);
-        pw.println("      mOverviewProxyListenerRegistered: " + mOverviewProxyListenerRegistered);
-        pw.println("      mLayoutAttachedToWindow: " + mLayoutAttachedToWindow);
-        pw.println("      mHasDismissedSwipeUpTip: " + mHasDismissedSwipeUpTip);
-        pw.println("      mHasDismissedQuickScrubTip: " + mHasDismissedQuickScrubTip);
-        pw.println("      mNumAppsLaunchedSinceSwipeUpTipDismiss: "
+        pw.println("RecentsOnboarding");
+        pw.println("  mTaskListenerRegistered: " + mTaskListenerRegistered);
+        pw.println("  mOverviewProxyListenerRegistered: " + mOverviewProxyListenerRegistered);
+        pw.println("  mLayoutAttachedToWindow: " + mLayoutAttachedToWindow);
+        pw.println("  mHasDismissedSwipeUpTip: " + mHasDismissedSwipeUpTip);
+        pw.println("  mHasDismissedQuickScrubTip: " + mHasDismissedQuickScrubTip);
+        pw.println("  mNumAppsLaunchedSinceSwipeUpTipDismiss: "
                 + mNumAppsLaunchedSinceSwipeUpTipDismiss);
-        pw.println("      hasSeenSwipeUpOnboarding: " + hasSeenSwipeUpOnboarding());
-        pw.println("      hasSeenQuickScrubOnboarding: " + hasSeenQuickScrubOnboarding());
-        pw.println("      getDismissedSwipeUpOnboardingCount: "
+        pw.println("  hasSeenSwipeUpOnboarding: " + hasSeenSwipeUpOnboarding());
+        pw.println("  hasSeenQuickScrubOnboarding: " + hasSeenQuickScrubOnboarding());
+        pw.println("  getDismissedSwipeUpOnboardingCount: "
                 + getDismissedSwipeUpOnboardingCount());
-        pw.println("      hasDismissedQuickScrubOnboardingOnce: "
+        pw.println("  hasDismissedQuickScrubOnboardingOnce: "
                 + hasDismissedQuickScrubOnboardingOnce());
-        pw.println("      getOpenedOverviewCount: " + getOpenedOverviewCount());
-        pw.println("      getOpenedOverviewFromHomeCount: " + getOpenedOverviewFromHomeCount());
-        pw.println("    }");
+        pw.println("  getOpenedOverviewCount: " + getOpenedOverviewCount());
+        pw.println("  getOpenedOverviewFromHomeCount: " + getOpenedOverviewFromHomeCount());
     }
 
     private WindowManager.LayoutParams getWindowLayoutParams(int gravity, int x) {
diff --git a/packages/SystemUI/src/com/android/systemui/recents/ScreenPinningRequest.java b/packages/SystemUI/src/com/android/systemui/recents/ScreenPinningRequest.java
index 3874903..6afc756 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/ScreenPinningRequest.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/ScreenPinningRequest.java
@@ -51,8 +51,8 @@
 import com.android.systemui.broadcast.BroadcastDispatcher;
 import com.android.systemui.shared.system.QuickStepContract;
 import com.android.systemui.shared.system.WindowManagerWrapper;
-import com.android.systemui.statusbar.phone.NavigationBarView;
-import com.android.systemui.statusbar.phone.NavigationModeController;
+import com.android.systemui.navigationbar.NavigationBarView;
+import com.android.systemui.navigationbar.NavigationModeController;
 import com.android.systemui.statusbar.phone.StatusBar;
 import com.android.systemui.util.leak.RotationUtils;
 
diff --git a/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingController.java b/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingController.java
index 82ac1f6..10a44dd 100644
--- a/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingController.java
+++ b/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingController.java
@@ -26,19 +26,21 @@
 import android.os.UserHandle;
 import android.util.Log;
 
+import androidx.annotation.NonNull;
+
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.systemui.broadcast.BroadcastDispatcher;
+import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.statusbar.policy.CallbackController;
 
 import java.util.ArrayList;
 
 import javax.inject.Inject;
-import javax.inject.Singleton;
 
 /**
  * Helper class to initiate a screen recording
  */
-@Singleton
+@SysUISingleton
 public class RecordingController
         implements CallbackController<RecordingController.RecordingStateChangeCallback> {
     private static final String TAG = "RecordingController";
@@ -191,12 +193,12 @@
     }
 
     @Override
-    public void addCallback(RecordingStateChangeCallback listener) {
+    public void addCallback(@NonNull RecordingStateChangeCallback listener) {
         mListeners.add(listener);
     }
 
     @Override
-    public void removeCallback(RecordingStateChangeCallback listener) {
+    public void removeCallback(@NonNull RecordingStateChangeCallback listener) {
         mListeners.remove(listener);
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingService.java b/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingService.java
index 469c4a7..8ec3db5 100644
--- a/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingService.java
+++ b/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingService.java
@@ -21,7 +21,6 @@
 import android.app.NotificationManager;
 import android.app.PendingIntent;
 import android.app.Service;
-import android.content.ContentResolver;
 import android.content.Context;
 import android.content.Intent;
 import android.content.res.Resources;
@@ -70,7 +69,6 @@
     private static final String ACTION_STOP_NOTIF =
             "com.android.systemui.screenrecord.STOP_FROM_NOTIF";
     private static final String ACTION_SHARE = "com.android.systemui.screenrecord.SHARE";
-    private static final String ACTION_DELETE = "com.android.systemui.screenrecord.DELETE";
 
     private final RecordingController mController;
     private final KeyguardDismissUtil mKeyguardDismissUtil;
@@ -184,25 +182,6 @@
                 // Close quick shade
                 sendBroadcast(new Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS));
                 break;
-            case ACTION_DELETE:
-                mKeyguardDismissUtil.executeWhenUnlocked(() -> {
-                    // Close quick shade
-                    sendBroadcast(new Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS));
-                    ContentResolver resolver = getContentResolver();
-                    Uri uri = Uri.parse(intent.getStringExtra(EXTRA_PATH));
-                    resolver.delete(uri, null, null);
-
-                    Toast.makeText(
-                            this,
-                            R.string.screenrecord_delete_description,
-                            Toast.LENGTH_LONG).show();
-                    Log.d(TAG, "Deleted recording " + uri);
-
-                    // Remove notification
-                    mNotificationManager.cancelAsUser(null, NOTIFICATION_VIEW_ID, currentUser);
-                    return false;
-                }, false);
-                break;
         }
         return Service.START_STICKY;
     }
@@ -312,16 +291,6 @@
                         PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE))
                 .build();
 
-        Notification.Action deleteAction = new Notification.Action.Builder(
-                Icon.createWithResource(this, R.drawable.ic_screenrecord),
-                getResources().getString(R.string.screenrecord_delete_label),
-                PendingIntent.getService(
-                        this,
-                        REQUEST_CODE,
-                        getDeleteIntent(this, uri.toString()),
-                        PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE))
-                .build();
-
         Bundle extras = new Bundle();
         extras.putString(Notification.EXTRA_SUBSTITUTE_APP_NAME,
                 getResources().getString(R.string.screenrecord_name));
@@ -335,7 +304,6 @@
                         viewIntent,
                         PendingIntent.FLAG_IMMUTABLE))
                 .addAction(shareAction)
-                .addAction(deleteAction)
                 .setAutoCancel(true)
                 .addExtras(extras);
 
@@ -414,11 +382,6 @@
                 .putExtra(EXTRA_PATH, path);
     }
 
-    private static Intent getDeleteIntent(Context context, String path) {
-        return new Intent(context, RecordingService.class).setAction(ACTION_DELETE)
-                .putExtra(EXTRA_PATH, path);
-    }
-
     @Override
     public void onInfo(MediaRecorder mr, int what, int extra) {
         Log.d(TAG, "Media recorder info: " + what);
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java b/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java
index c535230..e24fbc6 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java
@@ -50,6 +50,7 @@
 import android.media.MediaActionSound;
 import android.net.Uri;
 import android.os.Handler;
+import android.os.IBinder;
 import android.os.Looper;
 import android.os.Message;
 import android.os.RemoteException;
@@ -80,6 +81,7 @@
 
 import com.android.internal.logging.UiEventLogger;
 import com.android.systemui.R;
+import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.shared.system.QuickStepContract;
 
@@ -88,12 +90,11 @@
 import java.util.function.Consumer;
 
 import javax.inject.Inject;
-import javax.inject.Singleton;
 
 /**
  * Class for handling device screen shots
  */
-@Singleton
+@SysUISingleton
 public class GlobalScreenshot implements ViewTreeObserver.OnComputeInternalInsetsListener {
 
     /**
@@ -556,11 +557,18 @@
     private void takeScreenshotInternal(Consumer<Uri> finisher, Rect crop) {
         // copy the input Rect, since SurfaceControl.screenshot can mutate it
         Rect screenRect = new Rect(crop);
-        int rot = mDisplay.getRotation();
         int width = crop.width();
         int height = crop.height();
-        saveScreenshot(SurfaceControl.screenshot(crop, width, height, rot), finisher, screenRect,
-                Insets.NONE, true);
+        final IBinder displayToken = SurfaceControl.getInternalDisplayToken();
+        final SurfaceControl.DisplayCaptureArgs captureArgs =
+                new SurfaceControl.DisplayCaptureArgs.Builder(displayToken)
+                        .setSourceCrop(crop)
+                        .setSize(width, height)
+                        .build();
+        final SurfaceControl.ScreenshotHardwareBuffer screenshotBuffer =
+                SurfaceControl.captureDisplay(captureArgs);
+        final Bitmap screenshot = screenshotBuffer == null ? null : screenshotBuffer.asBitmap();
+        saveScreenshot(screenshot, finisher, screenRect, Insets.NONE, true);
     }
 
     private void saveScreenshot(Bitmap screenshot, Consumer<Uri> finisher, Rect screenRect,
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotActionChip.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotActionChip.java
index b5209bb..a488702 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotActionChip.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotActionChip.java
@@ -65,10 +65,10 @@
     }
 
     void setIcon(Icon icon, boolean tint) {
-        if (tint) {
-            icon.setTint(mIconColor);
-        }
         mIcon.setImageIcon(icon);
+        if (!tint) {
+            mIcon.setImageTintList(null);
+        }
     }
 
     void setText(CharSequence text) {
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotEvent.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotEvent.java
index 20fa991..6b42f2e 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotEvent.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotEvent.java
@@ -22,6 +22,7 @@
 import static android.view.WindowManager.ScreenshotSource.SCREENSHOT_KEY_OTHER;
 import static android.view.WindowManager.ScreenshotSource.SCREENSHOT_OTHER;
 import static android.view.WindowManager.ScreenshotSource.SCREENSHOT_OVERVIEW;
+import static android.view.WindowManager.ScreenshotSource.SCREENSHOT_VENDOR_GESTURE;
 
 import com.android.internal.logging.UiEvent;
 import com.android.internal.logging.UiEventLogger;
@@ -37,6 +38,8 @@
     SCREENSHOT_REQUESTED_OVERVIEW(304),
     @UiEvent(doc = "screenshot requested from accessibility actions")
     SCREENSHOT_REQUESTED_ACCESSIBILITY_ACTIONS(382),
+    @UiEvent(doc = "screenshot requested from vendor gesture")
+    SCREENSHOT_REQUESTED_VENDOR_GESTURE(638),
     @UiEvent(doc = "screenshot requested (other)")
     SCREENSHOT_REQUESTED_OTHER(305),
     @UiEvent(doc = "screenshot was saved")
@@ -81,6 +84,8 @@
                 return ScreenshotEvent.SCREENSHOT_REQUESTED_OVERVIEW;
             case SCREENSHOT_ACCESSIBILITY_ACTIONS:
                 return ScreenshotEvent.SCREENSHOT_REQUESTED_ACCESSIBILITY_ACTIONS;
+            case SCREENSHOT_VENDOR_GESTURE:
+                return ScreenshotEvent.SCREENSHOT_REQUESTED_VENDOR_GESTURE;
             case SCREENSHOT_OTHER:
             default:
                 return ScreenshotEvent.SCREENSHOT_REQUESTED_OTHER;
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotNotificationsController.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotNotificationsController.java
index 6f5ceab..6d1299b 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotNotificationsController.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotNotificationsController.java
@@ -205,7 +205,8 @@
         mPublicNotificationBuilder
                 .setContentTitle(mResources.getString(R.string.screenshot_saved_title))
                 .setContentText(mResources.getString(R.string.screenshot_saved_text))
-                .setContentIntent(PendingIntent.getActivity(mContext, 0, launchIntent, 0))
+                .setContentIntent(PendingIntent
+                        .getActivity(mContext, 0, launchIntent, PendingIntent.FLAG_IMMUTABLE))
                 .setWhen(now)
                 .setAutoCancel(true)
                 .setColor(mContext.getColor(
@@ -213,7 +214,8 @@
         mNotificationBuilder
                 .setContentTitle(mResources.getString(R.string.screenshot_saved_title))
                 .setContentText(mResources.getString(R.string.screenshot_saved_text))
-                .setContentIntent(PendingIntent.getActivity(mContext, 0, launchIntent, 0))
+                .setContentIntent(PendingIntent
+                        .getActivity(mContext, 0, launchIntent, PendingIntent.FLAG_IMMUTABLE))
                 .setWhen(now)
                 .setAutoCancel(true)
                 .setColor(mContext.getColor(
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotSmartActions.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotSmartActions.java
index 633cdd6..468602a 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotSmartActions.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotSmartActions.java
@@ -31,6 +31,7 @@
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.systemui.SystemUIFactory;
+import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.shared.system.ActivityManagerWrapper;
 
 import java.util.Collections;
@@ -40,12 +41,11 @@
 import java.util.concurrent.TimeoutException;
 
 import javax.inject.Inject;
-import javax.inject.Singleton;
 
 /**
  * Collects the static functions for retrieving and acting on smart actions.
  */
-@Singleton
+@SysUISingleton
 public class ScreenshotSmartActions {
     private static final String TAG = "ScreenshotSmartActions";
 
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/dagger/ScreenshotModule.java b/packages/SystemUI/src/com/android/systemui/screenshot/dagger/ScreenshotModule.java
new file mode 100644
index 0000000..91ef3c3
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/dagger/ScreenshotModule.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.screenshot.dagger;
+
+import android.app.Service;
+
+import com.android.systemui.screenshot.TakeScreenshotService;
+
+import dagger.Binds;
+import dagger.Module;
+import dagger.multibindings.ClassKey;
+import dagger.multibindings.IntoMap;
+
+/**
+ * Defines injectable resources for Screenshots
+ */
+@Module
+public abstract class ScreenshotModule {
+
+    /** */
+    @Binds
+    @IntoMap
+    @ClassKey(TakeScreenshotService.class)
+    public abstract Service bindTakeScreenshotService(TakeScreenshotService service);
+
+}
diff --git a/packages/SystemUI/src/com/android/systemui/settings/BrightnessController.java b/packages/SystemUI/src/com/android/systemui/settings/BrightnessController.java
index 71e7883..1bea72a 100644
--- a/packages/SystemUI/src/com/android/systemui/settings/BrightnessController.java
+++ b/packages/SystemUI/src/com/android/systemui/settings/BrightnessController.java
@@ -385,7 +385,7 @@
         if (stopTracking) {
             // TODO(brightnessfloat): change to use float value instead.
             MetricsLogger.action(mContext, metric,
-                    BrightnessSynchronizer.brightnessFloatToInt(mContext, valFloat));
+                    BrightnessSynchronizer.brightnessFloatToInt(valFloat));
 
         }
         setBrightness(valFloat);
diff --git a/tests/AutoVerify/app1/src/com/android/test/autoverify/MainActivity.java b/packages/SystemUI/src/com/android/systemui/settings/CurrentUserContentResolverProvider.kt
similarity index 76%
copy from tests/AutoVerify/app1/src/com/android/test/autoverify/MainActivity.java
copy to packages/SystemUI/src/com/android/systemui/settings/CurrentUserContentResolverProvider.kt
index 09ef472..9d05843 100644
--- a/tests/AutoVerify/app1/src/com/android/test/autoverify/MainActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/settings/CurrentUserContentResolverProvider.kt
@@ -13,3 +13,12 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
+
+package com.android.systemui.settings
+
+import android.content.ContentResolver
+
+interface CurrentUserContentResolverProvider {
+
+    val currentUserContentResolver: ContentResolver
+}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/settings/CurrentUserContextTracker.kt b/packages/SystemUI/src/com/android/systemui/settings/CurrentUserContextTracker.kt
index 825a7f3..d7c4caaa 100644
--- a/packages/SystemUI/src/com/android/systemui/settings/CurrentUserContextTracker.kt
+++ b/packages/SystemUI/src/com/android/systemui/settings/CurrentUserContextTracker.kt
@@ -16,6 +16,7 @@
 
 package com.android.systemui.settings
 
+import android.content.ContentResolver
 import android.content.Context
 import android.os.UserHandle
 import androidx.annotation.VisibleForTesting
@@ -31,7 +32,7 @@
 class CurrentUserContextTracker internal constructor(
     private val sysuiContext: Context,
     broadcastDispatcher: BroadcastDispatcher
-) {
+) : CurrentUserContentResolverProvider {
     private val userTracker: CurrentUserTracker
     private var initialized = false
 
@@ -44,6 +45,9 @@
             return _curUserContext!!
         }
 
+    override val currentUserContentResolver: ContentResolver
+        get() = currentUserContext.contentResolver
+
     init {
         userTracker = object : CurrentUserTracker(broadcastDispatcher) {
             override fun onUserSwitched(newUserId: Int) {
@@ -54,8 +58,8 @@
 
     fun initialize() {
         initialized = true
-        _curUserContext = makeUserContext(userTracker.currentUserId)
         userTracker.startTracking()
+        _curUserContext = makeUserContext(userTracker.currentUserId)
     }
 
     @VisibleForTesting
diff --git a/packages/SystemUI/src/com/android/systemui/settings/dagger/SettingsModule.java b/packages/SystemUI/src/com/android/systemui/settings/dagger/SettingsModule.java
index 2c5c3ce..b1ed772 100644
--- a/packages/SystemUI/src/com/android/systemui/settings/dagger/SettingsModule.java
+++ b/packages/SystemUI/src/com/android/systemui/settings/dagger/SettingsModule.java
@@ -19,10 +19,11 @@
 import android.content.Context;
 
 import com.android.systemui.broadcast.BroadcastDispatcher;
+import com.android.systemui.dagger.SysUISingleton;
+import com.android.systemui.settings.CurrentUserContentResolverProvider;
 import com.android.systemui.settings.CurrentUserContextTracker;
 
-import javax.inject.Singleton;
-
+import dagger.Binds;
 import dagger.Module;
 import dagger.Provides;
 
@@ -30,12 +31,12 @@
  * Dagger Module for classes found within the com.android.systemui.settings package.
  */
 @Module
-public interface SettingsModule {
+public abstract class SettingsModule {
 
     /**
      * Provides and initializes a CurrentUserContextTracker
      */
-    @Singleton
+    @SysUISingleton
     @Provides
     static CurrentUserContextTracker provideCurrentUserContextTracker(
             Context context,
@@ -45,4 +46,9 @@
         tracker.initialize();
         return tracker;
     }
+
+    @Binds
+    @SysUISingleton
+    abstract CurrentUserContentResolverProvider bindCurrentUserContentResolverTracker(
+            CurrentUserContextTracker tracker);
 }
diff --git a/packages/SystemUI/src/com/android/systemui/shortcut/ShortcutKeyDispatcher.java b/packages/SystemUI/src/com/android/systemui/shortcut/ShortcutKeyDispatcher.java
index f7f1223..e2118a7 100644
--- a/packages/SystemUI/src/com/android/systemui/shortcut/ShortcutKeyDispatcher.java
+++ b/packages/SystemUI/src/com/android/systemui/shortcut/ShortcutKeyDispatcher.java
@@ -28,22 +28,24 @@
 
 import com.android.internal.policy.DividerSnapAlgorithm;
 import com.android.systemui.SystemUI;
+import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.recents.Recents;
-import com.android.systemui.stackdivider.Divider;
 import com.android.systemui.stackdivider.DividerView;
+import com.android.systemui.stackdivider.SplitScreenController;
+
+import java.util.Optional;
 
 import javax.inject.Inject;
-import javax.inject.Singleton;
 
 /**
  * Dispatches shortcut to System UI components
  */
-@Singleton
+@SysUISingleton
 public class ShortcutKeyDispatcher extends SystemUI
         implements ShortcutKeyServiceProxy.Callbacks {
 
     private static final String TAG = "ShortcutKeyDispatcher";
-    private final Divider mDivider;
+    private final Optional<SplitScreenController> mSplitScreenControllerOptional;
     private final Recents mRecents;
 
     private ShortcutKeyServiceProxy mShortcutKeyServiceProxy = new ShortcutKeyServiceProxy(this);
@@ -58,14 +60,16 @@
     protected final long SC_DOCK_RIGHT = META_MASK | KeyEvent.KEYCODE_RIGHT_BRACKET;
 
     @Inject
-    public ShortcutKeyDispatcher(Context context, Divider divider, Recents recents) {
+    public ShortcutKeyDispatcher(Context context,
+            Optional<SplitScreenController> splitScreenControllerOptional, Recents recents) {
         super(context);
-        mDivider = divider;
+        mSplitScreenControllerOptional = splitScreenControllerOptional;
         mRecents = recents;
     }
 
     /**
      * Registers a shortcut key to window manager.
+     *
      * @param shortcutCode packed representation of shortcut key code and meta information
      */
     public void registerShortcutKey(long shortcutCode) {
@@ -92,24 +96,28 @@
     }
 
     private void handleDockKey(long shortcutCode) {
-        if (mDivider == null || !mDivider.isDividerVisible()) {
-            // Split the screen
-            mRecents.splitPrimaryTask((shortcutCode == SC_DOCK_LEFT)
-                    ? SPLIT_SCREEN_CREATE_MODE_TOP_OR_LEFT
-                    : SPLIT_SCREEN_CREATE_MODE_BOTTOM_OR_RIGHT, null, -1);
-        } else {
-            // If there is already a docked window, we respond by resizing the docking pane.
-            DividerView dividerView = mDivider.getView();
-            DividerSnapAlgorithm snapAlgorithm = dividerView.getSnapAlgorithm();
-            int dividerPosition = dividerView.getCurrentPosition();
-            DividerSnapAlgorithm.SnapTarget currentTarget =
-                    snapAlgorithm.calculateNonDismissingSnapTarget(dividerPosition);
-            DividerSnapAlgorithm.SnapTarget target = (shortcutCode == SC_DOCK_LEFT)
-                    ? snapAlgorithm.getPreviousTarget(currentTarget)
-                    : snapAlgorithm.getNextTarget(currentTarget);
-            dividerView.startDragging(true /* animate */, false /* touching */);
-            dividerView.stopDragging(target.position, 0f, false /* avoidDismissStart */,
-                    true /* logMetrics */);
+        if (mSplitScreenControllerOptional.isPresent()) {
+            SplitScreenController splitScreenController = mSplitScreenControllerOptional.get();
+            if (splitScreenController.isDividerVisible()) {
+                // If there is already a docked window, we respond by resizing the docking pane.
+                DividerView dividerView = splitScreenController.getDividerView();
+                DividerSnapAlgorithm snapAlgorithm = dividerView.getSnapAlgorithm();
+                int dividerPosition = dividerView.getCurrentPosition();
+                DividerSnapAlgorithm.SnapTarget currentTarget =
+                        snapAlgorithm.calculateNonDismissingSnapTarget(dividerPosition);
+                DividerSnapAlgorithm.SnapTarget target = (shortcutCode == SC_DOCK_LEFT)
+                        ? snapAlgorithm.getPreviousTarget(currentTarget)
+                        : snapAlgorithm.getNextTarget(currentTarget);
+                dividerView.startDragging(true /* animate */, false /* touching */);
+                dividerView.stopDragging(target.position, 0f, false /* avoidDismissStart */,
+                        true /* logMetrics */);
+                return;
+            }
         }
+
+        // Split the screen
+        mRecents.splitPrimaryTask((shortcutCode == SC_DOCK_LEFT)
+                ? SPLIT_SCREEN_CREATE_MODE_TOP_OR_LEFT
+                : SPLIT_SCREEN_CREATE_MODE_BOTTOM_OR_RIGHT, null, -1);
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/stackdivider/Divider.java b/packages/SystemUI/src/com/android/systemui/stackdivider/Divider.java
deleted file mode 100644
index 4007abb..0000000
--- a/packages/SystemUI/src/com/android/systemui/stackdivider/Divider.java
+++ /dev/null
@@ -1,142 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.stackdivider;
-
-import static com.android.systemui.shared.system.WindowManagerWrapper.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY;
-
-import android.app.ActivityManager;
-import android.content.Context;
-import android.window.WindowContainerToken;
-
-import com.android.systemui.SystemUI;
-import com.android.systemui.shared.system.ActivityManagerWrapper;
-import com.android.systemui.shared.system.TaskStackChangeListener;
-import com.android.systemui.statusbar.policy.KeyguardStateController;
-
-import java.io.FileDescriptor;
-import java.io.PrintWriter;
-import java.util.function.Consumer;
-
-import javax.inject.Singleton;
-
-/**
- * Controls the docked stack divider.
- */
-@Singleton
-public class Divider extends SystemUI {
-    private final KeyguardStateController mKeyguardStateController;
-    private final DividerController mDividerController;
-
-    Divider(Context context, DividerController dividerController,
-            KeyguardStateController keyguardStateController) {
-        super(context);
-        mDividerController = dividerController;
-        mKeyguardStateController = keyguardStateController;
-    }
-
-    @Override
-    public void start() {
-        mDividerController.start();
-        // Hide the divider when keyguard is showing. Even though keyguard/statusbar is above
-        // everything, it is actually transparent except for notifications, so we still need to
-        // hide any surfaces that are below it.
-        // TODO(b/148906453): Figure out keyguard dismiss animation for divider view.
-        mKeyguardStateController.addCallback(new KeyguardStateController.Callback() {
-            @Override
-            public void onKeyguardShowingChanged() {
-                mDividerController.onKeyguardShowingChanged(mKeyguardStateController.isShowing());
-            }
-        });
-        // Don't initialize the divider or anything until we get the default display.
-
-        ActivityManagerWrapper.getInstance().registerTaskStackListener(
-                new TaskStackChangeListener() {
-                    @Override
-                    public void onActivityRestartAttempt(ActivityManager.RunningTaskInfo task,
-                            boolean homeTaskVisible, boolean clearedTask, boolean wasVisible) {
-                        if (!wasVisible || task.configuration.windowConfiguration.getWindowingMode()
-                                != WINDOWING_MODE_SPLIT_SCREEN_PRIMARY
-                                || !mDividerController.isSplitScreenSupported()) {
-                            return;
-                        }
-
-                        if (mDividerController.isMinimized()) {
-                            onUndockingTask();
-                        }
-                    }
-                }
-        );
-    }
-
-    @Override
-    public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
-        mDividerController.dump(pw);
-    }
-
-    /** Switch to minimized state if appropriate. */
-    public void setMinimized(final boolean minimized) {
-        mDividerController.setMinimized(minimized);
-    }
-
-    public boolean isMinimized() {
-        return mDividerController.isMinimized();
-    }
-
-    public boolean isHomeStackResizable() {
-        return mDividerController.isHomeStackResizable();
-    }
-
-    /** Callback for undocking task. */
-    public void onUndockingTask() {
-        mDividerController.onUndockingTask();
-    }
-
-    public void onRecentsDrawn() {
-        mDividerController.onRecentsDrawn();
-    }
-
-    public void onDockedFirstAnimationFrame() {
-        mDividerController.onDockedFirstAnimationFrame();
-    }
-
-    public void onDockedTopTask() {
-        mDividerController.onDockedTopTask();
-    }
-
-    public void onAppTransitionFinished() {
-        mDividerController.onAppTransitionFinished();
-    }
-
-    public DividerView getView() {
-        return mDividerController.getDividerView();
-    }
-
-    /** @return the container token for the secondary split root task. */
-    public WindowContainerToken getSecondaryRoot() {
-        return mDividerController.getSecondaryRoot();
-    }
-
-    /** Register a listener that gets called whenever the existence of the divider changes */
-    public void registerInSplitScreenListener(Consumer<Boolean> listener) {
-        mDividerController.registerInSplitScreenListener(listener);
-    }
-
-    /** {@code true} if this is visible */
-    public boolean isDividerVisible() {
-        return mDividerController.isDividerVisible();
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/stackdivider/DividerImeController.java b/packages/SystemUI/src/com/android/systemui/stackdivider/DividerImeController.java
index c915f07..64ee7ed5 100644
--- a/packages/SystemUI/src/com/android/systemui/stackdivider/DividerImeController.java
+++ b/packages/SystemUI/src/com/android/systemui/stackdivider/DividerImeController.java
@@ -38,7 +38,7 @@
 
 class DividerImeController implements DisplayImeController.ImePositionProcessor {
     private static final String TAG = "DividerImeController";
-    private static final boolean DEBUG = DividerController.DEBUG;
+    private static final boolean DEBUG = SplitScreenController.DEBUG;
 
     private static final float ADJUSTED_NONFOCUS_DIM = 0.3f;
 
@@ -100,15 +100,15 @@
     }
 
     private DividerView getView() {
-        return mSplits.mDividerController.getDividerView();
+        return mSplits.mSplitScreenController.getDividerView();
     }
 
     private SplitDisplayLayout getLayout() {
-        return mSplits.mDividerController.getSplitLayout();
+        return mSplits.mSplitScreenController.getSplitLayout();
     }
 
     private boolean isDividerVisible() {
-        return mSplits.mDividerController.isDividerVisible();
+        return mSplits.mSplitScreenController.isDividerVisible();
     }
 
     private boolean getSecondaryHasFocus(int displayId) {
@@ -151,7 +151,7 @@
         mSecondaryHasFocus = getSecondaryHasFocus(displayId);
         final boolean targetAdjusted = splitIsVisible && imeShouldShow && mSecondaryHasFocus
                 && !imeIsFloating && !getLayout().mDisplayLayout.isLandscape()
-                && !mSplits.mDividerController.isMinimized();
+                && !mSplits.mSplitScreenController.isMinimized();
         if (mLastAdjustTop < 0) {
             mLastAdjustTop = imeShouldShow ? hiddenTop : shownTop;
         } else if (mLastAdjustTop != (imeShouldShow ? mShownTop : mHiddenTop)) {
@@ -236,7 +236,7 @@
                         SCREEN_WIDTH_DP_UNDEFINED, SCREEN_HEIGHT_DP_UNDEFINED);
             }
 
-            if (!mSplits.mDividerController.getWmProxy().queueSyncTransactionIfWaiting(wct)) {
+            if (!mSplits.mSplitScreenController.getWmProxy().queueSyncTransactionIfWaiting(wct)) {
                 WindowOrganizer.applyTransaction(wct);
             }
         }
@@ -250,7 +250,7 @@
                         : DisplayImeController.ANIMATION_DURATION_HIDE_MS);
             }
         }
-        mSplits.mDividerController.setAdjustedForIme(mTargetShown && !mPaused);
+        mSplits.mSplitScreenController.setAdjustedForIme(mTargetShown && !mPaused);
     }
 
     public void updateAdjustForIme() {
@@ -402,7 +402,7 @@
             mTargetAdjusted = mPausedTargetAdjusted;
             updateDimTargets();
             final DividerView view = getView();
-            if ((mTargetAdjusted != mAdjusted) && !mSplits.mDividerController.isMinimized()
+            if ((mTargetAdjusted != mAdjusted) && !mSplits.mSplitScreenController.isMinimized()
                     && view != null) {
                 // End unminimize animations since they conflict with adjustment animations.
                 view.finishAnimations();
diff --git a/packages/SystemUI/src/com/android/systemui/stackdivider/DividerModule.java b/packages/SystemUI/src/com/android/systemui/stackdivider/DividerModule.java
deleted file mode 100644
index db0aef8..0000000
--- a/packages/SystemUI/src/com/android/systemui/stackdivider/DividerModule.java
+++ /dev/null
@@ -1,49 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.stackdivider;
-
-import android.content.Context;
-import android.os.Handler;
-
-import com.android.systemui.dagger.qualifiers.Main;
-import com.android.systemui.statusbar.policy.KeyguardStateController;
-import com.android.wm.shell.common.DisplayController;
-import com.android.wm.shell.common.DisplayImeController;
-import com.android.wm.shell.common.SystemWindows;
-import com.android.wm.shell.common.TransactionPool;
-
-import javax.inject.Singleton;
-
-import dagger.Module;
-import dagger.Provides;
-
-/**
- * Module which provides a Divider.
- */
-@Module
-public class DividerModule {
-    @Singleton
-    @Provides
-    static Divider provideDivider(Context context, DisplayController displayController,
-            SystemWindows systemWindows, DisplayImeController imeController, @Main Handler handler,
-            KeyguardStateController keyguardStateController, TransactionPool transactionPool) {
-        // TODO(b/161116823): fetch DividerProxy from WM shell lib.
-        DividerController dividerController = new DividerController(context, displayController,
-                systemWindows, imeController, handler, transactionPool);
-        return new Divider(context, dividerController, keyguardStateController);
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/stackdivider/DividerView.java b/packages/SystemUI/src/com/android/systemui/stackdivider/DividerView.java
index e5c02d6..95f048b 100644
--- a/packages/SystemUI/src/com/android/systemui/stackdivider/DividerView.java
+++ b/packages/SystemUI/src/com/android/systemui/stackdivider/DividerView.java
@@ -76,7 +76,7 @@
 public class DividerView extends FrameLayout implements OnTouchListener,
         OnComputeInternalInsetsListener {
     private static final String TAG = "DividerView";
-    private static final boolean DEBUG = DividerController.DEBUG;
+    private static final boolean DEBUG = SplitScreenController.DEBUG;
 
     public interface DividerCallbacks {
         void onDraggingStart();
diff --git a/packages/SystemUI/src/com/android/systemui/stackdivider/ForcedResizableInfoActivityController.java b/packages/SystemUI/src/com/android/systemui/stackdivider/ForcedResizableInfoActivityController.java
index f412cc0..4c26694 100644
--- a/packages/SystemUI/src/com/android/systemui/stackdivider/ForcedResizableInfoActivityController.java
+++ b/packages/SystemUI/src/com/android/systemui/stackdivider/ForcedResizableInfoActivityController.java
@@ -28,15 +28,13 @@
 import android.widget.Toast;
 
 import com.android.systemui.R;
-import com.android.systemui.shared.system.ActivityManagerWrapper;
-import com.android.systemui.shared.system.TaskStackChangeListener;
 
 import java.util.function.Consumer;
 
 /**
  * Controller that decides when to show the {@link ForcedResizableInfoActivity}.
  */
-public class ForcedResizableInfoActivityController {
+final class ForcedResizableInfoActivityController implements DividerView.DividerCallbacks {
 
     private static final String SELF_PACKAGE_NAME = "com.android.systemui";
 
@@ -47,12 +45,7 @@
     private final ArraySet<String> mPackagesShownInSession = new ArraySet<>();
     private boolean mDividerDragging;
 
-    private final Runnable mTimeoutRunnable = new Runnable() {
-        @Override
-        public void run() {
-            showPending();
-        }
-    };
+    private final Runnable mTimeoutRunnable = this::showPending;
 
     private final Consumer<Boolean> mDockedStackExistsListener = exists -> {
         if (!exists) {
@@ -76,46 +69,30 @@
     }
 
     public ForcedResizableInfoActivityController(Context context,
-            DividerController dividerController) {
+            SplitScreenController splitScreenController) {
         mContext = context;
-        ActivityManagerWrapper.getInstance().registerTaskStackListener(
-                new TaskStackChangeListener() {
-                    @Override
-                    public void onActivityForcedResizable(String packageName, int taskId,
-                            int reason) {
-                        activityForcedResizable(packageName, taskId, reason);
-                    }
-
-                    @Override
-                    public void onActivityDismissingDockedStack() {
-                        activityDismissingDockedStack();
-                    }
-
-                    @Override
-                    public void onActivityLaunchOnSecondaryDisplayFailed() {
-                        activityLaunchOnSecondaryDisplayFailed();
-                    }
-                });
-        dividerController.registerInSplitScreenListener(mDockedStackExistsListener);
+        splitScreenController.registerInSplitScreenListener(mDockedStackExistsListener);
     }
 
-    public void onAppTransitionFinished() {
+    @Override
+    public void onDraggingStart() {
+        mDividerDragging = true;
+        mHandler.removeCallbacks(mTimeoutRunnable);
+    }
+
+    @Override
+    public void onDraggingEnd() {
+        mDividerDragging = false;
+        showPending();
+    }
+
+    void onAppTransitionFinished() {
         if (!mDividerDragging) {
             showPending();
         }
     }
 
-    void onDraggingStart() {
-        mDividerDragging = true;
-        mHandler.removeCallbacks(mTimeoutRunnable);
-    }
-
-    void onDraggingEnd() {
-        mDividerDragging = false;
-        showPending();
-    }
-
-    private void activityForcedResizable(String packageName, int taskId, int reason) {
+    void activityForcedResizable(String packageName, int taskId, int reason) {
         if (debounce(packageName)) {
             return;
         }
@@ -123,12 +100,12 @@
         postTimeout();
     }
 
-    private void activityDismissingDockedStack() {
+    void activityDismissingSplitScreen() {
         Toast.makeText(mContext, R.string.dock_non_resizeble_failed_to_dock_text,
                 Toast.LENGTH_SHORT).show();
     }
 
-    private void activityLaunchOnSecondaryDisplayFailed() {
+    void activityLaunchOnSecondaryDisplayFailed() {
         Toast.makeText(mContext, R.string.activity_launch_on_secondary_display_failed_text,
                 Toast.LENGTH_SHORT).show();
     }
diff --git a/packages/SystemUI/src/com/android/systemui/stackdivider/DividerController.java b/packages/SystemUI/src/com/android/systemui/stackdivider/SplitScreenController.java
similarity index 91%
rename from packages/SystemUI/src/com/android/systemui/stackdivider/DividerController.java
rename to packages/SystemUI/src/com/android/systemui/stackdivider/SplitScreenController.java
index 81649f6..4cba9c7 100644
--- a/packages/SystemUI/src/com/android/systemui/stackdivider/DividerController.java
+++ b/packages/SystemUI/src/com/android/systemui/stackdivider/SplitScreenController.java
@@ -35,6 +35,7 @@
 
 import com.android.internal.policy.DividerSnapAlgorithm;
 import com.android.systemui.R;
+import com.android.wm.shell.ShellTaskOrganizer;
 import com.android.wm.shell.common.DisplayChangeController;
 import com.android.wm.shell.common.DisplayController;
 import com.android.wm.shell.common.DisplayImeController;
@@ -48,27 +49,34 @@
 import java.util.function.Consumer;
 
 /**
- * Controls the docked stack divider.
+ * Controls split screen.
  */
-public class DividerController implements DividerView.DividerCallbacks,
-        DisplayController.OnDisplaysChangedListener {
+// TODO(b/161116823): Extract as an interface to expose to SysUISingleton scope.
+public class SplitScreenController implements DisplayController.OnDisplaysChangedListener {
     static final boolean DEBUG = false;
-    private static final String TAG = "Divider";
 
-    static final int DEFAULT_APP_TRANSITION_DURATION = 336;
+    private static final String TAG = "Divider";
+    private static final int DEFAULT_APP_TRANSITION_DURATION = 336;
+
+    private final Context mContext;
+    private final DisplayChangeController.OnDisplayChangingListener mRotationController;
+    private final DisplayController mDisplayController;
+    private final DisplayImeController mImeController;
+    private final DividerImeController mImePositionProcessor;
+    private final DividerState mDividerState = new DividerState();
+    private final ForcedResizableInfoActivityController mForcedResizableController;
+    private final Handler mHandler;
+    private final SplitScreenTaskOrganizer mSplits;
+    private final SystemWindows mSystemWindows;
+    final TransactionPool mTransactionPool;
+    private final WindowManagerProxy mWindowManagerProxy;
+
+    private final ArrayList<WeakReference<Consumer<Boolean>>> mDockedStackExistsListeners =
+            new ArrayList<>();
+
 
     private DividerWindowManager mWindowManager;
     private DividerView mView;
-    private final DividerState mDividerState = new DividerState();
-    private boolean mVisible = false;
-    private boolean mMinimized = false;
-    private boolean mAdjustedForIme = false;
-    private boolean mHomeStackResizable = false;
-    private ForcedResizableInfoActivityController mForcedResizableController;
-    private SystemWindows mSystemWindows;
-    private DisplayController mDisplayController;
-    private DisplayImeController mImeController;
-    final TransactionPool mTransactionPool;
 
     // Keeps track of real-time split geometry including snap positions and ime adjustments
     private SplitDisplayLayout mSplitLayout;
@@ -78,21 +86,16 @@
     // layout that we sent back to WM.
     private SplitDisplayLayout mRotateSplitLayout;
 
-    private final Handler mHandler;
-    private final WindowManagerProxy mWindowManagerProxy;
-
-    private final ArrayList<WeakReference<Consumer<Boolean>>> mDockedStackExistsListeners =
-            new ArrayList<>();
-
-    private final SplitScreenTaskOrganizer mSplits;
-    private final DisplayChangeController.OnDisplayChangingListener mRotationController;
-    private final DividerImeController mImePositionProcessor;
-    private final Context mContext;
     private boolean mIsKeyguardShowing;
+    private boolean mVisible = false;
+    private boolean mMinimized = false;
+    private boolean mAdjustedForIme = false;
+    private boolean mHomeStackResizable = false;
 
-    public DividerController(Context context,
+    public SplitScreenController(Context context,
             DisplayController displayController, SystemWindows systemWindows,
-            DisplayImeController imeController, Handler handler, TransactionPool transactionPool) {
+            DisplayImeController imeController, Handler handler, TransactionPool transactionPool,
+            ShellTaskOrganizer shellTaskOrganizer) {
         mContext = context;
         mDisplayController = displayController;
         mSystemWindows = systemWindows;
@@ -101,7 +104,7 @@
         mForcedResizableController = new ForcedResizableInfoActivityController(context, this);
         mTransactionPool = transactionPool;
         mWindowManagerProxy = new WindowManagerProxy(mTransactionPool, mHandler);
-        mSplits = new SplitScreenTaskOrganizer(this);
+        mSplits = new SplitScreenTaskOrganizer(this, shellTaskOrganizer);
         mImePositionProcessor = new DividerImeController(mSplits, mTransactionPool, mHandler);
         mRotationController =
                 (display, fromRotation, toRotation, wct) -> {
@@ -141,10 +144,7 @@
                         wct.merge(t, true /* transfer */);
                     }
                 };
-    }
 
-    /** Inits the divider service. */
-    public void start() {
         mWindowManager = new DividerWindowManager(mSystemWindows);
         mDisplayController.addDisplayWindowListener(this);
         // Don't initialize the divider or anything until we get the default display.
@@ -156,15 +156,15 @@
     }
 
     /** Called when keyguard showing state changed. */
-    public void onKeyguardShowingChanged(boolean isShowing) {
+    public void onKeyguardVisibilityChanged(boolean showing) {
         if (!isSplitActive() || mView == null) {
             return;
         }
-        mView.setHidden(isShowing);
-        if (!isShowing) {
+        mView.setHidden(showing);
+        if (!showing) {
             mImePositionProcessor.updateAdjustForIme();
         }
-        mIsKeyguardShowing = isShowing;
+        mIsKeyguardShowing = showing;
     }
 
     @Override
@@ -257,8 +257,8 @@
         mView = (DividerView)
                 LayoutInflater.from(dctx).inflate(R.layout.docked_stack_divider, null);
         DisplayLayout displayLayout = mDisplayController.getDisplayLayout(mContext.getDisplayId());
-        mView.injectDependencies(mWindowManager, mDividerState, this, mSplits, mSplitLayout,
-                mImePositionProcessor, mWindowManagerProxy);
+        mView.injectDependencies(mWindowManager, mDividerState, mForcedResizableController, mSplits,
+                mSplitLayout, mImePositionProcessor, mWindowManagerProxy);
         mView.setVisibility(mVisible ? View.VISIBLE : View.INVISIBLE);
         mView.setMinimizedDockStack(mMinimized, mHomeStackResizable, null /* transaction */);
         final int size = dctx.getResources().getDimensionPixelSize(
@@ -397,7 +397,22 @@
         }
     }
 
-    /** Called when there's a task undocking.  */
+    /** Called when there's an activity forced resizable. */
+    public void onActivityForcedResizable(String packageName, int taskId, int reason) {
+        mForcedResizableController.activityForcedResizable(packageName, taskId, reason);
+    }
+
+    /** Called when there's an activity dismissing split screen. */
+    public void onActivityDismissingSplitScreen() {
+        mForcedResizableController.activityDismissingSplitScreen();
+    }
+
+    /** Called when there's an activity launch on secondary display failed. */
+    public void onActivityLaunchOnSecondaryDisplayFailed() {
+        mForcedResizableController.activityLaunchOnSecondaryDisplayFailed();
+    }
+
+    /** Called when there's a task undocking. */
     public void onUndockingTask() {
         if (mView != null) {
             mView.onUndockingTask();
@@ -426,17 +441,7 @@
         mForcedResizableController.onAppTransitionFinished();
     }
 
-    @Override
-    public void onDraggingStart() {
-        mForcedResizableController.onDraggingStart();
-    }
-
-    @Override
-    public void onDraggingEnd() {
-        mForcedResizableController.onDraggingEnd();
-    }
-
-    /** Dumps current status of Divider.*/
+    /** Dumps current status of Split Screen. */
     public void dump(PrintWriter pw) {
         pw.print("  mVisible="); pw.println(mVisible);
         pw.print("  mMinimized="); pw.println(mMinimized);
diff --git a/packages/SystemUI/src/com/android/systemui/stackdivider/SplitScreenTaskOrganizer.java b/packages/SystemUI/src/com/android/systemui/stackdivider/SplitScreenTaskOrganizer.java
index ef5e8a1..325c559 100644
--- a/packages/SystemUI/src/com/android/systemui/stackdivider/SplitScreenTaskOrganizer.java
+++ b/packages/SystemUI/src/com/android/systemui/stackdivider/SplitScreenTaskOrganizer.java
@@ -33,9 +33,13 @@
 import android.view.SurfaceSession;
 import android.window.TaskOrganizer;
 
-class SplitScreenTaskOrganizer extends TaskOrganizer {
+import com.android.wm.shell.ShellTaskOrganizer;
+
+class SplitScreenTaskOrganizer implements ShellTaskOrganizer.TaskListener {
     private static final String TAG = "SplitScreenTaskOrg";
-    private static final boolean DEBUG = DividerController.DEBUG;
+    private static final boolean DEBUG = SplitScreenController.DEBUG;
+
+    private final ShellTaskOrganizer mTaskOrganizer;
 
     RunningTaskInfo mPrimary;
     RunningTaskInfo mSecondary;
@@ -44,18 +48,20 @@
     SurfaceControl mPrimaryDim;
     SurfaceControl mSecondaryDim;
     Rect mHomeBounds = new Rect();
-    final DividerController mDividerController;
+    final SplitScreenController mSplitScreenController;
     private boolean mSplitScreenSupported = false;
 
     final SurfaceSession mSurfaceSession = new SurfaceSession();
 
-    SplitScreenTaskOrganizer(DividerController dividerController) {
-        mDividerController = dividerController;
+    SplitScreenTaskOrganizer(SplitScreenController splitScreenController,
+                    ShellTaskOrganizer shellTaskOrganizer) {
+        mSplitScreenController = splitScreenController;
+        mTaskOrganizer = shellTaskOrganizer;
+        mTaskOrganizer.addListener(this, WINDOWING_MODE_SPLIT_SCREEN_PRIMARY,
+                WINDOWING_MODE_SPLIT_SCREEN_SECONDARY);
     }
 
     void init() throws RemoteException {
-        registerOrganizer(WINDOWING_MODE_SPLIT_SCREEN_PRIMARY);
-        registerOrganizer(WINDOWING_MODE_SPLIT_SCREEN_SECONDARY);
         synchronized (this) {
             try {
                 mPrimary = TaskOrganizer.createRootTask(Display.DEFAULT_DISPLAY,
@@ -64,7 +70,7 @@
                         WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_SECONDARY);
             } catch (Exception e) {
                 // teardown to prevent callbacks
-                unregisterOrganizer();
+                mTaskOrganizer.removeListener(this);
                 throw e;
             }
         }
@@ -75,11 +81,11 @@
     }
 
     SurfaceControl.Transaction getTransaction() {
-        return mDividerController.mTransactionPool.acquire();
+        return mSplitScreenController.mTransactionPool.acquire();
     }
 
     void releaseTransaction(SurfaceControl.Transaction t) {
-        mDividerController.mTransactionPool.release(t);
+        mSplitScreenController.mTransactionPool.release(t);
     }
 
     @Override
@@ -140,7 +146,7 @@
                 t.apply();
                 releaseTransaction(t);
 
-                mDividerController.onTaskVanished();
+                mSplitScreenController.onTaskVanished();
             }
         }
     }
@@ -150,7 +156,7 @@
         if (taskInfo.displayId != DEFAULT_DISPLAY) {
             return;
         }
-        mDividerController.post(() -> handleTaskInfoChanged(taskInfo));
+        mSplitScreenController.post(() -> handleTaskInfoChanged(taskInfo));
     }
 
     /**
@@ -169,7 +175,7 @@
         }
         final boolean secondaryImpliedMinimize = mSecondary.topActivityType == ACTIVITY_TYPE_HOME
                 || (mSecondary.topActivityType == ACTIVITY_TYPE_RECENTS
-                        && mDividerController.isHomeStackResizable());
+                        && mSplitScreenController.isHomeStackResizable());
         final boolean primaryWasEmpty = mPrimary.topActivityType == ACTIVITY_TYPE_UNDEFINED;
         final boolean secondaryWasEmpty = mSecondary.topActivityType == ACTIVITY_TYPE_UNDEFINED;
         if (info.token.asBinder() == mPrimary.token.asBinder()) {
@@ -181,7 +187,7 @@
         final boolean secondaryIsEmpty = mSecondary.topActivityType == ACTIVITY_TYPE_UNDEFINED;
         final boolean secondaryImpliesMinimize = mSecondary.topActivityType == ACTIVITY_TYPE_HOME
                 || (mSecondary.topActivityType == ACTIVITY_TYPE_RECENTS
-                        && mDividerController.isHomeStackResizable());
+                        && mSplitScreenController.isHomeStackResizable());
         if (DEBUG) {
             Log.d(TAG, "onTaskInfoChanged " + mPrimary + "  " + mSecondary);
         }
@@ -197,14 +203,14 @@
                 Log.d(TAG, " at-least one split empty " + mPrimary.topActivityType
                         + "  " + mSecondary.topActivityType);
             }
-            if (mDividerController.isDividerVisible()) {
+            if (mSplitScreenController.isDividerVisible()) {
                 // Was in split-mode, which means we are leaving split, so continue that.
                 // This happens when the stack in the primary-split is dismissed.
                 if (DEBUG) {
                     Log.d(TAG, "    was in split, so this means leave it "
                             + mPrimary.topActivityType + "  " + mSecondary.topActivityType);
                 }
-                mDividerController.startDismissSplit();
+                mSplitScreenController.startDismissSplit();
             } else if (!primaryIsEmpty && primaryWasEmpty && secondaryWasEmpty) {
                 // Wasn't in split-mode (both were empty), but now that the primary split is
                 // populated, we should fully enter split by moving everything else into secondary.
@@ -213,15 +219,15 @@
                 if (DEBUG) {
                     Log.d(TAG, "   was not in split, but primary is populated, so enter it");
                 }
-                mDividerController.startEnterSplit();
+                mSplitScreenController.startEnterSplit();
             }
         } else if (secondaryImpliesMinimize) {
             // Both splits are populated but the secondary split has a home/recents stack on top,
             // so enter minimized mode.
-            mDividerController.ensureMinimizedSplit();
+            mSplitScreenController.ensureMinimizedSplit();
         } else {
             // Both splits are populated by normal activities, so make sure we aren't minimized.
-            mDividerController.ensureNormalSplit();
+            mSplitScreenController.ensureNormalSplit();
         }
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/stackdivider/SyncTransactionQueue.java b/packages/SystemUI/src/com/android/systemui/stackdivider/SyncTransactionQueue.java
index f2500e5..13ed02e 100644
--- a/packages/SystemUI/src/com/android/systemui/stackdivider/SyncTransactionQueue.java
+++ b/packages/SystemUI/src/com/android/systemui/stackdivider/SyncTransactionQueue.java
@@ -33,7 +33,7 @@
  * Helper for serializing sync-transactions and corresponding callbacks.
  */
 class SyncTransactionQueue {
-    private static final boolean DEBUG = DividerController.DEBUG;
+    private static final boolean DEBUG = SplitScreenController.DEBUG;
     private static final String TAG = "SyncTransactionQueue";
 
     // Just a little longer than the sync-engine timeout of 5s
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/BlurUtils.kt b/packages/SystemUI/src/com/android/systemui/statusbar/BlurUtils.kt
index c70d3847..f758db8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/BlurUtils.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/BlurUtils.kt
@@ -26,14 +26,14 @@
 import com.android.internal.util.IndentingPrintWriter
 import com.android.systemui.Dumpable
 import com.android.systemui.R
+import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Main
 import com.android.systemui.dump.DumpManager
 import java.io.FileDescriptor
 import java.io.PrintWriter
 import javax.inject.Inject
-import javax.inject.Singleton
 
-@Singleton
+@SysUISingleton
 open class BlurUtils @Inject constructor(
     @Main private val resources: Resources,
     dumpManager: DumpManager
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
index 1638dd9..4673ec7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
@@ -49,6 +49,8 @@
 import android.view.InsetsState.InternalInsetsType;
 import android.view.WindowInsetsController.Appearance;
 
+import androidx.annotation.NonNull;
+
 import com.android.internal.os.SomeArgs;
 import com.android.internal.statusbar.IStatusBar;
 import com.android.internal.statusbar.StatusBarIcon;
@@ -391,7 +393,8 @@
                 && !ONLY_CORE_APPS;
     }
 
-    public void addCallback(Callbacks callbacks) {
+    @Override
+    public void addCallback(@NonNull Callbacks callbacks) {
         mCallbacks.add(callbacks);
         // TODO(b/117478341): find a better way to pass disable flags by display.
         for (int i = 0; i < mDisplayDisabled.size(); i++) {
@@ -402,7 +405,8 @@
         }
     }
 
-    public void removeCallback(Callbacks callbacks) {
+    @Override
+    public void removeCallback(@NonNull Callbacks callbacks) {
         mCallbacks.remove(callbacks);
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/FeatureFlags.java b/packages/SystemUI/src/com/android/systemui/statusbar/FeatureFlags.java
index 6839921..3811ca9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/FeatureFlags.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/FeatureFlags.java
@@ -20,13 +20,13 @@
 import android.provider.DeviceConfig;
 import android.util.ArrayMap;
 
+import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dagger.qualifiers.Background;
 
 import java.util.Map;
 import java.util.concurrent.Executor;
 
 import javax.inject.Inject;
-import javax.inject.Singleton;
 
 /**
  * Class to manage simple DeviceConfig-based feature flags.
@@ -43,7 +43,7 @@
  *  $ adb shell am restart com.android.systemui
  * }
  */
-@Singleton
+@SysUISingleton
 public class FeatureFlags {
     private final Map<String, Boolean> mCachedDeviceConfigFlags = new ArrayMap<>();
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
index 7e1dc66..a59ff38 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
@@ -55,6 +55,7 @@
 import com.android.systemui.Interpolators;
 import com.android.systemui.R;
 import com.android.systemui.broadcast.BroadcastDispatcher;
+import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dock.DockManager;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.plugins.statusbar.StatusBarStateController.StateListener;
@@ -71,12 +72,11 @@
 import java.util.IllegalFormatConversionException;
 
 import javax.inject.Inject;
-import javax.inject.Singleton;
 
 /**
  * Controls the indications and error messages shown on the Keyguard
  */
-@Singleton
+@SysUISingleton
 public class KeyguardIndicationController implements StateListener,
         KeyguardStateController.Callback {
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/MediaArtworkProcessor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/MediaArtworkProcessor.kt
index 326757e..750272d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/MediaArtworkProcessor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/MediaArtworkProcessor.kt
@@ -28,17 +28,16 @@
 import android.util.Log
 import android.util.MathUtils
 import com.android.internal.graphics.ColorUtils
+import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.statusbar.notification.MediaNotificationProcessor
-
 import javax.inject.Inject
-import javax.inject.Singleton
 
 private const val TAG = "MediaArtworkProcessor"
 private const val COLOR_ALPHA = (255 * 0.7f).toInt()
 private const val BLUR_RADIUS = 25f
 private const val DOWNSAMPLE = 6
 
-@Singleton
+@SysUISingleton
 class MediaArtworkProcessor @Inject constructor() {
 
     private val mTmpSize = Point()
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NavigationBarController.java b/packages/SystemUI/src/com/android/systemui/statusbar/NavigationBarController.java
deleted file mode 100644
index 2638d28..0000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NavigationBarController.java
+++ /dev/null
@@ -1,248 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.statusbar;
-
-import static android.view.Display.DEFAULT_DISPLAY;
-
-import android.content.Context;
-import android.hardware.display.DisplayManager;
-import android.os.Handler;
-import android.os.RemoteException;
-import android.util.Log;
-import android.util.SparseArray;
-import android.view.Display;
-import android.view.IWindowManager;
-import android.view.View;
-import android.view.WindowManagerGlobal;
-
-import androidx.annotation.Nullable;
-
-import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.statusbar.RegisterStatusBarResult;
-import com.android.systemui.Dependency;
-import com.android.systemui.assist.AssistHandleViewController;
-import com.android.systemui.dagger.qualifiers.Main;
-import com.android.systemui.fragments.FragmentHostManager;
-import com.android.systemui.plugins.DarkIconDispatcher;
-import com.android.systemui.statusbar.CommandQueue.Callbacks;
-import com.android.systemui.statusbar.phone.AutoHideController;
-import com.android.systemui.statusbar.phone.BarTransitions.TransitionMode;
-import com.android.systemui.statusbar.phone.LightBarController;
-import com.android.systemui.statusbar.phone.NavigationBarFragment;
-import com.android.systemui.statusbar.phone.NavigationBarView;
-import com.android.systemui.statusbar.phone.NavigationModeController;
-import com.android.systemui.statusbar.policy.BatteryController;
-
-import javax.inject.Inject;
-import javax.inject.Singleton;
-
-
-/** A controller to handle navigation bars. */
-@Singleton
-public class NavigationBarController implements Callbacks {
-
-    private static final String TAG = NavigationBarController.class.getSimpleName();
-
-    private final Context mContext;
-    private final Handler mHandler;
-    private final DisplayManager mDisplayManager;
-
-    /** A displayId - nav bar maps. */
-    @VisibleForTesting
-    SparseArray<NavigationBarFragment> mNavigationBars = new SparseArray<>();
-
-    @Inject
-    public NavigationBarController(Context context, @Main Handler handler,
-            CommandQueue commandQueue) {
-        mContext = context;
-        mHandler = handler;
-        mDisplayManager = (DisplayManager) mContext.getSystemService(Context.DISPLAY_SERVICE);
-        commandQueue.addCallback(this);
-    }
-
-    @Override
-    public void onDisplayRemoved(int displayId) {
-        removeNavigationBar(displayId);
-    }
-
-    @Override
-    public void onDisplayReady(int displayId) {
-        Display display = mDisplayManager.getDisplay(displayId);
-        createNavigationBar(display, null);
-    }
-
-    // TODO(b/117478341): I use {@code includeDefaultDisplay} to make this method compatible to
-    // CarStatusBar because they have their own nav bar. Think about a better way for it.
-    /**
-     * Creates navigation bars when car/status bar initializes.
-     *
-     * @param includeDefaultDisplay {@code true} to create navigation bar on default display.
-     */
-    public void createNavigationBars(final boolean includeDefaultDisplay,
-            RegisterStatusBarResult result) {
-        Display[] displays = mDisplayManager.getDisplays();
-        for (Display display : displays) {
-            if (includeDefaultDisplay || display.getDisplayId() != DEFAULT_DISPLAY) {
-                createNavigationBar(display, result);
-            }
-        }
-    }
-
-    /**
-     * Adds a navigation bar on default display or an external display if the display supports
-     * system decorations.
-     *
-     * @param display the display to add navigation bar on.
-     */
-    @VisibleForTesting
-    void createNavigationBar(Display display, RegisterStatusBarResult result) {
-        if (display == null) {
-            return;
-        }
-
-        final int displayId = display.getDisplayId();
-        final boolean isOnDefaultDisplay = displayId == DEFAULT_DISPLAY;
-        final IWindowManager wms = WindowManagerGlobal.getWindowManagerService();
-
-        try {
-            if (!wms.hasNavigationBar(displayId)) {
-                return;
-            }
-        } catch (RemoteException e) {
-            // Cannot get wms, just return with warning message.
-            Log.w(TAG, "Cannot get WindowManager.");
-            return;
-        }
-        final Context context = isOnDefaultDisplay
-                ? mContext
-                : mContext.createDisplayContext(display);
-        NavigationBarFragment.create(context, (tag, fragment) -> {
-            NavigationBarFragment navBar = (NavigationBarFragment) fragment;
-
-            // Unfortunately, we still need it because status bar needs LightBarController
-            // before notifications creation. We cannot directly use getLightBarController()
-            // from NavigationBarFragment directly.
-            LightBarController lightBarController = isOnDefaultDisplay
-                    ? Dependency.get(LightBarController.class)
-                    : new LightBarController(context,
-                            Dependency.get(DarkIconDispatcher.class),
-                            Dependency.get(BatteryController.class),
-                            Dependency.get(NavigationModeController.class));
-            navBar.setLightBarController(lightBarController);
-
-            // TODO(b/118592525): to support multi-display, we start to add something which is
-            //                    per-display, while others may be global. I think it's time to add
-            //                    a new class maybe named DisplayDependency to solve per-display
-            //                    Dependency problem.
-            AutoHideController autoHideController = isOnDefaultDisplay
-                    ? Dependency.get(AutoHideController.class)
-                    : new AutoHideController(context, mHandler,
-                            Dependency.get(IWindowManager.class));
-            navBar.setAutoHideController(autoHideController);
-            navBar.restoreAppearanceAndTransientState();
-            mNavigationBars.put(displayId, navBar);
-
-            if (result != null) {
-                navBar.setImeWindowStatus(display.getDisplayId(), result.mImeToken,
-                        result.mImeWindowVis, result.mImeBackDisposition,
-                        result.mShowImeSwitcher);
-            }
-        });
-    }
-
-    private void removeNavigationBar(int displayId) {
-        NavigationBarFragment navBar = mNavigationBars.get(displayId);
-        if (navBar != null) {
-            navBar.setAutoHideController(/* autoHideController */ null);
-            View navigationWindow = navBar.getView().getRootView();
-            WindowManagerGlobal.getInstance()
-                    .removeView(navigationWindow, true /* immediate */);
-            // Also remove FragmentHostState here in case that onViewDetachedFromWindow has not yet
-            // invoked after display removal.
-            FragmentHostManager.removeAndDestroy(navigationWindow);
-            mNavigationBars.remove(displayId);
-        }
-    }
-
-    /** @see NavigationBarFragment#checkNavBarModes() */
-    public void checkNavBarModes(int displayId) {
-        NavigationBarFragment navBar = mNavigationBars.get(displayId);
-        if (navBar != null) {
-            navBar.checkNavBarModes();
-        }
-    }
-
-    /** @see NavigationBarFragment#finishBarAnimations() */
-    public void finishBarAnimations(int displayId) {
-        NavigationBarFragment navBar = mNavigationBars.get(displayId);
-        if (navBar != null) {
-            navBar.finishBarAnimations();
-        }
-    }
-
-    /** @see NavigationBarFragment#touchAutoDim() */
-    public void touchAutoDim(int displayId) {
-        NavigationBarFragment navBar = mNavigationBars.get(displayId);
-        if (navBar != null) {
-            navBar.touchAutoDim();
-        }
-    }
-
-    /** @see NavigationBarFragment#transitionTo(int, boolean) */
-    public void transitionTo(int displayId, @TransitionMode int barMode, boolean animate) {
-        NavigationBarFragment navBar = mNavigationBars.get(displayId);
-        if (navBar != null) {
-            navBar.transitionTo(barMode, animate);
-        }
-    }
-
-    /** @see NavigationBarFragment#disableAnimationsDuringHide(long) */
-    public void disableAnimationsDuringHide(int displayId, long delay) {
-        NavigationBarFragment navBar = mNavigationBars.get(displayId);
-        if (navBar != null) {
-            navBar.disableAnimationsDuringHide(delay);
-        }
-    }
-
-    /** @return {@link NavigationBarView} on the default display. */
-    public @Nullable NavigationBarView getDefaultNavigationBarView() {
-        return getNavigationBarView(DEFAULT_DISPLAY);
-    }
-
-    /**
-     * @param displayId the ID of display which Navigation bar is on
-     * @return {@link NavigationBarView} on the display with {@code displayId}.
-     *         {@code null} if no navigation bar on that display.
-     */
-    public @Nullable NavigationBarView getNavigationBarView(int displayId) {
-        NavigationBarFragment navBar = mNavigationBars.get(displayId);
-        return (navBar == null) ? null : (NavigationBarView) navBar.getView();
-    }
-
-    /** @return {@link NavigationBarFragment} on the default display. */
-    @Nullable
-    public NavigationBarFragment getDefaultNavigationBarFragment() {
-        return mNavigationBars.get(DEFAULT_DISPLAY);
-    }
-
-    /** @return {@link AssistHandleViewController} (only on the default display). */
-    @Nullable
-    public AssistHandleViewController getAssistHandlerViewController() {
-        NavigationBarFragment navBar = getDefaultNavigationBarFragment();
-        return navBar == null ? null : navBar.getAssistHandlerViewController();
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationClickNotifier.kt b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationClickNotifier.kt
index 8248fc9..abf81c5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationClickNotifier.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationClickNotifier.kt
@@ -4,11 +4,11 @@
 import android.os.RemoteException
 import com.android.internal.statusbar.IStatusBarService
 import com.android.internal.statusbar.NotificationVisibility
+import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Main
 import com.android.systemui.util.Assert
 import java.util.concurrent.Executor
 import javax.inject.Inject
-import javax.inject.Singleton
 
 /**
  * Class to shim calls to IStatusBarManager#onNotificationClick/#onNotificationActionClick that
@@ -18,7 +18,7 @@
  * NOTE: this class eats exceptions from system server, as no current client of these APIs cares
  * about errors
  */
-@Singleton
+@SysUISingleton
 public class NotificationClickNotifier @Inject constructor(
     val barService: IStatusBarService,
     @Main val mainExecutor: Executor
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationInteractionTracker.kt b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationInteractionTracker.kt
index 9dbec10..2ca1beb 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationInteractionTracker.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationInteractionTracker.kt
@@ -1,16 +1,16 @@
 package com.android.systemui.statusbar
 
+import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.statusbar.notification.NotificationEntryManager
 import com.android.systemui.statusbar.notification.collection.NotificationEntry
 import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener
 import javax.inject.Inject
-import javax.inject.Singleton
 
 /**
  * Class to track user interaction with notifications. It's a glorified map of key : bool that can
  * merge multiple "user interacted with notification" signals into a single place.
  */
-@Singleton
+@SysUISingleton
 class NotificationInteractionTracker @Inject constructor(
     private val clicker: NotificationClickNotifier,
     private val entryManager: NotificationEntryManager
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java
index 03424c4..8d82270 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java
@@ -48,6 +48,7 @@
 import com.android.systemui.Dependency;
 import com.android.systemui.Dumpable;
 import com.android.systemui.broadcast.BroadcastDispatcher;
+import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.plugins.statusbar.StatusBarStateController.StateListener;
@@ -65,13 +66,12 @@
 import java.util.List;
 
 import javax.inject.Inject;
-import javax.inject.Singleton;
 
 /**
  * Handles keeping track of the current user, profiles, and various things related to hiding
  * contents, redacting notifications, and the lockscreen.
  */
-@Singleton
+@SysUISingleton
 public class NotificationLockscreenUserManagerImpl implements
         Dumpable, NotificationLockscreenUserManager, StateListener {
     private static final String TAG = "LockscreenUserManager";
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java
index 739d30c..c01bdc4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java
@@ -60,7 +60,6 @@
 import com.android.systemui.statusbar.phone.BiometricUnlockController;
 import com.android.systemui.statusbar.phone.KeyguardBypassController;
 import com.android.systemui.statusbar.phone.LockscreenWallpaper;
-import com.android.systemui.statusbar.phone.NotificationShadeWindowController;
 import com.android.systemui.statusbar.phone.ScrimController;
 import com.android.systemui.statusbar.phone.ScrimState;
 import com.android.systemui.statusbar.phone.StatusBar;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationRemoteInputManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationRemoteInputManager.java
index 6b023c0..9586795 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationRemoteInputManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationRemoteInputManager.java
@@ -161,7 +161,9 @@
                 ActivityManager.getService().resumeAppSwitches();
             } catch (RemoteException e) {
             }
-            return mCallback.handleRemoteViewClick(view, pendingIntent, () -> {
+            Notification.Action action = getActionFromView(view, entry, pendingIntent);
+            return mCallback.handleRemoteViewClick(view, pendingIntent,
+                    action == null ? false : action.isAuthenticationRequired(), () -> {
                 Pair<Intent, ActivityOptions> options = response.getLaunchOptions(view);
                 options.second.setLaunchWindowingMode(
                         WINDOWING_MODE_FULLSCREEN_OR_SPLIT_SCREEN_SECONDARY);
@@ -170,23 +172,45 @@
             });
         }
 
+        private @Nullable Notification.Action getActionFromView(View view,
+                NotificationEntry entry, PendingIntent actionIntent) {
+            Integer actionIndex = (Integer)
+                    view.getTag(com.android.internal.R.id.notification_action_index_tag);
+            if (actionIndex == null) {
+                return null;
+            }
+            if (entry == null) {
+                Log.w(TAG, "Couldn't determine notification for click.");
+                return null;
+            }
+
+            // Notification may be updated before this function is executed, and thus play safe
+            // here and verify that the action object is still the one that where the click happens.
+            StatusBarNotification statusBarNotification = entry.getSbn();
+            Notification.Action[] actions = statusBarNotification.getNotification().actions;
+            if (actions == null || actionIndex >= actions.length) {
+                Log.w(TAG, "statusBarNotification.getNotification().actions is null or invalid");
+                return null ;
+            }
+            final Notification.Action action =
+                    statusBarNotification.getNotification().actions[actionIndex];
+            if (!Objects.equals(action.actionIntent, actionIntent)) {
+                Log.w(TAG, "actionIntent does not match");
+                return null;
+            }
+            return action;
+        }
+
         private void logActionClick(
                 View view,
                 NotificationEntry entry,
                 PendingIntent actionIntent) {
-            Integer actionIndex = (Integer)
-                    view.getTag(com.android.internal.R.id.notification_action_index_tag);
-            if (actionIndex == null) {
-                // Custom action button, not logging.
+            Notification.Action action = getActionFromView(view, entry, actionIntent);
+            if (action == null) {
                 return;
             }
             ViewParent parent = view.getParent();
-            if (entry == null) {
-                Log.w(TAG, "Couldn't determine notification for click.");
-                return;
-            }
-            StatusBarNotification statusBarNotification = entry.getSbn();
-            String key = statusBarNotification.getKey();
+            String key = entry.getSbn().getKey();
             int buttonIndex = -1;
             // If this is a default template, determine the index of the button.
             if (view.getId() == com.android.internal.R.id.action0 &&
@@ -198,19 +222,6 @@
             final int rank = mEntryManager
                     .getActiveNotificationUnfiltered(key).getRanking().getRank();
 
-            // Notification may be updated before this function is executed, and thus play safe
-            // here and verify that the action object is still the one that where the click happens.
-            Notification.Action[] actions = statusBarNotification.getNotification().actions;
-            if (actions == null || actionIndex >= actions.length) {
-                Log.w(TAG, "statusBarNotification.getNotification().actions is null or invalid");
-                return;
-            }
-            final Notification.Action action =
-                    statusBarNotification.getNotification().actions[actionIndex];
-            if (!Objects.equals(action.actionIntent, actionIntent)) {
-                Log.w(TAG, "actionIntent does not match");
-                return;
-            }
             NotificationVisibility.NotificationLocation location =
                     NotificationLogger.getNotificationLocation(
                             mEntryManager.getActiveNotificationUnfiltered(key));
@@ -813,11 +824,12 @@
          *
          * @param view
          * @param pendingIntent
+         * @param appRequestedAuth
          * @param defaultHandler
          * @return  true iff the click was handled
          */
         boolean handleRemoteViewClick(View view, PendingIntent pendingIntent,
-                ClickHandler defaultHandler);
+                boolean appRequestedAuth, ClickHandler defaultHandler);
     }
 
     /**
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeDepthController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeDepthController.kt
index 0445c98..c1196d6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeDepthController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeDepthController.kt
@@ -32,27 +32,26 @@
 import com.android.internal.util.IndentingPrintWriter
 import com.android.systemui.Dumpable
 import com.android.systemui.Interpolators
+import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dump.DumpManager
 import com.android.systemui.plugins.statusbar.StatusBarStateController
 import com.android.systemui.statusbar.notification.ActivityLaunchAnimator
 import com.android.systemui.statusbar.phone.BiometricUnlockController
 import com.android.systemui.statusbar.phone.BiometricUnlockController.MODE_WAKE_AND_UNLOCK
 import com.android.systemui.statusbar.phone.DozeParameters
-import com.android.systemui.statusbar.phone.NotificationShadeWindowController
 import com.android.systemui.statusbar.phone.PanelExpansionListener
 import com.android.systemui.statusbar.phone.ScrimController
 import com.android.systemui.statusbar.policy.KeyguardStateController
 import java.io.FileDescriptor
 import java.io.PrintWriter
 import javax.inject.Inject
-import javax.inject.Singleton
 import kotlin.math.max
 import kotlin.math.sign
 
 /**
  * Controller responsible for statusbar window blur.
  */
-@Singleton
+@SysUISingleton
 class NotificationShadeDepthController @Inject constructor(
     private val statusBarStateController: StatusBarStateController,
     private val blurUtils: BlurUtils,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeWindowController.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeWindowController.java
new file mode 100644
index 0000000..24515f7
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeWindowController.java
@@ -0,0 +1,191 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar;
+
+import android.view.ViewGroup;
+
+import androidx.annotation.Nullable;
+
+import com.android.systemui.statusbar.phone.StatusBarWindowCallback;
+
+import java.util.function.Consumer;
+
+/**
+ * Interface to control the state of the notification shade window. Not all methods of this
+ * interface will be used by each implementation of {@link NotificationShadeWindowController}.
+ */
+public interface NotificationShadeWindowController extends RemoteInputController.Callback {
+
+    /**
+     * Registers a {@link StatusBarWindowCallback} to receive notifications about status bar
+     * window state changes.
+     */
+    default void registerCallback(StatusBarWindowCallback callback) {}
+
+    /** Notifies the registered {@link StatusBarWindowCallback} instances. */
+    default void notifyStateChangedCallbacks() {}
+
+    /**
+     * Registers a listener to monitor scrims visibility.
+     *
+     * @param listener A listener to monitor scrims visibility
+     */
+    default void setScrimsVisibilityListener(Consumer<Integer> listener) {}
+
+    /**
+     * Adds the notification shade view to the window manager.
+     */
+    default void attach() {}
+
+    /** Sets the notification shade view. */
+    default void setNotificationShadeView(ViewGroup view) {}
+
+    /** Gets the notification shade view. */
+    @Nullable
+    default ViewGroup getNotificationShadeView() {
+        return null;
+    }
+
+    /** Sets the state of whether the keyguard is currently showing or not. */
+    default void setKeyguardShowing(boolean showing) {}
+
+    /** Sets the state of whether the keyguard is currently occluded or not. */
+    default void setKeyguardOccluded(boolean occluded) {}
+
+    /** Sets the state of whether the keyguard is currently needs input or not. */
+    default void setKeyguardNeedsInput(boolean needsInput) {}
+
+    /** Sets the state of whether the notification shade panel is currently visible or not. */
+    default void setPanelVisible(boolean visible) {}
+
+    /** Sets the state of whether the notification shade is focusable or not. */
+    default void setNotificationShadeFocusable(boolean focusable) {}
+
+    /** Sets the state of whether the bouncer is showing or not. */
+    default void setBouncerShowing(boolean showing) {}
+
+    /** Sets the state of whether the backdrop is showing or not. */
+    default void setBackdropShowing(boolean showing) {}
+
+    /** Sets the state of whether the keyguard is fading away or not. */
+    default void setKeyguardFadingAway(boolean keyguardFadingAway) {}
+
+    /** Sets the state of whether the quick settings is expanded or not. */
+    default void setQsExpanded(boolean expanded) {}
+
+    /** Sets the state of whether the user activities are forced or not. */
+    default void setForceUserActivity(boolean forceUserActivity) {}
+
+    /** Sets the state of whether the user activities are forced or not. */
+    default void setLaunchingActivity(boolean launching) {}
+
+    /** Sets the state of whether the scrim is visible or not. */
+    default void setScrimsVisibility(int scrimsVisibility) {}
+
+    /** Sets the background blur radius of the notification shade window. */
+    default void setBackgroundBlurRadius(int backgroundBlurRadius) {}
+
+    /** Sets the state of whether heads up is showing or not. */
+    default void setHeadsUpShowing(boolean showing) {}
+
+    /** Sets whether the wallpaper supports ambient mode or not. */
+    default void setWallpaperSupportsAmbientMode(boolean supportsAmbientMode) {}
+
+    /** Gets whether the wallpaper is showing or not. */
+    default boolean isShowingWallpaper() {
+        return false;
+    }
+
+    /** Sets whether the window was collapsed by force or not. */
+    default void setForceWindowCollapsed(boolean force) {}
+
+    /** Sets whether panel is expanded or not. */
+    default void setPanelExpanded(boolean isExpanded) {}
+
+    /** Gets whether the panel is expanded or not. */
+    default boolean getPanelExpanded() {
+        return false;
+    }
+
+    /** Sets the state of whether the remote input is active or not. */
+    default void onRemoteInputActive(boolean remoteInputActive) {}
+
+    /** Sets the screen brightness level for when the device is dozing. */
+    default void setDozeScreenBrightness(int value) {}
+
+    /**
+     * Sets whether the screen brightness is forced to the value we use for doze mode by the status
+     * bar window. No-op if the device does not support dozing.
+     */
+    default void setForceDozeBrightness(boolean forceDozeBrightness) {}
+
+    /** Sets the state of whether sysui is dozing or not. */
+    default void setDozing(boolean dozing) {}
+
+    /** Sets the state of whether plugin open is forced or not. */
+    default void setForcePluginOpen(boolean forcePluginOpen) {}
+
+    /** Gets whether we are forcing plugin open or not. */
+    default boolean getForcePluginOpen() {
+        return false;
+    }
+
+    /** Sets the state of whether the notification shade is touchable or not. */
+    default void setNotTouchable(boolean notTouchable) {}
+
+    /** Sets a {@link OtherwisedCollapsedListener}. */
+    default void setStateListener(OtherwisedCollapsedListener listener) {}
+
+    /** Sets a {@link ForcePluginOpenListener}. */
+    default void setForcePluginOpenListener(ForcePluginOpenListener listener) {}
+
+    /** Sets whether the system is in a state where the keyguard is going away. */
+    default void setKeyguardGoingAway(boolean goingAway) {}
+
+    /**
+     * SystemUI may need top-ui to avoid jank when performing animations.  After the
+     * animation is performed, the component should remove itself from the list of features that
+     * are forcing SystemUI to be top-ui.
+     */
+    default void setRequestTopUi(boolean requestTopUi, String componentTag) {}
+
+    /**
+     * Under low light conditions, we might want to increase the display brightness on devices that
+     * don't have an IR camera.
+     * @param brightness float from 0 to 1 or {@code LayoutParams.BRIGHTNESS_OVERRIDE_NONE}
+     */
+    default void setFaceAuthDisplayBrightness(float brightness) {}
+
+    /**
+     * Custom listener to pipe data back to plugins about whether or not the status bar would be
+     * collapsed if not for the plugin.
+     * TODO: Find cleaner way to do this.
+     */
+    interface OtherwisedCollapsedListener {
+        void setWouldOtherwiseCollapse(boolean otherwiseCollapse);
+    }
+
+    /**
+     * Listener to indicate forcePluginOpen has changed
+     */
+    interface ForcePluginOpenListener {
+        /**
+         * Called when mState.forcePluginOpen is changed
+         */
+        void onChange(boolean forceOpen);
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java
index d798692..8f3033e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java
@@ -18,7 +18,6 @@
 
 import static com.android.systemui.Interpolators.FAST_OUT_SLOW_IN_REVERSE;
 import static com.android.systemui.statusbar.phone.NotificationIconContainer.IconState.NO_VALUE;
-import static com.android.systemui.util.InjectionInflationController.VIEW_CONTEXT;
 
 import android.content.Context;
 import android.content.res.Configuration;
@@ -26,7 +25,6 @@
 import android.graphics.Rect;
 import android.os.SystemProperties;
 import android.util.AttributeSet;
-import android.util.Log;
 import android.util.MathUtils;
 import android.view.DisplayCutout;
 import android.view.View;
@@ -36,10 +34,8 @@
 import android.view.accessibility.AccessibilityNodeInfo;
 
 import com.android.internal.annotations.VisibleForTesting;
-import com.android.systemui.Dependency;
 import com.android.systemui.Interpolators;
 import com.android.systemui.R;
-import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.plugins.statusbar.StatusBarStateController.StateListener;
 import com.android.systemui.statusbar.notification.NotificationUtils;
 import com.android.systemui.statusbar.notification.row.ActivatableNotificationView;
@@ -48,14 +44,10 @@
 import com.android.systemui.statusbar.notification.stack.AmbientState;
 import com.android.systemui.statusbar.notification.stack.AnimationProperties;
 import com.android.systemui.statusbar.notification.stack.ExpandableViewState;
-import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout;
+import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController;
 import com.android.systemui.statusbar.notification.stack.ViewState;
-import com.android.systemui.statusbar.phone.KeyguardBypassController;
 import com.android.systemui.statusbar.phone.NotificationIconContainer;
 
-import javax.inject.Inject;
-import javax.inject.Named;
-
 /**
  * A notification shelf view that is placed inside the notification scroller. It manages the
  * overflow icons that don't fit into the regular list anymore.
@@ -69,7 +61,6 @@
             = SystemProperties.getBoolean("debug.icon_scroll_animations", true);
     private static final int TAG_CONTINUOUS_CLIPPING = R.id.continuous_clipping_tag;
     private static final String TAG = "NotificationShelf";
-    private final KeyguardBypassController mBypassController;
 
     private NotificationIconContainer mShelfIcons;
     private int[] mTmp = new int[2];
@@ -77,9 +68,8 @@
     private int mIconAppearTopPadding;
     private float mHiddenShelfIconSize;
     private int mStatusBarHeight;
-    private int mStatusBarPaddingStart;
     private AmbientState mAmbientState;
-    private NotificationStackScrollLayout mHostLayout;
+    private NotificationStackScrollLayoutController mHostLayoutController;
     private int mMaxLayoutHeight;
     private int mPaddingBetweenElements;
     private int mNotGoneIndex;
@@ -88,7 +78,6 @@
     private int mScrollFastThreshold;
     private int mIconSize;
     private int mStatusBarState;
-    private float mMaxShelfEnd;
     private int mRelativeOffset;
     private boolean mInteractive;
     private float mOpenedAmount;
@@ -99,13 +88,10 @@
     private Rect mClipRect = new Rect();
     private int mCutoutHeight;
     private int mGapHeight;
+    private NotificationShelfController mController;
 
-    @Inject
-    public NotificationShelf(@Named(VIEW_CONTEXT) Context context,
-            AttributeSet attrs,
-            KeyguardBypassController keyguardBypassController) {
+    public NotificationShelf(Context context, AttributeSet attrs) {
         super(context, attrs);
-        mBypassController = keyguardBypassController;
     }
 
     @Override
@@ -128,29 +114,16 @@
         initDimens();
     }
 
-    @Override
-    protected void onAttachedToWindow() {
-        super.onAttachedToWindow();
-        ((SysuiStatusBarStateController) Dependency.get(StatusBarStateController.class))
-                .addCallback(this, SysuiStatusBarStateController.RANK_SHELF);
-    }
-
-    @Override
-    protected void onDetachedFromWindow() {
-        super.onDetachedFromWindow();
-        Dependency.get(StatusBarStateController.class).removeCallback(this);
-    }
-
-    public void bind(AmbientState ambientState, NotificationStackScrollLayout hostLayout) {
+    public void bind(AmbientState ambientState,
+            NotificationStackScrollLayoutController hostLayoutController) {
         mAmbientState = ambientState;
-        mHostLayout = hostLayout;
+        mHostLayoutController = hostLayoutController;
     }
 
     private void initDimens() {
         Resources res = getResources();
         mIconAppearTopPadding = res.getDimensionPixelSize(R.dimen.notification_icon_appear_padding);
         mStatusBarHeight = res.getDimensionPixelOffset(R.dimen.status_bar_height);
-        mStatusBarPaddingStart = res.getDimensionPixelOffset(R.dimen.status_bar_padding_start);
         mPaddingBetweenElements = res.getDimensionPixelSize(R.dimen.notification_divider_height);
 
         ViewGroup.LayoutParams layoutParams = getLayoutParams();
@@ -276,8 +249,8 @@
         float firstElementRoundness = 0.0f;
         ActivatableNotificationView previousAnv = null;
 
-        for (int i = 0; i < mHostLayout.getChildCount(); i++) {
-            ExpandableView child = (ExpandableView) mHostLayout.getChildAt(i);
+        for (int i = 0; i < mHostLayoutController.getChildCount(); i++) {
+            ExpandableView child = (ExpandableView) mHostLayoutController.getChildAt(i);
 
             if (!child.needsClippingToShelf() || child.getVisibility() == GONE) {
                 continue;
@@ -315,9 +288,7 @@
                     transitionAmount = inShelfAmount;
                 }
                 // We don't want to modify the color if the notification is hun'd
-                boolean canModifyColor = mAmbientState.isShadeExpanded()
-                        && !(mAmbientState.isOnKeyguard() && mBypassController.getBypassEnabled());
-                if (isLastChild && canModifyColor) {
+                if (isLastChild && mController.canModifyColorOfNotifications()) {
                     if (colorOfViewBeforeLast == NO_COLOR) {
                         colorOfViewBeforeLast = ownColorUntinted;
                     }
@@ -384,8 +355,8 @@
         mShelfIcons.setSpeedBumpIndex(mAmbientState.getSpeedBumpIndex());
         mShelfIcons.calculateIconTranslations();
         mShelfIcons.applyIconStates();
-        for (int i = 0; i < mHostLayout.getChildCount(); i++) {
-            View child = mHostLayout.getChildAt(i);
+        for (int i = 0; i < mHostLayoutController.getChildCount(); i++) {
+            View child = mHostLayoutController.getChildAt(i);
             if (!(child instanceof ExpandableNotificationRow)
                     || child.getVisibility() == GONE) {
                 continue;
@@ -408,8 +379,8 @@
      * swipes quickly.
      */
     private void clipTransientViews() {
-        for (int i = 0; i < mHostLayout.getTransientViewCount(); i++) {
-            View transientView = mHostLayout.getTransientView(i);
+        for (int i = 0; i < mHostLayoutController.getTransientViewCount(); i++) {
+            View transientView = mHostLayoutController.getTransientView(i);
             if (transientView instanceof ExpandableView) {
                 ExpandableView transientExpandableView = (ExpandableView) transientView;
                 updateNotificationClipHeight(transientExpandableView, getTranslationY(), -1);
@@ -648,7 +619,7 @@
             // We need to persist this, since after the expansion, the behavior should still be the
             // same.
             float position = mAmbientState.getIntrinsicPadding()
-                    + mHostLayout.getPositionInLinearLayout(view);
+                    + mHostLayoutController.getPositionInLinearLayout(view);
             int maxShelfStart = mMaxLayoutHeight - getIntrinsicHeight();
             if (position < maxShelfStart && position + view.getIntrinsicHeight() >= maxShelfStart
                     && view.getTranslationY() < position) {
@@ -999,10 +970,6 @@
         return mInteractive;
     }
 
-    public void setMaxShelfEnd(float maxShelfEnd) {
-        mMaxShelfEnd = maxShelfEnd;
-    }
-
     public void setAnimationsEnabled(boolean enabled) {
         mAnimationsEnabled = enabled;
         if (!enabled) {
@@ -1044,6 +1011,10 @@
         updateBackgroundColors();
     }
 
+    public void setController(NotificationShelfController notificationShelfController) {
+        mController = notificationShelfController;
+    }
+
     private class ShelfState extends ExpandableViewState {
         private float openedAmount;
         private boolean hasItemsInStableShelf;
@@ -1056,7 +1027,6 @@
             }
 
             super.applyToView(view);
-            setMaxShelfEnd(maxShelfEnd);
             setOpenedAmount(openedAmount);
             updateAppearance();
             setHasItemsInStableShelf(hasItemsInStableShelf);
@@ -1070,7 +1040,6 @@
             }
 
             super.animateTo(child, properties);
-            setMaxShelfEnd(maxShelfEnd);
             setOpenedAmount(openedAmount);
             updateAppearance();
             setHasItemsInStableShelf(hasItemsInStableShelf);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelfController.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelfController.java
new file mode 100644
index 0000000..77abcfa
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelfController.java
@@ -0,0 +1,122 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar;
+
+import android.view.View;
+
+import com.android.systemui.statusbar.notification.row.ActivatableNotificationViewController;
+import com.android.systemui.statusbar.notification.row.dagger.NotificationRowScope;
+import com.android.systemui.statusbar.notification.stack.AmbientState;
+import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController;
+import com.android.systemui.statusbar.phone.KeyguardBypassController;
+import com.android.systemui.statusbar.phone.NotificationIconContainer;
+import com.android.systemui.statusbar.phone.StatusBarNotificationPresenter;
+
+import javax.inject.Inject;
+
+/**
+ * Controller class for {@link NotificationShelf}.
+ */
+@NotificationRowScope
+public class NotificationShelfController {
+    private final NotificationShelf mView;
+    private final ActivatableNotificationViewController mActivatableNotificationViewController;
+    private final KeyguardBypassController mKeyguardBypassController;
+    private final SysuiStatusBarStateController mStatusBarStateController;
+    private final View.OnAttachStateChangeListener mOnAttachStateChangeListener;
+    private AmbientState mAmbientState;
+
+    @Inject
+    public NotificationShelfController(NotificationShelf notificationShelf,
+            ActivatableNotificationViewController activatableNotificationViewController,
+            KeyguardBypassController keyguardBypassController,
+            SysuiStatusBarStateController statusBarStateController) {
+        mView = notificationShelf;
+        mActivatableNotificationViewController = activatableNotificationViewController;
+        mKeyguardBypassController = keyguardBypassController;
+        mStatusBarStateController = statusBarStateController;
+        mOnAttachStateChangeListener = new View.OnAttachStateChangeListener() {
+            @Override
+            public void onViewAttachedToWindow(View v) {
+                mStatusBarStateController.addCallback(
+                        mView, SysuiStatusBarStateController.RANK_SHELF);
+            }
+
+            @Override
+            public void onViewDetachedFromWindow(View v) {
+                mStatusBarStateController.removeCallback(mView);
+            }
+        };
+    }
+
+    public void init() {
+        mActivatableNotificationViewController.init();
+        mView.setController(this);
+        mView.addOnAttachStateChangeListener(mOnAttachStateChangeListener);
+        if (mView.isAttachedToWindow()) {
+            mOnAttachStateChangeListener.onViewAttachedToWindow(mView);
+        }
+    }
+
+    public NotificationShelf getView() {
+        return mView;
+    }
+
+    public boolean canModifyColorOfNotifications() {
+        return mAmbientState.isShadeExpanded()
+                && !(mAmbientState.isOnKeyguard() && mKeyguardBypassController.getBypassEnabled());
+    }
+
+    public NotificationIconContainer getShelfIcons() {
+        return mView.getShelfIcons();
+    }
+
+    public void setCollapsedIcons(NotificationIconContainer notificationIcons) {
+        mView.setCollapsedIcons(notificationIcons);
+    }
+
+    public void bind(AmbientState ambientState,
+            NotificationStackScrollLayoutController notificationStackScrollLayoutController) {
+        mView.bind(ambientState, notificationStackScrollLayoutController);
+        mAmbientState = ambientState;
+    }
+
+    public int getHeight() {
+        return mView.getHeight();
+    }
+
+    public void updateState(AmbientState ambientState) {
+        mAmbientState = ambientState;
+        mView.updateState(ambientState);
+    }
+
+    public int getIntrinsicHeight() {
+        return mView.getIntrinsicHeight();
+    }
+
+    public void setOnActivatedListener(StatusBarNotificationPresenter presenter) {
+        mView.setOnActivatedListener(presenter);
+    }
+
+    public void setOnClickListener(View.OnClickListener onClickListener) {
+        mView.setOnClickListener(onClickListener);
+    }
+
+    public int getNotGoneIndex() {
+        return mView.getNotGoneIndex();
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationViewHierarchyManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationViewHierarchyManager.java
index 02a8fee..1cd1b60 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationViewHierarchyManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationViewHierarchyManager.java
@@ -21,7 +21,6 @@
 import android.os.Handler;
 import android.os.Trace;
 import android.os.UserHandle;
-import android.service.notification.NotificationListenerService.Ranking;
 import android.util.Log;
 import android.view.View;
 import android.view.ViewGroup;
@@ -35,9 +34,9 @@
 import com.android.systemui.statusbar.notification.DynamicChildBindController;
 import com.android.systemui.statusbar.notification.DynamicPrivacyController;
 import com.android.systemui.statusbar.notification.NotificationEntryManager;
-import com.android.systemui.statusbar.notification.VisualStabilityManager;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 import com.android.systemui.statusbar.notification.collection.inflation.LowPriorityInflationHelper;
+import com.android.systemui.statusbar.notification.collection.legacy.VisualStabilityManager;
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
 import com.android.systemui.statusbar.notification.stack.ForegroundServiceSectionController;
 import com.android.systemui.statusbar.notification.stack.NotificationListContainer;
@@ -491,7 +490,6 @@
                 }
             }
 
-            row.showAppOpsIcons(entry.mActiveAppOps);
             row.showFeedbackIcon(mAssistantFeedbackController.showFeedbackIndicator(entry));
             row.setLastAudiblyAlertedMs(entry.getLastAudiblyAlertedMs());
         }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/OperatorNameView.java b/packages/SystemUI/src/com/android/systemui/statusbar/OperatorNameView.java
index 0a7ee3b..2aba103 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/OperatorNameView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/OperatorNameView.java
@@ -28,8 +28,8 @@
 import com.android.keyguard.KeyguardUpdateMonitor;
 import com.android.keyguard.KeyguardUpdateMonitorCallback;
 import com.android.settingslib.WirelessUtils;
-import com.android.systemui.DemoMode;
 import com.android.systemui.Dependency;
+import com.android.systemui.demomode.DemoModeCommandReceiver;
 import com.android.systemui.plugins.DarkIconDispatcher;
 import com.android.systemui.plugins.DarkIconDispatcher.DarkReceiver;
 import com.android.systemui.statusbar.policy.NetworkController;
@@ -40,7 +40,8 @@
 
 import java.util.List;
 
-public class OperatorNameView extends TextView implements DemoMode, DarkReceiver,
+/** Shows the operator name */
+public class OperatorNameView extends TextView implements DemoModeCommandReceiver, DarkReceiver,
         SignalCallback, Tunable {
 
     private static final String KEY_SHOW_OPERATOR_NAME = "show_operator_name";
@@ -103,14 +104,18 @@
 
     @Override
     public void dispatchDemoCommand(String command, Bundle args) {
-        if (!mDemoMode && command.equals(COMMAND_ENTER)) {
-            mDemoMode = true;
-        } else if (mDemoMode && command.equals(COMMAND_EXIT)) {
-            mDemoMode = false;
-            update();
-        } else if (mDemoMode && command.equals(COMMAND_OPERATOR)) {
-            setText(args.getString("name"));
-        }
+        setText(args.getString("name"));
+    }
+
+    @Override
+    public void onDemoModeStarted() {
+        mDemoMode = true;
+    }
+
+    @Override
+    public void onDemoModeFinished() {
+        mDemoMode = false;
+        update();
     }
 
     private void update() {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/PulseExpansionHandler.kt b/packages/SystemUI/src/com/android/systemui/statusbar/PulseExpansionHandler.kt
index 7b25853..ba54d1b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/PulseExpansionHandler.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/PulseExpansionHandler.kt
@@ -30,24 +30,24 @@
 import com.android.systemui.Gefingerpoken
 import com.android.systemui.Interpolators
 import com.android.systemui.R
+import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.plugins.FalsingManager
 import com.android.systemui.plugins.statusbar.StatusBarStateController
 import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow
 import com.android.systemui.statusbar.notification.row.ExpandableView
 import com.android.systemui.statusbar.notification.stack.NotificationRoundnessManager
-import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout
+import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController
 import com.android.systemui.statusbar.phone.HeadsUpManagerPhone
 import com.android.systemui.statusbar.phone.KeyguardBypassController
 import com.android.systemui.statusbar.phone.ShadeController
 import javax.inject.Inject
-import javax.inject.Singleton
 import kotlin.math.max
 
 /**
  * A utility class to enable the downward swipe on when pulsing.
  */
-@Singleton
+@SysUISingleton
 class PulseExpansionHandler @Inject
 constructor(
     context: Context,
@@ -93,7 +93,7 @@
         private set
     private val mTouchSlop: Float
     private lateinit var expansionCallback: ExpansionCallback
-    private lateinit var stackScroller: NotificationStackScrollLayout
+    private lateinit var stackScrollerController: NotificationStackScrollLayoutController
     private val mTemp2 = IntArray(2)
     private var mDraggedFarEnough: Boolean = false
     private var mStartingChild: ExpandableView? = null
@@ -315,23 +315,23 @@
     private fun findView(x: Float, y: Float): ExpandableView? {
         var totalX = x
         var totalY = y
-        stackScroller.getLocationOnScreen(mTemp2)
+        stackScrollerController.getLocationOnScreen(mTemp2)
         totalX += mTemp2[0].toFloat()
         totalY += mTemp2[1].toFloat()
-        val childAtRawPosition = stackScroller.getChildAtRawPosition(totalX, totalY)
+        val childAtRawPosition = stackScrollerController.getChildAtRawPosition(totalX, totalY)
         return if (childAtRawPosition != null && childAtRawPosition.isContentExpandable) {
             childAtRawPosition
         } else null
     }
 
     fun setUp(
-        stackScroller: NotificationStackScrollLayout,
+        stackScrollerController: NotificationStackScrollLayoutController,
         expansionCallback: ExpansionCallback,
         shadeController: ShadeController
     ) {
         this.expansionCallback = expansionCallback
         this.shadeController = shadeController
-        this.stackScroller = stackScroller
+        this.stackScrollerController = stackScrollerController
     }
 
     fun setPulsing(pulsing: Boolean) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerImpl.java
index 2bef355..e944249 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerImpl.java
@@ -23,11 +23,14 @@
 import android.util.Log;
 import android.view.animation.Interpolator;
 
+import androidx.annotation.NonNull;
+
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.logging.UiEventLogger;
 import com.android.systemui.DejankUtils;
 import com.android.systemui.Dumpable;
 import com.android.systemui.Interpolators;
+import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.plugins.statusbar.StatusBarStateController.StateListener;
 import com.android.systemui.statusbar.notification.stack.StackStateAnimator;
 import com.android.systemui.statusbar.policy.CallbackController;
@@ -38,12 +41,11 @@
 import java.util.Comparator;
 
 import javax.inject.Inject;
-import javax.inject.Singleton;
 
 /**
  * Tracks and reports on {@link StatusBarState}.
  */
-@Singleton
+@SysUISingleton
 public class StatusBarStateControllerImpl implements SysuiStatusBarStateController,
         CallbackController<StateListener>, Dumpable {
     private static final String TAG = "SbStateController";
@@ -101,6 +103,11 @@
     private boolean mIsDozing;
 
     /**
+     * If the status bar is currently expanded or not.
+     */
+    private boolean mIsExpanded;
+
+    /**
      * Current {@link #mDozeAmount} animator.
      */
     private ValueAnimator mDarkAnimator;
@@ -188,6 +195,26 @@
     }
 
     @Override
+    public boolean isExpanded() {
+        return mIsExpanded;
+    }
+
+    @Override
+    public boolean setPanelExpanded(boolean expanded) {
+        if (mIsExpanded == expanded) {
+            return false;
+        }
+        mIsExpanded = expanded;
+        String tag = getClass().getSimpleName() + "#setIsExpanded";
+        DejankUtils.startDetectingBlockingIpcs(tag);
+        for (RankedListener rl : new ArrayList<>(mListeners)) {
+            rl.mListener.onExpandedChanged(mIsExpanded);
+        }
+        DejankUtils.stopDetectingBlockingIpcs(tag);
+        return true;
+    }
+
+    @Override
     public float getInterpolatedDozeAmount() {
         return mDozeInterpolator.getInterpolation(mDozeAmount);
     }
@@ -276,7 +303,7 @@
     }
 
     @Override
-    public void addCallback(StateListener listener) {
+    public void addCallback(@NonNull StateListener listener) {
         synchronized (mListeners) {
             addListenerInternalLocked(listener, Integer.MAX_VALUE);
         }
@@ -316,7 +343,7 @@
 
 
     @Override
-    public void removeCallback(StateListener listener) {
+    public void removeCallback(@NonNull StateListener listener) {
         synchronized (mListeners) {
             mListeners.removeIf((it) -> it.mListener.equals(listener));
         }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/SuperStatusBarViewFactory.java b/packages/SystemUI/src/com/android/systemui/statusbar/SuperStatusBarViewFactory.java
index 7cda235..1ec043c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/SuperStatusBarViewFactory.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/SuperStatusBarViewFactory.java
@@ -21,7 +21,8 @@
 import android.view.ViewGroup;
 
 import com.android.systemui.R;
-import com.android.systemui.statusbar.notification.row.dagger.NotificationRowComponent;
+import com.android.systemui.dagger.SysUISingleton;
+import com.android.systemui.statusbar.notification.row.dagger.NotificationShelfComponent;
 import com.android.systemui.statusbar.phone.LockIcon;
 import com.android.systemui.statusbar.phone.LockscreenLockIconController;
 import com.android.systemui.statusbar.phone.NotificationPanelView;
@@ -30,33 +31,32 @@
 import com.android.systemui.util.InjectionInflationController;
 
 import javax.inject.Inject;
-import javax.inject.Singleton;
 
 /**
  * Creates a single instance of super_status_bar and super_notification_shade that can be shared
  * across various system ui objects.
  */
-@Singleton
+@SysUISingleton
 public class SuperStatusBarViewFactory {
 
     private final Context mContext;
     private final InjectionInflationController mInjectionInflationController;
-    private final NotificationRowComponent.Builder mNotificationRowComponentBuilder;
     private final LockscreenLockIconController mLockIconController;
+    private final NotificationShelfComponent.Builder mNotificationShelfComponentBuilder;
 
     private NotificationShadeWindowView mNotificationShadeWindowView;
     private StatusBarWindowView mStatusBarWindowView;
-    private NotificationShelf mNotificationShelf;
+    private NotificationShelfController mNotificationShelfController;
 
     @Inject
     public SuperStatusBarViewFactory(Context context,
             InjectionInflationController injectionInflationController,
-            NotificationRowComponent.Builder notificationRowComponentBuilder,
+            NotificationShelfComponent.Builder notificationShelfComponentBuilder,
             LockscreenLockIconController lockIconController) {
         mContext = context;
         mInjectionInflationController = injectionInflationController;
-        mNotificationRowComponentBuilder = notificationRowComponentBuilder;
         mLockIconController = lockIconController;
+        mNotificationShelfComponentBuilder = notificationShelfComponentBuilder;
     }
 
     /**
@@ -114,25 +114,27 @@
      *                  isn't immediately attached, but the layout params of this view is used
      *                  during inflation.
      */
-    public NotificationShelf getNotificationShelf(ViewGroup container) {
-        if (mNotificationShelf != null) {
-            return mNotificationShelf;
+    public NotificationShelfController getNotificationShelfController(ViewGroup container) {
+        if (mNotificationShelfController != null) {
+            return mNotificationShelfController;
         }
 
-        mNotificationShelf = (NotificationShelf) mInjectionInflationController.injectable(
-                LayoutInflater.from(mContext)).inflate(R.layout.status_bar_notification_shelf,
-                container, /* attachToRoot= */ false);
+        NotificationShelf view = (NotificationShelf) LayoutInflater.from(mContext)
+                .inflate(R.layout.status_bar_notification_shelf, container, /* attachToRoot= */
+                        false);
 
-        NotificationRowComponent component = mNotificationRowComponentBuilder
-                .activatableNotificationView(mNotificationShelf)
-                .build();
-        component.getActivatableNotificationViewController().init();
-
-        if (mNotificationShelf == null) {
+        if (view == null) {
             throw new IllegalStateException(
                     "R.layout.status_bar_notification_shelf could not be properly inflated");
         }
-        return mNotificationShelf;
+
+        NotificationShelfComponent component = mNotificationShelfComponentBuilder
+                .notificationShelf(view)
+                .build();
+        mNotificationShelfController = component.getNotificationShelfController();
+        mNotificationShelfController.init();
+
+        return mNotificationShelfController;
     }
 
     public NotificationPanelView getNotificationPanelView() {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/SysuiStatusBarStateController.java b/packages/SystemUI/src/com/android/systemui/statusbar/SysuiStatusBarStateController.java
index 07b3550..9f8fe35 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/SysuiStatusBarStateController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/SysuiStatusBarStateController.java
@@ -75,6 +75,14 @@
      */
     void setDozeAmount(float dozeAmount, boolean animated);
 
+
+    /**
+     * Update the expanded state from {@link StatusBar}'s perspective
+     * @param expanded are we expanded?
+     * @return {@code true} if the state changed, else {@code false}
+     */
+    boolean setPanelExpanded(boolean expanded);
+
     /**
      * Sets whether to leave status bar open when hiding keyguard
      */
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/VibratorHelper.java b/packages/SystemUI/src/com/android/systemui/statusbar/VibratorHelper.java
index 442416f..ea90bdd 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/VibratorHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/VibratorHelper.java
@@ -26,12 +26,13 @@
 import android.os.Vibrator;
 import android.provider.Settings;
 
+import com.android.systemui.dagger.SysUISingleton;
+
 import javax.inject.Inject;
-import javax.inject.Singleton;
 
 /**
  */
-@Singleton
+@SysUISingleton
 public class VibratorHelper {
 
     private final Vibrator mVibrator;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarDependenciesModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarDependenciesModule.java
index 992f2da..44550b7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarDependenciesModule.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarDependenciesModule.java
@@ -22,6 +22,7 @@
 
 import com.android.internal.statusbar.IStatusBarService;
 import com.android.systemui.bubbles.BubbleController;
+import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.media.MediaDataManager;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
@@ -33,26 +34,24 @@
 import com.android.systemui.statusbar.NotificationLockscreenUserManager;
 import com.android.systemui.statusbar.NotificationMediaManager;
 import com.android.systemui.statusbar.NotificationRemoteInputManager;
+import com.android.systemui.statusbar.NotificationShadeWindowController;
 import com.android.systemui.statusbar.NotificationViewHierarchyManager;
 import com.android.systemui.statusbar.SmartReplyController;
 import com.android.systemui.statusbar.notification.AssistantFeedbackController;
 import com.android.systemui.statusbar.notification.DynamicChildBindController;
 import com.android.systemui.statusbar.notification.DynamicPrivacyController;
 import com.android.systemui.statusbar.notification.NotificationEntryManager;
-import com.android.systemui.statusbar.notification.VisualStabilityManager;
 import com.android.systemui.statusbar.notification.collection.inflation.LowPriorityInflationHelper;
+import com.android.systemui.statusbar.notification.collection.legacy.VisualStabilityManager;
 import com.android.systemui.statusbar.notification.stack.ForegroundServiceSectionController;
 import com.android.systemui.statusbar.phone.KeyguardBypassController;
 import com.android.systemui.statusbar.phone.NotificationGroupManager;
-import com.android.systemui.statusbar.phone.NotificationShadeWindowController;
 import com.android.systemui.statusbar.phone.StatusBar;
 import com.android.systemui.statusbar.policy.RemoteInputUriController;
 import com.android.systemui.tracing.ProtoTracer;
 import com.android.systemui.util.DeviceConfigProxy;
 import com.android.systemui.util.concurrency.DelayableExecutor;
 
-import javax.inject.Singleton;
-
 import dagger.Lazy;
 import dagger.Module;
 import dagger.Provides;
@@ -65,7 +64,7 @@
 @Module
 public interface StatusBarDependenciesModule {
     /** */
-    @Singleton
+    @SysUISingleton
     @Provides
     static NotificationRemoteInputManager provideNotificationRemoteInputManager(
             Context context,
@@ -92,7 +91,7 @@
     }
 
     /** */
-    @Singleton
+    @SysUISingleton
     @Provides
     static NotificationMediaManager provideNotificationMediaManager(
             Context context,
@@ -117,7 +116,7 @@
     }
 
     /** */
-    @Singleton
+    @SysUISingleton
     @Provides
     static NotificationListener provideNotificationListener(
             Context context,
@@ -128,7 +127,7 @@
     }
 
     /** */
-    @Singleton
+    @SysUISingleton
     @Provides
     static SmartReplyController provideSmartReplyController(
             NotificationEntryManager entryManager,
@@ -138,7 +137,7 @@
     }
 
     /** */
-    @Singleton
+    @SysUISingleton
     @Provides
     static NotificationViewHierarchyManager provideNotificationViewHierarchyManager(
             Context context,
@@ -176,7 +175,7 @@
      * Provides our instance of CommandQueue which is considered optional.
      */
     @Provides
-    @Singleton
+    @SysUISingleton
     static CommandQueue provideCommandQueue(Context context, ProtoTracer protoTracer) {
         return new CommandQueue(context, protoTracer);
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/AssistantFeedbackController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/AssistantFeedbackController.java
index ca0f62e..87a3f07 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/AssistantFeedbackController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/AssistantFeedbackController.java
@@ -20,13 +20,20 @@
 
 import android.content.ContentResolver;
 import android.content.Context;
+import android.database.ContentObserver;
+import android.net.Uri;
+import android.os.Handler;
+import android.os.Looper;
 import android.os.UserHandle;
 import android.provider.Settings;
 
+import androidx.annotation.Nullable;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 
 import javax.inject.Inject;
-import javax.inject.Singleton;
 
 /**
  * Determines whether to show any indicators or controls related to notification assistant.
@@ -34,24 +41,43 @@
  * Flags protect any changes from being shown. Notifications that are adjusted by the assistant
  * should show an indicator.
  */
-@Singleton
-public class AssistantFeedbackController {
+@SysUISingleton
+public class AssistantFeedbackController extends ContentObserver {
+    private final Uri FEEDBACK_URI
+            = Settings.Global.getUriFor(Settings.Global.NOTIFICATION_FEEDBACK_ENABLED);
     private ContentResolver mResolver;
 
+    private boolean mFeedbackEnabled;
+
     /** Injected constructor */
     @Inject
     public AssistantFeedbackController(Context context) {
+        super(new Handler(Looper.getMainLooper()));
         mResolver = context.getContentResolver();
+        mResolver.registerContentObserver(FEEDBACK_URI, false, this, UserHandle.USER_ALL);
+        update(null);
+    }
+
+    @Override
+    public void onChange(boolean selfChange, @Nullable Uri uri, int flags) {
+        update(uri);
+    }
+
+    @VisibleForTesting
+    public void update(@Nullable Uri uri) {
+        if (uri == null || FEEDBACK_URI.equals(uri)) {
+            mFeedbackEnabled = Settings.Global.getInt(mResolver,
+                    Settings.Global.NOTIFICATION_FEEDBACK_ENABLED, 0)
+                    != 0;
+        }
     }
 
     /**
      * Determines whether to show any user controls related to the assistant. This is based on the
-     * settings flag {@link Settings.Secure.NOTIFICATION_FEEDBACK_ENABLED}
+     * settings flag {@link Settings.Global.NOTIFICATION_FEEDBACK_ENABLED}
      */
     public boolean isFeedbackEnabled() {
-        return Settings.Secure.getIntForUser(mResolver,
-                Settings.Secure.NOTIFICATION_FEEDBACK_ENABLED, 0,
-                UserHandle.USER_CURRENT) == 1;
+        return mFeedbackEnabled;
     }
 
     /**
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/ConversationNotifications.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/ConversationNotifications.kt
index 1972b86..c68625c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/ConversationNotifications.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/ConversationNotifications.kt
@@ -24,6 +24,7 @@
 import android.service.notification.NotificationListenerService.RankingMap
 import com.android.internal.statusbar.NotificationVisibility
 import com.android.internal.widget.ConversationLayout
+import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Main
 import com.android.systemui.statusbar.notification.collection.NotificationEntry
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow
@@ -32,7 +33,6 @@
 import com.android.systemui.statusbar.phone.NotificationGroupManager
 import java.util.concurrent.ConcurrentHashMap
 import javax.inject.Inject
-import javax.inject.Singleton
 
 /** Populates additional information in conversation notifications */
 class ConversationNotificationProcessor @Inject constructor(
@@ -61,7 +61,7 @@
  * Tracks state related to conversation notifications, and updates the UI of existing notifications
  * when necessary.
  */
-@Singleton
+@SysUISingleton
 class ConversationNotificationManager @Inject constructor(
     private val notificationEntryManager: NotificationEntryManager,
     private val notificationGroupManager: NotificationGroupManager,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/DynamicPrivacyController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/DynamicPrivacyController.java
index 2ab329e..9482c17 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/DynamicPrivacyController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/DynamicPrivacyController.java
@@ -16,8 +16,10 @@
 
 package com.android.systemui.statusbar.notification;
 
+import android.annotation.Nullable;
 import android.util.ArraySet;
 
+import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.statusbar.NotificationLockscreenUserManager;
 import com.android.systemui.statusbar.StatusBarState;
@@ -25,22 +27,21 @@
 import com.android.systemui.statusbar.policy.KeyguardStateController;
 
 import javax.inject.Inject;
-import javax.inject.Singleton;
 
 /**
  * A controller which dynamically controls the visibility of Notification content
  */
-@Singleton
+@SysUISingleton
 public class DynamicPrivacyController implements KeyguardStateController.Callback {
 
     private final KeyguardStateController mKeyguardStateController;
     private final NotificationLockscreenUserManager mLockscreenUserManager;
     private final StatusBarStateController mStateController;
-    private ArraySet<Listener> mListeners = new ArraySet<>();
+    private final ArraySet<Listener> mListeners = new ArraySet<>();
 
     private boolean mLastDynamicUnlocked;
     private boolean mCacheInvalid;
-    private StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
+    @Nullable private StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
 
     @Inject
     DynamicPrivacyController(NotificationLockscreenUserManager notificationLockscreenUserManager,
@@ -96,8 +97,7 @@
      * contents aren't revealed yet?
      */
     public boolean isInLockedDownShade() {
-        if (!mStatusBarKeyguardViewManager.isShowing()
-                || !mKeyguardStateController.isMethodSecure()) {
+        if (!isStatusBarKeyguardShowing() || !mKeyguardStateController.isMethodSecure()) {
             return false;
         }
         int state = mStateController.getState();
@@ -110,6 +110,10 @@
         return true;
     }
 
+    private boolean isStatusBarKeyguardShowing() {
+        return mStatusBarKeyguardViewManager != null && mStatusBarKeyguardViewManager.isShowing();
+    }
+
     public void setStatusBarKeyguardViewManager(
             StatusBarKeyguardViewManager statusBarKeyguardViewManager) {
         mStatusBarKeyguardViewManager = statusBarKeyguardViewManager;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/ForegroundServiceDismissalFeatureController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/ForegroundServiceDismissalFeatureController.kt
index dfc2fc1..314051c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/ForegroundServiceDismissalFeatureController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/ForegroundServiceDismissalFeatureController.kt
@@ -19,9 +19,9 @@
 import android.content.Context
 import android.provider.DeviceConfig
 import com.android.internal.config.sysui.SystemUiDeviceConfigFlags.NOTIFICATIONS_ALLOW_FGS_DISMISSAL
+import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.util.DeviceConfigProxy
 import javax.inject.Inject
-import javax.inject.Singleton
 
 private var sIsEnabled: Boolean? = null
 
@@ -29,7 +29,7 @@
  * Feature controller for NOTIFICATIONS_ALLOW_FGS_DISMISSAL config.
  */
 // TODO: this is really boilerplatey, make a base class that just wraps the device config
-@Singleton
+@SysUISingleton
 class ForegroundServiceDismissalFeatureController @Inject constructor(
     val proxy: DeviceConfigProxy,
     val context: Context
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/InstantAppNotifier.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/InstantAppNotifier.java
index 312b2c5..c27f663 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/InstantAppNotifier.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/InstantAppNotifier.java
@@ -53,22 +53,23 @@
 import com.android.systemui.Dependency;
 import com.android.systemui.R;
 import com.android.systemui.SystemUI;
+import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dagger.qualifiers.UiBackground;
-import com.android.systemui.stackdivider.Divider;
+import com.android.systemui.stackdivider.SplitScreenController;
 import com.android.systemui.statusbar.CommandQueue;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
 import com.android.systemui.util.NotificationChannels;
 
 import java.util.List;
+import java.util.Optional;
 import java.util.concurrent.Executor;
 
 import javax.inject.Inject;
-import javax.inject.Singleton;
 
 /** The class to show notification(s) of instant apps. This may show multiple notifications on
  * splitted screen.
  */
-@Singleton
+@SysUISingleton
 public class InstantAppNotifier extends SystemUI
         implements CommandQueue.Callbacks, KeyguardStateController.Callback {
     private static final String TAG = "InstantAppNotifier";
@@ -80,13 +81,14 @@
     private final CommandQueue mCommandQueue;
     private boolean mDockedStackExists;
     private KeyguardStateController mKeyguardStateController;
-    private final Divider mDivider;
+    private final Optional<SplitScreenController> mSplitScreenControllerOptional;
 
     @Inject
     public InstantAppNotifier(Context context, CommandQueue commandQueue,
-            @UiBackground Executor uiBgExecutor, Divider divider) {
+            @UiBackground Executor uiBgExecutor,
+            Optional<SplitScreenController> splitScreenControllerOptional) {
         super(context);
-        mDivider = divider;
+        mSplitScreenControllerOptional = splitScreenControllerOptional;
         mCommandQueue = commandQueue;
         mUiBgExecutor = uiBgExecutor;
     }
@@ -105,11 +107,11 @@
         mCommandQueue.addCallback(this);
         mKeyguardStateController.addCallback(this);
 
-        mDivider.registerInSplitScreenListener(
-                exists -> {
+        mSplitScreenControllerOptional.ifPresent(splitScreen ->
+                splitScreen.registerInSplitScreenListener(exists -> {
                     mDockedStackExists = exists;
                     updateForegroundInstantApps();
-                });
+                }));
 
         // Clear out all old notifications on startup (only present in the case where sysui dies)
         NotificationManager noMan = mContext.getSystemService(NotificationManager.class);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java
index e1ff872..b5f1c7f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java
@@ -48,6 +48,7 @@
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 import com.android.systemui.statusbar.notification.collection.NotificationRankingManager;
 import com.android.systemui.statusbar.notification.collection.inflation.NotificationRowBinder;
+import com.android.systemui.statusbar.notification.collection.legacy.VisualStabilityManager;
 import com.android.systemui.statusbar.notification.collection.notifcollection.CommonNotifCollection;
 import com.android.systemui.statusbar.notification.collection.notifcollection.DismissedByUserStats;
 import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationFilter.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationFilter.java
index 6335a09..590ccf83 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationFilter.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationFilter.java
@@ -29,6 +29,7 @@
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.systemui.Dependency;
 import com.android.systemui.ForegroundServiceController;
+import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.media.MediaFeatureFlag;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.statusbar.NotificationLockscreenUserManager;
@@ -37,13 +38,12 @@
 import com.android.systemui.statusbar.phone.ShadeController;
 
 import javax.inject.Inject;
-import javax.inject.Singleton;
 
 /** Component which manages the various reasons a notification might be filtered out.*/
 // TODO: delete NotificationFilter.java after migrating to new NotifPipeline b/145659174.
 //  Notification filtering is taken care of across the different Coordinators (mostly
 //  KeyguardCoordinator.java)
-@Singleton
+@SysUISingleton
 public class NotificationFilter {
 
     private final NotificationGroupManager mGroupManager = Dependency.get(
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinator.kt
index f982cf0..1326d92 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinator.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinator.kt
@@ -19,22 +19,21 @@
 import android.animation.ObjectAnimator
 import android.util.FloatProperty
 import com.android.systemui.Interpolators
+import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.plugins.statusbar.StatusBarStateController
 import com.android.systemui.statusbar.StatusBarState
 import com.android.systemui.statusbar.notification.collection.NotificationEntry
-import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout
+import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController
 import com.android.systemui.statusbar.notification.stack.StackStateAnimator
 import com.android.systemui.statusbar.phone.DozeParameters
 import com.android.systemui.statusbar.phone.KeyguardBypassController
-import com.android.systemui.statusbar.phone.NotificationIconAreaController
 import com.android.systemui.statusbar.phone.PanelExpansionListener
 import com.android.systemui.statusbar.policy.HeadsUpManager
 import com.android.systemui.statusbar.policy.OnHeadsUpChangedListener
-
 import javax.inject.Inject
-import javax.inject.Singleton
+import kotlin.math.min
 
-@Singleton
+@SysUISingleton
 class NotificationWakeUpCoordinator @Inject constructor(
     private val mHeadsUpManager: HeadsUpManager,
     private val statusBarStateController: StatusBarStateController,
@@ -53,7 +52,7 @@
             return coordinator.mLinearVisibilityAmount
         }
     }
-    private lateinit var mStackScroller: NotificationStackScrollLayout
+    private lateinit var mStackScrollerController: NotificationStackScrollLayoutController
     private var mVisibilityInterpolator = Interpolators.FAST_OUT_SLOW_IN_REVERSE
 
     private var mLinearDozeAmount: Float = 0.0f
@@ -79,7 +78,7 @@
                 if (mNotificationsVisible && !mNotificationsVisibleForExpansion &&
                     !bypassController.bypassEnabled) {
                     // We're waking up while pulsing, let's make sure the animation looks nice
-                    mStackScroller.wakeUpFromPulse()
+                    mStackScrollerController.wakeUpFromPulse()
                 }
                 if (bypassController.bypassEnabled && !mNotificationsVisible) {
                     // Let's make sure our huns become visible once we are waking up in case
@@ -98,7 +97,6 @@
         }
 
     private var collapsedEnoughToHide: Boolean = false
-    lateinit var iconAreaController: NotificationIconAreaController
 
     var pulsing: Boolean = false
         set(value) {
@@ -156,10 +154,10 @@
         })
     }
 
-    fun setStackScroller(stackScroller: NotificationStackScrollLayout) {
-        mStackScroller = stackScroller
-        pulseExpanding = stackScroller.isPulseExpanding
-        stackScroller.setOnPulseHeightChangedListener {
+    fun setStackScroller(stackScrollerController: NotificationStackScrollLayoutController) {
+        mStackScrollerController = stackScrollerController
+        pulseExpanding = stackScrollerController.isPulseExpanding
+        stackScrollerController.setOnPulseHeightChangedListener {
             val nowExpanding = isPulseExpanding()
             val changed = nowExpanding != pulseExpanding
             pulseExpanding = nowExpanding
@@ -169,7 +167,7 @@
         }
     }
 
-    fun isPulseExpanding(): Boolean = mStackScroller.isPulseExpanding
+    fun isPulseExpanding(): Boolean = mStackScrollerController.isPulseExpanding
 
     /**
      * @param visible should notifications be visible
@@ -249,7 +247,7 @@
         val changed = linear != mLinearDozeAmount
         mLinearDozeAmount = linear
         mDozeAmount = eased
-        mStackScroller.setDozeAmount(mDozeAmount)
+        mStackScrollerController.setDozeAmount(mDozeAmount)
         updateHideAmount()
         if (changed && linear == 0.0f) {
             setNotificationsVisible(visible = false, animate = false, increaseSpeed = false)
@@ -330,18 +328,18 @@
     }
 
     fun getWakeUpHeight(): Float {
-        return mStackScroller.wakeUpHeight
+        return mStackScrollerController.wakeUpHeight
     }
 
     private fun updateHideAmount() {
-        val linearAmount = Math.min(1.0f - mLinearVisibilityAmount, mLinearDozeAmount)
-        val amount = Math.min(1.0f - mVisibilityAmount, mDozeAmount)
-        mStackScroller.setHideAmount(linearAmount, amount)
+        val linearAmount = min(1.0f - mLinearVisibilityAmount, mLinearDozeAmount)
+        val amount = min(1.0f - mVisibilityAmount, mDozeAmount)
+        mStackScrollerController.setHideAmount(linearAmount, amount)
         notificationsFullyHidden = linearAmount == 1.0f
     }
 
     private fun notifyAnimationStart(awake: Boolean) {
-        mStackScroller.notifyHideAnimationStart(!awake)
+        mStackScrollerController.notifyHideAnimationStart(!awake)
     }
 
     override fun onDozingChanged(isDozing: Boolean) {
@@ -355,7 +353,7 @@
      * from a pulse and determines how much the notifications are expanded.
      */
     fun setPulseHeight(height: Float): Float {
-        val overflow = mStackScroller.setPulseHeight(height)
+        val overflow = mStackScrollerController.setPulseHeight(height)
         //  no overflow for the bypass experience
         return if (bypassController.bypassEnabled) 0.0f else overflow
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ListAttachState.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ListAttachState.kt
index 57f8a6a..3bfdf5c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ListAttachState.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ListAttachState.kt
@@ -16,9 +16,9 @@
 
 package com.android.systemui.statusbar.notification.collection
 
+import com.android.systemui.statusbar.notification.collection.listbuilder.NotifSection
 import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifFilter
 import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifPromoter
-import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifSection
 
 /**
  * Stores the state that [ShadeListBuilder] assigns to this [ListEntry]
@@ -35,7 +35,6 @@
      * parent's section. Null if not attached to the list.
      */
     var section: NotifSection?,
-    var sectionIndex: Int,
 
     /**
      * If a [NotifFilter] is excluding this entry from the list, then that filter. Always null for
@@ -46,25 +45,32 @@
     /**
      * The [NotifPromoter] promoting this entry to top-level, if any. Always null for [GroupEntry]s.
      */
-    var promoter: NotifPromoter?
+    var promoter: NotifPromoter?,
+
+    /**
+     * If the [VisualStabilityManager] is suppressing group or section changes for this entry,
+     * suppressedChanges will contain the new parent or section that we would have assigned to
+     * the entry had it not been suppressed by the VisualStabilityManager.
+     */
+    var suppressedChanges: SuppressedAttachState
 ) {
 
     /** Copies the state of another instance. */
     fun clone(other: ListAttachState) {
         parent = other.parent
         section = other.section
-        sectionIndex = other.sectionIndex
         excludingFilter = other.excludingFilter
         promoter = other.promoter
+        suppressedChanges.clone(other.suppressedChanges)
     }
 
     /** Resets back to a "clean" state (the same as created by the factory method) */
     fun reset() {
         parent = null
         section = null
-        sectionIndex = -1
         excludingFilter = null
         promoter = null
+        suppressedChanges.reset()
     }
 
     companion object {
@@ -73,9 +79,9 @@
             return ListAttachState(
                     null,
                     null,
-                    -1,
                     null,
-                    null)
+                    null,
+                SuppressedAttachState.create())
         }
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ListDumper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ListDumper.java
index 3a05201..52c5c3e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ListDumper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ListDumper.java
@@ -112,11 +112,9 @@
                     .append(")");
         }
 
-        if (entry.getNotifSection() != null) {
-            sb.append(" sectionIndex=")
-                    .append(entry.getSection())
-                    .append(" sectionName=")
-                    .append(entry.getNotifSection().getName());
+        if (entry.getSection() != null) {
+            sb.append(" section=")
+                    .append(entry.getSection().getLabel());
         }
 
         if (includeRecordKeeping) {
@@ -167,6 +165,20 @@
                         .append(" ");
             }
 
+            if (notifEntry.getAttachState().getSuppressedChanges().getParent() != null) {
+                rksb.append("suppressedParent=")
+                        .append(notifEntry.getAttachState().getSuppressedChanges()
+                                .getParent().getKey())
+                        .append(" ");
+            }
+
+            if (notifEntry.getAttachState().getSuppressedChanges().getSection() != null) {
+                rksb.append("suppressedSection=")
+                        .append(notifEntry.getAttachState().getSuppressedChanges()
+                                .getSection())
+                        .append(" ");
+            }
+
             if (hasBeenInteractedWith) {
                 rksb.append("interacted=yes ");
             }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ListEntry.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ListEntry.java
index 65f5dc4..82c1f24 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ListEntry.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ListEntry.java
@@ -21,7 +21,7 @@
 
 import androidx.annotation.Nullable;
 
-import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifSection;
+import com.android.systemui.statusbar.notification.collection.listbuilder.NotifSection;
 
 /**
  * Abstract superclass for top-level entries, i.e. things that can appear in the final notification
@@ -78,13 +78,12 @@
         return mPreviousAttachState.getParent();
     }
 
-    /** The section this notification was assigned to (0 to N-1, where N is number of sections). */
-    public int getSection() {
-        return mAttachState.getSectionIndex();
+    @Nullable public NotifSection getSection() {
+        return mAttachState.getSection();
     }
 
-    @Nullable public NotifSection getNotifSection() {
-        return mAttachState.getSection();
+    public int getSectionIndex() {
+        return mAttachState.getSection() != null ? mAttachState.getSection().getIndex() : -1;
     }
 
     ListAttachState getAttachState() {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollection.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollection.java
index 285cf7a..90492b5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollection.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollection.java
@@ -59,6 +59,7 @@
 
 import com.android.internal.statusbar.IStatusBarService;
 import com.android.systemui.Dumpable;
+import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dump.DumpManager;
 import com.android.systemui.dump.LogBufferEulogizer;
 import com.android.systemui.statusbar.FeatureFlags;
@@ -98,7 +99,6 @@
 import java.util.concurrent.TimeUnit;
 
 import javax.inject.Inject;
-import javax.inject.Singleton;
 
 /**
  * Keeps a record of all of the "active" notifications, i.e. the notifications that are currently
@@ -123,7 +123,7 @@
  * events occur.
  */
 @MainThread
-@Singleton
+@SysUISingleton
 public class NotifCollection implements Dumpable {
     private final IStatusBarService mStatusBarService;
     private final SystemClock mClock;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifInflaterImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifInflaterImpl.java
index aaf5c4d..8562a2e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifInflaterImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifInflaterImpl.java
@@ -17,6 +17,7 @@
 package com.android.systemui.statusbar.notification.collection;
 
 import com.android.internal.statusbar.IStatusBarService;
+import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.statusbar.notification.InflationException;
 import com.android.systemui.statusbar.notification.collection.inflation.NotifInflater;
 import com.android.systemui.statusbar.notification.collection.inflation.NotificationRowBinderImpl;
@@ -24,14 +25,13 @@
 import com.android.systemui.statusbar.notification.row.NotificationContentInflater;
 
 import javax.inject.Inject;
-import javax.inject.Singleton;
 
 /**
  * Handles notification inflating, rebinding, and inflation aborting.
  *
  * Currently a wrapper for NotificationRowBinderImpl.
  */
-@Singleton
+@SysUISingleton
 public class NotifInflaterImpl implements NotifInflater {
 
     private final IStatusBarService mStatusBarService;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifPipeline.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifPipeline.java
index 17899e9..a1844ff 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifPipeline.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifPipeline.java
@@ -16,6 +16,7 @@
 
 package com.android.systemui.statusbar.notification.collection;
 
+import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.statusbar.notification.collection.listbuilder.OnBeforeFinalizeFilterListener;
 import com.android.systemui.statusbar.notification.collection.listbuilder.OnBeforeRenderListListener;
 import com.android.systemui.statusbar.notification.collection.listbuilder.OnBeforeSortListener;
@@ -23,7 +24,8 @@
 import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifComparator;
 import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifFilter;
 import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifPromoter;
-import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifSection;
+import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifSectioner;
+import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifStabilityManager;
 import com.android.systemui.statusbar.notification.collection.notifcollection.CommonNotifCollection;
 import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener;
 import com.android.systemui.statusbar.notification.collection.notifcollection.NotifDismissInterceptor;
@@ -33,7 +35,6 @@
 import java.util.List;
 
 import javax.inject.Inject;
-import javax.inject.Singleton;
 
 /**
  * The system that constructs the "shade list", the filtered, grouped, and sorted list of
@@ -68,7 +69,7 @@
  *  9. OnBeforeRenderListListeners are fired ({@link #addOnBeforeRenderListListener})
  *  9. The list is handed off to the view layer to be rendered
  */
-@Singleton
+@SysUISingleton
 public class NotifPipeline implements CommonNotifCollection {
     private final NotifCollection mNotifCollection;
     private final ShadeListBuilder mShadeListBuilder;
@@ -154,10 +155,18 @@
      * Sections that are used to sort top-level entries.  If two entries have the same section,
      * NotifComparators are consulted. Sections from this list are called in order for each
      * notification passed through the pipeline. The first NotifSection to return true for
-     * {@link NotifSection#isInSection(ListEntry)} sets the entry as part of its Section.
+     * {@link NotifSectioner#isInSection(ListEntry)} sets the entry as part of its Section.
      */
-    public void setSections(List<NotifSection> sections) {
-        mShadeListBuilder.setSections(sections);
+    public void setSections(List<NotifSectioner> sections) {
+        mShadeListBuilder.setSectioners(sections);
+    }
+
+    /**
+     * StabilityManager that is used to determine whether to suppress group and section changes.
+     * This should only be set once.
+     */
+    public void setVisualStabilityManager(NotifStabilityManager notifStabilityManager) {
+        mShadeListBuilder.setNotifStabilityManager(notifStabilityManager);
     }
 
     /**
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilder.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilder.java
index d45f89c..2b545c5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilder.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilder.java
@@ -21,22 +21,26 @@
 import static com.android.systemui.statusbar.notification.collection.listbuilder.PipelineState.STATE_FINALIZE_FILTERING;
 import static com.android.systemui.statusbar.notification.collection.listbuilder.PipelineState.STATE_FINALIZING;
 import static com.android.systemui.statusbar.notification.collection.listbuilder.PipelineState.STATE_GROUPING;
+import static com.android.systemui.statusbar.notification.collection.listbuilder.PipelineState.STATE_GROUP_STABILIZING;
 import static com.android.systemui.statusbar.notification.collection.listbuilder.PipelineState.STATE_IDLE;
 import static com.android.systemui.statusbar.notification.collection.listbuilder.PipelineState.STATE_PRE_GROUP_FILTERING;
 import static com.android.systemui.statusbar.notification.collection.listbuilder.PipelineState.STATE_RESETTING;
 import static com.android.systemui.statusbar.notification.collection.listbuilder.PipelineState.STATE_SORTING;
 import static com.android.systemui.statusbar.notification.collection.listbuilder.PipelineState.STATE_TRANSFORMING;
 
+import static java.util.Objects.requireNonNull;
+
 import android.annotation.MainThread;
 import android.annotation.Nullable;
 import android.util.ArrayMap;
-import android.util.Pair;
 
 import androidx.annotation.NonNull;
 
 import com.android.systemui.Dumpable;
+import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dump.DumpManager;
 import com.android.systemui.statusbar.NotificationInteractionTracker;
+import com.android.systemui.statusbar.notification.collection.listbuilder.NotifSection;
 import com.android.systemui.statusbar.notification.collection.listbuilder.OnBeforeFinalizeFilterListener;
 import com.android.systemui.statusbar.notification.collection.listbuilder.OnBeforeRenderListListener;
 import com.android.systemui.statusbar.notification.collection.listbuilder.OnBeforeSortListener;
@@ -46,7 +50,8 @@
 import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifComparator;
 import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifFilter;
 import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifPromoter;
-import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifSection;
+import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifSectioner;
+import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifStabilityManager;
 import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.Pluggable;
 import com.android.systemui.statusbar.notification.collection.notifcollection.CollectionReadyForBuildListener;
 import com.android.systemui.util.Assert;
@@ -63,7 +68,6 @@
 import java.util.Objects;
 
 import javax.inject.Inject;
-import javax.inject.Singleton;
 
 /**
  * The second half of {@link NotifPipeline}. Sits downstream of the NotifCollection and transforms
@@ -71,7 +75,7 @@
  * notifications that are currently present in the notification shade.
  */
 @MainThread
-@Singleton
+@SysUISingleton
 public class ShadeListBuilder implements Dumpable {
     private final SystemClock mSystemClock;
     private final ShadeListBuilderLogger mLogger;
@@ -90,6 +94,7 @@
     private final List<NotifFilter> mNotifFinalizeFilters = new ArrayList<>();
     private final List<NotifComparator> mNotifComparators = new ArrayList<>();
     private final List<NotifSection> mNotifSections = new ArrayList<>();
+    @Nullable private NotifStabilityManager mNotifStabilityManager;
 
     private final List<OnBeforeTransformGroupsListener> mOnBeforeTransformGroupsListeners =
             new ArrayList<>();
@@ -109,12 +114,15 @@
             SystemClock systemClock,
             ShadeListBuilderLogger logger,
             DumpManager dumpManager,
-            NotificationInteractionTracker interactionTracker) {
+            NotificationInteractionTracker interactionTracker
+    ) {
         Assert.isMainThread();
         mSystemClock = systemClock;
         mLogger = logger;
         mInteractionTracker = interactionTracker;
         dumpManager.registerDumpable(TAG, this);
+
+        setSectioners(Collections.emptyList());
     }
 
     /**
@@ -189,15 +197,33 @@
         promoter.setInvalidationListener(this::onPromoterInvalidated);
     }
 
-    void setSections(List<NotifSection> sections) {
+    void setSectioners(List<NotifSectioner> sectioners) {
         Assert.isMainThread();
         mPipelineState.requireState(STATE_IDLE);
 
         mNotifSections.clear();
-        for (NotifSection section : sections) {
-            mNotifSections.add(section);
-            section.setInvalidationListener(this::onNotifSectionInvalidated);
+        for (NotifSectioner sectioner : sectioners) {
+            mNotifSections.add(new NotifSection(sectioner, mNotifSections.size()));
+            sectioner.setInvalidationListener(this::onNotifSectionInvalidated);
         }
+
+        mNotifSections.add(new NotifSection(DEFAULT_SECTIONER, mNotifSections.size()));
+    }
+
+    void setNotifStabilityManager(NotifStabilityManager notifStabilityManager) {
+        Assert.isMainThread();
+        mPipelineState.requireState(STATE_IDLE);
+
+        if (mNotifStabilityManager != null) {
+            throw new IllegalStateException(
+                    "Attempting to set the NotifStabilityManager more than once. There should "
+                            + "only be one visual stability manager. Manager is being set by "
+                            + mNotifStabilityManager.getName() + " and "
+                            + notifStabilityManager.getName());
+        }
+
+        mNotifStabilityManager = notifStabilityManager;
+        mNotifStabilityManager.setInvalidationListener(this::onReorderingAllowedInvalidated);
     }
 
     void setComparators(List<NotifComparator> comparators) {
@@ -237,6 +263,16 @@
         rebuildListIfBefore(STATE_PRE_GROUP_FILTERING);
     }
 
+    private void onReorderingAllowedInvalidated(NotifStabilityManager stabilityManager) {
+        Assert.isMainThread();
+
+        mLogger.logReorderingAllowedInvalidated(
+                stabilityManager.getName(),
+                mPipelineState.getState());
+
+        rebuildListIfBefore(STATE_GROUPING);
+    }
+
     private void onPromoterInvalidated(NotifPromoter promoter) {
         Assert.isMainThread();
 
@@ -245,7 +281,7 @@
         rebuildListIfBefore(STATE_TRANSFORMING);
     }
 
-    private void onNotifSectionInvalidated(NotifSection section) {
+    private void onNotifSectionInvalidated(NotifSectioner section) {
         Assert.isMainThread();
 
         mLogger.logNotifSectionInvalidated(section.getName(), mPipelineState.getState());
@@ -285,6 +321,7 @@
         // Step 1: Reset notification states
         mPipelineState.incrementTo(STATE_RESETTING);
         resetNotifs();
+        onBeginRun();
 
         // Step 2: Filter out any notifications that shouldn't be shown right now
         mPipelineState.incrementTo(STATE_PRE_GROUP_FILTERING);
@@ -303,6 +340,10 @@
         promoteNotifs(mNotifList);
         pruneIncompleteGroups(mNotifList);
 
+        // Step 4.5: Reassign/revert any groups to maintain visual stability
+        mPipelineState.incrementTo(STATE_GROUP_STABILIZING);
+        stabilizeGroupingNotifs(mNotifList);
+
         // Step 5: Sort
         // Assign each top-level entry a section, then sort the list by section and then within
         // section by our list of custom comparators
@@ -472,6 +513,28 @@
         }
     }
 
+    private void stabilizeGroupingNotifs(List<ListEntry> list) {
+        if (mNotifStabilityManager == null) {
+            return;
+        }
+
+        for (int i = 0; i < list.size(); i++) {
+            final ListEntry tle = list.get(i);
+            if (tle.getPreviousAttachState().getParent() == null) {
+                continue; // new entries are allowed
+            }
+
+            final GroupEntry prevParent = tle.getPreviousAttachState().getParent();
+            final GroupEntry assignedParent = tle.getParent();
+            if (prevParent != assignedParent) {
+                if (!mNotifStabilityManager.isGroupChangeAllowed(tle.getRepresentativeEntry())) {
+                    tle.getAttachState().getSuppressedChanges().setParent(assignedParent);
+                    tle.setParent(prevParent);
+                }
+            }
+        }
+    }
+
     private void promoteNotifs(List<ListEntry> list) {
         for (int i = 0; i < list.size(); i++) {
             final ListEntry tle = list.get(i);
@@ -595,7 +658,6 @@
      */
     private void annulAddition(ListEntry entry) {
         entry.setParent(null);
-        entry.getAttachState().setSectionIndex(-1);
         entry.getAttachState().setSection(null);
         entry.getAttachState().setPromoter(null);
         if (entry.mFirstAddedIteration == mIterationCount) {
@@ -606,12 +668,12 @@
     private void sortList() {
         // Assign sections to top-level elements and sort their children
         for (ListEntry entry : mNotifList) {
-            Pair<NotifSection, Integer> sectionWithIndex = applySections(entry);
+            NotifSection section = applySections(entry);
             if (entry instanceof GroupEntry) {
                 GroupEntry parent = (GroupEntry) entry;
                 for (NotificationEntry child : parent.getChildren()) {
-                    child.getAttachState().setSection(sectionWithIndex.first);
-                    child.getAttachState().setSectionIndex(sectionWithIndex.second);
+                    child.getAttachState().setSection(section);
+                    child.getAttachState().setSection(section);
                 }
                 parent.sortChildren(sChildComparator);
             }
@@ -650,9 +712,18 @@
                 mLogger.logParentChanged(mIterationCount, prev.getParent(), curr.getParent());
             }
 
+            if (curr.getSuppressedChanges().getParent() != null) {
+                mLogger.logParentChangeSuppressed(
+                        mIterationCount,
+                        curr.getSuppressedChanges().getParent(),
+                        curr.getParent());
+            }
+
             if (curr.getExcludingFilter() != prev.getExcludingFilter()) {
                 mLogger.logFilterChanged(
-                        mIterationCount, prev.getExcludingFilter(), curr.getExcludingFilter());
+                        mIterationCount,
+                        prev.getExcludingFilter(),
+                        curr.getExcludingFilter());
             }
 
             // When something gets detached, its promoter and section are always set to null, so
@@ -661,17 +732,30 @@
 
             if (!wasDetached && curr.getPromoter() != prev.getPromoter()) {
                 mLogger.logPromoterChanged(
-                        mIterationCount, prev.getPromoter(), curr.getPromoter());
+                        mIterationCount,
+                        prev.getPromoter(),
+                        curr.getPromoter());
             }
 
             if (!wasDetached && curr.getSection() != prev.getSection()) {
                 mLogger.logSectionChanged(
                         mIterationCount,
                         prev.getSection(),
-                        prev.getSectionIndex(),
-                        curr.getSection(),
-                        curr.getSectionIndex());
+                        curr.getSection());
             }
+
+            if (curr.getSuppressedChanges().getSection() != null) {
+                mLogger.logSectionChangeSuppressed(
+                        mIterationCount,
+                        curr.getSuppressedChanges().getSection(),
+                        curr.getSection());
+            }
+        }
+    }
+
+    private void onBeginRun() {
+        if (mNotifStabilityManager != null) {
+            mNotifStabilityManager.onBeginRun();
         }
     }
 
@@ -680,7 +764,14 @@
         callOnCleanup(mNotifPromoters);
         callOnCleanup(mNotifFinalizeFilters);
         callOnCleanup(mNotifComparators);
-        callOnCleanup(mNotifSections);
+
+        for (int i = 0; i < mNotifSections.size(); i++) {
+            mNotifSections.get(i).getSectioner().onCleanup();
+        }
+
+        if (mNotifStabilityManager != null) {
+            callOnCleanup(List.of(mNotifStabilityManager));
+        }
     }
 
     private void callOnCleanup(List<? extends Pluggable<?>> pluggables) {
@@ -691,7 +782,9 @@
 
     private final Comparator<ListEntry> mTopLevelComparator = (o1, o2) -> {
 
-        int cmp = Integer.compare(o1.getSection(), o2.getSection());
+        int cmp = Integer.compare(
+                requireNonNull(o1.getSection()).getIndex(),
+                requireNonNull(o2.getSection()).getIndex());
 
         if (cmp == 0) {
             for (int i = 0; i < mNotifComparators.size(); i++) {
@@ -769,25 +862,41 @@
         return null;
     }
 
-    private Pair<NotifSection, Integer> applySections(ListEntry entry) {
-        final Pair<NotifSection, Integer> sectionWithIndex = findSection(entry);
-        final NotifSection section = sectionWithIndex.first;
-        final Integer sectionIndex = sectionWithIndex.second;
+    private NotifSection applySections(ListEntry entry) {
+        final NotifSection newSection = findSection(entry);
+        final ListAttachState prevAttachState = entry.getPreviousAttachState();
 
-        entry.getAttachState().setSection(section);
-        entry.getAttachState().setSectionIndex(sectionIndex);
+        NotifSection finalSection = newSection;
 
-        return sectionWithIndex;
-    }
+        // are we changing sections of this entry?
+        if (mNotifStabilityManager != null
+                && prevAttachState.getParent() != null
+                && newSection != prevAttachState.getSection()) {
 
-    private Pair<NotifSection, Integer> findSection(ListEntry entry) {
-        for (int i = 0; i < mNotifSections.size(); i++) {
-            NotifSection sectioner = mNotifSections.get(i);
-            if (sectioner.isInSection(entry)) {
-                return new Pair<>(sectioner, i);
+            // are section changes allowed?
+            if (!mNotifStabilityManager.isSectionChangeAllowed(entry.getRepresentativeEntry())) {
+                // record the section that we wanted to change to
+                entry.getAttachState().getSuppressedChanges().setSection(newSection);
+
+                // keep the previous section
+                finalSection = prevAttachState.getSection();
             }
         }
-        return new Pair<>(sDefaultSection, mNotifSections.size());
+
+        entry.getAttachState().setSection(finalSection);
+
+        return finalSection;
+    }
+
+    @NonNull
+    private NotifSection findSection(ListEntry entry) {
+        for (int i = 0; i < mNotifSections.size(); i++) {
+            NotifSection section = mNotifSections.get(i);
+            if (section.getSectioner().isInSection(entry)) {
+                return section;
+            }
+        }
+        throw new RuntimeException("Missing default sectioner!");
     }
 
     private void rebuildListIfBefore(@PipelineState.StateName int state) {
@@ -857,15 +966,15 @@
         void onRenderList(@NonNull List<ListEntry> entries);
     }
 
-    private static final NotifSection sDefaultSection =
-            new NotifSection("UnknownSection") {
+    private static final NotifSectioner DEFAULT_SECTIONER =
+            new NotifSectioner("UnknownSection") {
                 @Override
                 public boolean isInSection(ListEntry entry) {
                     return true;
                 }
             };
 
-    private static final String TAG = "ShadeListBuilder";
-
     private static final int MIN_CHILDREN_FOR_GROUP = 2;
+
+    private static final String TAG = "ShadeListBuilder";
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/SuppressedAttachState.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/SuppressedAttachState.kt
new file mode 100644
index 0000000..3eb2e61
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/SuppressedAttachState.kt
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification.collection
+
+import com.android.systemui.statusbar.notification.collection.listbuilder.NotifSection
+
+/**
+ * Stores the suppressed state that [ShadeListBuilder] assigned to this [ListEntry] before the
+ * VisualStabilityManager suppressed group and section changes.
+ */
+data class SuppressedAttachState private constructor(
+    /**
+     * Null if not attached to the current shade list. If top-level, then the shade list root. If
+     * part of a group, then that group's GroupEntry.
+     */
+    var parent: GroupEntry?,
+
+    /**
+     * The assigned section for this ListEntry. If the child of the group, this will be the
+     * parent's section. Null if not attached to the list.
+     */
+    var section: NotifSection?
+) {
+
+    /** Copies the state of another instance. */
+    fun clone(other: SuppressedAttachState) {
+        parent = other.parent
+        section = other.section
+    }
+
+    /** Resets back to a "clean" state (the same as created by the factory method) */
+    fun reset() {
+        parent = null
+        section = null
+    }
+
+    companion object {
+        @JvmStatic
+        fun create(): SuppressedAttachState {
+            return SuppressedAttachState(
+                null,
+                null)
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/TargetSdkResolver.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/TargetSdkResolver.kt
index a0f9dc9..5dc0dcc 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/TargetSdkResolver.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/TargetSdkResolver.kt
@@ -20,13 +20,13 @@
 import android.content.pm.PackageManager
 import android.service.notification.StatusBarNotification
 import android.util.Log
+import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.statusbar.notification.collection.notifcollection.CommonNotifCollection
 import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener
 import com.android.systemui.statusbar.phone.StatusBar
 import javax.inject.Inject
-import javax.inject.Singleton
 
-@Singleton
+@SysUISingleton
 class TargetSdkResolver @Inject constructor(
     private val context: Context
 ) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/AppOpsCoordinator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/AppOpsCoordinator.java
index 68ec6b6..c7ac403 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/AppOpsCoordinator.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/AppOpsCoordinator.java
@@ -19,28 +19,24 @@
 import static android.app.NotificationManager.IMPORTANCE_MIN;
 
 import android.app.Notification;
-import android.os.UserHandle;
 import android.service.notification.StatusBarNotification;
-import android.util.ArraySet;
 
 import com.android.systemui.ForegroundServiceController;
 import com.android.systemui.appops.AppOpsController;
+import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.statusbar.notification.collection.ListEntry;
 import com.android.systemui.statusbar.notification.collection.NotifPipeline;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifFilter;
-import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifSection;
-import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener;
+import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifSectioner;
 import com.android.systemui.statusbar.notification.collection.notifcollection.NotifLifetimeExtender;
-import com.android.systemui.util.Assert;
 import com.android.systemui.util.concurrency.DelayableExecutor;
 
 import java.util.HashMap;
 import java.util.Map;
 
 import javax.inject.Inject;
-import javax.inject.Singleton;
 
 /**
  * Handles ForegroundService and AppOp interactions with notifications.
@@ -55,7 +51,7 @@
  *  frameworks/base/packages/SystemUI/src/com/android/systemui/ForegroundServiceNotificationListener
  *  frameworks/base/packages/SystemUI/src/com/android/systemui/ForegroundServiceLifetimeExtender
  */
-@Singleton
+@SysUISingleton
 public class AppOpsCoordinator implements Coordinator {
     private static final String TAG = "AppOpsCoordinator";
 
@@ -82,18 +78,13 @@
         // extend the lifetime of foreground notification services to show for at least 5 seconds
         mNotifPipeline.addNotificationLifetimeExtender(mForegroundLifetimeExtender);
 
-        // listen for new notifications to add appOps
-        mNotifPipeline.addCollectionListener(mNotifCollectionListener);
-
         // filter out foreground service notifications that aren't necessary anymore
         mNotifPipeline.addPreGroupFilter(mNotifFilter);
 
-        // when appOps change, update any relevant notifications to update appOps for
-        mAppOpsController.addCallback(ForegroundServiceController.APP_OPS, this::onAppOpsChanged);
     }
 
-    public NotifSection getSection() {
-        return mNotifSection;
+    public NotifSectioner getSectioner() {
+        return mNotifSectioner;
     }
 
     /**
@@ -186,38 +177,9 @@
     };
 
     /**
-     * Adds appOps to incoming and updating notifications
-     */
-    private NotifCollectionListener mNotifCollectionListener = new NotifCollectionListener() {
-        @Override
-        public void onEntryAdded(NotificationEntry entry) {
-            tagAppOps(entry);
-        }
-
-        @Override
-        public void onEntryUpdated(NotificationEntry entry) {
-            tagAppOps(entry);
-        }
-
-        private void tagAppOps(NotificationEntry entry) {
-            final StatusBarNotification sbn = entry.getSbn();
-            // note: requires that the ForegroundServiceController is updating their appOps first
-            ArraySet<Integer> activeOps =
-                    mForegroundServiceController.getAppOps(
-                            sbn.getUser().getIdentifier(),
-                            sbn.getPackageName());
-
-            entry.mActiveAppOps.clear();
-            if (activeOps != null) {
-                entry.mActiveAppOps.addAll(activeOps);
-            }
-        }
-    };
-
-    /**
      * Puts foreground service notifications into its own section.
      */
-    private final NotifSection mNotifSection = new NotifSection("ForegroundService") {
+    private final NotifSectioner mNotifSectioner = new NotifSectioner("ForegroundService") {
         @Override
         public boolean isInSection(ListEntry entry) {
             NotificationEntry notificationEntry = entry.getRepresentativeEntry();
@@ -230,53 +192,4 @@
             return false;
         }
     };
-
-    private void onAppOpsChanged(int code, int uid, String packageName, boolean active) {
-        mMainExecutor.execute(() -> handleAppOpsChanged(code, uid, packageName, active));
-    }
-
-    /**
-     * Update the appOp for the posted notification associated with the current foreground service
-     *
-     * @param code code for appOp to add/remove
-     * @param uid of user the notification is sent to
-     * @param packageName package that created the notification
-     * @param active whether the appOpCode is active or not
-     */
-    private void handleAppOpsChanged(int code, int uid, String packageName, boolean active) {
-        Assert.isMainThread();
-
-        int userId = UserHandle.getUserId(uid);
-
-        // Update appOps of the app's posted notifications with standard layouts
-        final ArraySet<String> notifKeys =
-                mForegroundServiceController.getStandardLayoutKeys(userId, packageName);
-        if (notifKeys != null) {
-            boolean changed = false;
-            for (int i = 0; i < notifKeys.size(); i++) {
-                final NotificationEntry entry = findNotificationEntryWithKey(notifKeys.valueAt(i));
-                if (entry != null
-                        && uid == entry.getSbn().getUid()
-                        && packageName.equals(entry.getSbn().getPackageName())) {
-                    if (active) {
-                        changed |= entry.mActiveAppOps.add(code);
-                    } else {
-                        changed |= entry.mActiveAppOps.remove(code);
-                    }
-                }
-            }
-            if (changed) {
-                mNotifFilter.invalidateList();
-            }
-        }
-    }
-
-    private NotificationEntry findNotificationEntryWithKey(String key) {
-        for (NotificationEntry entry : mNotifPipeline.getAllNotifs()) {
-            if (entry.getKey().equals(key)) {
-                return entry;
-            }
-        }
-        return null;
-    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/BubbleCoordinator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/BubbleCoordinator.java
index 08462c1..4ddc1dc 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/BubbleCoordinator.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/BubbleCoordinator.java
@@ -17,6 +17,7 @@
 package com.android.systemui.statusbar.notification.collection.coordinator;
 
 import com.android.systemui.bubbles.BubbleController;
+import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.statusbar.notification.collection.NotifCollection;
 import com.android.systemui.statusbar.notification.collection.NotifPipeline;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
@@ -28,7 +29,6 @@
 import java.util.Set;
 
 import javax.inject.Inject;
-import javax.inject.Singleton;
 
 /**
  * Coordinates hiding, intercepting (the dismissal), and deletion of bubbled notifications.
@@ -50,7 +50,7 @@
  * respond to app-cancellations (ie: remove the bubble if the app cancels the notification).
  *
  */
-@Singleton
+@SysUISingleton
 public class BubbleCoordinator implements Coordinator {
     private static final String TAG = "BubbleCoordinator";
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/ConversationCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/ConversationCoordinator.kt
index 1a9de88..dea1162 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/ConversationCoordinator.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/ConversationCoordinator.kt
@@ -16,22 +16,22 @@
 
 package com.android.systemui.statusbar.notification.collection.coordinator
 
+import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.statusbar.notification.collection.ListEntry
 import com.android.systemui.statusbar.notification.collection.NotifPipeline
 import com.android.systemui.statusbar.notification.collection.NotificationEntry
 import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifPromoter
-import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifSection
+import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifSectioner
 import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier
 import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier.Companion.TYPE_NON_PERSON
 import javax.inject.Inject
-import javax.inject.Singleton
 
 /**
  * A Conversation/People Coordinator that:
  * - Elevates important conversation notifications
  * - Puts conversations into its own people section. @see [NotifCoordinators] for section ordering.
  */
-@Singleton
+@SysUISingleton
 class ConversationCoordinator @Inject constructor(
     private val peopleNotificationIdentifier: PeopleNotificationIdentifier
 ) : Coordinator {
@@ -42,7 +42,7 @@
         }
     }
 
-    private val mNotifSection: NotifSection = object : NotifSection("People") {
+    val sectioner = object : NotifSectioner("People") {
         override fun isInSection(entry: ListEntry): Boolean {
             return isConversation(entry.representativeEntry!!)
         }
@@ -52,10 +52,6 @@
         pipeline.addPromoter(notificationPromoter)
     }
 
-    fun getSection(): NotifSection {
-        return mNotifSection
-    }
-
     private fun isConversation(entry: NotificationEntry): Boolean =
         peopleNotificationIdentifier.getPeopleNotificationType(entry.sbn, entry.ranking) !=
             TYPE_NON_PERSON
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/DeviceProvisionedCoordinator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/DeviceProvisionedCoordinator.java
index 625d1b9..47928b4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/DeviceProvisionedCoordinator.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/DeviceProvisionedCoordinator.java
@@ -23,20 +23,20 @@
 import android.os.RemoteException;
 import android.service.notification.StatusBarNotification;
 
+import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.statusbar.notification.collection.NotifPipeline;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifFilter;
 import com.android.systemui.statusbar.policy.DeviceProvisionedController;
 
 import javax.inject.Inject;
-import javax.inject.Singleton;
 
 /**
  * Filters out most notifications when the device is unprovisioned.
  * Special notifications with extra permissions and tags won't be filtered out even when the
  * device is unprovisioned.
  */
-@Singleton
+@SysUISingleton
 public class DeviceProvisionedCoordinator implements Coordinator {
     private static final String TAG = "DeviceProvisionedCoordinator";
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinator.java
index 72597af..c023400 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinator.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinator.java
@@ -21,12 +21,13 @@
 
 import android.annotation.Nullable;
 
+import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.statusbar.NotificationRemoteInputManager;
 import com.android.systemui.statusbar.notification.collection.ListEntry;
 import com.android.systemui.statusbar.notification.collection.NotifPipeline;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifPromoter;
-import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifSection;
+import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifSectioner;
 import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener;
 import com.android.systemui.statusbar.notification.collection.notifcollection.NotifLifetimeExtender;
 import com.android.systemui.statusbar.notification.interruption.HeadsUpViewBinder;
@@ -37,7 +38,6 @@
 import java.util.Objects;
 
 import javax.inject.Inject;
-import javax.inject.Singleton;
 
 /**
  * Coordinates heads up notification (HUN) interactions with the notification pipeline based on
@@ -53,7 +53,7 @@
  *
  * Note: The inflation callback in {@link PreparationCoordinator} handles showing HUNs.
  */
-@Singleton
+@SysUISingleton
 public class HeadsUpCoordinator implements Coordinator {
     private static final String TAG = "HeadsUpCoordinator";
 
@@ -88,8 +88,8 @@
         pipeline.addNotificationLifetimeExtender(mLifetimeExtender);
     }
 
-    public NotifSection getSection() {
-        return mNotifSection;
+    public NotifSectioner getSectioner() {
+        return mNotifSectioner;
     }
 
     private void onHeadsUpViewBound(NotificationEntry entry) {
@@ -191,7 +191,7 @@
         }
     };
 
-    private final NotifSection mNotifSection = new NotifSection("HeadsUp") {
+    private final NotifSectioner mNotifSectioner = new NotifSectioner("HeadsUp") {
         @Override
         public boolean isInSection(ListEntry entry) {
             return isCurrentlyShowingHun(entry);
@@ -207,7 +207,7 @@
                 endNotifLifetimeExtension();
                 mCurrentHun = newHUN;
                 mNotifPromoter.invalidateList();
-                mNotifSection.invalidateList();
+                mNotifSectioner.invalidateList();
             }
             if (!isHeadsUp) {
                 mHeadsUpViewBinder.unbindHeadsUpView(entry);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinator.java
index b773856..318cdb1 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinator.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinator.java
@@ -34,6 +34,7 @@
 import com.android.keyguard.KeyguardUpdateMonitor;
 import com.android.keyguard.KeyguardUpdateMonitorCallback;
 import com.android.systemui.broadcast.BroadcastDispatcher;
+import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.statusbar.NotificationLockscreenUserManager;
 import com.android.systemui.statusbar.notification.NotificationUtils;
@@ -46,12 +47,11 @@
 import com.android.systemui.statusbar.policy.KeyguardStateController;
 
 import javax.inject.Inject;
-import javax.inject.Singleton;
 
 /**
  * Filters low priority and privacy-sensitive notifications from the lockscreen.
  */
-@Singleton
+@SysUISingleton
 public class KeyguardCoordinator implements Coordinator {
     private static final String TAG = "KeyguardCoordinator";
 
@@ -64,6 +64,8 @@
     private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
     private final HighPriorityProvider mHighPriorityProvider;
 
+    private boolean mHideSilentNotificationsOnLockscreen;
+
     @Inject
     public KeyguardCoordinator(
             Context context,
@@ -86,6 +88,8 @@
 
     @Override
     public void attach(NotifPipeline pipeline) {
+        readShowSilentNotificationSetting();
+
         setupInvalidateNotifListCallbacks();
         pipeline.addFinalizeFilter(mNotifFilter);
     }
@@ -147,7 +151,7 @@
             return false;
         }
         if (NotificationUtils.useNewInterruptionModel(mContext)
-                && hideSilentNotificationsOnLockscreen()) {
+                && mHideSilentNotificationsOnLockscreen) {
             return mHighPriorityProvider.isHighPriority(entry);
         } else {
             return entry.getRepresentativeEntry() != null
@@ -155,11 +159,6 @@
         }
     }
 
-    private boolean hideSilentNotificationsOnLockscreen() {
-        return Settings.Secure.getInt(mContext.getContentResolver(),
-                Settings.Secure.LOCK_SCREEN_SHOW_SILENT_NOTIFICATIONS, 1) == 0;
-    }
-
     private void setupInvalidateNotifListCallbacks() {
         // register onKeyguardShowing callback
         mKeyguardStateController.addCallback(mKeyguardCallback);
@@ -169,6 +168,11 @@
         final ContentObserver settingsObserver = new ContentObserver(mMainHandler) {
             @Override
             public void onChange(boolean selfChange, Uri uri) {
+                if (uri.equals(Settings.Secure.getUriFor(
+                        Settings.Secure.LOCK_SCREEN_SHOW_SILENT_NOTIFICATIONS))) {
+                    readShowSilentNotificationSetting();
+                }
+
                 if (mKeyguardStateController.isShowing()) {
                     invalidateListFromFilter("Settings " + uri + " changed");
                 }
@@ -192,6 +196,12 @@
                 false,
                 settingsObserver);
 
+        mContext.getContentResolver().registerContentObserver(
+                Settings.Secure.getUriFor(Settings.Secure.LOCK_SCREEN_SHOW_SILENT_NOTIFICATIONS),
+                false,
+                settingsObserver,
+                UserHandle.USER_ALL);
+
         // register (maybe) public mode changed callbacks:
         mStatusBarStateController.addCallback(mStatusBarStateListener);
         mBroadcastDispatcher.registerReceiver(new BroadcastReceiver() {
@@ -208,6 +218,14 @@
         mNotifFilter.invalidateList();
     }
 
+    private void readShowSilentNotificationSetting() {
+        mHideSilentNotificationsOnLockscreen =
+                Settings.Secure.getInt(
+                        mContext.getContentResolver(),
+                        Settings.Secure.LOCK_SCREEN_SHOW_SILENT_NOTIFICATIONS,
+                        1) == 0;
+    }
+
     private final KeyguardStateController.Callback mKeyguardCallback =
             new KeyguardStateController.Callback() {
         @Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/NotifCoordinators.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/NotifCoordinators.java
index a09c650..ded5e46 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/NotifCoordinators.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/NotifCoordinators.java
@@ -17,10 +17,11 @@
 package com.android.systemui.statusbar.notification.collection.coordinator;
 
 import com.android.systemui.Dumpable;
+import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dump.DumpManager;
 import com.android.systemui.statusbar.FeatureFlags;
 import com.android.systemui.statusbar.notification.collection.NotifPipeline;
-import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifSection;
+import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifSectioner;
 import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.Pluggable;
 import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener;
 import com.android.systemui.statusbar.notification.collection.notifcollection.NotifLifetimeExtender;
@@ -31,17 +32,17 @@
 import java.util.List;
 
 import javax.inject.Inject;
-import javax.inject.Singleton;
 
 /**
  * Handles the attachment of {@link Coordinator}s to the {@link NotifPipeline} so that the
  * Coordinators can register their respective callbacks.
  */
-@Singleton
+@SysUISingleton
 public class NotifCoordinators implements Dumpable {
     private static final String TAG = "NotifCoordinators";
     private final List<Coordinator> mCoordinators = new ArrayList<>();
-    private final List<NotifSection> mOrderedSections = new ArrayList<>();
+    private final List<NotifSectioner> mOrderedSections = new ArrayList<>();
+
     /**
      * Creates all the coordinators.
      */
@@ -58,7 +59,8 @@
             HeadsUpCoordinator headsUpCoordinator,
             ConversationCoordinator conversationCoordinator,
             PreparationCoordinator preparationCoordinator,
-            MediaCoordinator mediaCoordinator) {
+            MediaCoordinator mediaCoordinator,
+            VisualStabilityCoordinator visualStabilityCoordinator) {
         dumpManager.registerDumpable(TAG, this);
 
         mCoordinators.add(new HideLocallyDismissedNotifsCoordinator());
@@ -70,6 +72,7 @@
         mCoordinators.add(bubbleCoordinator);
         mCoordinators.add(mediaCoordinator);
         mCoordinators.add(conversationCoordinator);
+        mCoordinators.add(visualStabilityCoordinator);
         if (featureFlags.isNewNotifPipelineRenderingEnabled()) {
             mCoordinators.add(headsUpCoordinator);
             mCoordinators.add(preparationCoordinator);
@@ -78,12 +81,12 @@
         // Manually add Ordered Sections
         // HeadsUp > FGS > People > Alerting > Silent > Unknown/Default
         if (featureFlags.isNewNotifPipelineRenderingEnabled()) {
-            mOrderedSections.add(headsUpCoordinator.getSection()); // HeadsUp
+            mOrderedSections.add(headsUpCoordinator.getSectioner()); // HeadsUp
         }
-        mOrderedSections.add(appOpsCoordinator.getSection()); // ForegroundService
-        mOrderedSections.add(conversationCoordinator.getSection()); // People
-        mOrderedSections.add(rankingCoordinator.getAlertingSection()); // Alerting
-        mOrderedSections.add(rankingCoordinator.getSilentSection()); // Silent
+        mOrderedSections.add(appOpsCoordinator.getSectioner()); // ForegroundService
+        mOrderedSections.add(conversationCoordinator.getSectioner()); // People
+        mOrderedSections.add(rankingCoordinator.getAlertingSectioner()); // Alerting
+        mOrderedSections.add(rankingCoordinator.getSilentSectioner()); // Silent
     }
 
     /**
@@ -106,7 +109,7 @@
             pw.println("\t" + c.getClass());
         }
 
-        for (NotifSection s : mOrderedSections) {
+        for (NotifSectioner s : mOrderedSections) {
             pw.println("\t" + s.getName());
         }
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinator.java
index ee2bb6b1..31826c7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinator.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinator.java
@@ -28,6 +28,7 @@
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.statusbar.IStatusBarService;
+import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.statusbar.notification.collection.GroupEntry;
 import com.android.systemui.statusbar.notification.collection.ListEntry;
 import com.android.systemui.statusbar.notification.collection.NotifPipeline;
@@ -48,7 +49,6 @@
 import java.util.Set;
 
 import javax.inject.Inject;
-import javax.inject.Singleton;
 
 /**
  * Kicks off core notification inflation and view rebinding when a notification is added or updated.
@@ -57,7 +57,7 @@
  * If a notification was uninflated, this coordinator will filter the notification out from the
  * {@link ShadeListBuilder} until it is inflated.
  */
-@Singleton
+@SysUISingleton
 public class PreparationCoordinator implements Coordinator {
     private static final String TAG = "PreparationCoordinator";
 
@@ -307,7 +307,7 @@
     private void onInflationFinished(NotificationEntry entry) {
         mLogger.logNotifInflated(entry.getKey());
         mInflatingNotifs.remove(entry);
-        mViewBarn.registerViewForEntry(entry, entry.getRow());
+        mViewBarn.registerViewForEntry(entry, entry.getRowController());
         mInflationStates.put(entry, STATE_INFLATED);
         mNotifInflatingFilter.invalidateList();
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/RankingCoordinator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/RankingCoordinator.java
index 0d2f9da..0f08e0f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/RankingCoordinator.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/RankingCoordinator.java
@@ -16,16 +16,16 @@
 
 package com.android.systemui.statusbar.notification.collection.coordinator;
 
+import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.statusbar.notification.collection.ListEntry;
 import com.android.systemui.statusbar.notification.collection.NotifPipeline;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifFilter;
-import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifSection;
+import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifSectioner;
 import com.android.systemui.statusbar.notification.collection.provider.HighPriorityProvider;
 
 import javax.inject.Inject;
-import javax.inject.Singleton;
 
 /**
  * Filters out NotificationEntries based on its Ranking and dozing state.
@@ -34,7 +34,7 @@
  *  - whether the notification's app is suspended or hiding its notifications
  *  - whether DND settings are hiding notifications from ambient display or the notification list
  */
-@Singleton
+@SysUISingleton
 public class RankingCoordinator implements Coordinator {
     private static final String TAG = "RankingNotificationCoordinator";
 
@@ -57,22 +57,22 @@
         pipeline.addPreGroupFilter(mDozingFilter);
     }
 
-    public NotifSection getAlertingSection() {
-        return mAlertingNotifSection;
+    public NotifSectioner getAlertingSectioner() {
+        return mAlertingNotifSectioner;
     }
 
-    public NotifSection getSilentSection() {
-        return mSilentNotifSection;
+    public NotifSectioner getSilentSectioner() {
+        return mSilentNotifSectioner;
     }
 
-    private final NotifSection mAlertingNotifSection = new NotifSection("Alerting") {
+    private final NotifSectioner mAlertingNotifSectioner = new NotifSectioner("Alerting") {
         @Override
         public boolean isInSection(ListEntry entry) {
             return mHighPriorityProvider.isHighPriority(entry);
         }
     };
 
-    private final NotifSection mSilentNotifSection = new NotifSection("Silent") {
+    private final NotifSectioner mSilentNotifSectioner = new NotifSectioner("Silent") {
         @Override
         public boolean isInSection(ListEntry entry) {
             return !mHighPriorityProvider.isHighPriority(entry);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinator.java
new file mode 100644
index 0000000..08030f8
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinator.java
@@ -0,0 +1,205 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification.collection.coordinator;
+
+import static com.android.systemui.keyguard.WakefulnessLifecycle.WAKEFULNESS_AWAKE;
+import static com.android.systemui.keyguard.WakefulnessLifecycle.WAKEFULNESS_WAKING;
+
+import android.annotation.NonNull;
+
+import androidx.annotation.VisibleForTesting;
+
+import com.android.systemui.dagger.SysUISingleton;
+import com.android.systemui.keyguard.WakefulnessLifecycle;
+import com.android.systemui.plugins.statusbar.StatusBarStateController;
+import com.android.systemui.statusbar.NotificationViewHierarchyManager;
+import com.android.systemui.statusbar.notification.collection.NotifPipeline;
+import com.android.systemui.statusbar.notification.collection.NotificationEntry;
+import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifStabilityManager;
+import com.android.systemui.statusbar.policy.HeadsUpManager;
+import com.android.systemui.util.concurrency.DelayableExecutor;
+
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+import javax.inject.Inject;
+
+/**
+ * Ensures that notifications are visually stable if the user is looking at the notifications.
+ * Group and section changes are re-allowed when the notification entries are no longer being
+ * viewed.
+ *
+ * Previously this was implemented in the view-layer {@link NotificationViewHierarchyManager} by
+ * {@link com.android.systemui.statusbar.notification.collection.legacy.VisualStabilityManager}.
+ * This is now integrated in the data-layer via
+ * {@link com.android.systemui.statusbar.notification.collection.ShadeListBuilder}.
+ */
+@SysUISingleton
+public class VisualStabilityCoordinator implements Coordinator {
+    private final DelayableExecutor mDelayableExecutor;
+    private final WakefulnessLifecycle mWakefulnessLifecycle;
+    private final StatusBarStateController mStatusBarStateController;
+    private final HeadsUpManager mHeadsUpManager;
+
+    private boolean mScreenOn;
+    private boolean mPanelExpanded;
+    private boolean mPulsing;
+
+    private boolean mReorderingAllowed;
+    private boolean mIsSuppressingGroupChange = false;
+    private final Set<String> mEntriesWithSuppressedSectionChange = new HashSet<>();
+
+    // key: notification key that can temporarily change its section
+    // value: runnable that when run removes its associated RemoveOverrideSuppressionRunnable
+    // from the DelayableExecutor's queue
+    private Map<String, Runnable> mEntriesThatCanChangeSection = new HashMap<>();
+
+    @VisibleForTesting
+    protected static final long ALLOW_SECTION_CHANGE_TIMEOUT = 500;
+
+    @Inject
+    public VisualStabilityCoordinator(
+            HeadsUpManager headsUpManager,
+            WakefulnessLifecycle wakefulnessLifecycle,
+            StatusBarStateController statusBarStateController,
+            DelayableExecutor delayableExecutor
+    ) {
+        mHeadsUpManager = headsUpManager;
+        mWakefulnessLifecycle = wakefulnessLifecycle;
+        mStatusBarStateController = statusBarStateController;
+        mDelayableExecutor = delayableExecutor;
+    }
+
+    @Override
+    public void attach(NotifPipeline pipeline) {
+        mWakefulnessLifecycle.addObserver(mWakefulnessObserver);
+        mScreenOn = mWakefulnessLifecycle.getWakefulness() == WAKEFULNESS_AWAKE
+                || mWakefulnessLifecycle.getWakefulness() == WAKEFULNESS_WAKING;
+
+        mStatusBarStateController.addCallback(mStatusBarStateControllerListener);
+        mPulsing = mStatusBarStateController.isPulsing();
+
+        pipeline.setVisualStabilityManager(mNotifStabilityManager);
+    }
+
+    private final NotifStabilityManager mNotifStabilityManager =
+            new NotifStabilityManager("VisualStabilityCoordinator") {
+                @Override
+                public void onBeginRun() {
+                    mIsSuppressingGroupChange = false;
+                    mEntriesWithSuppressedSectionChange.clear();
+                }
+
+                @Override
+                public boolean isGroupChangeAllowed(NotificationEntry entry) {
+                    final boolean isGroupChangeAllowedForEntry =
+                            mReorderingAllowed || mHeadsUpManager.isAlerting(entry.getKey());
+                    mIsSuppressingGroupChange |= isGroupChangeAllowedForEntry;
+                    return isGroupChangeAllowedForEntry;
+                }
+
+                @Override
+                public boolean isSectionChangeAllowed(NotificationEntry entry) {
+                    final boolean isSectionChangeAllowedForEntry =
+                            mReorderingAllowed
+                                    || mHeadsUpManager.isAlerting(entry.getKey())
+                                    || mEntriesThatCanChangeSection.containsKey(entry.getKey());
+                    if (isSectionChangeAllowedForEntry) {
+                        mEntriesWithSuppressedSectionChange.add(entry.getKey());
+                    }
+                    return isSectionChangeAllowedForEntry;
+                }
+            };
+
+    private void updateAllowedStates() {
+        mReorderingAllowed = isReorderingAllowed();
+        if (mReorderingAllowed && (mIsSuppressingGroupChange || isSuppressingSectionChange())) {
+            mNotifStabilityManager.invalidateList();
+        }
+    }
+
+    private boolean isSuppressingSectionChange() {
+        return !mEntriesWithSuppressedSectionChange.isEmpty();
+    }
+
+    private boolean isReorderingAllowed() {
+        return (!mScreenOn || !mPanelExpanded) && !mPulsing;
+    }
+
+    /**
+     * Allows this notification entry to be re-ordered in the notification list temporarily until
+     * the timeout has passed.
+     *
+     * Typically this is allowed because the user has directly changed something about the
+     * notification and we are reordering based on the user's change.
+     *
+     * @param entry notification entry that can change sections even if isReorderingAllowed is false
+     * @param now current time SystemClock.uptimeMillis
+     */
+    public void temporarilyAllowSectionChanges(@NonNull NotificationEntry entry, long now) {
+        final String entryKey = entry.getKey();
+        final boolean wasSectionChangeAllowed =
+                mNotifStabilityManager.isSectionChangeAllowed(entry);
+
+        // If it exists, cancel previous timeout
+        if (mEntriesThatCanChangeSection.containsKey(entryKey)) {
+            mEntriesThatCanChangeSection.get(entryKey).run();
+        }
+
+        // Schedule & store new timeout cancellable
+        mEntriesThatCanChangeSection.put(
+                entryKey,
+                mDelayableExecutor.executeAtTime(
+                        () -> mEntriesThatCanChangeSection.remove(entryKey),
+                        now + ALLOW_SECTION_CHANGE_TIMEOUT));
+
+        if (!wasSectionChangeAllowed) {
+            mNotifStabilityManager.invalidateList();
+        }
+    }
+
+    final StatusBarStateController.StateListener mStatusBarStateControllerListener =
+            new StatusBarStateController.StateListener() {
+                @Override
+                public void onPulsingChanged(boolean pulsing) {
+                    mPulsing = pulsing;
+                    updateAllowedStates();
+                }
+
+                @Override
+                public void onExpandedChanged(boolean expanded) {
+                    mPanelExpanded = expanded;
+                    updateAllowedStates();
+                }
+            };
+
+    final WakefulnessLifecycle.Observer mWakefulnessObserver = new WakefulnessLifecycle.Observer() {
+        @Override
+        public void onFinishedGoingToSleep() {
+            mScreenOn = false;
+            updateAllowedStates();
+        }
+
+        @Override
+        public void onStartedWakingUp() {
+            mScreenOn = true;
+            updateAllowedStates();
+        }
+    };
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/LowPriorityInflationHelper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/LowPriorityInflationHelper.java
index 73c0fdc..6089aa2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/LowPriorityInflationHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/LowPriorityInflationHelper.java
@@ -16,6 +16,7 @@
 
 package com.android.systemui.statusbar.notification.collection.inflation;
 
+import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.statusbar.FeatureFlags;
 import com.android.systemui.statusbar.notification.collection.GroupEntry;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
@@ -25,13 +26,12 @@
 import com.android.systemui.statusbar.phone.NotificationGroupManager;
 
 import javax.inject.Inject;
-import javax.inject.Singleton;
 
 /**
  * Helper class that provide methods to help check when we need to inflate a low priority version
  * ot notification content.
  */
-@Singleton
+@SysUISingleton
 public class LowPriorityInflationHelper {
     private final FeatureFlags mFeatureFlags;
     private final NotificationGroupManager mGroupManager;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotificationRowBinderImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotificationRowBinderImpl.java
index 8849824..7c061aa 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotificationRowBinderImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotificationRowBinderImpl.java
@@ -24,6 +24,7 @@
 import android.view.ViewGroup;
 
 import com.android.internal.util.NotificationMessagingUtil;
+import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.statusbar.NotificationLockscreenUserManager;
 import com.android.systemui.statusbar.NotificationPresenter;
 import com.android.systemui.statusbar.NotificationRemoteInputManager;
@@ -44,10 +45,9 @@
 
 import javax.inject.Inject;
 import javax.inject.Provider;
-import javax.inject.Singleton;
 
 /** Handles inflating and updating views for notifications. */
-@Singleton
+@SysUISingleton
 public class NotificationRowBinderImpl implements NotificationRowBinder {
 
     private static final String TAG = "NotificationViewManager";
@@ -136,6 +136,7 @@
                                         .expandableNotificationRow(row)
                                         .notificationEntry(entry)
                                         .onExpandClickListener(mPresenter)
+                                        .listContainer(mListContainer)
                                         .build();
                         ExpandableNotificationRowController rowController =
                                 component.getExpandableNotificationRowController();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/OnDismissCallbackImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/OnUserInteractionCallbackImpl.java
similarity index 71%
rename from packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/OnDismissCallbackImpl.java
rename to packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/OnUserInteractionCallbackImpl.java
index 36adf9b..19a356b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/OnDismissCallbackImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/OnUserInteractionCallbackImpl.java
@@ -18,6 +18,7 @@
 
 import static android.service.notification.NotificationStats.DISMISS_SENTIMENT_NEUTRAL;
 
+import android.os.SystemClock;
 import android.service.notification.NotificationListenerService;
 import android.service.notification.NotificationStats;
 
@@ -26,35 +27,43 @@
 import com.android.systemui.statusbar.notification.collection.NotifCollection;
 import com.android.systemui.statusbar.notification.collection.NotifPipeline;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
+import com.android.systemui.statusbar.notification.collection.coordinator.VisualStabilityCoordinator;
 import com.android.systemui.statusbar.notification.collection.notifcollection.DismissedByUserStats;
 import com.android.systemui.statusbar.notification.logging.NotificationLogger;
-import com.android.systemui.statusbar.notification.row.OnDismissCallback;
+import com.android.systemui.statusbar.notification.row.OnUserInteractionCallback;
 import com.android.systemui.statusbar.policy.HeadsUpManager;
 
 /**
- * Callback used when a user:
- * 1. Manually dismisses a notification {@see ExpandableNotificationRow}.
- * 2. Clicks on a notification with flag {@link android.app.Notification#FLAG_AUTO_CANCEL}.
- * {@see StatusBarNotificationActivityStarter}
+ * Callback for when a user interacts with a {@see ExpandableNotificationRow}. Sends relevant
+ * information about the interaction to the notification pipeline.
  */
-public class OnDismissCallbackImpl implements OnDismissCallback {
+public class OnUserInteractionCallbackImpl implements OnUserInteractionCallback {
     private final NotifPipeline mNotifPipeline;
     private final NotifCollection mNotifCollection;
     private final HeadsUpManager mHeadsUpManager;
     private final StatusBarStateController mStatusBarStateController;
+    private final VisualStabilityCoordinator mVisualStabilityCoordinator;
 
-    public OnDismissCallbackImpl(
+    public OnUserInteractionCallbackImpl(
             NotifPipeline notifPipeline,
             NotifCollection notifCollection,
             HeadsUpManager headsUpManager,
-            StatusBarStateController statusBarStateController
+            StatusBarStateController statusBarStateController,
+            VisualStabilityCoordinator visualStabilityCoordinator
     ) {
         mNotifPipeline = notifPipeline;
         mNotifCollection = notifCollection;
         mHeadsUpManager = headsUpManager;
         mStatusBarStateController = statusBarStateController;
+        mVisualStabilityCoordinator = visualStabilityCoordinator;
     }
 
+    /**
+     * Callback triggered when a user:
+     * 1. Manually dismisses a notification {@see ExpandableNotificationRow}.
+     * 2. Clicks on a notification with flag {@link android.app.Notification#FLAG_AUTO_CANCEL}.
+     * {@see StatusBarNotificationActivityStarter}
+     */
     @Override
     public void onDismiss(
             NotificationEntry entry,
@@ -80,4 +89,11 @@
                             NotificationLogger.getNotificationLocation(entry)))
         );
     }
+
+    @Override
+    public void onImportanceChanged(NotificationEntry entry) {
+        mVisualStabilityCoordinator.temporarilyAllowSectionChanges(
+                entry,
+                SystemClock.uptimeMillis());
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/init/NotifPipelineInitializer.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/init/NotifPipelineInitializer.java
index 9782c3e..db49e44 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/init/NotifPipelineInitializer.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/init/NotifPipelineInitializer.java
@@ -19,6 +19,7 @@
 import android.util.Log;
 
 import com.android.systemui.Dumpable;
+import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dump.DumpManager;
 import com.android.systemui.statusbar.FeatureFlags;
 import com.android.systemui.statusbar.NotificationListener;
@@ -29,20 +30,18 @@
 import com.android.systemui.statusbar.notification.collection.coalescer.GroupCoalescer;
 import com.android.systemui.statusbar.notification.collection.coordinator.NotifCoordinators;
 import com.android.systemui.statusbar.notification.collection.inflation.NotificationRowBinderImpl;
-import com.android.systemui.statusbar.notification.collection.render.NotifViewManager;
-import com.android.systemui.statusbar.notification.collection.render.NotifViewManagerBuilder;
+import com.android.systemui.statusbar.notification.collection.render.ShadeViewManagerFactory;
 import com.android.systemui.statusbar.notification.stack.NotificationListContainer;
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
 
 import javax.inject.Inject;
-import javax.inject.Singleton;
 
 /**
  * Initialization code for the new notification pipeline.
  */
-@Singleton
+@SysUISingleton
 public class NotifPipelineInitializer implements Dumpable {
     private final NotifPipeline mPipelineWrapper;
     private final GroupCoalescer mGroupCoalescer;
@@ -51,7 +50,7 @@
     private final NotifCoordinators mNotifPluggableCoordinators;
     private final NotifInflaterImpl mNotifInflater;
     private final DumpManager mDumpManager;
-    private final NotifViewManagerBuilder mNotifViewManagerBuilder;
+    private final ShadeViewManagerFactory mShadeViewManagerFactory;
     private final FeatureFlags mFeatureFlags;
 
 
@@ -64,7 +63,7 @@
             NotifCoordinators notifCoordinators,
             NotifInflaterImpl notifInflater,
             DumpManager dumpManager,
-            NotifViewManagerBuilder notifViewManagerBuilder,
+            ShadeViewManagerFactory shadeViewManagerFactory,
             FeatureFlags featureFlags) {
         mPipelineWrapper = pipelineWrapper;
         mGroupCoalescer = groupCoalescer;
@@ -73,8 +72,8 @@
         mNotifPluggableCoordinators = notifCoordinators;
         mDumpManager = dumpManager;
         mNotifInflater = notifInflater;
+        mShadeViewManagerFactory = shadeViewManagerFactory;
         mFeatureFlags = featureFlags;
-        mNotifViewManagerBuilder = notifViewManagerBuilder;
     }
 
     /** Hooks the new pipeline up to NotificationManager */
@@ -95,8 +94,7 @@
 
         // Wire up pipeline
         if (mFeatureFlags.isNewNotifPipelineRenderingEnabled()) {
-            NotifViewManager notifViewManager = mNotifViewManagerBuilder.build(listContainer);
-            notifViewManager.attach(mListBuilder);
+            mShadeViewManagerFactory.create(listContainer).attach(mListBuilder);
         }
         mListBuilder.attach(mNotifCollection);
         mNotifCollection.attach(mGroupCoalescer);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/legacy/OnDismissCallbackImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/legacy/OnUserInteractionCallbackImplLegacy.java
similarity index 75%
rename from packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/legacy/OnDismissCallbackImpl.java
rename to packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/legacy/OnUserInteractionCallbackImplLegacy.java
index 94ffa8f..cce8cdc 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/legacy/OnDismissCallbackImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/legacy/OnUserInteractionCallbackImplLegacy.java
@@ -27,30 +27,36 @@
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 import com.android.systemui.statusbar.notification.collection.notifcollection.DismissedByUserStats;
 import com.android.systemui.statusbar.notification.logging.NotificationLogger;
-import com.android.systemui.statusbar.notification.row.OnDismissCallback;
+import com.android.systemui.statusbar.notification.row.OnUserInteractionCallback;
 import com.android.systemui.statusbar.policy.HeadsUpManager;
 
 /**
- * Callback used when a user:
- * 1. Manually dismisses a notification {@see ExpandableNotificationRow}.
- * 2. Clicks on a notification with flag {@link android.app.Notification#FLAG_AUTO_CANCEL}.
- * {@see StatusBarNotificationActivityStarter}
+ * Callback for when a user interacts with a {@see ExpandableNotificationRow}.
  */
-public class OnDismissCallbackImpl implements OnDismissCallback {
+public class OnUserInteractionCallbackImplLegacy implements OnUserInteractionCallback {
     private final NotificationEntryManager mNotificationEntryManager;
     private final HeadsUpManager mHeadsUpManager;
     private final StatusBarStateController mStatusBarStateController;
+    private final VisualStabilityManager mVisualStabilityManager;
 
-    public OnDismissCallbackImpl(
+    public OnUserInteractionCallbackImplLegacy(
             NotificationEntryManager notificationEntryManager,
             HeadsUpManager headsUpManager,
-            StatusBarStateController statusBarStateController
+            StatusBarStateController statusBarStateController,
+            VisualStabilityManager visualStabilityManager
     ) {
         mNotificationEntryManager = notificationEntryManager;
         mHeadsUpManager = headsUpManager;
         mStatusBarStateController = statusBarStateController;
+        mVisualStabilityManager = visualStabilityManager;
     }
 
+    /**
+     * Callback triggered when a user:
+     * 1. Manually dismisses a notification {@see ExpandableNotificationRow}.
+     * 2. Clicks on a notification with flag {@link android.app.Notification#FLAG_AUTO_CANCEL}.
+     * {@see StatusBarNotificationActivityStarter}
+     */
     @Override
     public void onDismiss(
             NotificationEntry entry,
@@ -77,5 +83,10 @@
                 cancellationReason
         );
     }
+
+    @Override
+    public void onImportanceChanged(NotificationEntry entry) {
+        mVisualStabilityManager.temporarilyAllowReordering();
+    }
 }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/VisualStabilityManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/legacy/VisualStabilityManager.java
similarity index 78%
rename from packages/SystemUI/src/com/android/systemui/statusbar/notification/VisualStabilityManager.java
rename to packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/legacy/VisualStabilityManager.java
index 8341c02..165df30 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/VisualStabilityManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/legacy/VisualStabilityManager.java
@@ -11,10 +11,10 @@
  * distributed under the License is distributed on an "AS IS" BASIS,
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  * See the License for the specific language governing permissions and
- * limitations under the License
+ * limitations under the License.
  */
 
-package com.android.systemui.statusbar.notification;
+package com.android.systemui.statusbar.notification.collection.legacy;
 
 import android.os.Handler;
 import android.os.SystemClock;
@@ -24,7 +24,11 @@
 
 import com.android.systemui.Dumpable;
 import com.android.systemui.dagger.qualifiers.Main;
-import com.android.systemui.statusbar.NotificationPresenter;
+import com.android.systemui.keyguard.WakefulnessLifecycle;
+import com.android.systemui.plugins.statusbar.StatusBarStateController;
+import com.android.systemui.statusbar.notification.NotificationEntryListener;
+import com.android.systemui.statusbar.notification.NotificationEntryManager;
+import com.android.systemui.statusbar.notification.VisibilityLocationProvider;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 import com.android.systemui.statusbar.notification.dagger.NotificationsModule;
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
@@ -65,24 +69,44 @@
      */
     public VisualStabilityManager(
             NotificationEntryManager notificationEntryManager,
-            @Main Handler handler) {
+            @Main Handler handler,
+            StatusBarStateController statusBarStateController,
+            WakefulnessLifecycle wakefulnessLifecycle) {
 
         mHandler = handler;
 
-        notificationEntryManager.addNotificationEntryListener(new NotificationEntryListener() {
-            @Override
-            public void onPreEntryUpdated(NotificationEntry entry) {
-                final boolean ambientStateHasChanged =
-                        entry.isAmbient() != entry.getRow().isLowPriority();
-                if (ambientStateHasChanged) {
-                    // note: entries are removed in onReorderingFinished
-                    mLowPriorityReorderingViews.add(entry);
+        if (notificationEntryManager != null) {
+            notificationEntryManager.addNotificationEntryListener(new NotificationEntryListener() {
+                @Override
+                public void onPreEntryUpdated(NotificationEntry entry) {
+                    final boolean ambientStateHasChanged =
+                            entry.isAmbient() != entry.getRow().isLowPriority();
+                    if (ambientStateHasChanged) {
+                        // note: entries are removed in onReorderingFinished
+                        mLowPriorityReorderingViews.add(entry);
+                    }
                 }
-            }
-        });
-    }
+            });
+        }
 
-    public void setUpWithPresenter(NotificationPresenter presenter) {
+        if (statusBarStateController != null) {
+            setPulsing(statusBarStateController.isPulsing());
+            statusBarStateController.addCallback(new StatusBarStateController.StateListener() {
+                @Override
+                public void onPulsingChanged(boolean pulsing) {
+                    setPulsing(pulsing);
+                }
+
+                @Override
+                public void onExpandedChanged(boolean expanded) {
+                    setPanelExpanded(expanded);
+                }
+            });
+        }
+
+        if (wakefulnessLifecycle != null) {
+            wakefulnessLifecycle.addObserver(mWakefulnessObserver);
+        }
     }
 
     /**
@@ -120,25 +144,25 @@
     }
 
     /**
-     * Set the panel to be expanded.
+     * @param screenOn whether the screen is on
      */
-    public void setPanelExpanded(boolean expanded) {
-        mPanelExpanded = expanded;
+    private void setScreenOn(boolean screenOn) {
+        mScreenOn = screenOn;
         updateAllowedStates();
     }
 
     /**
-     * @param screenOn whether the screen is on
+     * Set the panel to be expanded.
      */
-    public void setScreenOn(boolean screenOn) {
-        mScreenOn = screenOn;
+    private void setPanelExpanded(boolean expanded) {
+        mPanelExpanded = expanded;
         updateAllowedStates();
     }
 
     /**
      * @param pulsing whether we are currently pulsing for ambient display.
      */
-    public void setPulsing(boolean pulsing) {
+    private void setPulsing(boolean pulsing) {
         if (mPulsing == pulsing) {
             return;
         }
@@ -215,6 +239,10 @@
         mVisibilityLocationProvider = visibilityLocationProvider;
     }
 
+    /**
+     * Notifications have been reordered, so reset all the allowed list of views that are allowed
+     * to reorder.
+     */
     public void onReorderingFinished() {
         mAllowedReorderViews.clear();
         mAddedChildren.clear();
@@ -271,11 +299,27 @@
         pw.println();
     }
 
+    final WakefulnessLifecycle.Observer mWakefulnessObserver = new WakefulnessLifecycle.Observer() {
+        @Override
+        public void onFinishedGoingToSleep() {
+            setScreenOn(false);
+        }
+
+        @Override
+        public void onStartedWakingUp() {
+            setScreenOn(true);
+        }
+    };
+
+
+    /**
+     * See {@link Callback#onChangeAllowed()}
+     */
     public interface Callback {
+
         /**
          * Called when changing is allowed again.
          */
         void onChangeAllowed();
     }
-
 }
diff --git a/media/java/android/media/IStrategyPreferredDeviceDispatcher.aidl b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/NotifSection.kt
similarity index 64%
copy from media/java/android/media/IStrategyPreferredDeviceDispatcher.aidl
copy to packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/NotifSection.kt
index b1f99e6..c09122e 100644
--- a/media/java/android/media/IStrategyPreferredDeviceDispatcher.aidl
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/NotifSection.kt
@@ -14,17 +14,14 @@
  * limitations under the License.
  */
 
-package android.media;
+package com.android.systemui.statusbar.notification.collection.listbuilder
 
-import android.media.AudioDeviceAttributes;
+import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifSectioner
 
-/**
- * AIDL for AudioService to signal audio strategy-preferred device updates.
- *
- * {@hide}
- */
-oneway interface IStrategyPreferredDeviceDispatcher {
-
-    void dispatchPrefDeviceChanged(int strategyId, in AudioDeviceAttributes device);
-
+data class NotifSection(
+    val sectioner: NotifSectioner,
+    val index: Int
+) {
+    val label: String
+        get() = "Section($index, \"${sectioner.name}\")"
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/PipelineState.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/PipelineState.java
index f1f7d63..798bfe7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/PipelineState.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/PipelineState.java
@@ -81,9 +81,10 @@
     public static final int STATE_PRE_GROUP_FILTERING = 3;
     public static final int STATE_GROUPING = 4;
     public static final int STATE_TRANSFORMING = 5;
-    public static final int STATE_SORTING = 6;
-    public static final int STATE_FINALIZE_FILTERING = 7;
-    public static final int STATE_FINALIZING = 8;
+    public static final int STATE_GROUP_STABILIZING = 6;
+    public static final int STATE_SORTING = 7;
+    public static final int STATE_FINALIZE_FILTERING = 8;
+    public static final int STATE_FINALIZING = 9;
 
     @IntDef(prefix = { "STATE_" }, value = {
             STATE_IDLE,
@@ -92,6 +93,7 @@
             STATE_PRE_GROUP_FILTERING,
             STATE_GROUPING,
             STATE_TRANSFORMING,
+            STATE_GROUP_STABILIZING,
             STATE_SORTING,
             STATE_FINALIZE_FILTERING,
             STATE_FINALIZING,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/ShadeListBuilderLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/ShadeListBuilderLogger.kt
index 67f8bfe..9ee7db7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/ShadeListBuilderLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/ShadeListBuilderLogger.kt
@@ -25,7 +25,6 @@
 import com.android.systemui.statusbar.notification.collection.ListEntry
 import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifFilter
 import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifPromoter
-import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifSection
 import javax.inject.Inject
 
 class ShadeListBuilderLogger @Inject constructor(
@@ -57,6 +56,15 @@
         })
     }
 
+    fun logReorderingAllowedInvalidated(name: String, pipelineState: Int) {
+        buffer.log(TAG, DEBUG, {
+            str1 = name
+            int1 = pipelineState
+        }, {
+            """ReorderingNowAllowed "$str1" invalidated; pipeline state is $int1"""
+        })
+    }
+
     fun logPromoterInvalidated(name: String, pipelineState: Int) {
         buffer.log(TAG, DEBUG, {
             str1 = name
@@ -156,6 +164,21 @@
         })
     }
 
+    fun logParentChangeSuppressed(
+        buildId: Int,
+        suppressedParent: GroupEntry?,
+        keepingParent: GroupEntry?
+    ) {
+        buffer.log(TAG, INFO, {
+            int1 = buildId
+            str1 = suppressedParent?.key
+            str2 = keepingParent?.key
+        }, {
+            "(Build $long1)     Change of parent to '$str1' suppressed; " +
+                "keeping parent '$str2'"
+        })
+    }
+
     fun logFilterChanged(
         buildId: Int,
         prevFilter: NotifFilter?,
@@ -187,25 +210,35 @@
     fun logSectionChanged(
         buildId: Int,
         prevSection: NotifSection?,
-        prevIndex: Int,
-        newSection: NotifSection?,
-        newIndex: Int
+        newSection: NotifSection?
     ) {
         buffer.log(TAG, INFO, {
             long1 = buildId.toLong()
-            str1 = prevSection?.name
-            int1 = prevIndex
-            str2 = newSection?.name
-            int2 = newIndex
+            str1 = prevSection?.label
+            str2 = newSection?.label
         }, {
             if (str1 == null) {
-                "(Build $long1)     Section assigned: '$str2' (#$int2)"
+                "(Build $long1)     Section assigned: $str2"
             } else {
-                "(Build $long1)     Section changed: '$str1' (#$int1) -> '$str2' (#$int2)"
+                "(Build $long1)     Section changed: $str1 -> $str2"
             }
         })
     }
 
+    fun logSectionChangeSuppressed(
+        buildId: Int,
+        suppressedSection: NotifSection?,
+        assignedSection: NotifSection?
+    ) {
+        buffer.log(TAG, INFO, {
+            long1 = buildId.toLong()
+            str1 = suppressedSection?.label
+            str2 = assignedSection?.label
+        }, {
+            "(Build $long1)     Suppressing section change to $str1 (staying at $str2)"
+        })
+    }
+
     fun logFinalList(entries: List<ListEntry>) {
         if (entries.isEmpty()) {
             buffer.log(TAG, DEBUG, {}, { "(empty list)" })
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/pluggable/NotifSection.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/pluggable/NotifSectioner.java
similarity index 92%
rename from packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/pluggable/NotifSection.java
rename to packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/pluggable/NotifSectioner.java
index fe5ba3c..b57f504 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/pluggable/NotifSection.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/pluggable/NotifSectioner.java
@@ -22,8 +22,8 @@
 /**
  * Pluggable for participating in notif sectioning. See {@link ShadeListBuilder#setSections}.
  */
-public abstract class NotifSection extends Pluggable<NotifSection> {
-    protected NotifSection(String name) {
+public abstract class NotifSectioner extends Pluggable<NotifSectioner> {
+    protected NotifSectioner(String name) {
         super(name);
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/pluggable/NotifStabilityManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/pluggable/NotifStabilityManager.java
new file mode 100644
index 0000000..58d4b97
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/pluggable/NotifStabilityManager.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification.collection.listbuilder.pluggable;
+
+import com.android.systemui.statusbar.notification.collection.NotificationEntry;
+
+/**
+ * Pluggable for participating in notif stabilization. In particular, suppressing group and
+ * section changes.
+ *
+ * The stability manager should be invalidated when previously suppressed a group or
+ * section change is now allowed.
+ */
+public abstract class NotifStabilityManager extends Pluggable<NotifStabilityManager> {
+
+    protected NotifStabilityManager(String name) {
+        super(name);
+    }
+
+    /**
+     * Called at the beginning of every pipeline run to perform any necessary cleanup from the
+     * previous run.
+     */
+    public abstract void onBeginRun();
+
+    /**
+     * Returns whether this notification can currently change groups/parents.
+     * Per iteration of the notification pipeline, locally stores this information until the next
+     * run of the pipeline. When this method returns true, it's expected that a group change for
+     * this entry is being suppressed.
+     */
+    public abstract boolean isGroupChangeAllowed(NotificationEntry entry);
+
+    /**
+     * Returns whether this notification entry can currently change sections.
+     * Per iteration of the notification pipeline, locally stores this information until the next
+     * run of the pipeline. When this method returns true, it's expected that a section change is
+     * being suppressed.
+     */
+    public abstract boolean isSectionChangeAllowed(NotificationEntry entry);
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/provider/HighPriorityProvider.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/provider/HighPriorityProvider.java
index 1c076c4..8b803b5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/provider/HighPriorityProvider.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/provider/HighPriorityProvider.java
@@ -19,6 +19,7 @@
 import android.app.Notification;
 import android.app.NotificationManager;
 
+import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.statusbar.notification.collection.GroupEntry;
 import com.android.systemui.statusbar.notification.collection.ListEntry;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
@@ -28,7 +29,6 @@
 import java.util.List;
 
 import javax.inject.Inject;
-import javax.inject.Singleton;
 
 /**
  * Determines whether a notification is considered 'high priority'.
@@ -36,7 +36,7 @@
  * Notifications that are high priority are visible on the lock screen/status bar and in the top
  * section in the shade.
  */
-@Singleton
+@SysUISingleton
 public class HighPriorityProvider {
     private final PeopleNotificationIdentifier mPeopleNotificationIdentifier;
     private final NotificationGroupManager mGroupManager;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NodeController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NodeController.kt
new file mode 100644
index 0000000..67f7b1c
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NodeController.kt
@@ -0,0 +1,90 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification.collection.render
+
+import android.view.View
+import java.lang.RuntimeException
+import java.lang.StringBuilder
+
+/**
+ * A controller that represents a single unit of addable/removable view(s) in the notification
+ * shade. Some nodes are just a single view (such as a header), while some might involve many views
+ * (such as a notification row).
+ *
+ * It's possible for nodes to support having child nodes (for example, some notification rows
+ * contain other notification rows). If so, they must implement all of the child-related methods
+ * below.
+ */
+interface NodeController {
+    /** A string that uniquely(ish) represents the node in the tree. Used for debugging. */
+    val nodeLabel: String
+
+    val view: View
+
+    fun getChildAt(index: Int): View? {
+        throw RuntimeException("Not supported")
+    }
+
+    fun getChildCount(): Int {
+        throw RuntimeException("Not supported")
+    }
+
+    fun addChildAt(child: NodeController, index: Int) {
+        throw RuntimeException("Not supported")
+    }
+
+    fun moveChildTo(child: NodeController, index: Int) {
+        throw RuntimeException("Not supported")
+    }
+
+    fun removeChild(child: NodeController, isTransfer: Boolean) {
+        throw RuntimeException("Not supported")
+    }
+}
+
+/**
+ * Used to specify the tree of [NodeController]s that currently make up the shade.
+ */
+interface NodeSpec {
+    val parent: NodeSpec?
+    val controller: NodeController
+    val children: List<NodeSpec>
+}
+
+class NodeSpecImpl(
+    override val parent: NodeSpec?,
+    override val controller: NodeController
+) : NodeSpec {
+    override val children = mutableListOf<NodeSpec>()
+}
+
+/**
+ * Converts a tree spec to human-readable string, for dumping purposes.
+ */
+fun treeSpecToStr(tree: NodeSpec): String {
+    return StringBuilder().also { treeSpecToStrHelper(tree, it, "") }.toString()
+}
+
+private fun treeSpecToStrHelper(tree: NodeSpec, sb: StringBuilder, indent: String) {
+    sb.append("${indent}ns{${tree.controller.nodeLabel}")
+    if (tree.children.isNotEmpty()) {
+        val childIndent = "$indent  "
+        for (child in tree.children) {
+            treeSpecToStrHelper(child, sb, childIndent)
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NotifViewBarn.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NotifViewBarn.kt
index 5400095..79bc3d7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NotifViewBarn.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NotifViewBarn.kt
@@ -17,19 +17,20 @@
 package com.android.systemui.statusbar.notification.collection.render
 
 import android.view.textclassifier.Log
+import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.statusbar.notification.collection.ListEntry
-import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow
+import com.android.systemui.statusbar.notification.row.ExpandableNotificationRowController
 import javax.inject.Inject
-import javax.inject.Singleton
 
 /**
- * The ViewBarn is just a map from [ListEntry] to an instance of an [ExpandableNotificationRow].
+ * The ViewBarn is just a map from [ListEntry] to an instance of an
+ * [ExpandableNotificationRowController].
  */
-@Singleton
+@SysUISingleton
 class NotifViewBarn @Inject constructor() {
-    private val rowMap = mutableMapOf<String, ExpandableNotificationRow>()
+    private val rowMap = mutableMapOf<String, ExpandableNotificationRowController>()
 
-    fun requireView(forEntry: ListEntry): ExpandableNotificationRow {
+    fun requireView(forEntry: ListEntry): ExpandableNotificationRowController {
         if (DEBUG) {
             Log.d(TAG, "requireView: $forEntry.key")
         }
@@ -41,11 +42,11 @@
         return li
     }
 
-    fun registerViewForEntry(entry: ListEntry, view: ExpandableNotificationRow) {
+    fun registerViewForEntry(entry: ListEntry, controller: ExpandableNotificationRowController) {
         if (DEBUG) {
             Log.d(TAG, "registerViewForEntry: $entry.key")
         }
-        rowMap[entry.key] = view
+        rowMap[entry.key] = controller
     }
 
     fun removeViewForEntry(entry: ListEntry) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NotifViewManager.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NotifViewManager.kt
deleted file mode 100644
index f2e2c39..0000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NotifViewManager.kt
+++ /dev/null
@@ -1,240 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.statusbar.notification.collection.render
-
-import android.annotation.MainThread
-import android.view.View
-import com.android.systemui.statusbar.notification.collection.GroupEntry
-import com.android.systemui.statusbar.notification.collection.GroupEntry.ROOT_ENTRY
-import com.android.systemui.statusbar.notification.collection.ListEntry
-import com.android.systemui.statusbar.notification.collection.NotificationEntry
-import com.android.systemui.statusbar.notification.collection.ShadeListBuilder
-import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow
-import com.android.systemui.statusbar.notification.stack.NotificationListContainer
-import javax.inject.Inject
-
-/**
- * A consumer of a Notification tree built by [ShadeListBuilder] which will update the notification
- * presenter with the minimum operations required to make the old tree match the new one
- */
-@MainThread
-class NotifViewManager constructor(
-    private val listContainer: NotificationListContainer,
-    private val viewBarn: NotifViewBarn,
-    private val logger: NotifViewManagerLogger
-) {
-    private val rootNode = RootWrapper(listContainer)
-    private val rows = mutableMapOf<ListEntry, RowNode>()
-
-    fun attach(listBuilder: ShadeListBuilder) {
-        listBuilder.setOnRenderListListener(::onNewNotifTree)
-    }
-
-    private fun onNewNotifTree(tree: List<ListEntry>) {
-        // Step 1: Detach all views whose parents have changed
-        detachRowsWithModifiedParents()
-
-        // Step 2: Attach all new views and reattach all views whose parents changed.
-        // Also reorder existing children to match the spec we've received
-        val orderChanged = addAndReorderChildren(rootNode, tree)
-        if (orderChanged) {
-            listContainer.generateChildOrderChangedEvent()
-        }
-    }
-
-    private fun detachRowsWithModifiedParents() {
-        val toRemove = mutableListOf<ListEntry>()
-        for (row in rows.values) {
-            val oldParentEntry = row.nodeParent?.entry
-            val newParentEntry = row.entry.parent
-
-            if (newParentEntry != oldParentEntry) {
-                // If the parent is null, then we should remove the child completely. If not, then
-                // the parent merely changed: we'll detach it for now and then attach it to the
-                // new parent in step 2.
-                val isTransfer = newParentEntry != null
-                if (!isTransfer) {
-                    toRemove.add(row.entry)
-                }
-
-                if (!isTransfer && !isAttachedToRootEntry(oldParentEntry)) {
-                    // If our view parent has also been removed (i.e. is no longer attached to the
-                    // root entry) then we skip removing the child here
-                    logger.logSkippingDetach(row.entry.key, row.nodeParent?.entry?.key)
-                } else {
-                    logger.logDetachingChild(
-                            row.entry.key,
-                            isTransfer,
-                            oldParentEntry?.key,
-                            newParentEntry?.key)
-                    row.nodeParent?.removeChild(row, isTransfer)
-                    row.nodeParent = null
-                }
-            }
-        }
-        rows.keys.removeAll(toRemove)
-    }
-
-    private fun addAndReorderChildren(parent: ParentNode, childEntries: List<ListEntry>): Boolean {
-        var orderChanged = false
-        for ((index, entry) in childEntries.withIndex()) {
-            val row = getRowNode(entry)
-            val currView = parent.getChildViewAt(index)
-            if (currView != row.view) {
-                when (row.nodeParent) {
-                    null -> {
-                        logger.logAttachingChild(row.entry.key, parent.entry.key)
-                        parent.addChildAt(row, index)
-                        row.nodeParent = parent
-                    }
-                    parent -> {
-                        logger.logMovingChild(row.entry.key, parent.entry.key, index)
-                        parent.moveChild(row, index)
-                        orderChanged = true
-                    }
-                    else -> {
-                        throw IllegalStateException("Child ${row.entry.key} should have parent " +
-                                "${parent.entry.key} but is actually " +
-                                "${row.nodeParent?.entry?.key}")
-                    }
-                }
-            }
-            if (row is GroupWrapper) {
-                val childOrderChanged = addAndReorderChildren(row, row.entry.children)
-                orderChanged = orderChanged || childOrderChanged
-            }
-        }
-        // TODO: setUntruncatedChildCount
-
-        return orderChanged
-    }
-
-    private fun getRowNode(entry: ListEntry): RowNode {
-        return rows.getOrPut(entry) {
-            when (entry) {
-                is NotificationEntry -> RowWrapper(entry, viewBarn.requireView(entry))
-                is GroupEntry ->
-                    GroupWrapper(
-                            entry,
-                            viewBarn.requireView(checkNotNull(entry.summary)),
-                            listContainer)
-                else -> throw RuntimeException(
-                        "Unexpected entry type for ${entry.key}: ${entry.javaClass}")
-            }
-        }
-    }
-}
-
-class NotifViewManagerBuilder @Inject constructor(
-    private val viewBarn: NotifViewBarn,
-    private val logger: NotifViewManagerLogger
-) {
-    fun build(listContainer: NotificationListContainer): NotifViewManager {
-        return NotifViewManager(listContainer, viewBarn, logger)
-    }
-}
-
-private fun isAttachedToRootEntry(entry: ListEntry?): Boolean {
-    return when (entry) {
-        null -> false
-        ROOT_ENTRY -> true
-        else -> isAttachedToRootEntry(entry.parent)
-    }
-}
-
-private interface Node {
-    val entry: ListEntry
-    val nodeParent: ParentNode?
-}
-
-private interface ParentNode : Node {
-    fun getChildViewAt(index: Int): View?
-    fun addChildAt(child: RowNode, index: Int)
-    fun moveChild(child: RowNode, index: Int)
-    fun removeChild(child: RowNode, isTransfer: Boolean)
-}
-
-private interface RowNode : Node {
-    val view: ExpandableNotificationRow
-    override var nodeParent: ParentNode?
-}
-
-private class RootWrapper(
-    private val listContainer: NotificationListContainer
-) : ParentNode {
-    override val entry: ListEntry = ROOT_ENTRY
-    override val nodeParent: ParentNode? = null
-
-    override fun getChildViewAt(index: Int): View? {
-        return listContainer.getContainerChildAt(index)
-    }
-
-    override fun addChildAt(child: RowNode, index: Int) {
-        listContainer.addContainerViewAt(child.view, index)
-    }
-
-    override fun moveChild(child: RowNode, index: Int) {
-        listContainer.changeViewPosition(child.view, index)
-    }
-
-    override fun removeChild(child: RowNode, isTransfer: Boolean) {
-        if (isTransfer) {
-            listContainer.setChildTransferInProgress(true)
-        }
-        listContainer.removeContainerView(child.view)
-        if (isTransfer) {
-            listContainer.setChildTransferInProgress(false)
-        }
-    }
-}
-
-private class GroupWrapper(
-    override val entry: GroupEntry,
-    override val view: ExpandableNotificationRow,
-    val listContainer: NotificationListContainer
-) : RowNode, ParentNode {
-
-    override var nodeParent: ParentNode? = null
-
-    override fun getChildViewAt(index: Int): View? {
-        return view.getChildNotificationAt(index)
-    }
-
-    override fun addChildAt(child: RowNode, index: Int) {
-        view.addChildNotification(child.view, index)
-        listContainer.notifyGroupChildAdded(child.view)
-    }
-
-    override fun moveChild(child: RowNode, index: Int) {
-        view.removeChildNotification(child.view)
-        view.addChildNotification(child.view, index)
-    }
-
-    override fun removeChild(child: RowNode, isTransfer: Boolean) {
-        view.removeChildNotification(child.view)
-        if (isTransfer) {
-            listContainer.notifyGroupChildRemoved(child.view, view)
-        }
-    }
-}
-
-private class RowWrapper(
-    override val entry: NotificationEntry,
-    override val view: ExpandableNotificationRow
-) : RowNode {
-    override var nodeParent: ParentNode? = null
-}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/RootNodeController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/RootNodeController.kt
new file mode 100644
index 0000000..a1800ed
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/RootNodeController.kt
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification.collection.render
+
+import android.view.View
+import com.android.systemui.statusbar.notification.row.ExpandableView
+import com.android.systemui.statusbar.notification.stack.NotificationListContainer
+
+/**
+ * Temporary wrapper around [NotificationListContainer], for use by [ShadeViewDiffer]. Long term,
+ * we should just modify NLC to implement the NodeController interface.
+ */
+class RootNodeController(
+    private val listContainer: NotificationListContainer,
+    override val view: View
+) : NodeController {
+    override val nodeLabel: String = "<root>"
+
+    override fun getChildAt(index: Int): View? {
+        return listContainer.getContainerChildAt(index)
+    }
+
+    override fun getChildCount(): Int {
+        return listContainer.containerChildCount
+    }
+
+    override fun addChildAt(child: NodeController, index: Int) {
+        listContainer.addContainerViewAt(child.view, index)
+    }
+
+    override fun moveChildTo(child: NodeController, index: Int) {
+        listContainer.changeViewPosition(child.view as ExpandableView, index)
+    }
+
+    override fun removeChild(child: NodeController, isTransfer: Boolean) {
+        if (isTransfer) {
+            listContainer.setChildTransferInProgress(true)
+        }
+        listContainer.removeContainerView(child.view)
+        if (isTransfer) {
+            listContainer.setChildTransferInProgress(false)
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewDiffer.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewDiffer.kt
new file mode 100644
index 0000000..019520f
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewDiffer.kt
@@ -0,0 +1,221 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification.collection.render
+
+import android.annotation.MainThread
+import android.view.View
+import com.android.systemui.util.kotlin.transform
+
+/**
+ * Given a "spec" that describes a "tree" of views, adds and removes views from the
+ * [rootController] and its children until the actual tree matches the spec.
+ *
+ * Every node in the spec tree must specify both a view and its associated [NodeController].
+ * Commands to add/remove/reorder children are sent to the controller. How the controller
+ * interprets these commands is left to its own discretion -- it might add them directly to its
+ * associated view or to some subview container.
+ *
+ * It's possible for nodes to mix "unmanaged" views in alongside managed ones within the same
+ * container. In this case, whenever the differ runs it will move all unmanaged views to the end
+ * of the node's child list.
+ */
+@MainThread
+class ShadeViewDiffer(
+    rootController: NodeController,
+    private val logger: ShadeViewDifferLogger
+) {
+    private val rootNode = ShadeNode(rootController)
+    private val nodes = mutableMapOf(rootController to rootNode)
+    private val views = mutableMapOf<View, ShadeNode>()
+
+    /**
+     * Adds and removes views from the root (and its children) until their structure matches the
+     * provided [spec]. The root node of the spec must match the root controller passed to the
+     * differ's constructor.
+     */
+    fun applySpec(spec: NodeSpec) {
+        val specMap = treeToMap(spec)
+
+        if (spec.controller != rootNode.controller) {
+            throw IllegalArgumentException("Tree root ${spec.controller.nodeLabel} does not " +
+                    "match own root at ${rootNode.label}")
+        }
+
+        detachChildren(rootNode, specMap)
+        attachChildren(rootNode, specMap)
+    }
+
+    /**
+     * If [view] is managed by this differ, then returns the label of the view's controller.
+     * Otherwise returns View.toString().
+     *
+     * For debugging purposes.
+     */
+    fun getViewLabel(view: View): String {
+        return views[view]?.label ?: view.toString()
+    }
+
+    private fun detachChildren(
+        parentNode: ShadeNode,
+        specMap: Map<NodeController, NodeSpec>
+    ) {
+        val parentSpec = specMap[parentNode.controller]
+
+        for (i in parentNode.getChildCount() - 1 downTo 0) {
+            val childView = parentNode.getChildAt(i)
+            views[childView]?.let { childNode ->
+                val childSpec = specMap[childNode.controller]
+
+                maybeDetachChild(parentNode, parentSpec, childNode, childSpec)
+
+                if (childNode.controller.getChildCount() > 0) {
+                    detachChildren(childNode, specMap)
+                }
+            }
+        }
+    }
+
+    private fun maybeDetachChild(
+        parentNode: ShadeNode,
+        parentSpec: NodeSpec?,
+        childNode: ShadeNode,
+        childSpec: NodeSpec?
+    ) {
+        val newParentNode = transform(childSpec?.parent) { getNode(it) }
+
+        if (newParentNode != parentNode) {
+            val childCompletelyRemoved = newParentNode == null
+
+            if (childCompletelyRemoved) {
+                nodes.remove(childNode.controller)
+                views.remove(childNode.controller.view)
+            }
+
+            if (childCompletelyRemoved && parentSpec == null) {
+                // If both the child and the parent are being removed at the same time, then
+                // keep the child attached to the parent for animation purposes
+                logger.logSkippingDetach(childNode.label, parentNode.label)
+            } else {
+                logger.logDetachingChild(
+                        childNode.label,
+                        !childCompletelyRemoved,
+                        parentNode.label,
+                        newParentNode?.label)
+                parentNode.removeChild(childNode, !childCompletelyRemoved)
+                childNode.parent = null
+            }
+        }
+    }
+
+    private fun attachChildren(
+        parentNode: ShadeNode,
+        specMap: Map<NodeController, NodeSpec>
+    ) {
+        val parentSpec = checkNotNull(specMap[parentNode.controller])
+
+        for ((index, childSpec) in parentSpec.children.withIndex()) {
+            val currView = parentNode.getChildAt(index)
+            val childNode = getNode(childSpec)
+
+            if (childNode.view != currView) {
+
+                when (childNode.parent) {
+                    null -> {
+                        // A new child (either newly created or coming from some other parent)
+                        logger.logAttachingChild(childNode.label, parentNode.label)
+                        parentNode.addChildAt(childNode, index)
+                        childNode.parent = parentNode
+                    }
+                    parentNode -> {
+                        // A pre-existing child, just in the wrong position. Move it into place
+                        logger.logMovingChild(childNode.label, parentNode.label, index)
+                        parentNode.moveChildTo(childNode, index)
+                    }
+                    else -> {
+                        // Error: child still has a parent. We should have detached it in the
+                        // previous step.
+                        throw IllegalStateException("Child ${childNode.label} should have " +
+                                "parent ${parentNode.label} but is actually " +
+                                "${childNode.parent?.label}")
+                    }
+                }
+            }
+
+            if (childSpec.children.isNotEmpty()) {
+                attachChildren(childNode, specMap)
+            }
+        }
+    }
+
+    private fun getNode(spec: NodeSpec): ShadeNode {
+        var node = nodes[spec.controller]
+        if (node == null) {
+            node = ShadeNode(spec.controller)
+            nodes[node.controller] = node
+            views[node.view] = node
+        }
+        return node
+    }
+
+    private fun treeToMap(tree: NodeSpec): Map<NodeController, NodeSpec> {
+        val map = mutableMapOf<NodeController, NodeSpec>()
+
+        registerNodes(tree, map)
+
+        return map
+    }
+
+    private fun registerNodes(node: NodeSpec, map: MutableMap<NodeController, NodeSpec>) {
+        if (map.containsKey(node.controller)) {
+            throw RuntimeException("Node ${node.controller.nodeLabel} appears more than once")
+        }
+        map[node.controller] = node
+
+        if (node.children.isNotEmpty()) {
+            for (child in node.children) {
+                registerNodes(child, map)
+            }
+        }
+    }
+}
+
+private class ShadeNode(
+    val controller: NodeController
+) {
+    val view = controller.view
+
+    var parent: ShadeNode? = null
+
+    val label: String
+        get() = controller.nodeLabel
+
+    fun getChildAt(index: Int): View? = controller.getChildAt(index)
+
+    fun getChildCount(): Int = controller.getChildCount()
+
+    fun addChildAt(child: ShadeNode, index: Int) {
+        controller.addChildAt(child.controller, index)
+    }
+
+    fun moveChildTo(child: ShadeNode, index: Int) {
+        controller.moveChildTo(child.controller, index)
+    }
+
+    fun removeChild(child: ShadeNode, isTransfer: Boolean) {
+        controller.removeChild(child.controller, isTransfer)
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NotifViewManagerLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewDifferLogger.kt
similarity index 97%
rename from packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NotifViewManagerLogger.kt
rename to packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewDifferLogger.kt
index 3d56126..19e156f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NotifViewManagerLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewDifferLogger.kt
@@ -21,7 +21,7 @@
 import com.android.systemui.log.dagger.NotificationLog
 import javax.inject.Inject
 
-class NotifViewManagerLogger @Inject constructor(
+class ShadeViewDifferLogger @Inject constructor(
     @NotificationLog private val buffer: LogBuffer
 ) {
     fun logDetachingChild(
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewManager.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewManager.kt
new file mode 100644
index 0000000..3c35b7bd
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewManager.kt
@@ -0,0 +1,103 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification.collection.render
+
+import android.content.Context
+import android.view.View
+import com.android.systemui.statusbar.notification.collection.GroupEntry
+import com.android.systemui.statusbar.notification.collection.ListEntry
+import com.android.systemui.statusbar.notification.collection.NotificationEntry
+import com.android.systemui.statusbar.notification.collection.ShadeListBuilder
+import com.android.systemui.statusbar.notification.stack.NotificationListContainer
+import com.android.systemui.statusbar.phone.NotificationIconAreaController
+import java.lang.RuntimeException
+import javax.inject.Inject
+
+/**
+ * Responsible for building and applying the "shade node spec": the list (tree) of things that
+ * currently populate the notification shade.
+ */
+class ShadeViewManager constructor(
+    context: Context,
+    listContainer: NotificationListContainer,
+    logger: ShadeViewDifferLogger,
+    private val viewBarn: NotifViewBarn,
+    private val notificationIconAreaController: NotificationIconAreaController
+) {
+    // We pass a shim view here because the listContainer may not actually have a view associated
+    // with it and the differ never actually cares about the root node's view.
+    private val rootController = RootNodeController(listContainer, View(context))
+    private val viewDiffer = ShadeViewDiffer(rootController, logger)
+
+    fun attach(listBuilder: ShadeListBuilder) {
+        listBuilder.setOnRenderListListener(::onNewNotifTree)
+    }
+
+    private fun onNewNotifTree(tree: List<ListEntry>) {
+        viewDiffer.applySpec(buildTree(tree))
+    }
+
+    private fun buildTree(notifList: List<ListEntry>): NodeSpec {
+        val root = NodeSpecImpl(null, rootController)
+
+        for (entry in notifList) {
+            // TODO: Add section header logic here
+            root.children.add(buildNotifNode(entry, root))
+        }
+
+        notificationIconAreaController.updateNotificationIcons(notifList)
+        return root
+    }
+
+    private fun buildNotifNode(entry: ListEntry, parent: NodeSpec): NodeSpec {
+        return when (entry) {
+            is NotificationEntry -> {
+                NodeSpecImpl(parent, viewBarn.requireView(entry))
+            }
+            is GroupEntry -> {
+                val groupNode = NodeSpecImpl(
+                        parent,
+                        viewBarn.requireView(checkNotNull(entry.summary)))
+
+                for (childEntry in entry.children) {
+                    groupNode.children.add(buildNotifNode(childEntry, groupNode))
+                }
+
+                groupNode
+            }
+            else -> {
+                throw RuntimeException("Unexpected entry: $entry")
+            }
+        }
+    }
+}
+
+class ShadeViewManagerFactory @Inject constructor(
+    private val context: Context,
+    private val logger: ShadeViewDifferLogger,
+    private val viewBarn: NotifViewBarn,
+    private val notificationIconAreaController: NotificationIconAreaController
+) {
+    fun create(listContainer: NotificationListContainer): ShadeViewManager {
+        return ShadeViewManager(
+                context,
+                listContainer,
+                logger,
+                viewBarn,
+                notificationIconAreaController)
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java
index 046dc3c8..6d01324 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java
@@ -28,9 +28,11 @@
 import com.android.internal.statusbar.IStatusBarService;
 import com.android.systemui.R;
 import com.android.systemui.bubbles.BubbleController;
+import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dagger.qualifiers.Background;
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.dagger.qualifiers.UiBackground;
+import com.android.systemui.keyguard.WakefulnessLifecycle;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.settings.CurrentUserContextTracker;
 import com.android.systemui.statusbar.FeatureFlags;
@@ -40,14 +42,16 @@
 import com.android.systemui.statusbar.notification.ForegroundServiceDismissalFeatureController;
 import com.android.systemui.statusbar.notification.NotificationEntryManager;
 import com.android.systemui.statusbar.notification.NotificationEntryManagerLogger;
-import com.android.systemui.statusbar.notification.VisualStabilityManager;
 import com.android.systemui.statusbar.notification.collection.NotifCollection;
 import com.android.systemui.statusbar.notification.collection.NotifInflaterImpl;
 import com.android.systemui.statusbar.notification.collection.NotifPipeline;
 import com.android.systemui.statusbar.notification.collection.NotificationRankingManager;
+import com.android.systemui.statusbar.notification.collection.coordinator.VisualStabilityCoordinator;
 import com.android.systemui.statusbar.notification.collection.inflation.NotifInflater;
 import com.android.systemui.statusbar.notification.collection.inflation.NotificationRowBinder;
-import com.android.systemui.statusbar.notification.collection.inflation.OnDismissCallbackImpl;
+import com.android.systemui.statusbar.notification.collection.inflation.OnUserInteractionCallbackImpl;
+import com.android.systemui.statusbar.notification.collection.legacy.OnUserInteractionCallbackImplLegacy;
+import com.android.systemui.statusbar.notification.collection.legacy.VisualStabilityManager;
 import com.android.systemui.statusbar.notification.collection.notifcollection.CommonNotifCollection;
 import com.android.systemui.statusbar.notification.collection.provider.HighPriorityProvider;
 import com.android.systemui.statusbar.notification.init.NotificationsController;
@@ -61,7 +65,7 @@
 import com.android.systemui.statusbar.notification.row.ChannelEditorDialogController;
 import com.android.systemui.statusbar.notification.row.NotificationBlockingHelperManager;
 import com.android.systemui.statusbar.notification.row.NotificationGutsManager;
-import com.android.systemui.statusbar.notification.row.OnDismissCallback;
+import com.android.systemui.statusbar.notification.row.OnUserInteractionCallback;
 import com.android.systemui.statusbar.notification.row.PriorityOnboardingDialogController;
 import com.android.systemui.statusbar.phone.NotificationGroupManager;
 import com.android.systemui.statusbar.phone.StatusBar;
@@ -71,7 +75,6 @@
 import java.util.concurrent.Executor;
 
 import javax.inject.Provider;
-import javax.inject.Singleton;
 
 import dagger.Binds;
 import dagger.Lazy;
@@ -84,7 +87,7 @@
 @Module
 public interface NotificationsModule {
     /** Provides an instance of {@link NotificationEntryManager} */
-    @Singleton
+    @SysUISingleton
     @Provides
     static NotificationEntryManager provideNotificationEntryManager(
             NotificationEntryManagerLogger logger,
@@ -111,11 +114,10 @@
     }
 
     /** Provides an instance of {@link NotificationGutsManager} */
-    @Singleton
+    @SysUISingleton
     @Provides
     static NotificationGutsManager provideNotificationGutsManager(
             Context context,
-            VisualStabilityManager visualStabilityManager,
             Lazy<StatusBar> statusBarLazy,
             @Main Handler mainHandler,
             @Background Handler bgHandler,
@@ -129,10 +131,10 @@
             Provider<PriorityOnboardingDialogController.Builder> builderProvider,
             AssistantFeedbackController assistantFeedbackController,
             BubbleController bubbleController,
-            UiEventLogger uiEventLogger) {
+            UiEventLogger uiEventLogger,
+            OnUserInteractionCallback onUserInteractionCallback) {
         return new NotificationGutsManager(
                 context,
-                visualStabilityManager,
                 statusBarLazy,
                 mainHandler,
                 bgHandler,
@@ -146,19 +148,28 @@
                 builderProvider,
                 assistantFeedbackController,
                 bubbleController,
-                uiEventLogger);
+                uiEventLogger,
+                onUserInteractionCallback);
     }
 
     /** Provides an instance of {@link VisualStabilityManager} */
-    @Singleton
+    @SysUISingleton
     @Provides
     static VisualStabilityManager provideVisualStabilityManager(
-            NotificationEntryManager notificationEntryManager, Handler handler) {
-        return new VisualStabilityManager(notificationEntryManager, handler);
+            FeatureFlags featureFlags,
+            NotificationEntryManager notificationEntryManager,
+            Handler handler,
+            StatusBarStateController statusBarStateController,
+            WakefulnessLifecycle wakefulnessLifecycle) {
+        return new VisualStabilityManager(
+                notificationEntryManager,
+                handler,
+                statusBarStateController,
+                wakefulnessLifecycle);
     }
 
     /** Provides an instance of {@link NotificationLogger} */
-    @Singleton
+    @SysUISingleton
     @Provides
     static NotificationLogger provideNotificationLogger(
             NotificationListener notificationListener,
@@ -177,14 +188,14 @@
     }
 
     /** Provides an instance of {@link NotificationPanelLogger} */
-    @Singleton
+    @SysUISingleton
     @Provides
     static NotificationPanelLogger provideNotificationPanelLogger() {
         return new NotificationPanelLoggerImpl();
     }
 
     /** Provides an instance of {@link NotificationBlockingHelperManager} */
-    @Singleton
+    @SysUISingleton
     @Provides
     static NotificationBlockingHelperManager provideNotificationBlockingHelperManager(
             Context context,
@@ -196,7 +207,7 @@
     }
 
     /** Initializes the notification data pipeline (can be disabled via config). */
-    @Singleton
+    @SysUISingleton
     @Provides
     static NotificationsController provideNotificationsController(
             Context context,
@@ -213,7 +224,7 @@
      * Provide the active notification collection managing the notifications to render.
      */
     @Provides
-    @Singleton
+    @SysUISingleton
     static CommonNotifCollection provideCommonNotifCollection(
             FeatureFlags featureFlags,
             Lazy<NotifPipeline> pipeline,
@@ -226,21 +237,28 @@
      * from the notification shade or it gets auto-cancelled by click.
      */
     @Provides
-    @Singleton
-    static OnDismissCallback provideOnDismissCallback(
+    @SysUISingleton
+    static OnUserInteractionCallback provideOnUserInteractionCallback(
             FeatureFlags featureFlags,
             HeadsUpManager headsUpManager,
             StatusBarStateController statusBarStateController,
             Lazy<NotifPipeline> pipeline,
             Lazy<NotifCollection> notifCollection,
-            NotificationEntryManager entryManager) {
+            Lazy<VisualStabilityCoordinator> visualStabilityCoordinator,
+            NotificationEntryManager entryManager,
+            VisualStabilityManager visualStabilityManager) {
         return featureFlags.isNewNotifPipelineRenderingEnabled()
-                ? new OnDismissCallbackImpl(
-                        pipeline.get(), notifCollection.get(), headsUpManager,
-                        statusBarStateController)
-                : new com.android.systemui.statusbar.notification.collection
-                        .legacy.OnDismissCallbackImpl(
-                                entryManager, headsUpManager, statusBarStateController);
+                ? new OnUserInteractionCallbackImpl(
+                        pipeline.get(),
+                        notifCollection.get(),
+                        headsUpManager,
+                        statusBarStateController,
+                        visualStabilityCoordinator.get())
+                : new OnUserInteractionCallbackImplLegacy(
+                        entryManager,
+                        headsUpManager,
+                        statusBarStateController,
+                        visualStabilityManager);
     }
 
     /** */
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/init/NotificationsControllerImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/init/NotificationsControllerImpl.kt
index 6e4fcd5..6460892 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/init/NotificationsControllerImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/init/NotificationsControllerImpl.kt
@@ -17,6 +17,7 @@
 package com.android.systemui.statusbar.notification.init
 
 import android.service.notification.StatusBarNotification
+import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.plugins.statusbar.NotificationSwipeActionHelper.SnoozeOption
 import com.android.systemui.statusbar.FeatureFlags
 import com.android.systemui.statusbar.NotificationListener
@@ -26,10 +27,11 @@
 import com.android.systemui.statusbar.notification.NotificationEntryManager
 import com.android.systemui.statusbar.notification.NotificationListController
 import com.android.systemui.statusbar.notification.collection.NotifPipeline
+import com.android.systemui.statusbar.notification.collection.TargetSdkResolver
 import com.android.systemui.statusbar.notification.collection.inflation.NotificationRowBinderImpl
 import com.android.systemui.statusbar.notification.collection.init.NotifPipelineInitializer
-import com.android.systemui.statusbar.notification.collection.TargetSdkResolver
 import com.android.systemui.statusbar.notification.interruption.HeadsUpController
+import com.android.systemui.statusbar.notification.interruption.HeadsUpViewBinder
 import com.android.systemui.statusbar.notification.row.NotifBindPipelineInitializer
 import com.android.systemui.statusbar.notification.stack.NotificationListContainer
 import com.android.systemui.statusbar.phone.NotificationGroupAlertTransferHelper
@@ -37,14 +39,12 @@
 import com.android.systemui.statusbar.phone.StatusBar
 import com.android.systemui.statusbar.policy.DeviceProvisionedController
 import com.android.systemui.statusbar.policy.HeadsUpManager
-import com.android.systemui.statusbar.notification.interruption.HeadsUpViewBinder
 import com.android.systemui.statusbar.policy.RemoteInputUriController
 import dagger.Lazy
 import java.io.FileDescriptor
 import java.io.PrintWriter
 import java.util.Optional
 import javax.inject.Inject
-import javax.inject.Singleton
 
 /**
  * Master controller for all notifications-related work
@@ -53,7 +53,7 @@
  * Once we migrate away from the need for such things, this class becomes primarily a place to do
  * any initialization work that notifications require.
  */
-@Singleton
+@SysUISingleton
 class NotificationsControllerImpl @Inject constructor(
     private val featureFlags: FeatureFlags,
     private val notificationListener: NotificationListener,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/BypassHeadsUpNotifier.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/BypassHeadsUpNotifier.kt
index 0fd865b..f8d6c6d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/BypassHeadsUpNotifier.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/BypassHeadsUpNotifier.kt
@@ -20,6 +20,7 @@
 import android.media.MediaMetadata
 import android.provider.Settings
 import com.android.keyguard.KeyguardUpdateMonitor
+import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.plugins.statusbar.StatusBarStateController
 import com.android.systemui.statusbar.NotificationLockscreenUserManager
 import com.android.systemui.statusbar.NotificationMediaManager
@@ -30,13 +31,12 @@
 import com.android.systemui.statusbar.phone.KeyguardBypassController
 import com.android.systemui.tuner.TunerService
 import javax.inject.Inject
-import javax.inject.Singleton
 
 /**
  * A class that automatically creates heads up for important notification when bypassing the
  * lockscreen
  */
-@Singleton
+@SysUISingleton
 class BypassHeadsUpNotifier @Inject constructor(
     private val context: Context,
     private val bypassController: KeyguardBypassController,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/HeadsUpController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/HeadsUpController.java
index 9b6ae9a..4d56e60 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/HeadsUpController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/HeadsUpController.java
@@ -24,25 +24,25 @@
 
 import androidx.annotation.NonNull;
 
+import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.statusbar.NotificationListener;
 import com.android.systemui.statusbar.NotificationRemoteInputManager;
 import com.android.systemui.statusbar.notification.NotificationEntryManager;
-import com.android.systemui.statusbar.notification.VisualStabilityManager;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
+import com.android.systemui.statusbar.notification.collection.legacy.VisualStabilityManager;
 import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener;
 import com.android.systemui.statusbar.policy.HeadsUpManager;
 import com.android.systemui.statusbar.policy.OnHeadsUpChangedListener;
 
 import javax.inject.Inject;
-import javax.inject.Singleton;
 
 /**
  * Controller class for old pipeline heads up logic. It listens to {@link NotificationEntryManager}
  * entry events and appropriately binds or unbinds the heads up view and promotes it to the top
  * of the screen.
  */
-@Singleton
+@SysUISingleton
 public class HeadsUpController {
     private final HeadsUpViewBinder mHeadsUpViewBinder;
     private final NotificationInterruptStateProvider mInterruptStateProvider;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/HeadsUpViewBinder.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/HeadsUpViewBinder.java
index ff13995..ffec367 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/HeadsUpViewBinder.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/HeadsUpViewBinder.java
@@ -24,6 +24,7 @@
 import androidx.core.os.CancellationSignal;
 
 import com.android.internal.util.NotificationMessagingUtil;
+import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.statusbar.NotificationPresenter;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 import com.android.systemui.statusbar.notification.collection.coordinator.HeadsUpCoordinator;
@@ -34,7 +35,6 @@
 import java.util.Map;
 
 import javax.inject.Inject;
-import javax.inject.Singleton;
 
 /**
  * Wrapper around heads up view binding logic. {@link HeadsUpViewBinder} is responsible for
@@ -44,7 +44,7 @@
  * TODO: This should be moved into {@link HeadsUpCoordinator} when the old pipeline is deprecated
  * (i.e. when {@link HeadsUpController} is removed).
  */
-@Singleton
+@SysUISingleton
 public class HeadsUpViewBinder {
     private final RowContentBindStage mStage;
     private final NotificationMessagingUtil mNotificationMessagingUtil;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImpl.java
index 71f6dac..433d5e1 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImpl.java
@@ -32,6 +32,7 @@
 import android.util.Log;
 
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.statusbar.StatusBarState;
@@ -44,12 +45,11 @@
 import java.util.List;
 
 import javax.inject.Inject;
-import javax.inject.Singleton;
 
 /**
  * Provides heads-up and pulsing state for notification entries.
  */
-@Singleton
+@SysUISingleton
 public class NotificationInterruptStateProviderImpl implements NotificationInterruptStateProvider {
     private static final String TAG = "InterruptionStateProvider";
     private static final boolean DEBUG = true; //false;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/people/PeopleHubNotificationListener.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/people/PeopleHubNotificationListener.kt
index d32d09d..743bf33 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/people/PeopleHubNotificationListener.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/people/PeopleHubNotificationListener.kt
@@ -35,6 +35,7 @@
 import com.android.internal.widget.MessagingGroup
 import com.android.settingslib.notification.ConversationIconFactory
 import com.android.systemui.R
+import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Background
 import com.android.systemui.dagger.qualifiers.Main
 import com.android.systemui.plugins.NotificationPersonExtractorPlugin
@@ -48,7 +49,6 @@
 import java.util.ArrayDeque
 import java.util.concurrent.Executor
 import javax.inject.Inject
-import javax.inject.Singleton
 
 private const val MAX_STORED_INACTIVE_PEOPLE = 10
 
@@ -58,7 +58,7 @@
     fun isPersonNotification(sbn: StatusBarNotification): Boolean
 }
 
-@Singleton
+@SysUISingleton
 class NotificationPersonExtractorPluginBoundary @Inject constructor(
     extensionController: ExtensionController
 ) : NotificationPersonExtractor {
@@ -87,7 +87,7 @@
             plugin?.isPersonNotification(sbn) ?: false
 }
 
-@Singleton
+@SysUISingleton
 class PeopleHubDataSourceImpl @Inject constructor(
     private val notificationEntryManager: NotificationEntryManager,
     private val extractor: NotificationPersonExtractor,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/people/PeopleHubViewController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/people/PeopleHubViewController.kt
index 7f42fe0..55bd77f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/people/PeopleHubViewController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/people/PeopleHubViewController.kt
@@ -23,10 +23,10 @@
 import android.os.UserHandle
 import android.provider.Settings
 import android.view.View
+import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Main
 import com.android.systemui.plugins.ActivityStarter
 import javax.inject.Inject
-import javax.inject.Singleton
 
 /** Boundary between the View and PeopleHub, as seen by the View. */
 interface PeopleHubViewAdapter {
@@ -68,7 +68,7 @@
  *
  * @param dataSource PeopleHub data pipeline.
  */
-@Singleton
+@SysUISingleton
 class PeopleHubViewAdapterImpl @Inject constructor(
     private val dataSource: DataSource<@JvmSuppressWildcards PeopleHubViewModelFactory>
 ) : PeopleHubViewAdapter {
@@ -99,7 +99,7 @@
  * This class serves as the glue between the View layer (which depends on
  * [PeopleHubViewBoundary]) and the Data layer (which produces [PeopleHubModel]s).
  */
-@Singleton
+@SysUISingleton
 class PeopleHubViewModelFactoryDataSourceImpl @Inject constructor(
     private val activityStarter: ActivityStarter,
     private val dataSource: DataSource<@JvmSuppressWildcards PeopleHubModel>
@@ -151,7 +151,7 @@
     }
 }
 
-@Singleton
+@SysUISingleton
 class PeopleHubSettingChangeDataSourceImpl @Inject constructor(
     @Main private val handler: Handler,
     context: Context
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/people/PeopleNotificationIdentifier.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/people/PeopleNotificationIdentifier.kt
index d36627f..1ac2cb5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/people/PeopleNotificationIdentifier.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/people/PeopleNotificationIdentifier.kt
@@ -19,6 +19,7 @@
 import android.annotation.IntDef
 import android.service.notification.NotificationListenerService.Ranking
 import android.service.notification.StatusBarNotification
+import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier.Companion.PeopleNotificationType
 import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier.Companion.TYPE_FULL_PERSON
 import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier.Companion.TYPE_IMPORTANT_PERSON
@@ -26,7 +27,6 @@
 import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier.Companion.TYPE_PERSON
 import com.android.systemui.statusbar.phone.NotificationGroupManager
 import javax.inject.Inject
-import javax.inject.Singleton
 import kotlin.math.max
 
 interface PeopleNotificationIdentifier {
@@ -59,7 +59,7 @@
     }
 }
 
-@Singleton
+@SysUISingleton
 class PeopleNotificationIdentifierImpl @Inject constructor(
     private val personExtractor: NotificationPersonExtractor,
     private val groupManager: NotificationGroupManager
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/AppOpsInfo.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/AppOpsInfo.java
deleted file mode 100644
index 28c53dc..0000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/AppOpsInfo.java
+++ /dev/null
@@ -1,214 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package com.android.systemui.statusbar.notification.row;
-
-import android.app.AppOpsManager;
-import android.content.Context;
-import android.content.pm.ApplicationInfo;
-import android.content.pm.PackageManager;
-import android.graphics.drawable.Drawable;
-import android.service.notification.StatusBarNotification;
-import android.util.ArraySet;
-import android.util.AttributeSet;
-import android.view.View;
-import android.view.accessibility.AccessibilityEvent;
-import android.widget.ImageView;
-import android.widget.LinearLayout;
-import android.widget.TextView;
-
-import com.android.internal.logging.MetricsLogger;
-import com.android.internal.logging.UiEventLogger;
-import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
-import com.android.systemui.R;
-
-/**
- * The guts of a notification revealed when performing a long press.
- */
-public class AppOpsInfo extends LinearLayout implements NotificationGuts.GutsContent {
-    private static final String TAG = "AppOpsGuts";
-
-    private PackageManager mPm;
-
-    private String mPkg;
-    private String mAppName;
-    private int mAppUid;
-    private StatusBarNotification mSbn;
-    private ArraySet<Integer> mAppOps;
-    private MetricsLogger mMetricsLogger;
-    private OnSettingsClickListener mOnSettingsClickListener;
-    private NotificationGuts mGutsContainer;
-    private UiEventLogger mUiEventLogger;
-
-    private OnClickListener mOnOk = v -> {
-        mGutsContainer.closeControls(v, false);
-    };
-
-    public AppOpsInfo(Context context, AttributeSet attrs) {
-        super(context, attrs);
-    }
-
-    public interface OnSettingsClickListener {
-        void onClick(View v, String pkg, int uid, ArraySet<Integer> ops);
-    }
-
-    public void bindGuts(final PackageManager pm,
-            final OnSettingsClickListener onSettingsClick,
-            final StatusBarNotification sbn,
-            final UiEventLogger uiEventLogger,
-            ArraySet<Integer> activeOps) {
-        mPkg = sbn.getPackageName();
-        mSbn = sbn;
-        mPm = pm;
-        mAppName = mPkg;
-        mOnSettingsClickListener = onSettingsClick;
-        mAppOps = activeOps;
-        mUiEventLogger = uiEventLogger;
-
-        bindHeader();
-        bindPrompt();
-        bindButtons();
-
-        logUiEvent(NotificationAppOpsEvent.NOTIFICATION_APP_OPS_OPEN);
-        mMetricsLogger = new MetricsLogger();
-        mMetricsLogger.visibility(MetricsEvent.APP_OPS_GUTS, true);
-    }
-
-    private void bindHeader() {
-        // Package name
-        Drawable pkgicon = null;
-        ApplicationInfo info;
-        try {
-            info = mPm.getApplicationInfo(mPkg,
-                    PackageManager.MATCH_UNINSTALLED_PACKAGES
-                            | PackageManager.MATCH_DISABLED_COMPONENTS
-                            | PackageManager.MATCH_DIRECT_BOOT_UNAWARE
-                            | PackageManager.MATCH_DIRECT_BOOT_AWARE);
-            if (info != null) {
-                mAppUid = mSbn.getUid();
-                mAppName = String.valueOf(mPm.getApplicationLabel(info));
-                pkgicon = mPm.getApplicationIcon(info);
-            }
-        } catch (PackageManager.NameNotFoundException e) {
-            // app is gone, just show package name and generic icon
-            pkgicon = mPm.getDefaultActivityIcon();
-        }
-        ((ImageView) findViewById(R.id.pkgicon)).setImageDrawable(pkgicon);
-        ((TextView) findViewById(R.id.pkgname)).setText(mAppName);
-    }
-
-    private void bindPrompt() {
-        final TextView prompt = findViewById(R.id.prompt);
-        prompt.setText(getPrompt());
-    }
-
-    private void bindButtons() {
-        View settings =  findViewById(R.id.settings);
-        settings.setOnClickListener((View view) -> {
-            mOnSettingsClickListener.onClick(view, mPkg, mAppUid, mAppOps);
-        });
-        TextView ok = findViewById(R.id.ok);
-        ok.setOnClickListener(mOnOk);
-        ok.setAccessibilityDelegate(mGutsContainer.getAccessibilityDelegate());
-    }
-
-    private String getPrompt() {
-        if (mAppOps == null || mAppOps.size() == 0) {
-            return "";
-        } else if (mAppOps.size() == 1) {
-            if (mAppOps.contains(AppOpsManager.OP_CAMERA)) {
-                return mContext.getString(R.string.appops_camera);
-            } else if (mAppOps.contains(AppOpsManager.OP_RECORD_AUDIO)) {
-                return mContext.getString(R.string.appops_microphone);
-            } else {
-                return mContext.getString(R.string.appops_overlay);
-            }
-        } else if (mAppOps.size() == 2) {
-            if (mAppOps.contains(AppOpsManager.OP_CAMERA)) {
-                if (mAppOps.contains(AppOpsManager.OP_RECORD_AUDIO)) {
-                    return mContext.getString(R.string.appops_camera_mic);
-                } else {
-                    return mContext.getString(R.string.appops_camera_overlay);
-                }
-            } else {
-                return mContext.getString(R.string.appops_mic_overlay);
-            }
-        } else {
-            return mContext.getString(R.string.appops_camera_mic_overlay);
-        }
-    }
-
-    @Override
-    public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
-        super.onInitializeAccessibilityEvent(event);
-        if (mGutsContainer != null &&
-                event.getEventType() == AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED) {
-            if (mGutsContainer.isExposed()) {
-                event.getText().add(mContext.getString(
-                        R.string.notification_channel_controls_opened_accessibility, mAppName));
-            } else {
-                event.getText().add(mContext.getString(
-                        R.string.notification_channel_controls_closed_accessibility, mAppName));
-            }
-        }
-    }
-
-    @Override
-    public void setGutsParent(NotificationGuts guts) {
-        mGutsContainer = guts;
-    }
-
-    @Override
-    public boolean willBeRemoved() {
-        return false;
-    }
-
-    @Override
-    public boolean shouldBeSaved() {
-        return false;
-    }
-
-    @Override
-    public boolean needsFalsingProtection() {
-        return false;
-    }
-
-    @Override
-    public View getContentView() {
-        return this;
-    }
-
-    @Override
-    public boolean handleCloseControls(boolean save, boolean force) {
-        logUiEvent(NotificationAppOpsEvent.NOTIFICATION_APP_OPS_CLOSE);
-        if (mMetricsLogger != null) {
-            mMetricsLogger.visibility(MetricsEvent.APP_OPS_GUTS, false);
-        }
-        return false;
-    }
-
-    @Override
-    public int getActualHeight() {
-        return getHeight();
-    }
-
-    private void logUiEvent(NotificationAppOpsEvent event) {
-        if (mSbn != null) {
-            mUiEventLogger.logWithInstanceId(event,
-                    mSbn.getUid(), mSbn.getPackageName(), mSbn.getInstanceId());
-        }
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ChannelEditorDialogController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ChannelEditorDialogController.kt
index 93db9cd..9bba7ef 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ChannelEditorDialogController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ChannelEditorDialogController.kt
@@ -37,12 +37,10 @@
 import android.view.WindowInsets.Type.statusBars
 import android.view.WindowManager
 import android.widget.TextView
-
 import com.android.internal.annotations.VisibleForTesting
 import com.android.systemui.R
-
+import com.android.systemui.dagger.SysUISingleton
 import javax.inject.Inject
-import javax.inject.Singleton
 
 private const val TAG = "ChannelDialogController"
 
@@ -58,7 +56,7 @@
  *   - the next 3 channels sorted alphabetically for that app   <on/off>
  *   -                                                          <on/off>
  */
-@Singleton
+@SysUISingleton
 class ChannelEditorDialogController @Inject constructor(
     c: Context,
     private val noMan: INotificationManager,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
index 22d0357..9c09cba 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
@@ -84,8 +84,8 @@
 import com.android.systemui.statusbar.notification.AboveShelfChangedListener;
 import com.android.systemui.statusbar.notification.ActivityLaunchAnimator;
 import com.android.systemui.statusbar.notification.NotificationUtils;
-import com.android.systemui.statusbar.notification.VisualStabilityManager;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
+import com.android.systemui.statusbar.notification.collection.legacy.VisualStabilityManager;
 import com.android.systemui.statusbar.notification.logging.NotificationCounters;
 import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier;
 import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.InflationFlag;
@@ -239,7 +239,7 @@
     private boolean mShowNoBackground;
     private ExpandableNotificationRow mNotificationParent;
     private OnExpandClickListener mOnExpandClickListener;
-    private View.OnClickListener mOnAppOpsClickListener;
+    private View.OnClickListener mOnAppClickListener;
     private View.OnClickListener mOnFeedbackClickListener;
 
     // Listener will be called when receiving a long click event.
@@ -323,7 +323,7 @@
     private View mGroupParentWhenDismissed;
     private boolean mShelfIconVisible;
     private boolean mAboveShelf;
-    private OnDismissCallback mOnDismissCallback;
+    private OnUserInteractionCallback mOnUserInteractionCallback;
     private boolean mIsLowPriority;
     private boolean mIsColorized;
     private boolean mUseIncreasedCollapsedHeight;
@@ -1139,7 +1139,6 @@
             items.add(NotificationMenuRow.createPartialConversationItem(mContext));
             items.add(NotificationMenuRow.createInfoItem(mContext));
             items.add(NotificationMenuRow.createSnoozeItem(mContext));
-            items.add(NotificationMenuRow.createAppOpsItem(mContext));
             mMenuRow.setMenuItems(items);
         }
         if (existed) {
@@ -1446,8 +1445,8 @@
         }
         dismiss(fromAccessibility);
         if (mEntry.isClearable()) {
-            if (mOnDismissCallback != null) {
-                mOnDismissCallback.onDismiss(mEntry, REASON_CANCEL);
+            if (mOnUserInteractionCallback != null) {
+                mOnUserInteractionCallback.onDismiss(mEntry, REASON_CANCEL);
             }
         }
     }
@@ -1464,10 +1463,6 @@
         return mIsBlockingHelperShowing && mNotificationTranslationFinished;
     }
 
-    void setOnDismissCallback(OnDismissCallback onDismissCallback) {
-        mOnDismissCallback = onDismissCallback;
-    }
-
     @Override
     public View getShelfTransformationTarget() {
         if (mIsSummaryWithChildren && !shouldShowPublic()) {
@@ -1598,11 +1593,11 @@
             RowContentBindStage rowContentBindStage,
             OnExpandClickListener onExpandClickListener,
             NotificationMediaManager notificationMediaManager,
-            CoordinateOnClickListener onAppOpsClickListener,
             CoordinateOnClickListener onFeedbackClickListener,
             FalsingManager falsingManager,
             StatusBarStateController statusBarStateController,
-            PeopleNotificationIdentifier peopleNotificationIdentifier) {
+            PeopleNotificationIdentifier peopleNotificationIdentifier,
+            OnUserInteractionCallback onUserInteractionCallback) {
         mAppName = appName;
         if (mMenuRow == null) {
             mMenuRow = new NotificationMenuRow(mContext, peopleNotificationIdentifier);
@@ -1619,7 +1614,6 @@
         mRowContentBindStage = rowContentBindStage;
         mOnExpandClickListener = onExpandClickListener;
         mMediaManager = notificationMediaManager;
-        setAppOpsOnClickListener(onAppOpsClickListener);
         setOnFeedbackClickListener(onFeedbackClickListener);
         mFalsingManager = falsingManager;
         mStatusbarStateController = statusBarStateController;
@@ -1627,6 +1621,7 @@
         for (NotificationContentView l : mLayouts) {
             l.setPeopleNotificationIdentifier(mPeopleNotificationIdentifier);
         }
+        mOnUserInteractionCallback = onUserInteractionCallback;
     }
 
     private void initDimens() {
@@ -1677,14 +1672,6 @@
         requestLayout();
     }
 
-    public void showAppOpsIcons(ArraySet<Integer> activeOps) {
-        if (mIsSummaryWithChildren) {
-            mChildrenContainer.showAppOpsIcons(activeOps);
-        }
-        mPrivateLayout.showAppOpsIcons(activeOps);
-        mPublicLayout.showAppOpsIcons(activeOps);
-    }
-
     public void showFeedbackIcon(boolean show) {
         if (mIsSummaryWithChildren) {
             mChildrenContainer.showFeedbackIcon(show);
@@ -1721,24 +1708,6 @@
         mPublicLayout.setRecentlyAudiblyAlerted(audiblyAlertedRecently);
     }
 
-    public View.OnClickListener getAppOpsOnClickListener() {
-        return mOnAppOpsClickListener;
-    }
-
-    void setAppOpsOnClickListener(CoordinateOnClickListener l) {
-        mOnAppOpsClickListener = v -> {
-            createMenu();
-            NotificationMenuRowPlugin provider = getProvider();
-            if (provider == null) {
-                return;
-            }
-            MenuItem menuItem = provider.getAppOpsMenuItem(mContext);
-            if (menuItem != null) {
-                l.onClick(this, v.getWidth() / 2, v.getHeight() / 2, menuItem);
-            }
-        };
-    }
-
     public View.OnClickListener getFeedbackOnClickListener() {
         return mOnFeedbackClickListener;
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowController.java
index 86a3271..f8bc2be 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowController.java
@@ -22,21 +22,27 @@
 import android.view.View;
 import android.view.ViewGroup;
 
+import androidx.annotation.NonNull;
+
 import com.android.systemui.plugins.FalsingManager;
 import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.shared.plugins.PluginManager;
 import com.android.systemui.statusbar.NotificationMediaManager;
+import com.android.systemui.statusbar.notification.collection.render.NodeController;
 import com.android.systemui.statusbar.notification.logging.NotificationLogger;
 import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier;
 import com.android.systemui.statusbar.notification.row.dagger.AppName;
 import com.android.systemui.statusbar.notification.row.dagger.NotificationKey;
 import com.android.systemui.statusbar.notification.row.dagger.NotificationRowScope;
+import com.android.systemui.statusbar.notification.stack.NotificationListContainer;
 import com.android.systemui.statusbar.phone.KeyguardBypassController;
 import com.android.systemui.statusbar.phone.NotificationGroupManager;
 import com.android.systemui.statusbar.policy.HeadsUpManager;
 import com.android.systemui.util.time.SystemClock;
 
+import java.util.List;
+
 import javax.inject.Inject;
 import javax.inject.Named;
 
@@ -44,8 +50,9 @@
  * Controller for {@link ExpandableNotificationRow}.
  */
 @NotificationRowScope
-public class ExpandableNotificationRowController {
+public class ExpandableNotificationRowController implements NodeController {
     private final ExpandableNotificationRow mView;
+    private final NotificationListContainer mListContainer;
     private final ActivatableNotificationViewController mActivatableNotificationViewController;
     private final NotificationMediaManager mMediaManager;
     private final PluginManager mPluginManager;
@@ -62,16 +69,16 @@
 
     private final ExpandableNotificationRow.ExpansionLogger mExpansionLogger =
             this::logNotificationExpansion;
-    private final ExpandableNotificationRow.CoordinateOnClickListener mOnAppOpsClickListener;
     private final ExpandableNotificationRow.CoordinateOnClickListener mOnFeedbackClickListener;
     private final NotificationGutsManager mNotificationGutsManager;
-    private final OnDismissCallback mOnDismissCallback;
+    private final OnUserInteractionCallback mOnUserInteractionCallback;
     private final FalsingManager mFalsingManager;
     private final boolean mAllowLongPress;
     private final PeopleNotificationIdentifier mPeopleNotificationIdentifier;
 
     @Inject
     public ExpandableNotificationRowController(ExpandableNotificationRow view,
+            NotificationListContainer listContainer,
             ActivatableNotificationViewController activatableNotificationViewController,
             NotificationMediaManager mediaManager, PluginManager pluginManager,
             SystemClock clock, @AppName String appName, @NotificationKey String notificationKey,
@@ -83,9 +90,10 @@
             StatusBarStateController statusBarStateController,
             NotificationGutsManager notificationGutsManager,
             @Named(ALLOW_NOTIFICATION_LONG_PRESS_NAME) boolean allowLongPress,
-            OnDismissCallback onDismissCallback, FalsingManager falsingManager,
+            OnUserInteractionCallback onUserInteractionCallback, FalsingManager falsingManager,
             PeopleNotificationIdentifier peopleNotificationIdentifier) {
         mView = view;
+        mListContainer = listContainer;
         mActivatableNotificationViewController = activatableNotificationViewController;
         mMediaManager = mediaManager;
         mPluginManager = pluginManager;
@@ -100,8 +108,7 @@
         mOnExpandClickListener = onExpandClickListener;
         mStatusBarStateController = statusBarStateController;
         mNotificationGutsManager = notificationGutsManager;
-        mOnDismissCallback = onDismissCallback;
-        mOnAppOpsClickListener = mNotificationGutsManager::openGuts;
+        mOnUserInteractionCallback = onUserInteractionCallback;
         mOnFeedbackClickListener = mNotificationGutsManager::openGuts;
         mAllowLongPress = allowLongPress;
         mFalsingManager = falsingManager;
@@ -123,13 +130,12 @@
                 mRowContentBindStage,
                 mOnExpandClickListener,
                 mMediaManager,
-                mOnAppOpsClickListener,
                 mOnFeedbackClickListener,
                 mFalsingManager,
                 mStatusBarStateController,
-                mPeopleNotificationIdentifier
+                mPeopleNotificationIdentifier,
+                mOnUserInteractionCallback
         );
-        mView.setOnDismissCallback(mOnDismissCallback);
         mView.setDescendantFocusability(ViewGroup.FOCUS_BLOCK_DESCENDANTS);
         if (mAllowLongPress) {
             mView.setLongPressListener((v, x, y, item) -> {
@@ -162,4 +168,52 @@
     private void logNotificationExpansion(String key, boolean userAction, boolean expanded) {
         mNotificationLogger.onExpansionChanged(key, userAction, expanded);
     }
+
+    @Override
+    @NonNull
+    public String getNodeLabel() {
+        return mView.getEntry().getKey();
+    }
+
+    @Override
+    @NonNull
+    public View getView() {
+        return mView;
+    }
+
+    @Override
+    public View getChildAt(int index) {
+        return mView.getChildNotificationAt(index);
+    }
+
+    @Override
+    public void addChildAt(NodeController child, int index) {
+        ExpandableNotificationRow childView = (ExpandableNotificationRow) child.getView();
+
+        mView.addChildNotification((ExpandableNotificationRow) child.getView());
+        mListContainer.notifyGroupChildAdded(childView);
+    }
+
+    @Override
+    public void moveChildTo(NodeController child, int index) {
+        ExpandableNotificationRow childView = (ExpandableNotificationRow) child.getView();
+        mView.removeChildNotification(childView);
+        mView.addChildNotification(childView, index);
+    }
+
+    @Override
+    public void removeChild(NodeController child, boolean isTransfer) {
+        ExpandableNotificationRow childView = (ExpandableNotificationRow) child.getView();
+
+        mView.removeChildNotification(childView);
+        if (!isTransfer) {
+            mListContainer.notifyGroupChildRemoved(childView, mView);
+        }
+    }
+
+    @Override
+    public int getChildCount() {
+        final List<ExpandableNotificationRow> mChildren = mView.getAttachedChildren();
+        return mChildren != null ? mChildren.size() : 0;
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifBindPipeline.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifBindPipeline.java
index 90d30dc..f693ebb 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifBindPipeline.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifBindPipeline.java
@@ -28,6 +28,7 @@
 import androidx.annotation.Nullable;
 import androidx.core.os.CancellationSignal;
 
+import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 import com.android.systemui.statusbar.notification.collection.inflation.NotificationRowBinder;
@@ -41,7 +42,6 @@
 import java.util.Set;
 
 import javax.inject.Inject;
-import javax.inject.Singleton;
 
 /**
  * {@link NotifBindPipeline} is responsible for converting notifications from their data form to
@@ -77,7 +77,7 @@
  * views and assumes that a row is given to it when it's inflated.
  */
 @MainThread
-@Singleton
+@SysUISingleton
 public final class NotifBindPipeline {
     private final Map<NotificationEntry, BindEntry> mBindEntries = new ArrayMap<>();
     private final NotifBindPipelineLogger mLogger;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifInflationErrorManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifInflationErrorManager.java
index a3ca084..51eb9f7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifInflationErrorManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifInflationErrorManager.java
@@ -19,6 +19,7 @@
 import androidx.annotation.NonNull;
 import androidx.collection.ArraySet;
 
+import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 
 import java.util.ArrayList;
@@ -26,14 +27,13 @@
 import java.util.Set;
 
 import javax.inject.Inject;
-import javax.inject.Singleton;
 
 /**
  * A manager handling the error state of a notification when it encounters an exception while
  * inflating. We don't want to show these notifications to the user but may want to keep them
  * around for logging purposes.
  */
-@Singleton
+@SysUISingleton
 public class NotifInflationErrorManager {
 
     Set<NotificationEntry> mErroredNotifs = new ArraySet<>();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java
index a7d83b3..9bcac11 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java
@@ -36,6 +36,7 @@
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.widget.ImageMessageConsumer;
+import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dagger.qualifiers.Background;
 import com.android.systemui.media.MediaDataManagerKt;
 import com.android.systemui.media.MediaFeatureFlag;
@@ -58,7 +59,6 @@
 import java.util.concurrent.Executor;
 
 import javax.inject.Inject;
-import javax.inject.Singleton;
 
 import dagger.Lazy;
 
@@ -66,7 +66,7 @@
  * {@link NotificationContentInflater} binds content to a {@link ExpandableNotificationRow} by
  * asynchronously building the content's {@link RemoteViews} and applying it to the row.
  */
-@Singleton
+@SysUISingleton
 @VisibleForTesting(visibility = PACKAGE)
 public class NotificationContentInflater implements NotificationRowContentBinder {
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java
index 2986b9b..c7e44c5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java
@@ -1568,18 +1568,6 @@
         return header;
     }
 
-    public void showAppOpsIcons(ArraySet<Integer> activeOps) {
-        if (mContractedChild != null) {
-            mContractedWrapper.showAppOpsIcons(activeOps);
-        }
-        if (mExpandedChild != null) {
-            mExpandedWrapper.showAppOpsIcons(activeOps);
-        }
-        if (mHeadsUpChild != null) {
-            mHeadsUpWrapper.showAppOpsIcons(activeOps);
-        }
-    }
-
     public void showFeedbackIcon(boolean show) {
         if (mContractedChild != null) {
             mContractedWrapper.showFeedbackIcon(show);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfo.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfo.java
index f543db7..7c7bb5c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfo.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfo.java
@@ -69,7 +69,6 @@
 import com.android.systemui.dagger.qualifiers.Background;
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.statusbar.notification.NotificationChannelHelper;
-import com.android.systemui.statusbar.notification.VisualStabilityManager;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 import com.android.systemui.statusbar.notification.stack.StackStateAnimator;
 
@@ -89,7 +88,7 @@
     private ShortcutManager mShortcutManager;
     private PackageManager mPm;
     private ConversationIconFactory mIconFactory;
-    private VisualStabilityManager mVisualStabilityManager;
+    private OnUserInteractionCallback mOnUserInteractionCallback;
     private Handler mMainHandler;
     private Handler mBgHandler;
     private BubbleController mBubbleController;
@@ -207,7 +206,7 @@
             ShortcutManager shortcutManager,
             PackageManager pm,
             INotificationManager iNotificationManager,
-            VisualStabilityManager visualStabilityManager,
+            OnUserInteractionCallback onUserInteractionCallback,
             String pkg,
             NotificationChannel notificationChannel,
             NotificationEntry entry,
@@ -224,7 +223,7 @@
             BubbleController bubbleController) {
         mSelectedAction = -1;
         mINotificationManager = iNotificationManager;
-        mVisualStabilityManager = visualStabilityManager;
+        mOnUserInteractionCallback = onUserInteractionCallback;
         mPackageName = pkg;
         mEntry = entry;
         mSbn = entry.getSbn();
@@ -513,7 +512,7 @@
                         mAppUid, mSelectedAction, mNotificationChannel));
         mEntry.markForUserTriggeredMovement(true);
         mMainHandler.postDelayed(
-                mVisualStabilityManager::temporarilyAllowReordering,
+                () -> mOnUserInteractionCallback.onImportanceChanged(mEntry),
                 StackStateAnimator.ANIMATION_DURATION_STANDARD);
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java
index 729b131..60074f6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java
@@ -60,7 +60,6 @@
 import com.android.systemui.statusbar.StatusBarStateControllerImpl;
 import com.android.systemui.statusbar.notification.AssistantFeedbackController;
 import com.android.systemui.statusbar.notification.NotificationActivityStarter;
-import com.android.systemui.statusbar.notification.VisualStabilityManager;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 import com.android.systemui.statusbar.notification.collection.provider.HighPriorityProvider;
 import com.android.systemui.statusbar.notification.dagger.NotificationsModule;
@@ -88,10 +87,10 @@
 
     private final MetricsLogger mMetricsLogger = Dependency.get(MetricsLogger.class);
     private final Context mContext;
-    private final VisualStabilityManager mVisualStabilityManager;
     private final AccessibilityManager mAccessibilityManager;
     private final HighPriorityProvider mHighPriorityProvider;
     private final ChannelEditorDialogController mChannelEditorDialogController;
+    private final OnUserInteractionCallback mOnUserInteractionCallback;
 
     // Dependencies:
     private final NotificationLockscreenUserManager mLockscreenUserManager =
@@ -129,8 +128,10 @@
     /**
      * Injected constructor. See {@link NotificationsModule}.
      */
-    public NotificationGutsManager(Context context, VisualStabilityManager visualStabilityManager,
-            Lazy<StatusBar> statusBarLazy, @Main Handler mainHandler, @Background Handler bgHandler,
+    public NotificationGutsManager(Context context,
+            Lazy<StatusBar> statusBarLazy,
+            @Main Handler mainHandler,
+            @Background Handler bgHandler,
             AccessibilityManager accessibilityManager,
             HighPriorityProvider highPriorityProvider,
             INotificationManager notificationManager,
@@ -141,9 +142,9 @@
             Provider<PriorityOnboardingDialogController.Builder> builderProvider,
             AssistantFeedbackController assistantFeedbackController,
             BubbleController bubbleController,
-            UiEventLogger uiEventLogger) {
+            UiEventLogger uiEventLogger,
+            OnUserInteractionCallback onUserInteractionCallback) {
         mContext = context;
-        mVisualStabilityManager = visualStabilityManager;
         mStatusBarLazy = statusBarLazy;
         mMainHandler = mainHandler;
         mBgHandler = bgHandler;
@@ -158,6 +159,7 @@
         mAssistantFeedbackController = assistantFeedbackController;
         mBubbleController = bubbleController;
         mUiEventLogger = uiEventLogger;
+        mOnUserInteractionCallback = onUserInteractionCallback;
     }
 
     public void setUpWithPresenter(NotificationPresenter presenter,
@@ -268,8 +270,6 @@
         try {
             if (gutsView instanceof NotificationSnooze) {
                 initializeSnoozeView(row, (NotificationSnooze) gutsView);
-            } else if (gutsView instanceof AppOpsInfo) {
-                initializeAppOpsInfo(row, (AppOpsInfo) gutsView);
             } else if (gutsView instanceof NotificationInfo) {
                 initializeNotificationInfo(row, (NotificationInfo) gutsView);
             } else if (gutsView instanceof NotificationConversationInfo) {
@@ -309,36 +309,6 @@
     }
 
     /**
-     * Sets up the {@link AppOpsInfo} inside the notification row's guts.
-     *
-     * @param row view to set up the guts for
-     * @param appOpsInfoView view to set up/bind within {@code row}
-     */
-    private void initializeAppOpsInfo(
-            final ExpandableNotificationRow row,
-            AppOpsInfo appOpsInfoView) {
-        NotificationGuts guts = row.getGuts();
-        StatusBarNotification sbn = row.getEntry().getSbn();
-        UserHandle userHandle = sbn.getUser();
-        PackageManager pmUser = StatusBar.getPackageManagerForUser(mContext,
-                userHandle.getIdentifier());
-
-        AppOpsInfo.OnSettingsClickListener onSettingsClick =
-                (View v, String pkg, int uid, ArraySet<Integer> ops) -> {
-                    mUiEventLogger.logWithInstanceId(
-                            NotificationAppOpsEvent.NOTIFICATION_APP_OPS_SETTINGS_CLICK,
-                            sbn.getUid(), sbn.getPackageName(), sbn.getInstanceId());
-                    mMetricsLogger.action(MetricsProto.MetricsEvent.ACTION_OPS_GUTS_SETTINGS);
-                    guts.resetFalsingCheck();
-                    startAppOpsSettingsActivity(pkg, uid, ops, row);
-        };
-        if (!row.getEntry().mActiveAppOps.isEmpty()) {
-            appOpsInfoView.bindGuts(pmUser, onSettingsClick, sbn, mUiEventLogger,
-                    row.getEntry().mActiveAppOps);
-        }
-    }
-
-    /**
      * Sets up the {@link FeedbackInfo} inside the notification row's guts.
      *
      * @param row view to set up the guts for
@@ -395,7 +365,7 @@
         notificationInfoView.bindNotification(
                 pmUser,
                 mNotificationManager,
-                mVisualStabilityManager,
+                mOnUserInteractionCallback,
                 mChannelEditorDialogController,
                 packageName,
                 row.getEntry().getChannel(),
@@ -506,7 +476,7 @@
                 mShortcutManager,
                 pmUser,
                 mNotificationManager,
-                mVisualStabilityManager,
+                mOnUserInteractionCallback,
                 packageName,
                 entry.getChannel(),
                 entry,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationInfo.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationInfo.java
index a6ba85f..7a976ac 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationInfo.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationInfo.java
@@ -60,7 +60,6 @@
 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
 import com.android.systemui.Dependency;
 import com.android.systemui.R;
-import com.android.systemui.statusbar.notification.VisualStabilityManager;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 
 import java.lang.annotation.Retention;
@@ -93,9 +92,9 @@
     private TextView mAutomaticDescriptionView;
 
     private INotificationManager mINotificationManager;
+    private OnUserInteractionCallback mOnUserInteractionCallback;
     private PackageManager mPm;
     private MetricsLogger mMetricsLogger;
-    private VisualStabilityManager mVisualStabilityManager;
     private ChannelEditorDialogController mChannelEditorDialogController;
 
     private String mPackageName;
@@ -119,6 +118,7 @@
     private boolean mIsAutomaticChosen;
     private boolean mIsSingleDefaultChannel;
     private boolean mIsNonblockable;
+    private NotificationEntry mEntry;
     private StatusBarNotification mSbn;
     private boolean mIsDeviceProvisioned;
 
@@ -188,7 +188,7 @@
     public void bindNotification(
             PackageManager pm,
             INotificationManager iNotificationManager,
-            VisualStabilityManager visualStabilityManager,
+            OnUserInteractionCallback onUserInteractionCallback,
             ChannelEditorDialogController channelEditorDialogController,
             String pkg,
             NotificationChannel notificationChannel,
@@ -204,11 +204,12 @@
             throws RemoteException {
         mINotificationManager = iNotificationManager;
         mMetricsLogger = Dependency.get(MetricsLogger.class);
-        mVisualStabilityManager = visualStabilityManager;
+        mOnUserInteractionCallback = onUserInteractionCallback;
         mChannelEditorDialogController = channelEditorDialogController;
         mPackageName = pkg;
         mUniqueChannelsInRow = uniqueChannelsInRow;
         mNumUniqueChannelsInRow = uniqueChannelsInRow.size();
+        mEntry = entry;
         mSbn = entry.getSbn();
         mPm = pm;
         mAppSettingsClickListener = onAppSettingsClick;
@@ -438,7 +439,7 @@
                     new UpdateImportanceRunnable(mINotificationManager, mPackageName, mAppUid,
                             mNumUniqueChannelsInRow == 1 ? mSingleNotificationChannel : null,
                             mStartingChannelImportance, newImportance, mIsAutomaticChosen));
-            mVisualStabilityManager.temporarilyAllowReordering();
+            mOnUserInteractionCallback.onImportanceChanged(mEntry);
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationMenuRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationMenuRow.java
index d264af9..205cecc 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationMenuRow.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationMenuRow.java
@@ -76,7 +76,6 @@
     private Context mContext;
     private FrameLayout mMenuContainer;
     private NotificationMenuItem mInfoItem;
-    private MenuItem mAppOpsItem;
     private MenuItem mFeedbackItem;
     private MenuItem mSnoozeItem;
     private ArrayList<MenuItem> mLeftMenuItems;
@@ -138,11 +137,6 @@
     }
 
     @Override
-    public MenuItem getAppOpsMenuItem(Context context) {
-        return mAppOpsItem;
-    }
-
-    @Override
     public MenuItem getFeedbackMenuItem(Context context) {
         return mFeedbackItem;
     }
@@ -264,7 +258,6 @@
             // Only show snooze for non-foreground notifications, and if the setting is on
             mSnoozeItem = createSnoozeItem(mContext);
         }
-        mAppOpsItem = createAppOpsItem(mContext);
         mFeedbackItem = createFeedbackItem(mContext);
         NotificationEntry entry = mParent.getEntry();
         int personNotifType = mPeopleNotificationIdentifier
@@ -281,7 +274,6 @@
             mRightMenuItems.add(mSnoozeItem);
         }
         mRightMenuItems.add(mInfoItem);
-        mRightMenuItems.add(mAppOpsItem);
         mRightMenuItems.add(mFeedbackItem);
         mLeftMenuItems.addAll(mRightMenuItems);
 
@@ -690,14 +682,6 @@
                 R.drawable.ic_settings);
     }
 
-    static MenuItem createAppOpsItem(Context context) {
-        AppOpsInfo appOpsContent = (AppOpsInfo) LayoutInflater.from(context).inflate(
-                R.layout.app_ops_info, null, false);
-        MenuItem info = new NotificationMenuItem(context, null, appOpsContent,
-                -1 /*don't show in slow swipe menu */);
-        return info;
-    }
-
     static MenuItem createFeedbackItem(Context context) {
         FeedbackInfo feedbackContent = (FeedbackInfo) LayoutInflater.from(context).inflate(
                 R.layout.feedback_info, null, false);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowModule.java
index df8653c..111b575 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowModule.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowModule.java
@@ -16,7 +16,7 @@
 
 package com.android.systemui.statusbar.notification.row;
 
-import javax.inject.Singleton;
+import com.android.systemui.dagger.SysUISingleton;
 
 import dagger.Binds;
 import dagger.Module;
@@ -30,7 +30,7 @@
      * Provides notification row content binder instance.
      */
     @Binds
-    @Singleton
+    @SysUISingleton
     public abstract NotificationRowContentBinder provideNotificationRowContentBinder(
             NotificationContentInflater contentBinderImpl);
 
@@ -38,7 +38,7 @@
      * Provides notification remote view cache instance.
      */
     @Binds
-    @Singleton
+    @SysUISingleton
     public abstract NotifRemoteViewCache provideNotifRemoteViewCache(
             NotifRemoteViewCacheImpl cacheImpl);
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/OnDismissCallback.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/OnUserInteractionCallback.java
similarity index 70%
rename from packages/SystemUI/src/com/android/systemui/statusbar/notification/row/OnDismissCallback.java
rename to packages/SystemUI/src/com/android/systemui/statusbar/notification/row/OnUserInteractionCallback.java
index f1aed89..0c3f553 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/OnDismissCallback.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/OnUserInteractionCallback.java
@@ -21,15 +21,21 @@
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 
 /**
- * Callback when a user clicks on an auto-cancelled notification or manually swipes to dismiss the
- * notification.
+ * Callbacks for when a user interacts with an {@link ExpandableNotificationRow}.
  */
-public interface OnDismissCallback {
+public interface OnUserInteractionCallback {
 
     /**
-     * Handle a user interaction that triggers a notification dismissal.
+     * Handle a user interaction that triggers a notification dismissal. Called when a user clicks
+     * on an auto-cancelled notification or manually swipes to dismiss the notification.
      */
     void onDismiss(
             NotificationEntry entry,
             @NotificationListenerService.NotificationCancelReason int cancellationReason);
+
+    /**
+     * Triggered after a user has changed the importance of the notification via its
+     * {@link NotificationGuts}.
+     */
+    void onImportanceChanged(NotificationEntry entry);
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RowContentBindStage.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RowContentBindStage.java
index c6f0a13..3616f8f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RowContentBindStage.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RowContentBindStage.java
@@ -20,13 +20,13 @@
 
 import androidx.annotation.NonNull;
 
+import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.BindParams;
 import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.InflationCallback;
 import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.InflationFlag;
 
 import javax.inject.Inject;
-import javax.inject.Singleton;
 
 /**
  * A stage that binds all content views for an already inflated {@link ExpandableNotificationRow}.
@@ -34,7 +34,7 @@
  * In the farther future, the binder logic and consequently this stage should be broken into
  * smaller stages.
  */
-@Singleton
+@SysUISingleton
 public class RowContentBindStage extends BindStage<RowContentBindParams> {
     private final NotificationRowContentBinder mBinder;
     private final NotifInflationErrorManager mNotifInflationErrorManager;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/dagger/ExpandableNotificationRowComponent.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/dagger/ExpandableNotificationRowComponent.java
index 28ddf59..becc9a7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/dagger/ExpandableNotificationRowComponent.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/dagger/ExpandableNotificationRowComponent.java
@@ -25,6 +25,7 @@
 import com.android.systemui.statusbar.notification.row.ActivatableNotificationView;
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRowController;
+import com.android.systemui.statusbar.notification.stack.NotificationListContainer;
 import com.android.systemui.statusbar.phone.StatusBar;
 
 import dagger.Binds;
@@ -55,6 +56,8 @@
         Builder notificationEntry(NotificationEntry entry);
         @BindsInstance
         Builder onExpandClickListener(ExpandableNotificationRow.OnExpandClickListener presenter);
+        @BindsInstance
+        Builder listContainer(NotificationListContainer listContainer);
         ExpandableNotificationRowComponent build();
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/dagger/NotificationShelfComponent.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/dagger/NotificationShelfComponent.java
new file mode 100644
index 0000000..af8d6ec
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/dagger/NotificationShelfComponent.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification.row.dagger;
+
+import com.android.systemui.statusbar.NotificationShelf;
+import com.android.systemui.statusbar.NotificationShelfController;
+import com.android.systemui.statusbar.notification.row.ActivatableNotificationView;
+
+import dagger.Binds;
+import dagger.BindsInstance;
+import dagger.Module;
+import dagger.Subcomponent;
+
+/**
+ * Dagger subcomponent for NotificationShelf.
+ */
+@Subcomponent(modules = {ActivatableNotificationViewModule.class,
+        NotificationShelfComponent.NotificationShelfModule.class})
+@NotificationRowScope
+public interface NotificationShelfComponent {
+    /**
+     * Builder for {@link NotificationShelfComponent}.
+     */
+    @Subcomponent.Builder
+    interface Builder {
+        @BindsInstance
+        Builder notificationShelf(NotificationShelf view);
+        NotificationShelfComponent build();
+    }
+
+    /**
+     * Creates a NotificationShelfController.
+     */
+    @NotificationRowScope
+    NotificationShelfController getNotificationShelfController();
+    /**
+     * Dagger Module that extracts interesting properties from a NotificationShelf.
+     */
+    @Module
+    abstract class NotificationShelfModule {
+
+        /** NotificationShelf is provided as an instance of ActivatableNotificationView. */
+        @Binds
+        abstract ActivatableNotificationView bindNotificationShelf(NotificationShelf view);
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationHeaderViewWrapper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationHeaderViewWrapper.java
index 4651771..3f58674 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationHeaderViewWrapper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationHeaderViewWrapper.java
@@ -18,7 +18,6 @@
 
 import static com.android.systemui.statusbar.notification.TransformState.TRANSFORM_Y;
 
-import android.app.AppOpsManager;
 import android.app.Notification;
 import android.content.Context;
 import android.util.ArraySet;
@@ -64,10 +63,6 @@
     private TextView mHeaderText;
     private TextView mAppNameText;
     private ImageView mWorkProfileImage;
-    private View mCameraIcon;
-    private View mMicIcon;
-    private View mOverlayIcon;
-    private View mAppOps;
     private View mAudiblyAlertedIcon;
     private FrameLayout mIconContainer;
     private View mFeedbackIcon;
@@ -109,7 +104,6 @@
                     }
                 }, TRANSFORMING_VIEW_TITLE);
         resolveHeaderViews();
-        addAppOpsOnClickListener(row);
         addFeedbackOnClickListener(row);
     }
 
@@ -121,10 +115,6 @@
         mExpandButton = mView.findViewById(com.android.internal.R.id.expand_button);
         mWorkProfileImage = mView.findViewById(com.android.internal.R.id.profile_badge);
         mNotificationHeader = mView.findViewById(com.android.internal.R.id.notification_header);
-        mCameraIcon = mView.findViewById(com.android.internal.R.id.camera);
-        mMicIcon = mView.findViewById(com.android.internal.R.id.mic);
-        mOverlayIcon = mView.findViewById(com.android.internal.R.id.overlay);
-        mAppOps = mView.findViewById(com.android.internal.R.id.app_ops);
         mAudiblyAlertedIcon = mView.findViewById(com.android.internal.R.id.alerted_icon);
         mFeedbackIcon = mView.findViewById(com.android.internal.R.id.feedback);
         if (mNotificationHeader != null) {
@@ -133,38 +123,6 @@
         }
     }
 
-    private void addAppOpsOnClickListener(ExpandableNotificationRow row) {
-        View.OnClickListener listener = row.getAppOpsOnClickListener();
-        if (mNotificationHeader != null) {
-            mNotificationHeader.setAppOpsOnClickListener(listener);
-        }
-        if (mAppOps != null) {
-            mAppOps.setOnClickListener(listener);
-        }
-    }
-
-    /**
-     * Shows or hides 'app op in use' icons based on app usage.
-     */
-    @Override
-    public void showAppOpsIcons(ArraySet<Integer> appOps) {
-        if (appOps == null) {
-            return;
-        }
-        if (mOverlayIcon != null) {
-            mOverlayIcon.setVisibility(appOps.contains(AppOpsManager.OP_SYSTEM_ALERT_WINDOW)
-                    ? View.VISIBLE : View.GONE);
-        }
-        if (mCameraIcon != null) {
-            mCameraIcon.setVisibility(appOps.contains(AppOpsManager.OP_CAMERA)
-                    ? View.VISIBLE : View.GONE);
-        }
-        if (mMicIcon != null) {
-            mMicIcon.setVisibility(appOps.contains(AppOpsManager.OP_RECORD_AUDIO)
-                    ? View.VISIBLE : View.GONE);
-        }
-    }
-
     private void addFeedbackOnClickListener(ExpandableNotificationRow row) {
         View.OnClickListener listener = row.getFeedbackOnClickListener();
         if (mNotificationHeader != null) {
@@ -304,15 +262,6 @@
             mTransformationHelper.addTransformedView(TransformableView.TRANSFORMING_VIEW_TITLE,
                     mHeaderText);
         }
-        if (mCameraIcon != null) {
-            mTransformationHelper.addViewTransformingToSimilar(mCameraIcon);
-        }
-        if (mMicIcon != null) {
-            mTransformationHelper.addViewTransformingToSimilar(mMicIcon);
-        }
-        if (mOverlayIcon != null) {
-            mTransformationHelper.addViewTransformingToSimilar(mOverlayIcon);
-        }
         if (mAudiblyAlertedIcon != null) {
             mTransformationHelper.addViewTransformingToSimilar(mAudiblyAlertedIcon);
         }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationMediaTemplateViewWrapper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationMediaTemplateViewWrapper.java
index 93d3f3b..33c93905 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationMediaTemplateViewWrapper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationMediaTemplateViewWrapper.java
@@ -22,14 +22,12 @@
 import android.app.Notification;
 import android.content.Context;
 import android.content.res.ColorStateList;
-import android.graphics.drawable.Drawable;
 import android.media.MediaMetadata;
 import android.media.session.MediaController;
 import android.media.session.MediaSession;
 import android.media.session.PlaybackState;
 import android.metrics.LogMaker;
 import android.os.Handler;
-import android.service.notification.StatusBarNotification;
 import android.text.format.DateUtils;
 import android.view.LayoutInflater;
 import android.view.View;
@@ -43,13 +41,9 @@
 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
 import com.android.internal.widget.MediaNotificationView;
 import com.android.systemui.Dependency;
-import com.android.systemui.qs.QSPanel;
-import com.android.systemui.qs.QuickQSPanel;
 import com.android.systemui.statusbar.NotificationMediaManager;
 import com.android.systemui.statusbar.TransformableView;
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
-import com.android.systemui.statusbar.phone.NotificationShadeWindowController;
-import com.android.systemui.util.Utils;
 
 import java.util.Timer;
 import java.util.TimerTask;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationViewWrapper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationViewWrapper.java
index 605fbc0..42f5e38 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationViewWrapper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationViewWrapper.java
@@ -97,14 +97,6 @@
     }
 
     /**
-     * Show a set of app opp icons in the layout.
-     *
-     * @param appOps which app ops to show
-     */
-    public void showAppOpsIcons(ArraySet<Integer> appOps) {
-    }
-
-    /**
      * Shows or hides feedback icon.
      */
     public void showFeedbackIcon(boolean show) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ForegroundServiceSectionController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ForegroundServiceSectionController.kt
index 5757fe8..32d41a8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ForegroundServiceSectionController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ForegroundServiceSectionController.kt
@@ -26,23 +26,21 @@
 import android.view.LayoutInflater
 import android.view.View
 import android.widget.LinearLayout
-
-import com.android.systemui.statusbar.notification.collection.NotificationEntry
 import com.android.systemui.R
+import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.statusbar.notification.ForegroundServiceDismissalFeatureController
 import com.android.systemui.statusbar.notification.NotificationEntryListener
 import com.android.systemui.statusbar.notification.NotificationEntryManager
+import com.android.systemui.statusbar.notification.collection.NotificationEntry
 import com.android.systemui.statusbar.notification.row.DungeonRow
 import com.android.systemui.util.Assert
-
 import javax.inject.Inject
-import javax.inject.Singleton
 
 /**
  * Controller for the bottom area of NotificationStackScrollLayout. It owns swiped-away foreground
  * service notifications and can reinstantiate them when requested.
  */
-@Singleton
+@SysUISingleton
 class ForegroundServiceSectionController @Inject constructor(
     val entryManager: NotificationEntryManager,
     val featureController: ForegroundServiceDismissalFeatureController
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainer.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainer.java
index 93c2377..a396305 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainer.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainer.java
@@ -22,7 +22,6 @@
 import android.content.res.Resources;
 import android.graphics.drawable.ColorDrawable;
 import android.service.notification.StatusBarNotification;
-import android.util.ArraySet;
 import android.util.AttributeSet;
 import android.view.LayoutInflater;
 import android.view.NotificationHeaderView;
@@ -36,7 +35,7 @@
 import com.android.systemui.statusbar.CrossFadeHelper;
 import com.android.systemui.statusbar.NotificationHeaderUtil;
 import com.android.systemui.statusbar.notification.NotificationUtils;
-import com.android.systemui.statusbar.notification.VisualStabilityManager;
+import com.android.systemui.statusbar.notification.collection.legacy.VisualStabilityManager;
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
 import com.android.systemui.statusbar.notification.row.HybridGroupManager;
 import com.android.systemui.statusbar.notification.row.HybridNotificationView;
@@ -1303,20 +1302,6 @@
     }
 
     /**
-     * Show a set of app opp icons in the layout.
-     *
-     * @param appOps which app ops to show
-     */
-    public void showAppOpsIcons(ArraySet<Integer> appOps) {
-        if (mNotificationHeaderWrapper != null) {
-            mNotificationHeaderWrapper.showAppOpsIcons(appOps);
-        }
-        if (mNotificationHeaderWrapperLowPriority != null) {
-            mNotificationHeaderWrapperLowPriority.showAppOpsIcons(appOps);
-        }
-    }
-
-    /**
      * Shows or hides feedback icon.
      */
     public void showFeedbackIcon(boolean show) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationRoundnessManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationRoundnessManager.java
index 2c3239a..fe66669 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationRoundnessManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationRoundnessManager.java
@@ -18,6 +18,7 @@
 
 import android.util.MathUtils;
 
+import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.statusbar.notification.NotificationSectionsFeatureManager;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
@@ -28,12 +29,11 @@
 import java.util.HashSet;
 
 import javax.inject.Inject;
-import javax.inject.Singleton;
 
 /**
  * A class that manages the roundness for notification views
  */
-@Singleton
+@SysUISingleton
 public class NotificationRoundnessManager implements OnHeadsUpChangedListener {
 
     private final ExpandableView[] mFirstInSectionViews;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsLogger.kt
index 17b4143..cb7dfe8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsLogger.kt
@@ -16,15 +16,15 @@
 
 package com.android.systemui.statusbar.notification.stack
 
+import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.log.LogBuffer
 import com.android.systemui.log.LogLevel
 import com.android.systemui.log.dagger.NotificationSectionLog
 import javax.inject.Inject
-import javax.inject.Singleton
 
 private const val TAG = "NotifSections"
 
-@Singleton
+@SysUISingleton
 class NotificationSectionsLogger @Inject constructor(
     @NotificationSectionLog private val logBuffer: LogBuffer
 ) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
index 66a541a..e061472 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
@@ -19,7 +19,6 @@
 import static android.service.notification.NotificationStats.DISMISSAL_SHADE;
 import static android.service.notification.NotificationStats.DISMISS_SENTIMENT_NEUTRAL;
 
-import static com.android.systemui.Dependency.ALLOW_NOTIFICATION_LONG_PRESS_NAME;
 import static com.android.systemui.statusbar.notification.ActivityLaunchAnimator.ExpandAnimationParameters;
 import static com.android.systemui.statusbar.notification.stack.NotificationSectionsManagerKt.BUCKET_SILENT;
 import static com.android.systemui.statusbar.notification.stack.StackScrollAlgorithm.ANCHOR_SCROLLING;
@@ -96,23 +95,18 @@
 import com.android.systemui.Interpolators;
 import com.android.systemui.R;
 import com.android.systemui.SwipeHelper;
-import com.android.systemui.colorextraction.SysuiColorExtractor;
-import com.android.systemui.media.KeyguardMediaController;
 import com.android.systemui.plugins.FalsingManager;
 import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin;
 import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin.MenuItem;
 import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin.OnMenuEventListener;
 import com.android.systemui.plugins.statusbar.NotificationSwipeActionHelper;
-import com.android.systemui.plugins.statusbar.StatusBarStateController;
-import com.android.systemui.plugins.statusbar.StatusBarStateController.StateListener;
 import com.android.systemui.statusbar.CommandQueue;
 import com.android.systemui.statusbar.DragDownHelper.DragDownCallback;
 import com.android.systemui.statusbar.EmptyShadeView;
 import com.android.systemui.statusbar.FeatureFlags;
-import com.android.systemui.statusbar.NotificationLockscreenUserManager;
-import com.android.systemui.statusbar.NotificationLockscreenUserManager.UserChangedListener;
 import com.android.systemui.statusbar.NotificationRemoteInputManager;
 import com.android.systemui.statusbar.NotificationShelf;
+import com.android.systemui.statusbar.NotificationShelfController;
 import com.android.systemui.statusbar.RemoteInputController;
 import com.android.systemui.statusbar.StatusBarState;
 import com.android.systemui.statusbar.SysuiStatusBarStateController;
@@ -125,10 +119,10 @@
 import com.android.systemui.statusbar.notification.NotificationUtils;
 import com.android.systemui.statusbar.notification.ShadeViewRefactor;
 import com.android.systemui.statusbar.notification.ShadeViewRefactor.RefactorComponent;
-import com.android.systemui.statusbar.notification.VisualStabilityManager;
 import com.android.systemui.statusbar.notification.collection.NotifCollection;
 import com.android.systemui.statusbar.notification.collection.NotifPipeline;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
+import com.android.systemui.statusbar.notification.collection.legacy.VisualStabilityManager;
 import com.android.systemui.statusbar.notification.collection.notifcollection.DismissedByUserStats;
 import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener;
 import com.android.systemui.statusbar.notification.logging.NotificationLogger;
@@ -145,21 +139,16 @@
 import com.android.systemui.statusbar.phone.HeadsUpAppearanceController;
 import com.android.systemui.statusbar.phone.HeadsUpManagerPhone;
 import com.android.systemui.statusbar.phone.HeadsUpTouchHelper;
-import com.android.systemui.statusbar.phone.KeyguardBypassController;
 import com.android.systemui.statusbar.phone.LockscreenGestureLogger;
 import com.android.systemui.statusbar.phone.LockscreenGestureLogger.LockscreenUiEvent;
 import com.android.systemui.statusbar.phone.NotificationGroupManager;
 import com.android.systemui.statusbar.phone.NotificationGroupManager.OnGroupChangeListener;
-import com.android.systemui.statusbar.phone.NotificationIconAreaController;
 import com.android.systemui.statusbar.phone.NotificationPanelViewController;
 import com.android.systemui.statusbar.phone.ScrimController;
 import com.android.systemui.statusbar.phone.ShadeController;
 import com.android.systemui.statusbar.phone.StatusBar;
-import com.android.systemui.statusbar.policy.ConfigurationController;
-import com.android.systemui.statusbar.policy.ConfigurationController.ConfigurationListener;
 import com.android.systemui.statusbar.policy.HeadsUpUtil;
 import com.android.systemui.statusbar.policy.ScrollAdapter;
-import com.android.systemui.statusbar.policy.ZenModeController;
 import com.android.systemui.tuner.TunerService;
 import com.android.systemui.util.Assert;
 
@@ -179,9 +168,7 @@
 /**
  * A layout which handles a dynamic amount of notifications and presents them in a scrollable stack.
  */
-public class NotificationStackScrollLayout extends ViewGroup implements ScrollAdapter,
-        NotificationListContainer, ConfigurationListener, Dumpable,
-        DynamicPrivacyController.Listener {
+public class NotificationStackScrollLayout extends ViewGroup implements Dumpable {
 
     public static final float BACKGROUND_ALPHA_DIMMED = 0.7f;
     private static final String TAG = "StackScroller";
@@ -198,10 +185,10 @@
      * gap is drawn between them). In this case we don't want to round their corners.
      */
     private static final int DISTANCE_BETWEEN_ADJACENT_SECTIONS_PX = 1;
-    private final KeyguardBypassController mKeyguardBypassController;
+    private OnMenuEventListener mMenuEventListener;
+    private KeyguardBypassEnabledProvider mKeyguardBypassEnabledProvider;
     private final DynamicPrivacyController mDynamicPrivacyController;
     private final SysuiStatusBarStateController mStatusbarStateController;
-    private final KeyguardMediaController mKeyguardMediaController;
 
     private ExpandHelper mExpandHelper;
     private final NotificationSwipeHelper mSwipeHelper;
@@ -209,11 +196,11 @@
     private final Paint mBackgroundPaint = new Paint();
     private final boolean mShouldDrawNotificationBackground;
     private boolean mHighPriorityBeforeSpeedBump;
-    private final boolean mAllowLongPress;
     private boolean mDismissRtl;
 
     private float mExpandedHeight;
     private int mOwnScrollY;
+
     private View mScrollAnchorView;
     private int mScrollAnchorViewY;
     private int mMaxLayoutHeight;
@@ -252,6 +239,7 @@
     private int mBottomMargin;
     private int mBottomInset = 0;
     private float mQsExpansionFraction;
+    private int mCurrentUserId;
 
     /**
      * The algorithm which calculates the properties for our children
@@ -329,7 +317,6 @@
      * motion.
      */
     private int mMaxScrollAfterExpand;
-    private ExpandableNotificationRow.LongPressListener mLongPressListener;
     boolean mCheckForLeavebehind;
 
     /**
@@ -351,12 +338,7 @@
             return true;
         }
     };
-    private final UserChangedListener mLockscreenUserChangeListener = new UserChangedListener() {
-        @Override
-        public void onUserChanged(int userId) {
-            updateSensitiveness(false /* animated */);
-        }
-    };
+
     private StatusBar mStatusBar;
     private int[] mTempInt2 = new int[2];
     private boolean mGenerateChildOrderChangedEvent;
@@ -371,7 +353,6 @@
     private boolean mForceNoOverlappingRendering;
     private final ArrayList<Pair<ExpandableNotificationRow, Boolean>> mTmpList = new ArrayList<>();
     private FalsingManager mFalsingManager;
-    private final ZenModeController mZenController;
     private boolean mAnimationRunning;
     private ViewTreeObserver.OnPreDrawListener mRunningAnimationUpdater
             = new ViewTreeObserver.OnPreDrawListener() {
@@ -501,8 +482,6 @@
     private ArrayList<BiConsumer<Float, Float>> mExpandedHeightListeners = new ArrayList<>();
     private int mHeadsUpInset;
     private HeadsUpAppearanceController mHeadsUpAppearanceController;
-    private NotificationIconAreaController mIconAreaController;
-    private final NotificationLockscreenUserManager mLockscreenUserManager;
     private final Rect mTmpRect = new Rect();
     private final FeatureFlags mFeatureFlags;
     private final NotifPipeline mNotifPipeline;
@@ -515,7 +494,6 @@
     protected final UiEventLogger mUiEventLogger;
     private final NotificationRemoteInputManager mRemoteInputManager =
             Dependency.get(NotificationRemoteInputManager.class);
-    private final SysuiColorExtractor mColorExtractor = Dependency.get(SysuiColorExtractor.class);
 
     private final DisplayMetrics mDisplayMetrics = Dependency.get(DisplayMetrics.class);
     private final LockscreenGestureLogger mLockscreenGestureLogger =
@@ -538,28 +516,61 @@
     private int mGapHeight;
 
     private int mWaterfallTopInset;
+    private NotificationStackScrollLayoutController mController;
 
-    private SysuiColorExtractor.OnColorsChangedListener mOnColorsChangedListener =
-            (colorExtractor, which) -> {
-                final boolean useDarkText = mColorExtractor.getNeutralColors().supportsDarkText();
-                updateDecorViews(useDarkText);
+    private boolean mKeyguardMediaControllorVisible;
+
+    private final ExpandableView.OnHeightChangedListener mOnChildHeightChangedListener =
+            new ExpandableView.OnHeightChangedListener() {
+                @Override
+                public void onHeightChanged(ExpandableView view, boolean needsAnimation) {
+                    onChildHeightChanged(view, needsAnimation);
+                }
+
+                @Override
+                public void onReset(ExpandableView view) {
+                    onChildHeightReset(view);
+                }
             };
 
+    private final ScrollAdapter mScrollAdapter = new ScrollAdapter() {
+        @Override
+        public boolean isScrolledToTop() {
+            if (ANCHOR_SCROLLING) {
+                updateScrollAnchor();
+                // TODO: once we're recycling this will need to check the adapter position of the
+                //  child
+                return mScrollAnchorView == getFirstChildNotGone() && mScrollAnchorViewY >= 0;
+            } else {
+                return mOwnScrollY == 0;
+            }
+        }
+
+        @Override
+        public boolean isScrolledToBottom() {
+            if (ANCHOR_SCROLLING) {
+                return getMaxPositiveScrollAmount() <= 0;
+            } else {
+                return mOwnScrollY >= getScrollRange();
+            }
+        }
+
+        @Override
+        public View getHostView() {
+            return NotificationStackScrollLayout.this;
+        }
+    };
+
     @Inject
     public NotificationStackScrollLayout(
             @Named(VIEW_CONTEXT) Context context,
             AttributeSet attrs,
-            @Named(ALLOW_NOTIFICATION_LONG_PRESS_NAME) boolean allowLongPress,
             NotificationRoundnessManager notificationRoundnessManager,
             DynamicPrivacyController dynamicPrivacyController,
-            SysuiStatusBarStateController statusBarStateController,
+            SysuiStatusBarStateController statusbarStateController,
             HeadsUpManagerPhone headsUpManager,
-            KeyguardBypassController keyguardBypassController,
-            KeyguardMediaController keyguardMediaController,
             FalsingManager falsingManager,
-            NotificationLockscreenUserManager notificationLockscreenUserManager,
             NotificationGutsManager notificationGutsManager,
-            ZenModeController zenController,
             NotificationSectionsManager notificationSectionsManager,
             ForegroundServiceSectionController fgsSectionController,
             ForegroundServiceDismissalFeatureController fgsFeatureController,
@@ -572,18 +583,12 @@
         super(context, attrs, 0, 0);
         Resources res = getResources();
 
-        mAllowLongPress = allowLongPress;
-
         mRoundnessManager = notificationRoundnessManager;
 
-        mLockscreenUserManager = notificationLockscreenUserManager;
         mNotificationGutsManager = notificationGutsManager;
         mHeadsUpManager = headsUpManager;
-        mHeadsUpManager.addListener(mRoundnessManager);
         mHeadsUpManager.setAnimationStateHandler(this::setHeadsUpGoingAwayAnimationsAllowed);
-        mKeyguardBypassController = keyguardBypassController;
         mFalsingManager = falsingManager;
-        mZenController = zenController;
         mFgsSectionController = fgsSectionController;
 
         mSectionsManager = notificationSectionsManager;
@@ -602,19 +607,33 @@
         mExpandHelper = new ExpandHelper(getContext(), mExpandHelperCallback,
                 minHeight, maxHeight);
         mExpandHelper.setEventSource(this);
-        mExpandHelper.setScrollAdapter(this);
+        mExpandHelper.setScrollAdapter(mScrollAdapter);
+
+        // TODO: move swipe helper into controller.
+        // The anonymous proxy through to mMenuEventListener is temporary until more can be moved
+        // into the controller.
         mSwipeHelper = new NotificationSwipeHelper(SwipeHelper.X, mNotificationCallback,
-                getContext(), mMenuEventListener, mFalsingManager);
+                getContext(), new OnMenuEventListener() {
+            @Override
+            public void onMenuClicked(View row, int x, int y, MenuItem menu) {
+                mMenuEventListener.onMenuClicked(row, x, y, menu);
+            }
+
+            @Override
+            public void onMenuReset(View row) {
+                mMenuEventListener.onMenuReset(row);
+            }
+
+            @Override
+            public void onMenuShown(View row) {
+                mMenuEventListener.onMenuShown(row);
+            }
+        }, mFalsingManager);
         mStackScrollAlgorithm = createStackScrollAlgorithm(context);
-        initView(context);
         mShouldDrawNotificationBackground =
                 res.getBoolean(R.bool.config_drawNotificationBackground);
         mFadeNotificationsOnDismiss =
                 res.getBoolean(R.bool.config_fadeNotificationsOnDismiss);
-        mRoundnessManager.setAnimatedChildren(mChildrenToAddAnimated);
-        mRoundnessManager.setOnRoundingChangedCallback(this::invalidate);
-        addOnExpandedHeightChangedListener(mRoundnessManager::setExpanded);
-        mLockscreenUserManager.addUserChangedListener(mLockscreenUserChangeListener);
         setOutlineProvider(mOutlineProvider);
 
         // Blocking helper manager wants to know the expanded state, update as well.
@@ -640,13 +659,8 @@
         tunerService.addTunable((key, newValue) -> {
             if (key.equals(HIGH_PRIORITY)) {
                 mHighPriorityBeforeSpeedBump = "1".equals(newValue);
-            } else if (key.equals(Settings.Secure.NOTIFICATION_DISMISS_RTL)) {
-                updateDismissRtlSetting("1".equals(newValue));
-            } else if (key.equals(Settings.Secure.NOTIFICATION_HISTORY_ENABLED)) {
-                updateFooter();
             }
-        }, HIGH_PRIORITY, Settings.Secure.NOTIFICATION_DISMISS_RTL,
-                Settings.Secure.NOTIFICATION_HISTORY_ENABLED);
+        }, HIGH_PRIORITY);
 
         mFeatureFlags = featureFlags;
         mNotifPipeline = notifPipeline;
@@ -668,22 +682,10 @@
             });
         }
 
-        dynamicPrivacyController.addListener(this);
         mDynamicPrivacyController = dynamicPrivacyController;
-        mStatusbarStateController = statusBarStateController;
+        mStatusbarStateController = statusbarStateController;
         initializeForegroundServiceSection(fgsFeatureController);
         mUiEventLogger = uiEventLogger;
-        mColorExtractor.addOnColorsChangedListener(mOnColorsChangedListener);
-        mKeyguardMediaController = keyguardMediaController;
-        keyguardMediaController.setVisibilityChangedListener((visible) -> {
-            if (visible) {
-                generateAddAnimation(keyguardMediaController.getView(), false /*fromMoreCard */);
-            } else {
-                generateRemoveAnimation(keyguardMediaController.getView());
-            }
-            requestChildrenUpdate();
-            return null;
-        });
     }
 
     private void initializeForegroundServiceSection(
@@ -696,7 +698,7 @@
         }
     }
 
-    private void updateDismissRtlSetting(boolean dismissRtl) {
+    void updateDismissRtlSetting(boolean dismissRtl) {
         mDismissRtl = dismissRtl;
         for (int i = 0; i < getChildCount(); i++) {
             View child = getChildAt(i);
@@ -714,9 +716,6 @@
         inflateEmptyShadeView();
         inflateFooterView();
         mVisualStabilityManager.setVisibilityLocationProvider(this::isInVisibleLocation);
-        if (mAllowLongPress) {
-            setLongPressListener(mNotificationGutsManager::openGuts);
-        }
     }
 
     /**
@@ -725,7 +724,7 @@
     public float getWakeUpHeight() {
         ExpandableView firstChild = getFirstChildWithBackground();
         if (firstChild != null) {
-            if (mKeyguardBypassController.getBypassEnabled()) {
+            if (mKeyguardBypassEnabledProvider.getBypassEnabled()) {
                 return firstChild.getHeadsUpHeightWithoutHeader();
             } else {
                 return firstChild.getCollapsedHeight();
@@ -734,36 +733,13 @@
         return 0f;
     }
 
-    @Override
-    @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
-    public void onDensityOrFontScaleChanged() {
-        reinflateViews();
-    }
-
-    private void reinflateViews() {
+    void reinflateViews() {
         inflateFooterView();
         inflateEmptyShadeView();
         updateFooter();
         mSectionsManager.reinflateViews(LayoutInflater.from(mContext));
     }
 
-    @Override
-    @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
-    public void onThemeChanged() {
-        updateFooter();
-    }
-
-    @Override
-    public void onOverlayChanged() {
-        int newRadius = mContext.getResources().getDimensionPixelSize(
-                Utils.getThemeAttr(mContext, android.R.attr.dialogCornerRadius));
-        if (mCornerRadius != newRadius) {
-            mCornerRadius = newRadius;
-            invalidate();
-        }
-        reinflateViews();
-    }
-
     @VisibleForTesting
     @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
     public void updateFooter() {
@@ -823,32 +799,12 @@
         };
     }
 
-    @Override
-    @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
-    protected void onAttachedToWindow() {
-        super.onAttachedToWindow();
-        ((SysuiStatusBarStateController) Dependency.get(StatusBarStateController.class))
-                .addCallback(mStateListener, SysuiStatusBarStateController.RANK_STACK_SCROLLER);
-        Dependency.get(ConfigurationController.class).addCallback(this);
-    }
-
-    @Override
-    @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
-    protected void onDetachedFromWindow() {
-        super.onDetachedFromWindow();
-        Dependency.get(StatusBarStateController.class).removeCallback(mStateListener);
-        Dependency.get(ConfigurationController.class).removeCallback(this);
-    }
-
-    @Override
     @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
     public NotificationSwipeActionHelper getSwipeActionHelper() {
         return mSwipeHelper;
     }
 
-    @Override
-    @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
-    public void onUiModeChanged() {
+    void updateBgColor() {
         mBgColor = mContext.getColor(R.color.notification_shade_background_color);
         updateBackgroundDimming();
         mShelf.onUiModeChanged();
@@ -932,7 +888,7 @@
             }
         }
         boolean shouldDrawBackground;
-        if (mKeyguardBypassController.getBypassEnabled() && onKeyguard()) {
+        if (mKeyguardBypassEnabledProvider.getBypassEnabled() && onKeyguard()) {
             shouldDrawBackground = isPulseExpanding();
         } else {
             shouldDrawBackground = !mAmbientState.isDozing() || anySectionHasVisibleChild;
@@ -1047,9 +1003,16 @@
         }
     }
 
+    private void reinitView() {
+        initView(getContext(), mKeyguardBypassEnabledProvider);
+    }
+
     @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
-    private void initView(Context context) {
+    void initView(Context context,
+            KeyguardBypassEnabledProvider keyguardBypassEnabledProvider) {
         mScroller = new OverScroller(getContext());
+        mKeyguardBypassEnabledProvider = keyguardBypassEnabledProvider;
+
         setDescendantFocusability(FOCUS_AFTER_DESCENDANTS);
         setClipChildren(false);
         final ViewConfiguration configuration = ViewConfiguration.get(context);
@@ -1081,6 +1044,15 @@
                 R.dimen.heads_up_status_bar_padding);
     }
 
+    void updateCornerRadius() {
+        int newRadius = getResources().getDimensionPixelSize(
+                Utils.getThemeAttr(getContext(), android.R.attr.dialogCornerRadius));
+        if (mCornerRadius != newRadius) {
+            mCornerRadius = newRadius;
+            invalidate();
+        }
+    }
+
     @ShadeViewRefactor(RefactorComponent.COORDINATOR)
     private void notifyHeightChangeListener(ExpandableView view) {
         notifyHeightChangeListener(view, false /* needsAnimation */);
@@ -1156,14 +1128,16 @@
         mNoAmbient = noAmbient;
     }
 
-    @Override
     @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
     public void setChildLocationsChangedListener(
             NotificationLogger.OnChildLocationsChangedListener listener) {
         mListener = listener;
     }
 
-    @Override
+    public void setScrollAnchorView(View scrollAnchorView) {
+        mScrollAnchorView = scrollAnchorView;
+    }
+
     @ShadeViewRefactor(RefactorComponent.LAYOUT_ALGORITHM)
     public boolean isInVisibleLocation(NotificationEntry entry) {
         ExpandableNotificationRow row = entry.getRow();
@@ -1250,7 +1224,7 @@
             float end = start + child.getActualHeight();
             boolean clip = clipStart > start && clipStart < end
                     || clipEnd >= start && clipEnd <= end;
-            clip &= !(first && isScrolledToTop());
+            clip &= !(first && mScrollAdapter.isScrolledToTop());
             child.setDistanceToTopRoundness(clip ? Math.max(start - clipStart, 0)
                     : ExpandableView.NO_ROUNDNESS);
             first = false;
@@ -1310,7 +1284,7 @@
     }
 
     @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
-    private void requestChildrenUpdate() {
+    void requestChildrenUpdate() {
         if (!mChildrenUpdateRequested) {
             getViewTreeObserver().addOnPreDrawListener(mChildrenUpdater);
             mChildrenUpdateRequested = true;
@@ -1440,7 +1414,7 @@
     private void notifyAppearChangedListeners() {
         float appear;
         float expandAmount;
-        if (mKeyguardBypassController.getBypassEnabled() && onKeyguard()) {
+        if (mKeyguardBypassEnabledProvider.getBypassEnabled() && onKeyguard()) {
             appear = calculateAppearFractionBypass();
             expandAmount = getPulseHeight();
         } else {
@@ -1842,7 +1816,7 @@
         mSwipeHelper.setDensityScale(densityScale);
         float pagingTouchSlop = ViewConfiguration.get(getContext()).getScaledPagingTouchSlop();
         mSwipeHelper.setPagingTouchSlop(pagingTouchSlop);
-        initView(getContext());
+        reinitView();
     }
 
     @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
@@ -1862,7 +1836,6 @@
         }
     }
 
-    @Override
     @ShadeViewRefactor(RefactorComponent.ADAPTER)
     public ViewGroup getViewParentForNotification(NotificationEntry entry) {
         return this;
@@ -2244,7 +2217,7 @@
                 springBack();
             } else {
                 float overScrollTop = getCurrentOverScrollAmount(true /* top */);
-                if (isScrolledToTop() && mScrollAnchorViewY > 0) {
+                if (mScrollAdapter.isScrolledToTop() && mScrollAnchorViewY > 0) {
                     notifyOverscrollTopListener(mScrollAnchorViewY,
                             isRubberbanded(true /* onTop */));
                 } else {
@@ -2292,7 +2265,7 @@
     @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
     private void springBack() {
         if (ANCHOR_SCROLLING) {
-            boolean overScrolledTop = isScrolledToTop() && mScrollAnchorViewY > 0;
+            boolean overScrolledTop = mScrollAdapter.isScrolledToTop() && mScrollAnchorViewY > 0;
             int maxPositiveScrollAmount = getMaxPositiveScrollAmount();
             boolean overscrolledBottom = maxPositiveScrollAmount < 0;
             if (overScrolledTop || overscrolledBottom) {
@@ -2552,7 +2525,6 @@
                 previous);
     }
 
-    @Override
     @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
     public boolean hasPulsingNotifications() {
         return mPulsing;
@@ -2570,8 +2542,8 @@
 
     @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
     private void updateForwardAndBackwardScrollability() {
-        boolean forwardScrollable = mScrollable && !isScrolledToBottom();
-        boolean backwardsScrollable = mScrollable && !isScrolledToTop();
+        boolean forwardScrollable = mScrollable && !mScrollAdapter.isScrolledToBottom();
+        boolean backwardsScrollable = mScrollable && !mScrollAdapter.isScrolledToTop();
         boolean changed = forwardScrollable != mForwardScrollable
                 || backwardsScrollable != mBackwardScrollable;
         mForwardScrollable = forwardScrollable;
@@ -2692,7 +2664,7 @@
         }
         boolean shiftPulsingWithFirst = mHeadsUpManager.getAllEntries().count() <= 1
                 && (mAmbientState.isDozing()
-                        || (mKeyguardBypassController.getBypassEnabled() && onKeyguard));
+                        || (mKeyguardBypassEnabledProvider.getBypassEnabled() && onKeyguard));
         for (NotificationSection section : mSections) {
             int minBottomPosition = minTopPosition;
             if (section == lastSection) {
@@ -2855,7 +2827,8 @@
             mLastScrollerY = 0;
             // x velocity is set to 1 to avoid overscroller bug
             mScroller.fling(0, 0, 1, velocityY, 0, 0, minY, maxY, 0,
-                    mExpandedInThisMotion && !isScrolledToTop() ? 0 : Integer.MAX_VALUE / 2);
+                    mExpandedInThisMotion
+                            && !mScrollAdapter.isScrolledToTop() ? 0 : Integer.MAX_VALUE / 2);
         }
     }
 
@@ -2962,7 +2935,7 @@
         } else {
             mTopPaddingOverflow = 0;
         }
-        setTopPadding(topPadding, animate && !mKeyguardBypassController.getBypassEnabled());
+        setTopPadding(topPadding, animate && !mKeyguardBypassEnabledProvider.getBypassEnabled());
         setExpandedHeight(mExpandedHeight);
     }
 
@@ -3056,7 +3029,6 @@
     }
 
     @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
-    @Override
     public void cleanUpViewStateForEntry(NotificationEntry entry) {
         View child = entry.getRow();
         if (child == mSwipeHelper.getTranslatingParentView()) {
@@ -3121,7 +3093,7 @@
      * @return Whether an animation was generated.
      */
     @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
-    private boolean generateRemoveAnimation(ExpandableView child) {
+    boolean generateRemoveAnimation(ExpandableView child) {
         if (removeRemovedChildFromHeadsUpChangeAnimations(child)) {
             mAddedHeadsUpChildren.remove(child);
             return false;
@@ -3323,7 +3295,9 @@
     @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
     public void onViewAdded(View child) {
         super.onViewAdded(child);
-        onViewAddedInternal((ExpandableView) child);
+        if (child instanceof ExpandableView) {
+            onViewAddedInternal((ExpandableView) child);
+        }
     }
 
     @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
@@ -3358,7 +3332,7 @@
     @ShadeViewRefactor(RefactorComponent.COORDINATOR)
     private void onViewAddedInternal(ExpandableView child) {
         updateHideSensitiveForChild(child);
-        child.setOnHeightChangedListener(this);
+        child.setOnHeightChangedListener(mOnChildHeightChangedListener);
         generateAddAnimation(child, false /* fromMoreCard */);
         updateAnimationState(child);
         updateChronometerForChild(child);
@@ -3367,7 +3341,8 @@
         }
         if (ANCHOR_SCROLLING) {
             // TODO: once we're recycling this will need to check the adapter position of the child
-            if (child == getFirstChildNotGone() && (isScrolledToTop() || !mIsExpanded)) {
+            if (child == getFirstChildNotGone()
+                    && (mScrollAdapter.isScrolledToTop() || !mIsExpanded)) {
                 // New child was added at the top while we're scrolled to the top;
                 // make it the new anchor view so that we stay at the top.
                 mScrollAnchorView = child;
@@ -3380,14 +3355,11 @@
         child.setHideSensitiveForIntrinsicHeight(mAmbientState.isHideSensitive());
     }
 
-    @Override
     @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
     public void notifyGroupChildRemoved(ExpandableView row, ViewGroup childrenContainer) {
         onViewRemovedInternal(row, childrenContainer);
     }
 
-    @Override
-    @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
     public void notifyGroupChildAdded(ExpandableView row) {
         onViewAddedInternal(row);
     }
@@ -3416,33 +3388,29 @@
     }
 
     @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
-    private void updateAnimationState(View child) {
+    void updateAnimationState(View child) {
         updateAnimationState((mAnimationsEnabled || hasPulsingNotifications())
                 && (mIsExpanded || isPinnedHeadsUp(child)), child);
     }
 
-    @Override
     @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
-    public void setExpandingNotification(ExpandableNotificationRow row) {
+    void setExpandingNotification(ExpandableNotificationRow row) {
         mAmbientState.setExpandingNotification(row);
         requestChildrenUpdate();
     }
 
-    @Override
     @ShadeViewRefactor(RefactorComponent.ADAPTER)
-    public void bindRow(ExpandableNotificationRow row) {
+    void bindRow(ExpandableNotificationRow row) {
         row.setHeadsUpAnimatingAwayListener(animatingAway -> {
             mRoundnessManager.onHeadsupAnimatingAwayChanged(row, animatingAway);
             mHeadsUpAppearanceController.updateHeader(row.getEntry());
         });
     }
 
-    @Override
     public boolean containsView(View v) {
         return v.getParent() == this;
     }
 
-    @Override
     @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
     public void applyExpandAnimationParams(ExpandAnimationParameters params) {
         mAmbientState.setExpandAnimationTopChange(params == null ? 0 : params.getTopChange());
@@ -3458,12 +3426,11 @@
     }
 
     @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
-    public boolean isAddOrRemoveAnimationPending() {
+    boolean isAddOrRemoveAnimationPending() {
         return mNeedsAnimation
                 && (!mChildrenToAddAnimated.isEmpty() || !mChildrenToRemoveAnimated.isEmpty());
     }
 
-    @Override
     @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
     public void generateAddAnimation(ExpandableView child, boolean fromMoreCard) {
         if (mIsExpanded && mAnimationsEnabled && !mChangePositionInProgress && !isFullyHidden()) {
@@ -3481,7 +3448,6 @@
         }
     }
 
-    @Override
     @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
     public void changeViewPosition(ExpandableView child, int newIndex) {
         Assert.isMainThread();
@@ -3570,7 +3536,7 @@
             boolean performDisappearAnimation = !mIsExpanded
                     // Only animate if we still have pinned heads up, otherwise we just have the
                     // regular collapse animation of the lock screen
-                    || (mKeyguardBypassController.getBypassEnabled() && onKeyguard()
+                    || (mKeyguardBypassEnabledProvider.getBypassEnabled() && onKeyguard()
                             && mHeadsUpManager.hasPinnedHeadsUp());
             if (performDisappearAnimation && !isHeadsUp) {
                 type = row.wasJustClicked()
@@ -3806,11 +3772,6 @@
         return y < getHeight() - getEmptyBottomMargin();
     }
 
-    @ShadeViewRefactor(RefactorComponent.INPUT)
-    public void setLongPressListener(ExpandableNotificationRow.LongPressListener listener) {
-        mLongPressListener = listener;
-    }
-
     private float getTouchSlop(MotionEvent event) {
         // Adjust the touch slop if another gesture may be being performed.
         return event.getClassification() == MotionEvent.CLASSIFICATION_AMBIGUOUS_GESTURE
@@ -4264,7 +4225,7 @@
 
             case MotionEvent.ACTION_DOWN: {
                 final int y = (int) ev.getY();
-                mScrolledToTopOnFirstDown = isScrolledToTop();
+                mScrolledToTopOnFirstDown = mScrollAdapter.isScrolledToTop();
                 final ExpandableView childAtTouchPos = getChildAtPosition(
                         ev.getX(), y, false /* requireMinHeight */, false /* ignoreDecors */);
                 if (childAtTouchPos == null) {
@@ -4448,41 +4409,17 @@
         }
     }
 
-    @Override
-    @ShadeViewRefactor(RefactorComponent.COORDINATOR)
-    public boolean isScrolledToTop() {
-        if (ANCHOR_SCROLLING) {
-            updateScrollAnchor();
-            // TODO: once we're recycling this will need to check the adapter position of the child
-            return mScrollAnchorView == getFirstChildNotGone() && mScrollAnchorViewY >= 0;
-        } else {
-            return mOwnScrollY == 0;
-        }
-    }
-
-    @Override
-    @ShadeViewRefactor(RefactorComponent.COORDINATOR)
-    public boolean isScrolledToBottom() {
-        if (ANCHOR_SCROLLING) {
-            return getMaxPositiveScrollAmount() <= 0;
-        } else {
-            return mOwnScrollY >= getScrollRange();
-        }
-    }
-
-    @Override
-    @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
-    public View getHostView() {
-        return this;
+    boolean isScrolledToBottom() {
+        return mScrollAdapter.isScrolledToBottom();
     }
 
     @ShadeViewRefactor(RefactorComponent.COORDINATOR)
-    public int getEmptyBottomMargin() {
+    int getEmptyBottomMargin() {
         return Math.max(mMaxLayoutHeight - mContentHeight, 0);
     }
 
     @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
-    public void checkSnoozeLeavebehind() {
+    void checkSnoozeLeavebehind() {
         if (mCheckForLeavebehind) {
             mNotificationGutsManager.closeAndSaveGuts(true /* removeLeavebehind */,
                     false /* force */, false /* removeControls */, -1 /* x */, -1 /* y */,
@@ -4492,19 +4429,19 @@
     }
 
     @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
-    public void resetCheckSnoozeLeavebehind() {
+    void resetCheckSnoozeLeavebehind() {
         mCheckForLeavebehind = true;
     }
 
     @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
-    public void onExpansionStarted() {
+    void onExpansionStarted() {
         mIsExpansionChanging = true;
         mAmbientState.setExpansionChanging(true);
         checkSnoozeLeavebehind();
     }
 
     @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
-    public void onExpansionStopped() {
+    void onExpansionStopped() {
         mIsExpansionChanging = false;
         resetCheckSnoozeLeavebehind();
         mAmbientState.setExpansionChanging(false);
@@ -4553,20 +4490,20 @@
     }
 
     @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
-    public void onPanelTrackingStarted() {
+    void onPanelTrackingStarted() {
         mPanelTracking = true;
         mAmbientState.setPanelTracking(true);
         resetExposedMenuView(true /* animate */, true /* force */);
     }
 
     @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
-    public void onPanelTrackingStopped() {
+    void onPanelTrackingStopped() {
         mPanelTracking = false;
         mAmbientState.setPanelTracking(false);
     }
 
     @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
-    public void resetScrollPosition() {
+    void resetScrollPosition() {
         mScroller.abortAnimation();
         if (ANCHOR_SCROLLING) {
             // TODO: once we're recycling this will need to modify the adapter position instead
@@ -4607,15 +4544,14 @@
     }
 
     @ShadeViewRefactor(RefactorComponent.COORDINATOR)
-    private void updateChronometerForChild(View child) {
+    void updateChronometerForChild(View child) {
         if (child instanceof ExpandableNotificationRow) {
             ExpandableNotificationRow row = (ExpandableNotificationRow) child;
             row.setChronometerRunning(mIsExpanded);
         }
     }
 
-    @Override
-    public void onHeightChanged(ExpandableView view, boolean needsAnimation) {
+    void onChildHeightChanged(ExpandableView view, boolean needsAnimation) {
         updateContentHeight();
         updateScrollPositionOnExpandInBottom(view);
         clampScrollPosition();
@@ -4638,8 +4574,7 @@
         requestChildrenUpdate();
     }
 
-    @Override
-    public void onReset(ExpandableView view) {
+    void onChildHeightReset(ExpandableView view) {
         updateAnimationState(view);
         updateChronometerForChild(view);
     }
@@ -4680,13 +4615,13 @@
     }
 
     @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
-    public void setOnHeightChangedListener(
+    void setOnHeightChangedListener(
             ExpandableView.OnHeightChangedListener onHeightChangedListener) {
         this.mOnHeightChangedListener = onHeightChangedListener;
     }
 
     @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
-    public void onChildAnimationFinished() {
+    void onChildAnimationFinished() {
         setAnimationRunning(false);
         requestChildrenUpdate();
         runAnimationFinishedRunnables();
@@ -4730,7 +4665,7 @@
      * See {@link AmbientState#setDimmed}.
      */
     @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
-    public void setDimmed(boolean dimmed, boolean animate) {
+    void setDimmed(boolean dimmed, boolean animate) {
         dimmed &= onKeyguard();
         mAmbientState.setDimmed(dimmed);
         if (animate && mAnimationsEnabled) {
@@ -4773,8 +4708,7 @@
     }
 
     @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
-    private void updateSensitiveness(boolean animate) {
-        boolean hideSensitive = mLockscreenUserManager.isAnyProfilePublicMode();
+    void updateSensitiveness(boolean animate, boolean hideSensitive) {
         if (hideSensitive != mAmbientState.isHideSensitive()) {
             int childCount = getChildCount();
             for (int i = 0; i < childCount; i++) {
@@ -4795,7 +4729,7 @@
      * See {@link AmbientState#setActivatedChild}.
      */
     @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
-    public void setActivatedChild(ActivatableNotificationView activatedChild) {
+    void setActivatedChild(ActivatableNotificationView activatedChild) {
         mAmbientState.setActivatedChild(activatedChild);
         if (mAnimationsEnabled) {
             mActivateNeedsAnimation = true;
@@ -4871,7 +4805,7 @@
      * @param lightTheme True if light theme should be used.
      */
     @ShadeViewRefactor(RefactorComponent.DECORATOR)
-    public void updateDecorViews(boolean lightTheme) {
+    void updateDecorViews(boolean lightTheme) {
         if (lightTheme == mUsingLightTheme) {
             return;
         }
@@ -4886,7 +4820,7 @@
     }
 
     @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
-    public void goToFullShade(long delay) {
+    void goToFullShade(long delay) {
         mGoToFullShadeNeedsAnimation = true;
         mGoToFullShadeDelay = delay;
         mNeedsAnimation = true;
@@ -4899,13 +4833,13 @@
     }
 
     @ShadeViewRefactor(RefactorComponent.COORDINATOR)
-    public void setIntrinsicPadding(int intrinsicPadding) {
+    void setIntrinsicPadding(int intrinsicPadding) {
         mIntrinsicPadding = intrinsicPadding;
         mAmbientState.setIntrinsicPadding(intrinsicPadding);
     }
 
     @ShadeViewRefactor(RefactorComponent.COORDINATOR)
-    public int getIntrinsicPadding() {
+    int getIntrinsicPadding() {
         return mIntrinsicPadding;
     }
 
@@ -4939,7 +4873,7 @@
      *                               animation curve.
      */
     @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
-    public void setHideAmount(float linearHideAmount, float interpolatedHideAmount) {
+    void setHideAmount(float linearHideAmount, float interpolatedHideAmount) {
         mLinearHideAmount = linearHideAmount;
         mInterpolatedHideAmount = interpolatedHideAmount;
         boolean wasFullyHidden = mAmbientState.isFullyHidden();
@@ -4966,7 +4900,7 @@
         // Since we are clipping to the outline we need to make sure that the shadows aren't
         // clipped when pulsing
         float ownTranslationZ = 0;
-        if (mKeyguardBypassController.getBypassEnabled() && mAmbientState.isHiddenAtAll()) {
+        if (mKeyguardBypassEnabledProvider.getBypassEnabled() && mAmbientState.isHiddenAtAll()) {
             ExpandableView firstChildNotGone = getFirstChildNotGone();
             if (firstChildNotGone != null && firstChildNotGone.showingPulsing()) {
                 ownTranslationZ = firstChildNotGone.getTranslationZ();
@@ -4981,7 +4915,7 @@
     }
 
     @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
-    public void notifyHideAnimationStart(boolean hide) {
+    void notifyHideAnimationStart(boolean hide) {
         // We only swap the scaling factor if we're fully hidden or fully awake to avoid
         // interpolation issues when playing with the power button.
         if (mInterpolatedHideAmount == 0 || mInterpolatedHideAmount == 1) {
@@ -5009,7 +4943,7 @@
     }
 
     @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
-    public void setFooterView(@NonNull FooterView footerView) {
+    void setFooterView(@NonNull FooterView footerView) {
         int index = -1;
         if (mFooterView != null) {
             index = indexOfChild(mFooterView);
@@ -5020,7 +4954,7 @@
     }
 
     @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
-    public void setEmptyShadeView(EmptyShadeView emptyShadeView) {
+    void setEmptyShadeView(EmptyShadeView emptyShadeView) {
         int index = -1;
         if (mEmptyShadeView != null) {
             index = indexOfChild(mEmptyShadeView);
@@ -5031,11 +4965,11 @@
     }
 
     @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
-    public void updateEmptyShadeView(boolean visible) {
+    void updateEmptyShadeView(boolean visible, boolean notifVisibleInShade) {
         mEmptyShadeView.setVisible(visible, mIsExpanded && mAnimationsEnabled);
 
         int oldTextRes = mEmptyShadeView.getTextResource();
-        int newTextRes = mZenController.areNotificationsHiddenInShade()
+        int newTextRes = notifVisibleInShade
                 ? R.string.dnd_suppressing_shade_text : R.string.empty_shade_text;
         if (oldTextRes != newTextRes) {
             mEmptyShadeView.setText(newTextRes);
@@ -5132,7 +5066,7 @@
     }
 
     @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
-    private void requestAnimateEverything() {
+    void requestAnimateEverything() {
         if (mIsExpanded && mAnimationsEnabled) {
             mEverythingNeedsAnimation = true;
             mNeedsAnimation = true;
@@ -5215,33 +5149,28 @@
         }
     }
 
-    @Override
     @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
     public int getContainerChildCount() {
         return getChildCount();
     }
 
-    @Override
     @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
     public View getContainerChildAt(int i) {
         return getChildAt(i);
     }
 
-    @Override
     @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
     public void removeContainerView(View v) {
         Assert.isMainThread();
         removeView(v);
     }
 
-    @Override
     @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
     public void addContainerView(View v) {
         Assert.isMainThread();
         addView(v);
     }
 
-    @Override
     public void addContainerViewAt(View v, int index) {
         Assert.isMainThread();
         addView(v, index);
@@ -5283,7 +5212,6 @@
         requestChildrenUpdate();
     }
 
-    @Override
     public void setWillExpand(boolean willExpand) {
         mWillExpand = willExpand;
     }
@@ -5419,28 +5347,23 @@
     }
 
     @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
-    public void setShelf(NotificationShelf shelf) {
+    public void setShelfController(NotificationShelfController notificationShelfController) {
         int index = -1;
         if (mShelf != null) {
             index = indexOfChild(mShelf);
             removeView(mShelf);
         }
-        mShelf = shelf;
+        mShelf = notificationShelfController.getView();
         addView(mShelf, index);
-        mAmbientState.setShelf(shelf);
-        mStateAnimator.setShelf(shelf);
-        shelf.bind(mAmbientState, this);
+        mAmbientState.setShelf(mShelf);
+        mStateAnimator.setShelf(mShelf);
+        notificationShelfController.bind(mAmbientState, mController);
         if (ANCHOR_SCROLLING) {
             mScrollAnchorView = mShelf;
         }
     }
 
     @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
-    public NotificationShelf getNotificationShelf() {
-        return mShelf;
-    }
-
-    @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
     public void setMaxDisplayedNotifications(int maxDisplayedNotifications) {
         if (mMaxDisplayedNotifications != maxDisplayedNotifications) {
             mMaxDisplayedNotifications = maxDisplayedNotifications;
@@ -5481,17 +5404,17 @@
         mAmbientState.setStatusBarState(statusBarState);
     }
 
-    private void onStatePostChange() {
+    void onStatePostChange(boolean fromShadeLocked) {
         boolean onKeyguard = onKeyguard();
 
+        mAmbientState.setActivatedChild(null);
+        mAmbientState.setDimmed(onKeyguard);
+
         if (mHeadsUpAppearanceController != null) {
             mHeadsUpAppearanceController.onStateChanged();
         }
 
-        SysuiStatusBarStateController state = (SysuiStatusBarStateController)
-                Dependency.get(StatusBarStateController.class);
-        updateSensitiveness(state.goingToFullShade() /* animate */);
-        setDimmed(onKeyguard, state.fromShadeLocked() /* animate */);
+        setDimmed(onKeyguard, fromShadeLocked);
         setExpandingEnabled(!onKeyguard);
         ActivatableNotificationView activatedChild = getActivatedChild();
         setActivatedChild(null);
@@ -5624,11 +5547,6 @@
     }
 
     @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
-    public void setIconAreaController(NotificationIconAreaController controller) {
-        mIconAreaController = controller;
-    }
-
-    @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
     @VisibleForTesting
     void clearNotifications(
             @SelectedRows int selection,
@@ -5753,7 +5671,6 @@
         }
     }
 
-    @Override
     public void setNotificationActivityStarter(
             NotificationActivityStarter notificationActivityStarter) {
         mNotificationActivityStarter = notificationActivityStarter;
@@ -5819,10 +5736,6 @@
         mNotificationPanelController = notificationPanelViewController;
     }
 
-    public void updateIconAreaViews() {
-        mIconAreaController.updateNotificationIcons();
-    }
-
     /**
      * Set how far the wake up is when waking up from pulsing. This is a height and will adjust the
      * notification positions accordingly.
@@ -5831,7 +5744,7 @@
      */
     public float setPulseHeight(float height) {
         mAmbientState.setPulseHeight(height);
-        if (mKeyguardBypassController.getBypassEnabled()) {
+        if (mKeyguardBypassEnabledProvider.getBypassEnabled()) {
             notifyAppearChangedListeners();
         }
         requestChildrenUpdate();
@@ -5881,17 +5794,8 @@
         mDimmedNeedsAnimation = true;
     }
 
-    @Override
-    public void onDynamicPrivacyChanged() {
-        if (mIsExpanded) {
-            // The bottom might change because we're using the final actual height of the view
-            mAnimateBottomOnLayout = true;
-        }
-        // Let's update the footer once the notifications have been updated (in the next frame)
-        post(() -> {
-            updateFooter();
-            updateSectionBoundaries("dynamic privacy changed");
-        });
+    void setAnimateBottomOnLayout(boolean animateBottomOnLayout) {
+        mAnimateBottomOnLayout = animateBottomOnLayout;
     }
 
     public void setOnPulseHeightChangedListener(Runnable listener) {
@@ -5908,6 +5812,41 @@
         return MathUtils.smoothStep(0, totalDistance, dragDownAmount);
     }
 
+    public void setController(
+            NotificationStackScrollLayoutController notificationStackScrollLayoutController) {
+        mController = notificationStackScrollLayoutController;
+        mController.getNoticationRoundessManager().setAnimatedChildren(mChildrenToAddAnimated);
+    }
+
+    public NotificationStackScrollLayoutController getController() {
+        return mController;
+    }
+
+    void setCurrentUserid(int userId) {
+        mCurrentUserId = userId;
+    }
+
+    void onMenuShown(View row) {
+        mSwipeHelper.onMenuShown(row);
+    }
+
+    void onMenuReset(View row) {
+        View translatingParentView = mSwipeHelper.getTranslatingParentView();
+        if (translatingParentView != null && row == translatingParentView) {
+            mSwipeHelper.clearExposedMenuView();
+            mSwipeHelper.clearTranslatingParentView();
+            if (row instanceof ExpandableNotificationRow) {
+                mHeadsUpManager.setMenuShown(
+                        ((ExpandableNotificationRow) row).getEntry(), false);
+
+            }
+        }
+    }
+
+    void setMenuEventListener(OnMenuEventListener menuEventListener) {
+        mMenuEventListener = menuEventListener;
+    }
+
     /**
      * A listener that is notified when the empty space below the notifications is clicked on
      */
@@ -6002,7 +5941,6 @@
         }
     }
 
-    @Override
     @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
     public void resetExposedMenuView(boolean animate, boolean force) {
         mSwipeHelper.resetExposedMenuView(animate, force);
@@ -6264,89 +6202,7 @@
         }
     }
 
-    @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
-    private final StateListener mStateListener = new StateListener() {
-        @Override
-        public void onStatePreChange(int oldState, int newState) {
-            if (oldState == StatusBarState.SHADE_LOCKED && newState == StatusBarState.KEYGUARD) {
-                requestAnimateEverything();
-            }
-        }
 
-        @Override
-        public void onStateChanged(int newState) {
-            setStatusBarState(newState);
-        }
-
-        @Override
-        public void onStatePostChange() {
-          NotificationStackScrollLayout.this.onStatePostChange();
-      }
-    };
-
-    @VisibleForTesting
-    @ShadeViewRefactor(RefactorComponent.INPUT)
-    protected final OnMenuEventListener mMenuEventListener = new OnMenuEventListener() {
-        @Override
-        public void onMenuClicked(View view, int x, int y, MenuItem item) {
-            if (mLongPressListener == null) {
-                return;
-            }
-            if (view instanceof ExpandableNotificationRow) {
-                ExpandableNotificationRow row = (ExpandableNotificationRow) view;
-                mMetricsLogger.write(row.getEntry().getSbn().getLogMaker()
-                        .setCategory(MetricsEvent.ACTION_TOUCH_GEAR)
-                        .setType(MetricsEvent.TYPE_ACTION)
-                        );
-            }
-            mLongPressListener.onLongPress(view, x, y, item);
-        }
-
-        @Override
-        public void onMenuReset(View row) {
-            View translatingParentView = mSwipeHelper.getTranslatingParentView();
-            if (translatingParentView != null && row == translatingParentView) {
-                mSwipeHelper.clearExposedMenuView();
-                mSwipeHelper.clearTranslatingParentView();
-                if (row instanceof ExpandableNotificationRow) {
-                    mHeadsUpManager.setMenuShown(
-                            ((ExpandableNotificationRow) row).getEntry(), false);
-
-                }
-            }
-        }
-
-        @Override
-        public void onMenuShown(View row) {
-            if (row instanceof ExpandableNotificationRow) {
-                ExpandableNotificationRow notificationRow = (ExpandableNotificationRow) row;
-                mMetricsLogger.write(notificationRow.getEntry().getSbn().getLogMaker()
-                        .setCategory(MetricsEvent.ACTION_REVEAL_GEAR)
-                        .setType(MetricsEvent.TYPE_ACTION));
-                mHeadsUpManager.setMenuShown(notificationRow.getEntry(), true);
-                mSwipeHelper.onMenuShown(row);
-                mNotificationGutsManager.closeAndSaveGuts(true /* removeLeavebehind */,
-                        false /* force */, false /* removeControls */, -1 /* x */, -1 /* y */,
-                        false /* resetMenu */);
-
-                // Check to see if we want to go directly to the notfication guts
-                NotificationMenuRowPlugin provider = notificationRow.getProvider();
-                if (provider.shouldShowGutsOnSnapOpen()) {
-                    MenuItem item = provider.menuItemToExposeOnSnap();
-                    if (item != null) {
-                        Point origin = provider.getRevealAnimationOrigin();
-                        mNotificationGutsManager.openGuts(row, origin.x, origin.y, item);
-                    } else  {
-                        Log.e(TAG, "Provider has shouldShowGutsOnSnapOpen, but provided no "
-                                + "menu item in menuItemtoExposeOnSnap. Skipping.");
-                    }
-
-                    // Close the menu row since we went directly to the guts
-                    resetExposedMenuView(false, true);
-                }
-            }
-        }
-    };
 
     @ShadeViewRefactor(RefactorComponent.INPUT)
     private final NotificationSwipeHelper.NotificationCallback mNotificationCallback =
@@ -6577,7 +6433,7 @@
             @SelectedRows int selectedRows) {
         if (mFeatureFlags.isNewNotifPipelineRenderingEnabled()) {
             if (selectedRows == ROWS_ALL) {
-                mNotifCollection.dismissAllNotifications(mLockscreenUserManager.getCurrentUserId());
+                mNotifCollection.dismissAllNotifications(mCurrentUserId);
             } else {
                 final List<Pair<NotificationEntry, DismissedByUserStats>>
                         entriesWithRowsDismissedFromShade = new ArrayList<>();
@@ -6606,7 +6462,7 @@
             }
             if (selectedRows == ROWS_ALL) {
                 try {
-                    mBarService.onClearAllNotifications(mLockscreenUserManager.getCurrentUserId());
+                    mBarService.onClearAllNotifications(mCurrentUserId);
                 } catch (Exception ex) {
                 }
             }
@@ -6628,6 +6484,10 @@
                     NotificationLogger.getNotificationLocation(entry)));
     }
 
+    public void setKeyguardMediaControllorVisible(boolean keyguardMediaControllorVisible) {
+        mKeyguardMediaControllorVisible = keyguardMediaControllorVisible;
+    }
+
     // ---------------------- DragDownHelper.OnDragDownListener ------------------------------------
 
     @ShadeViewRefactor(RefactorComponent.INPUT)
@@ -6636,8 +6496,7 @@
         /* Only ever called as a consequence of a lockscreen expansion gesture. */
         @Override
         public boolean onDraggedDown(View startingChild, int dragLengthY) {
-            boolean canDragDown = hasActiveNotifications()
-                    || mKeyguardMediaController.getView().getVisibility() == VISIBLE;
+            boolean canDragDown = hasActiveNotifications() || mKeyguardMediaControllorVisible;
             if (mStatusBarState == StatusBarState.KEYGUARD && canDragDown) {
                 mLockscreenGestureLogger.write(
                         MetricsEvent.ACTION_LS_SHADE,
@@ -6715,7 +6574,7 @@
         @Override
         public boolean isDragDownAnywhereEnabled() {
             return mStatusbarStateController.getState() == StatusBarState.KEYGUARD
-                    && !mKeyguardBypassController.getBypassEnabled();
+                    && !mKeyguardBypassEnabledProvider.getBypassEnabled();
         }
     };
 
@@ -6754,7 +6613,7 @@
             }
             changedRow.setChildrenExpanded(expanded, animated);
             if (!mGroupExpandedForMeasure) {
-                onHeightChanged(changedRow, false /* needsAnimation */);
+                onChildHeightChanged(changedRow, false /* needsAnimation */);
             }
             runAfterAnimationFinished(new Runnable() {
                 @Override
@@ -6898,4 +6757,8 @@
             return INVALID;
         }
     }
+
+    interface KeyguardBypassEnabledProvider {
+        boolean getBypassEnabled();
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
new file mode 100644
index 0000000..ca78b2a
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
@@ -0,0 +1,923 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification.stack;
+
+import static com.android.systemui.Dependency.ALLOW_NOTIFICATION_LONG_PRESS_NAME;
+
+import android.graphics.Point;
+import android.graphics.PointF;
+import android.provider.Settings;
+import android.util.Log;
+import android.view.Display;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.WindowInsets;
+import android.widget.FrameLayout;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.logging.MetricsLogger;
+import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
+import com.android.systemui.colorextraction.SysuiColorExtractor;
+import com.android.systemui.media.KeyguardMediaController;
+import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin;
+import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin.OnMenuEventListener;
+import com.android.systemui.plugins.statusbar.NotificationSwipeActionHelper;
+import com.android.systemui.plugins.statusbar.StatusBarStateController;
+import com.android.systemui.statusbar.NotificationLockscreenUserManager;
+import com.android.systemui.statusbar.NotificationLockscreenUserManager.UserChangedListener;
+import com.android.systemui.statusbar.NotificationShelfController;
+import com.android.systemui.statusbar.RemoteInputController;
+import com.android.systemui.statusbar.StatusBarState;
+import com.android.systemui.statusbar.SysuiStatusBarStateController;
+import com.android.systemui.statusbar.notification.ActivityLaunchAnimator;
+import com.android.systemui.statusbar.notification.DynamicPrivacyController;
+import com.android.systemui.statusbar.notification.NotificationActivityStarter;
+import com.android.systemui.statusbar.notification.collection.NotificationEntry;
+import com.android.systemui.statusbar.notification.logging.NotificationLogger;
+import com.android.systemui.statusbar.notification.row.ActivatableNotificationView;
+import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
+import com.android.systemui.statusbar.notification.row.ExpandableView;
+import com.android.systemui.statusbar.notification.row.NotificationGutsManager;
+import com.android.systemui.statusbar.phone.HeadsUpAppearanceController;
+import com.android.systemui.statusbar.phone.HeadsUpManagerPhone;
+import com.android.systemui.statusbar.phone.HeadsUpTouchHelper;
+import com.android.systemui.statusbar.phone.KeyguardBypassController;
+import com.android.systemui.statusbar.phone.NotificationGroupManager;
+import com.android.systemui.statusbar.phone.NotificationPanelViewController;
+import com.android.systemui.statusbar.phone.ScrimController;
+import com.android.systemui.statusbar.phone.StatusBar;
+import com.android.systemui.statusbar.phone.dagger.StatusBarComponent;
+import com.android.systemui.statusbar.policy.ConfigurationController;
+import com.android.systemui.statusbar.policy.ConfigurationController.ConfigurationListener;
+import com.android.systemui.statusbar.policy.ZenModeController;
+import com.android.systemui.tuner.TunerService;
+
+import java.util.function.BiConsumer;
+
+import javax.inject.Inject;
+import javax.inject.Named;
+
+import kotlin.Unit;
+
+/**
+ * Controller for {@link NotificationStackScrollLayout}.
+ */
+@StatusBarComponent.StatusBarScope
+public class NotificationStackScrollLayoutController {
+    private static final String TAG = "StackScrollerController";
+
+    private final boolean mAllowLongPress;
+    private final NotificationGutsManager mNotificationGutsManager;
+    private final HeadsUpManagerPhone mHeadsUpManager;
+    private final NotificationRoundnessManager mNotificationRoundnessManager;
+    private final TunerService mTunerService;
+    private final DynamicPrivacyController mDynamicPrivacyController;
+    private final ConfigurationController mConfigurationController;
+    private final ZenModeController mZenModeController;
+    private final MetricsLogger mMetricsLogger;
+    private final KeyguardMediaController mKeyguardMediaController;
+    private final SysuiStatusBarStateController mStatusBarStateController;
+    private final KeyguardBypassController mKeyguardBypassController;
+    private final SysuiColorExtractor mColorExtractor;
+    private final NotificationLockscreenUserManager mLockscreenUserManager;
+
+    private NotificationStackScrollLayout mView;
+
+    private final NotificationListContainerImpl mNotificationListContainer =
+            new NotificationListContainerImpl();
+
+    @VisibleForTesting
+    final View.OnAttachStateChangeListener mOnAttachStateChangeListener =
+            new View.OnAttachStateChangeListener() {
+                @Override
+                public void onViewAttachedToWindow(View v) {
+                    mConfigurationController.addCallback(mConfigurationListener);
+                    mStatusBarStateController.addCallback(
+                            mStateListener, SysuiStatusBarStateController.RANK_STACK_SCROLLER);
+                }
+
+                @Override
+                public void onViewDetachedFromWindow(View v) {
+                    mConfigurationController.removeCallback(mConfigurationListener);
+                    mStatusBarStateController.removeCallback(mStateListener);
+                }
+            };
+
+    private final DynamicPrivacyController.Listener mDynamicPrivacyControllerListener = () -> {
+        if (mView.isExpanded()) {
+            // The bottom might change because we're using the final actual height of the view
+            mView.setAnimateBottomOnLayout(true);
+        }
+        // Let's update the footer once the notifications have been updated (in the next frame)
+        mView.post(() -> {
+            updateFooter();
+            updateSectionBoundaries("dynamic privacy changed");
+        });
+    };
+
+    @VisibleForTesting
+    final ConfigurationListener mConfigurationListener = new ConfigurationListener() {
+        @Override
+        public void onDensityOrFontScaleChanged() {
+            mView.reinflateViews();
+        }
+
+        @Override
+        public void onOverlayChanged() {
+            mView.updateCornerRadius();
+            mView.reinflateViews();
+        }
+
+        @Override
+        public void onUiModeChanged() {
+            mView.updateBgColor();
+        }
+
+        @Override
+        public void onThemeChanged() {
+            updateFooter();
+        }
+    };
+
+    private final StatusBarStateController.StateListener mStateListener =
+            new StatusBarStateController.StateListener() {
+                @Override
+                public void onStatePreChange(int oldState, int newState) {
+                    if (oldState == StatusBarState.SHADE_LOCKED
+                            && newState == StatusBarState.KEYGUARD) {
+                        mView.requestAnimateEverything();
+                    }
+                }
+
+                @Override
+                public void onStateChanged(int newState) {
+                    mView.setStatusBarState(newState);
+                }
+
+                @Override
+                public void onStatePostChange() {
+                    mView.updateSensitiveness(mStatusBarStateController.goingToFullShade(),
+                            mLockscreenUserManager.isAnyProfilePublicMode());
+                    mView.onStatePostChange(mStatusBarStateController.fromShadeLocked());
+                }
+            };
+
+    private final UserChangedListener mLockscreenUserChangeListener = new UserChangedListener() {
+        @Override
+        public void onUserChanged(int userId) {
+            mView.setCurrentUserid(userId);
+            mView.updateSensitiveness(false, mLockscreenUserManager.isAnyProfilePublicMode());
+        }
+    };
+
+    private final OnMenuEventListener mMenuEventListener = new OnMenuEventListener() {
+        @Override
+        public void onMenuClicked(
+                View view, int x, int y, NotificationMenuRowPlugin.MenuItem item) {
+            if (!mAllowLongPress) {
+                return;
+            }
+            if (view instanceof ExpandableNotificationRow) {
+                ExpandableNotificationRow row = (ExpandableNotificationRow) view;
+                mMetricsLogger.write(row.getEntry().getSbn().getLogMaker()
+                        .setCategory(MetricsEvent.ACTION_TOUCH_GEAR)
+                        .setType(MetricsEvent.TYPE_ACTION)
+                );
+            }
+            mNotificationGutsManager.openGuts(view, x, y, item);
+        }
+
+        @Override
+        public void onMenuReset(View row) {
+            mView.onMenuReset(row);
+        }
+
+        @Override
+        public void onMenuShown(View row) {
+            if (row instanceof ExpandableNotificationRow) {
+                ExpandableNotificationRow notificationRow = (ExpandableNotificationRow) row;
+                mMetricsLogger.write(notificationRow.getEntry().getSbn().getLogMaker()
+                        .setCategory(MetricsEvent.ACTION_REVEAL_GEAR)
+                        .setType(MetricsEvent.TYPE_ACTION));
+                mHeadsUpManager.setMenuShown(notificationRow.getEntry(), true);
+                mView.onMenuShown(row);
+                mNotificationGutsManager.closeAndSaveGuts(true /* removeLeavebehind */,
+                        false /* force */, false /* removeControls */, -1 /* x */, -1 /* y */,
+                        false /* resetMenu */);
+
+                // Check to see if we want to go directly to the notification guts
+                NotificationMenuRowPlugin provider = notificationRow.getProvider();
+                if (provider.shouldShowGutsOnSnapOpen()) {
+                    NotificationMenuRowPlugin.MenuItem item = provider.menuItemToExposeOnSnap();
+                    if (item != null) {
+                        Point origin = provider.getRevealAnimationOrigin();
+                        mNotificationGutsManager.openGuts(row, origin.x, origin.y, item);
+                    } else  {
+                        Log.e(TAG, "Provider has shouldShowGutsOnSnapOpen, but provided no "
+                                + "menu item in menuItemtoExposeOnSnap. Skipping.");
+                    }
+
+                    // Close the menu row since we went directly to the guts
+                    mView.resetExposedMenuView(false, true);
+                }
+            }
+        }
+    };
+
+    @Inject
+    public NotificationStackScrollLayoutController(
+            @Named(ALLOW_NOTIFICATION_LONG_PRESS_NAME) boolean allowLongPress,
+            NotificationGutsManager notificationGutsManager,
+            HeadsUpManagerPhone headsUpManager,
+            NotificationRoundnessManager notificationRoundnessManager,
+            TunerService tunerService,
+            DynamicPrivacyController dynamicPrivacyController,
+            ConfigurationController configurationController,
+            SysuiStatusBarStateController statusBarStateController,
+            KeyguardMediaController keyguardMediaController,
+            KeyguardBypassController keyguardBypassController,
+            ZenModeController zenModeController,
+            SysuiColorExtractor colorExtractor,
+            NotificationLockscreenUserManager lockscreenUserManager,
+            MetricsLogger metricsLogger) {
+        mAllowLongPress = allowLongPress;
+        mNotificationGutsManager = notificationGutsManager;
+        mHeadsUpManager = headsUpManager;
+        mNotificationRoundnessManager = notificationRoundnessManager;
+        mTunerService = tunerService;
+        mDynamicPrivacyController = dynamicPrivacyController;
+        mConfigurationController = configurationController;
+        mStatusBarStateController = statusBarStateController;
+        mKeyguardMediaController = keyguardMediaController;
+        mKeyguardBypassController = keyguardBypassController;
+        mZenModeController = zenModeController;
+        mColorExtractor = colorExtractor;
+        mLockscreenUserManager = lockscreenUserManager;
+        mMetricsLogger = metricsLogger;
+    }
+
+    public void attach(NotificationStackScrollLayout view) {
+        mView = view;
+        mView.setController(this);
+        mView.initView(mView.getContext(), mKeyguardBypassController::getBypassEnabled);
+
+        mHeadsUpManager.addListener(mNotificationRoundnessManager); // TODO: why is this here?
+        mDynamicPrivacyController.addListener(mDynamicPrivacyControllerListener);
+
+        mLockscreenUserManager.addUserChangedListener(mLockscreenUserChangeListener);
+        mView.setCurrentUserid(mLockscreenUserManager.getCurrentUserId());
+
+        mView.setMenuEventListener(mMenuEventListener);
+
+        mNotificationRoundnessManager.setOnRoundingChangedCallback(mView::invalidate);
+        mView.addOnExpandedHeightChangedListener(mNotificationRoundnessManager::setExpanded);
+
+        mTunerService.addTunable(
+                (key, newValue) -> {
+                    if (key.equals(Settings.Secure.NOTIFICATION_DISMISS_RTL)) {
+                        mView.updateDismissRtlSetting("1".equals(newValue));
+                    } else if (key.equals(Settings.Secure.NOTIFICATION_HISTORY_ENABLED)) {
+                        updateFooter();
+                    }
+                },
+                Settings.Secure.NOTIFICATION_DISMISS_RTL,
+                Settings.Secure.NOTIFICATION_HISTORY_ENABLED);
+
+        mColorExtractor.addOnColorsChangedListener((colorExtractor, which) -> {
+            final boolean useDarkText = mColorExtractor.getNeutralColors().supportsDarkText();
+            mView.updateDecorViews(useDarkText);
+        });
+
+        mKeyguardMediaController.setVisibilityChangedListener(visible -> {
+            mView.setKeyguardMediaControllorVisible(visible);
+            if (visible) {
+                mView.generateAddAnimation(
+                        mKeyguardMediaController.getView(), false /*fromMoreCard */);
+            } else {
+                mView.generateRemoveAnimation(mKeyguardMediaController.getView());
+            }
+            mView.requestChildrenUpdate();
+            return Unit.INSTANCE;
+        });
+
+        if (mView.isAttachedToWindow()) {
+            mOnAttachStateChangeListener.onViewAttachedToWindow(mView);
+        }
+        mView.addOnAttachStateChangeListener(mOnAttachStateChangeListener);
+    }
+
+    public void addOnExpandedHeightChangedListener(BiConsumer<Float, Float> listener) {
+        mView.addOnExpandedHeightChangedListener(listener);
+    }
+
+    public void removeOnExpandedHeightChangedListener(BiConsumer<Float, Float> listener) {
+        mView.removeOnExpandedHeightChangedListener(listener);
+    }
+
+    public void addOnLayoutChangeListener(View.OnLayoutChangeListener listener) {
+        mView.addOnLayoutChangeListener(listener);
+    }
+
+    public void removeOnLayoutChangeListener(View.OnLayoutChangeListener listener) {
+        mView.removeOnLayoutChangeListener(listener);
+    }
+
+    public void setHeadsUpAppearanceController(HeadsUpAppearanceController controller) {
+        mView.setHeadsUpAppearanceController(controller);
+    }
+
+    public void requestLayout() {
+        mView.requestLayout();
+    }
+
+    public Display getDisplay() {
+        return mView.getDisplay();
+    }
+
+    public WindowInsets getRootWindowInsets() {
+        return mView.getRootWindowInsets();
+    }
+
+    public int getRight() {
+        return mView.getRight();
+    }
+
+    public boolean isLayoutRtl() {
+        return mView.isLayoutRtl();
+    }
+
+    public float getLeft() {
+        return  mView.getLeft();
+    }
+
+    public float getTranslationX() {
+        return mView.getTranslationX();
+    }
+
+    public void setOnHeightChangedListener(
+            ExpandableView.OnHeightChangedListener listener) {
+        mView.setOnHeightChangedListener(listener);
+    }
+
+    public void setOverscrollTopChangedListener(
+            NotificationStackScrollLayout.OnOverscrollTopChangedListener listener) {
+        mView.setOverscrollTopChangedListener(listener);
+    }
+
+    public void setOnEmptySpaceClickListener(
+            NotificationStackScrollLayout.OnEmptySpaceClickListener listener) {
+        mView.setOnEmptySpaceClickListener(listener);
+    }
+
+    public void setTrackingHeadsUp(ExpandableNotificationRow expandableNotificationRow) {
+        mView.setTrackingHeadsUp(expandableNotificationRow);
+    }
+
+    public void wakeUpFromPulse() {
+        mView.wakeUpFromPulse();
+    }
+
+    public boolean isPulseExpanding() {
+        return mView.isPulseExpanding();
+    }
+
+    public void setOnPulseHeightChangedListener(Runnable listener) {
+        mView.setOnPulseHeightChangedListener(listener);
+    }
+
+    public void setDozeAmount(float amount) {
+        mView.setDozeAmount(amount);
+    }
+
+    public float getWakeUpHeight() {
+        return mView.getWakeUpHeight();
+    }
+
+    public void setHideAmount(float linearAmount, float amount) {
+        mView.setHideAmount(linearAmount, amount);
+    }
+
+    public void notifyHideAnimationStart(boolean hide) {
+        mView.notifyHideAnimationStart(hide);
+    }
+
+    public float setPulseHeight(float height) {
+        return mView.setPulseHeight(height);
+    }
+
+    public void getLocationOnScreen(int[] outLocation) {
+        mView.getLocationOnScreen(outLocation);
+    }
+
+    public ExpandableView getChildAtRawPosition(float x, float y) {
+        return mView.getChildAtRawPosition(x, y);
+    }
+
+    public FrameLayout.LayoutParams getLayoutParams() {
+        return (FrameLayout.LayoutParams) mView.getLayoutParams();
+    }
+
+    public void setLayoutParams(FrameLayout.LayoutParams lp) {
+        mView.setLayoutParams(lp);
+    }
+
+    public void setIsFullWidth(boolean isFullWidth) {
+        mView.setIsFullWidth(isFullWidth);
+    }
+
+    public boolean isAddOrRemoveAnimationPending() {
+        return mView.isAddOrRemoveAnimationPending();
+    }
+
+    public int getVisibleNotificationCount() {
+        return mView.getVisibleNotificationCount();
+    }
+
+    public int getIntrinsicContentHeight() {
+        return mView.getIntrinsicContentHeight();
+    }
+
+    public void setIntrinsicPadding(int intrinsicPadding) {
+        mView.setIntrinsicPadding(intrinsicPadding);
+    }
+
+    public int getHeight() {
+        return mView.getHeight();
+    }
+
+    public int getChildCount() {
+        return mView.getChildCount();
+    }
+
+    public ExpandableView getChildAt(int i) {
+        return (ExpandableView) mView.getChildAt(i);
+    }
+
+    public void goToFullShade(long delay) {
+        mView.goToFullShade(delay);
+    }
+
+    public void setOverScrollAmount(float amount, boolean onTop, boolean animate,
+            boolean cancelAnimators) {
+        mView.setOverScrollAmount(amount, onTop, animate, cancelAnimators);
+    }
+
+    public void setOverScrollAmount(float amount, boolean onTop, boolean animate) {
+        mView.setOverScrollAmount(amount, onTop, animate);
+    }
+
+    public void setOverScrolledPixels(float numPixels, boolean onTop, boolean animate) {
+        mView.setOverScrolledPixels(numPixels, onTop, animate);
+    }
+
+    public void resetScrollPosition() {
+        mView.resetScrollPosition();
+    }
+
+    public void setShouldShowShelfOnly(boolean shouldShowShelfOnly) {
+        mView.setShouldShowShelfOnly(shouldShowShelfOnly);
+    }
+
+    public void cancelLongPress() {
+        mView.cancelLongPress();
+    }
+
+    public float getX() {
+        return mView.getX();
+    }
+
+    public boolean isBelowLastNotification(float x, float y) {
+        return mView.isBelowLastNotification(x, y);
+    }
+
+    public float getWidth() {
+        return mView.getWidth();
+    }
+
+    public float getOpeningHeight() {
+        return mView.getOpeningHeight();
+    }
+
+    public float getBottomMostNotificationBottom() {
+        return mView.getBottomMostNotificationBottom();
+    }
+
+    public void checkSnoozeLeavebehind() {
+        mView.checkSnoozeLeavebehind();
+    }
+
+    public void setQsExpanded(boolean expanded) {
+        mView.setQsExpanded(expanded);
+    }
+
+    public void setScrollingEnabled(boolean enabled) {
+        mView.setScrollingEnabled(enabled);
+    }
+
+    public void setQsExpansionFraction(float expansionFraction) {
+        mView.setQsExpansionFraction(expansionFraction);
+    }
+
+    public float calculateAppearFractionBypass() {
+        return mView.calculateAppearFractionBypass();
+    }
+
+    public void updateTopPadding(float qsHeight, boolean animate) {
+        mView.updateTopPadding(qsHeight, animate);
+    }
+
+    public void resetCheckSnoozeLeavebehind() {
+        mView.resetCheckSnoozeLeavebehind();
+    }
+
+    public boolean isScrolledToBottom() {
+        return mView.isScrolledToBottom();
+    }
+
+    public int getNotGoneChildCount() {
+        return mView.getNotGoneChildCount();
+    }
+
+    public float getIntrinsicPadding() {
+        return mView.getIntrinsicPadding();
+    }
+
+    public float getLayoutMinHeight() {
+        return mView.getLayoutMinHeight();
+    }
+
+    public int getEmptyBottomMargin() {
+        return mView.getEmptyBottomMargin();
+    }
+
+    public float getTopPaddingOverflow() {
+        return mView.getTopPaddingOverflow();
+    }
+
+    public int getTopPadding() {
+        return mView.getTopPadding();
+    }
+
+    public float getEmptyShadeViewHeight() {
+        return mView.getEmptyShadeViewHeight();
+    }
+
+    public void setAlpha(float alpha) {
+        mView.setAlpha(alpha);
+    }
+
+    public float getCurrentOverScrollAmount(boolean top) {
+        return mView.getCurrentOverScrollAmount(top);
+    }
+
+    public float getCurrentOverScrolledPixels(boolean top) {
+        return mView.getCurrentOverScrolledPixels(top);
+    }
+
+    public float calculateAppearFraction(float height) {
+        return mView.calculateAppearFraction(height);
+    }
+
+    public void onExpansionStarted() {
+        mView.onExpansionStarted();
+    }
+
+    public void onExpansionStopped() {
+        mView.onExpansionStopped();
+    }
+
+    public void onPanelTrackingStarted() {
+        mView.onPanelTrackingStarted();
+    }
+
+    public void onPanelTrackingStopped() {
+        mView.onPanelTrackingStopped();
+    }
+
+    public void setHeadsUpBoundaries(int height, int bottomBarHeight) {
+        mView.setHeadsUpBoundaries(height, bottomBarHeight);
+    }
+
+    public void setUnlockHintRunning(boolean running) {
+        mView.setUnlockHintRunning(running);
+    }
+
+    public float getPeekHeight() {
+        return mView.getPeekHeight();
+    }
+
+    public boolean isFooterViewNotGone() {
+        return mView.isFooterViewNotGone();
+    }
+
+    public boolean isFooterViewContentVisible() {
+        return mView.isFooterViewContentVisible();
+    }
+
+    public int getFooterViewHeightWithPadding() {
+        return mView.getFooterViewHeightWithPadding();
+    }
+
+    public void updateEmptyShadeView(boolean visible) {
+        mView.updateEmptyShadeView(visible, mZenModeController.areNotificationsHiddenInShade());
+    }
+
+    public void setHeadsUpAnimatingAway(boolean headsUpAnimatingAway) {
+        mView.setHeadsUpAnimatingAway(headsUpAnimatingAway);
+    }
+
+    public HeadsUpTouchHelper.Callback getHeadsUpCallback() {
+        return mView.getHeadsUpCallback();
+    }
+
+    public void forceNoOverlappingRendering(boolean force) {
+        mView.forceNoOverlappingRendering(force);
+    }
+
+    public void setTranslationX(float translation) {
+        mView.setTranslationX(translation);
+    }
+
+    public void setExpandingVelocity(float velocity) {
+        mView.setExpandingVelocity(velocity);
+    }
+
+    public void setExpandedHeight(float expandedHeight) {
+        mView.setExpandedHeight(expandedHeight);
+    }
+
+    public void setQsContainer(ViewGroup view) {
+        mView.setQsContainer(view);
+    }
+
+    public void setAnimationsEnabled(boolean enabled) {
+        mView.setAnimationsEnabled(enabled);
+    }
+
+    public void setDozing(boolean dozing, boolean animate, PointF wakeUpTouchLocation) {
+        mView.setDozing(dozing, animate, wakeUpTouchLocation);
+    }
+
+    public void setPulsing(boolean pulsing, boolean animatePulse) {
+        mView.setPulsing(pulsing, animatePulse);
+    }
+
+    public boolean hasActiveClearableNotifications(
+            @NotificationStackScrollLayout.SelectedRows int selection) {
+        return mView.hasActiveClearableNotifications(selection);
+    }
+
+    public RemoteInputController.Delegate createDelegate() {
+        return mView.createDelegate();
+    }
+
+    public void updateSectionBoundaries(String reason) {
+        mView.updateSectionBoundaries(reason);
+    }
+
+    public void updateSpeedBumpIndex() {
+        mView.updateSpeedBumpIndex();
+    }
+
+    public void updateFooter() {
+        mView.updateFooter();
+    }
+
+    public void onUpdateRowStates() {
+        mView.onUpdateRowStates();
+    }
+
+    public ActivatableNotificationView getActivatedChild() {
+        return mView.getActivatedChild();
+    }
+
+    public void setActivatedChild(ActivatableNotificationView view) {
+        mView.setActivatedChild(view);
+    }
+
+    public void runAfterAnimationFinished(Runnable r) {
+        mView.runAfterAnimationFinished(r);
+    }
+
+    public void setNotificationPanelController(
+            NotificationPanelViewController notificationPanelViewController) {
+        mView.setNotificationPanelController(notificationPanelViewController);
+    }
+
+    public void setStatusBar(StatusBar statusBar) {
+        mView.setStatusBar(statusBar);
+    }
+
+    public void setGroupManager(NotificationGroupManager groupManager) {
+        mView.setGroupManager(groupManager);
+    }
+
+    public void setShelfController(NotificationShelfController notificationShelfController) {
+        mView.setShelfController(notificationShelfController);
+    }
+
+    public void setScrimController(ScrimController scrimController) {
+        mView.setScrimController(scrimController);
+    }
+
+    public ExpandableView getFirstChildNotGone() {
+        return mView.getFirstChildNotGone();
+    }
+
+    public void setInHeadsUpPinnedMode(boolean inPinnedMode) {
+        mView.setInHeadsUpPinnedMode(inPinnedMode);
+    }
+
+    public void generateHeadsUpAnimation(NotificationEntry entry, boolean isHeadsUp) {
+        mView.generateHeadsUpAnimation(entry, isHeadsUp);
+    }
+
+    public void generateHeadsUpAnimation(ExpandableNotificationRow row, boolean isHeadsUp) {
+        mView.generateHeadsUpAnimation(row, isHeadsUp);
+    }
+
+    public void setMaxTopPadding(int padding) {
+        mView.setMaxTopPadding(padding);
+    }
+
+    public int getTransientViewCount() {
+        return mView.getTransientViewCount();
+    }
+
+    public View getTransientView(int i) {
+        return mView.getTransientView(i);
+    }
+
+    public int getPositionInLinearLayout(ExpandableView row) {
+        return mView.getPositionInLinearLayout(row);
+    }
+
+    public NotificationStackScrollLayout getView() {
+        return mView;
+    }
+
+    public float calculateGapHeight(ExpandableView previousView, ExpandableView child, int count) {
+        return mView.calculateGapHeight(previousView, child, count);
+    }
+
+    public NotificationRoundnessManager getNoticationRoundessManager() {
+        return mNotificationRoundnessManager;
+    }
+
+    public NotificationListContainer getNotificationListContainer() {
+        return mNotificationListContainer;
+    }
+
+    private class NotificationListContainerImpl implements NotificationListContainer {
+        @Override
+        public void setChildTransferInProgress(boolean childTransferInProgress) {
+            mView.setChildTransferInProgress(childTransferInProgress);
+        }
+
+        @Override
+        public void changeViewPosition(ExpandableView child, int newIndex) {
+            mView.changeViewPosition(child, newIndex);
+        }
+
+        @Override
+        public void notifyGroupChildAdded(ExpandableView row) {
+            mView.notifyGroupChildAdded(row);
+        }
+
+        @Override
+        public void notifyGroupChildRemoved(ExpandableView row, ViewGroup childrenContainer) {
+            mView.notifyGroupChildRemoved(row, childrenContainer);
+        }
+
+        @Override
+        public void generateAddAnimation(ExpandableView child, boolean fromMoreCard) {
+            mView.generateAddAnimation(child, fromMoreCard);
+        }
+
+        @Override
+        public void generateChildOrderChangedEvent() {
+            mView.generateChildOrderChangedEvent();
+        }
+
+        @Override
+        public int getContainerChildCount() {
+            return mView.getContainerChildCount();
+        }
+
+        @Override
+        public void setNotificationActivityStarter(
+                NotificationActivityStarter notificationActivityStarter) {
+            mView.setNotificationActivityStarter(notificationActivityStarter);
+        }
+
+        @Override
+        public View getContainerChildAt(int i) {
+            return mView.getContainerChildAt(i);
+        }
+
+        @Override
+        public void removeContainerView(View v) {
+            mView.removeContainerView(v);
+        }
+
+        @Override
+        public void addContainerView(View v) {
+            mView.addContainerView(v);
+        }
+
+        @Override
+        public void addContainerViewAt(View v, int index) {
+            mView.addContainerViewAt(v, index);
+        }
+
+        @Override
+        public void setMaxDisplayedNotifications(int maxNotifications) {
+            mView.setMaxDisplayedNotifications(maxNotifications);
+        }
+
+        @Override
+        public ViewGroup getViewParentForNotification(NotificationEntry entry) {
+            return mView.getViewParentForNotification(entry);
+        }
+
+        @Override
+        public void resetExposedMenuView(boolean animate, boolean force) {
+            mView.resetExposedMenuView(animate, force);
+        }
+
+        @Override
+        public NotificationSwipeActionHelper getSwipeActionHelper() {
+            return mView.getSwipeActionHelper();
+        }
+
+        @Override
+        public void cleanUpViewStateForEntry(NotificationEntry entry) {
+            mView.cleanUpViewStateForEntry(entry);
+        }
+
+        @Override
+        public void setChildLocationsChangedListener(
+                NotificationLogger.OnChildLocationsChangedListener listener) {
+            mView.setChildLocationsChangedListener(listener);
+        }
+
+        public boolean hasPulsingNotifications() {
+            return mView.hasPulsingNotifications();
+        }
+
+        @Override
+        public boolean isInVisibleLocation(NotificationEntry entry) {
+            return mView.isInVisibleLocation(entry);
+        }
+
+        @Override
+        public void onHeightChanged(ExpandableView view, boolean needsAnimation) {
+            mView.onChildHeightChanged(view, needsAnimation);
+        }
+
+        @Override
+        public void onReset(ExpandableView view) {
+            mView.onChildHeightReset(view);
+        }
+
+        @Override
+        public void bindRow(ExpandableNotificationRow row) {
+            mView.bindRow(row);
+        }
+
+        @Override
+        public void applyExpandAnimationParams(
+                ActivityLaunchAnimator.ExpandAnimationParameters params) {
+            mView.applyExpandAnimationParams(params);
+        }
+
+        @Override
+        public void setExpandingNotification(ExpandableNotificationRow row) {
+            mView.setExpandingNotification(row);
+        }
+
+        @Override
+        public boolean containsView(View v) {
+            return mView.containsView(v);
+        }
+
+        @Override
+        public void setWillExpand(boolean willExpand) {
+            mView.setWillExpand(willExpand);
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/AppButtonData.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/AppButtonData.java
deleted file mode 100644
index f6c1062..0000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/AppButtonData.java
+++ /dev/null
@@ -1,60 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.statusbar.phone;
-
-import android.app.ActivityManager.RecentTaskInfo;
-
-import java.util.ArrayList;
-
-/**
- * Data associated with an app button.
- */
-class AppButtonData {
-    public final AppInfo appInfo;
-    public boolean pinned;
-    // Recent tasks for this app, sorted by lastActiveTime, descending.
-    public ArrayList<RecentTaskInfo> tasks;
-
-    public AppButtonData(AppInfo appInfo, boolean pinned) {
-        this.appInfo = appInfo;
-        this.pinned = pinned;
-    }
-
-    public int getTaskCount() {
-        return tasks == null ? 0 : tasks.size();
-    }
-
-    /**
-     * Returns true if the button contains no useful information and should be removed.
-     */
-    public boolean isEmpty() {
-        return !pinned && getTaskCount() == 0;
-    }
-
-    public void addTask(RecentTaskInfo task) {
-        if (tasks == null) {
-            tasks = new ArrayList<RecentTaskInfo>();
-        }
-        tasks.add(task);
-    }
-
-    public void clearTasks() {
-        if (tasks != null) {
-            tasks.clear();
-        }
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/AppInfo.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/AppInfo.java
deleted file mode 100644
index 8f0b532..0000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/AppInfo.java
+++ /dev/null
@@ -1,54 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.statusbar.phone;
-
-import android.content.ComponentName;
-import android.os.UserHandle;
-
-/**
- * Navigation bar app information.
- */
-class AppInfo {
-    private final ComponentName mComponentName;
-    private final UserHandle mUser;
-
-    public AppInfo(ComponentName componentName, UserHandle user) {
-        if (componentName == null || user == null) throw new IllegalArgumentException();
-        mComponentName = componentName;
-        mUser = user;
-    }
-
-    public ComponentName getComponentName() {
-        return mComponentName;
-    }
-
-    public UserHandle getUser() {
-        return mUser;
-    }
-
-    @Override
-    public boolean equals(Object obj) {
-        if (obj == null) {
-            return false;
-        }
-        if (getClass() != obj.getClass()) {
-            return false;
-        }
-        final AppInfo other = (AppInfo) obj;
-        return mComponentName.equals(other.mComponentName) && mUser.equals(other.mUser);
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/AutoHideController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/AutoHideController.java
index d6039af..aeb2efd 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/AutoHideController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/AutoHideController.java
@@ -138,7 +138,7 @@
         mHandler.postDelayed(mAutoHide, AUTO_HIDE_TIMEOUT_MS);
     }
 
-    void checkUserAutoHide(MotionEvent event) {
+    public void checkUserAutoHide(MotionEvent event) {
         boolean shouldHide = isAnyTransientBarShown()
                 && event.getAction() == MotionEvent.ACTION_OUTSIDE // touch outside the source bar.
                 && event.getX() == 0 && event.getY() == 0;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java
index 0e76c904..e996378 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java
@@ -40,6 +40,7 @@
 import com.android.keyguard.KeyguardViewController;
 import com.android.systemui.Dependency;
 import com.android.systemui.Dumpable;
+import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.dump.DumpManager;
 import com.android.systemui.keyguard.KeyguardViewMediator;
@@ -47,6 +48,7 @@
 import com.android.systemui.keyguard.WakefulnessLifecycle;
 import com.android.systemui.statusbar.CommandQueue;
 import com.android.systemui.statusbar.NotificationMediaManager;
+import com.android.systemui.statusbar.NotificationShadeWindowController;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
 
 import java.io.FileDescriptor;
@@ -57,12 +59,11 @@
 import java.util.Optional;
 
 import javax.inject.Inject;
-import javax.inject.Singleton;
 
 /**
  * Controller which coordinates all the biometric unlocking actions with the UI.
  */
-@Singleton
+@SysUISingleton
 public class BiometricUnlockController extends KeyguardUpdateMonitorCallback implements Dumpable {
 
     private static final String TAG = "BiometricUnlockCtrl";
@@ -157,11 +158,11 @@
     private DozeScrimController mDozeScrimController;
     private KeyguardViewMediator mKeyguardViewMediator;
     private ScrimController mScrimController;
-    private StatusBar mStatusBar;
     private PendingAuthenticated mPendingAuthenticated = null;
     private boolean mPendingShowBouncer;
     private boolean mHasScreenTurnedOnSinceAuthenticating;
     private boolean mFadedAwayAfterWakeAndUnlock;
+    private BiometricModeListener mBiometricModeListener;
 
     private final MetricsLogger mMetricsLogger;
 
@@ -243,7 +244,7 @@
     @Inject
     public BiometricUnlockController(Context context, DozeScrimController dozeScrimController,
             KeyguardViewMediator keyguardViewMediator, ScrimController scrimController,
-            StatusBar statusBar, ShadeController shadeController,
+            ShadeController shadeController,
             NotificationShadeWindowController notificationShadeWindowController,
             KeyguardStateController keyguardStateController, Handler handler,
             KeyguardUpdateMonitor keyguardUpdateMonitor,
@@ -264,7 +265,6 @@
         mDozeScrimController = dozeScrimController;
         mKeyguardViewMediator = keyguardViewMediator;
         mScrimController = scrimController;
-        mStatusBar = statusBar;
         mKeyguardStateController = keyguardStateController;
         mHandler = handler;
         mWakeUpDelay = resources.getInteger(com.android.internal.R.integer.config_wakeUpDelayDoze);
@@ -278,6 +278,11 @@
         mKeyguardViewController = keyguardViewController;
     }
 
+    /** Sets a {@link BiometricModeListener}. */
+    public void setBiometricModeListener(BiometricModeListener biometricModeListener) {
+        mBiometricModeListener = biometricModeListener;
+    }
+
     private final Runnable mReleaseBiometricWakeLockRunnable = new Runnable() {
         @Override
         public void run() {
@@ -434,19 +439,25 @@
                 } else {
                     mKeyguardViewMediator.onWakeAndUnlocking();
                 }
-                if (mStatusBar.getNavigationBarView() != null) {
-                    mStatusBar.getNavigationBarView().setWakeAndUnlocking(true);
-                }
                 Trace.endSection();
                 break;
             case MODE_ONLY_WAKE:
             case MODE_NONE:
                 break;
         }
-        mStatusBar.notifyBiometricAuthModeChanged();
+        onModeChanged(mMode);
+        if (mBiometricModeListener != null) {
+            mBiometricModeListener.notifyBiometricAuthModeChanged();
+        }
         Trace.endSection();
     }
 
+    private void onModeChanged(@WakeAndUnlockMode int mode) {
+        if (mBiometricModeListener != null) {
+            mBiometricModeListener.onModeChanged(mode);
+        }
+    }
+
     private void showBouncer() {
         if (mMode == MODE_SHOW_BOUNCER) {
             mKeyguardViewController.showBouncer(false);
@@ -619,10 +630,10 @@
         mMode = MODE_NONE;
         mBiometricType = null;
         mNotificationShadeWindowController.setForceDozeBrightness(false);
-        if (mStatusBar.getNavigationBarView() != null) {
-            mStatusBar.getNavigationBarView().setWakeAndUnlocking(false);
+        if (mBiometricModeListener != null) {
+            mBiometricModeListener.onResetMode();
+            mBiometricModeListener.notifyBiometricAuthModeChanged();
         }
-        mStatusBar.notifyBiometricAuthModeChanged();
     }
 
     @VisibleForTesting
@@ -702,4 +713,14 @@
                 return 3;
         }
     }
+
+    /** An interface to interact with the {@link BiometricUnlockController}. */
+    public interface BiometricModeListener {
+        /** Called when {@code mMode} is reset to {@link #MODE_NONE}. */
+        void onResetMode();
+        /** Called when {@code mMode} has changed in {@link #startWakeAndUnlock(int)}. */
+        void onModeChanged(@WakeAndUnlockMode int mode);
+        /** Called after processing {@link #onModeChanged(int)}. */
+        void notifyBiometricAuthModeChanged();
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DarkIconDispatcherImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DarkIconDispatcherImpl.java
index ef0f7cd..f25359e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DarkIconDispatcherImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DarkIconDispatcherImpl.java
@@ -14,7 +14,6 @@
 
 package com.android.systemui.statusbar.phone;
 
-import static com.android.systemui.plugins.DarkIconDispatcher.DEFAULT_ICON_TINT;
 import static com.android.systemui.plugins.DarkIconDispatcher.getTint;
 
 import android.animation.ArgbEvaluator;
@@ -25,18 +24,17 @@
 import android.widget.ImageView;
 
 import com.android.systemui.R;
-import com.android.systemui.plugins.DarkIconDispatcher.DarkReceiver;
+import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.statusbar.CommandQueue;
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
 
 import javax.inject.Inject;
-import javax.inject.Singleton;
 
 /**
  */
-@Singleton
+@SysUISingleton
 public class DarkIconDispatcherImpl implements SysuiDarkIconDispatcher,
         LightBarTransitionsController.DarkIntensityApplier {
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DemoStatusIcons.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DemoStatusIcons.java
index 0731a56..31965d4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DemoStatusIcons.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DemoStatusIcons.java
@@ -27,8 +27,8 @@
 import android.widget.LinearLayout;
 
 import com.android.internal.statusbar.StatusBarIcon;
-import com.android.systemui.DemoMode;
 import com.android.systemui.R;
+import com.android.systemui.demomode.DemoMode;
 import com.android.systemui.plugins.DarkIconDispatcher;
 import com.android.systemui.plugins.DarkIconDispatcher.DarkReceiver;
 import com.android.systemui.statusbar.StatusBarIconView;
@@ -39,7 +39,9 @@
 import com.android.systemui.statusbar.phone.StatusBarSignalPolicy.WifiIconState;
 
 import java.util.ArrayList;
+import java.util.List;
 
+//TODO: This should be a controller, not its own view
 public class DemoStatusIcons extends StatusIconContainer implements DemoMode, DarkReceiver {
     private static final String TAG = "DemoStatusIcons";
 
@@ -90,73 +92,84 @@
     }
 
     @Override
+    public List<String> demoCommands() {
+        List<String> commands = new ArrayList<>();
+        commands.add(COMMAND_STATUS);
+        return commands;
+    }
+
+    @Override
+    public void onDemoModeStarted() {
+        mDemoMode = true;
+        mStatusIcons.setVisibility(View.GONE);
+        setVisibility(View.VISIBLE);
+    }
+
+    @Override
+    public void onDemoModeFinished() {
+        mDemoMode = false;
+        mStatusIcons.setVisibility(View.VISIBLE);
+        setVisibility(View.GONE);
+    }
+
+    @Override
     public void dispatchDemoCommand(String command, Bundle args) {
-        if (!mDemoMode && command.equals(COMMAND_ENTER)) {
-            mDemoMode = true;
-            mStatusIcons.setVisibility(View.GONE);
-            setVisibility(View.VISIBLE);
-        } else if (mDemoMode && command.equals(COMMAND_EXIT)) {
-            mDemoMode = false;
-            mStatusIcons.setVisibility(View.VISIBLE);
-            setVisibility(View.GONE);
-        } else if (mDemoMode && command.equals(COMMAND_STATUS)) {
-            String volume = args.getString("volume");
-            if (volume != null) {
-                int iconId = volume.equals("vibrate") ? R.drawable.stat_sys_ringer_vibrate
-                        : 0;
-                updateSlot("volume", null, iconId);
-            }
-            String zen = args.getString("zen");
-            if (zen != null) {
-                int iconId = zen.equals("dnd") ? R.drawable.stat_sys_dnd : 0;
-                updateSlot("zen", null, iconId);
-            }
-            String bt = args.getString("bluetooth");
-            if (bt != null) {
-                int iconId = bt.equals("connected")
-                        ? R.drawable.stat_sys_data_bluetooth_connected : 0;
-                updateSlot("bluetooth", null, iconId);
-            }
-            String location = args.getString("location");
-            if (location != null) {
-                int iconId = location.equals("show") ? PhoneStatusBarPolicy.LOCATION_STATUS_ICON_ID
-                        : 0;
-                updateSlot("location", null, iconId);
-            }
-            String alarm = args.getString("alarm");
-            if (alarm != null) {
-                int iconId = alarm.equals("show") ? R.drawable.stat_sys_alarm
-                        : 0;
-                updateSlot("alarm_clock", null, iconId);
-            }
-            String tty = args.getString("tty");
-            if (tty != null) {
-                int iconId = tty.equals("show") ? R.drawable.stat_sys_tty_mode
-                        : 0;
-                updateSlot("tty", null, iconId);
-            }
-            String mute = args.getString("mute");
-            if (mute != null) {
-                int iconId = mute.equals("show") ? android.R.drawable.stat_notify_call_mute
-                        : 0;
-                updateSlot("mute", null, iconId);
-            }
-            String speakerphone = args.getString("speakerphone");
-            if (speakerphone != null) {
-                int iconId = speakerphone.equals("show") ? android.R.drawable.stat_sys_speakerphone
-                        : 0;
-                updateSlot("speakerphone", null, iconId);
-            }
-            String cast = args.getString("cast");
-            if (cast != null) {
-                int iconId = cast.equals("show") ? R.drawable.stat_sys_cast : 0;
-                updateSlot("cast", null, iconId);
-            }
-            String hotspot = args.getString("hotspot");
-            if (hotspot != null) {
-                int iconId = hotspot.equals("show") ? R.drawable.stat_sys_hotspot : 0;
-                updateSlot("hotspot", null, iconId);
-            }
+        String volume = args.getString("volume");
+        if (volume != null) {
+            int iconId = volume.equals("vibrate") ? R.drawable.stat_sys_ringer_vibrate
+                    : 0;
+            updateSlot("volume", null, iconId);
+        }
+        String zen = args.getString("zen");
+        if (zen != null) {
+            int iconId = zen.equals("dnd") ? R.drawable.stat_sys_dnd : 0;
+            updateSlot("zen", null, iconId);
+        }
+        String bt = args.getString("bluetooth");
+        if (bt != null) {
+            int iconId = bt.equals("connected")
+                    ? R.drawable.stat_sys_data_bluetooth_connected : 0;
+            updateSlot("bluetooth", null, iconId);
+        }
+        String location = args.getString("location");
+        if (location != null) {
+            int iconId = location.equals("show") ? PhoneStatusBarPolicy.LOCATION_STATUS_ICON_ID
+                    : 0;
+            updateSlot("location", null, iconId);
+        }
+        String alarm = args.getString("alarm");
+        if (alarm != null) {
+            int iconId = alarm.equals("show") ? R.drawable.stat_sys_alarm
+                    : 0;
+            updateSlot("alarm_clock", null, iconId);
+        }
+        String tty = args.getString("tty");
+        if (tty != null) {
+            int iconId = tty.equals("show") ? R.drawable.stat_sys_tty_mode
+                    : 0;
+            updateSlot("tty", null, iconId);
+        }
+        String mute = args.getString("mute");
+        if (mute != null) {
+            int iconId = mute.equals("show") ? android.R.drawable.stat_notify_call_mute
+                    : 0;
+            updateSlot("mute", null, iconId);
+        }
+        String speakerphone = args.getString("speakerphone");
+        if (speakerphone != null) {
+            int iconId = speakerphone.equals("show") ? android.R.drawable.stat_sys_speakerphone
+                    : 0;
+            updateSlot("speakerphone", null, iconId);
+        }
+        String cast = args.getString("cast");
+        if (cast != null) {
+            int iconId = cast.equals("show") ? R.drawable.stat_sys_cast : 0;
+            updateSlot("cast", null, iconId);
+        }
+        String hotspot = args.getString("hotspot");
+        if (hotspot != null) {
+            int iconId = hotspot.equals("show") ? R.drawable.stat_sys_hotspot : 0;
+            updateSlot("hotspot", null, iconId);
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeParameters.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeParameters.java
index 5fab4be..6495144 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeParameters.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeParameters.java
@@ -25,6 +25,7 @@
 import android.util.MathUtils;
 
 import com.android.systemui.R;
+import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.doze.AlwaysOnDisplayPolicy;
 import com.android.systemui.doze.DozeScreenState;
@@ -34,12 +35,11 @@
 import java.io.PrintWriter;
 
 import javax.inject.Inject;
-import javax.inject.Singleton;
 
 /**
  * Retrieve doze information
  */
-@Singleton
+@SysUISingleton
 public class DozeParameters implements TunerService.Tunable,
         com.android.systemui.plugins.statusbar.DozeParameters {
     private static final int MAX_DURATION = 60 * 1000;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeScrimController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeScrimController.java
index e7d6eba..b2cf72a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeScrimController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeScrimController.java
@@ -22,18 +22,18 @@
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.systemui.Dependency;
+import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.doze.DozeHost;
 import com.android.systemui.doze.DozeLog;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.plugins.statusbar.StatusBarStateController.StateListener;
 
 import javax.inject.Inject;
-import javax.inject.Singleton;
 
 /**
  * Controller which handles all the doze animations of the scrims.
  */
-@Singleton
+@SysUISingleton
 public class DozeScrimController implements StateListener {
     private static final String TAG = "DozeScrimController";
     private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeServiceHost.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeServiceHost.java
index 4afeba8..efb2469 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeServiceHost.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeServiceHost.java
@@ -31,16 +31,18 @@
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.keyguard.KeyguardUpdateMonitor;
 import com.android.systemui.assist.AssistManager;
+import com.android.systemui.biometrics.AuthController;
+import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.doze.DozeHost;
 import com.android.systemui.doze.DozeLog;
 import com.android.systemui.doze.DozeReceiver;
 import com.android.systemui.keyguard.KeyguardViewMediator;
 import com.android.systemui.keyguard.WakefulnessLifecycle;
+import com.android.systemui.statusbar.NotificationShadeWindowController;
 import com.android.systemui.statusbar.PulseExpansionHandler;
 import com.android.systemui.statusbar.StatusBarState;
 import com.android.systemui.statusbar.SysuiStatusBarStateController;
 import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator;
-import com.android.systemui.statusbar.notification.VisualStabilityManager;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 import com.android.systemui.statusbar.policy.BatteryController;
 import com.android.systemui.statusbar.policy.DeviceProvisionedController;
@@ -48,14 +50,13 @@
 import java.util.ArrayList;
 
 import javax.inject.Inject;
-import javax.inject.Singleton;
 
 import dagger.Lazy;
 
 /**
  * Implementation of DozeHost for SystemUI.
  */
-@Singleton
+@SysUISingleton
 public final class DozeServiceHost implements DozeHost {
     private static final String TAG = "DozeServiceHost";
     private final ArrayList<Callback> mCallbacks = new ArrayList<>();
@@ -81,12 +82,12 @@
     private final Lazy<AssistManager> mAssistManagerLazy;
     private final DozeScrimController mDozeScrimController;
     private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
-    private final VisualStabilityManager mVisualStabilityManager;
     private final PulseExpansionHandler mPulseExpansionHandler;
     private final NotificationShadeWindowController mNotificationShadeWindowController;
     private final NotificationWakeUpCoordinator mNotificationWakeUpCoordinator;
     private NotificationShadeWindowViewController mNotificationShadeWindowViewController;
     private final LockscreenLockIconController mLockscreenLockIconController;
+    private final AuthController mAuthController;
     private NotificationIconAreaController mNotificationIconAreaController;
     private StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
     private NotificationPanelViewController mNotificationPanel;
@@ -105,11 +106,12 @@
             KeyguardViewMediator keyguardViewMediator,
             Lazy<AssistManager> assistManagerLazy,
             DozeScrimController dozeScrimController, KeyguardUpdateMonitor keyguardUpdateMonitor,
-            VisualStabilityManager visualStabilityManager,
             PulseExpansionHandler pulseExpansionHandler,
             NotificationShadeWindowController notificationShadeWindowController,
             NotificationWakeUpCoordinator notificationWakeUpCoordinator,
-            LockscreenLockIconController lockscreenLockIconController) {
+            LockscreenLockIconController lockscreenLockIconController,
+            AuthController authController,
+            NotificationIconAreaController notificationIconAreaController) {
         super();
         mDozeLog = dozeLog;
         mPowerManager = powerManager;
@@ -124,11 +126,12 @@
         mAssistManagerLazy = assistManagerLazy;
         mDozeScrimController = dozeScrimController;
         mKeyguardUpdateMonitor = keyguardUpdateMonitor;
-        mVisualStabilityManager = visualStabilityManager;
         mPulseExpansionHandler = pulseExpansionHandler;
         mNotificationShadeWindowController = notificationShadeWindowController;
         mNotificationWakeUpCoordinator = notificationWakeUpCoordinator;
         mLockscreenLockIconController = lockscreenLockIconController;
+        mAuthController = authController;
+        mNotificationIconAreaController = notificationIconAreaController;
     }
 
     // TODO: we should try to not pass status bar in here if we can avoid it.
@@ -136,13 +139,13 @@
     /**
      * Initialize instance with objects only available later during execution.
      */
-    public void initialize(StatusBar statusBar,
-            NotificationIconAreaController notificationIconAreaController,
+    public void initialize(
+            StatusBar statusBar,
             StatusBarKeyguardViewManager statusBarKeyguardViewManager,
             NotificationShadeWindowViewController notificationShadeWindowViewController,
-            NotificationPanelViewController notificationPanel, View ambientIndicationContainer) {
+            NotificationPanelViewController notificationPanel,
+            View ambientIndicationContainer) {
         mStatusBar = statusBar;
-        mNotificationIconAreaController = notificationIconAreaController;
         mStatusBarKeyguardViewManager = statusBarKeyguardViewManager;
         mNotificationPanel = notificationPanel;
         mNotificationShadeWindowViewController = notificationShadeWindowViewController;
@@ -254,11 +257,10 @@
             }
 
             private void setPulsing(boolean pulsing) {
-                mStatusBarStateController.setPulsing(pulsing);
                 mStatusBarKeyguardViewManager.setPulsing(pulsing);
                 mKeyguardViewMediator.setPulsing(pulsing);
                 mNotificationPanel.setPulsing(pulsing);
-                mVisualStabilityManager.setPulsing(pulsing);
+                mStatusBarStateController.setPulsing(pulsing);
                 mIgnoreTouchWhilePulsing = false;
                 if (mKeyguardUpdateMonitor != null && passiveAuthInterrupt) {
                     mKeyguardUpdateMonitor.onAuthInterruptDetected(pulsing /* active */);
@@ -296,6 +298,7 @@
     @Override
     public void dozeTimeTick() {
         mNotificationPanel.dozeTimeTick();
+        mAuthController.dozeTimeTick();
         if (mAmbientIndicationContainer instanceof DozeReceiver) {
             ((DozeReceiver) mAmbientIndicationContainer).dozeTimeTick();
         }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceController.java
index 51c02c9..8cdaa63 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceController.java
@@ -36,7 +36,7 @@
 import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
-import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout;
+import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
 import com.android.systemui.statusbar.policy.OnHeadsUpChangedListener;
 
@@ -52,7 +52,7 @@
     public static final int CONTENT_FADE_DELAY = 100;
     private final NotificationIconAreaController mNotificationIconAreaController;
     private final HeadsUpManagerPhone mHeadsUpManager;
-    private final NotificationStackScrollLayout mStackScroller;
+    private final NotificationStackScrollLayoutController mStackScrollerController;
     private final HeadsUpStatusBarView mHeadsUpStatusBarView;
     private final View mCenteredIconView;
     private final View mClockView;
@@ -93,7 +93,7 @@
     public HeadsUpAppearanceController(
             NotificationIconAreaController notificationIconAreaController,
             HeadsUpManagerPhone headsUpManager,
-            View notificationShadeView,
+            NotificationStackScrollLayoutController notificationStackScrollLayoutController,
             SysuiStatusBarStateController statusBarStateController,
             KeyguardBypassController keyguardBypassController,
             KeyguardStateController keyguardStateController,
@@ -101,10 +101,9 @@
             NotificationPanelViewController notificationPanelViewController, View statusBarView) {
         this(notificationIconAreaController, headsUpManager, statusBarStateController,
                 keyguardBypassController, wakeUpCoordinator, keyguardStateController,
-                commandQueue,
-                statusBarView.findViewById(R.id.heads_up_status_bar_view),
-                notificationShadeView.findViewById(R.id.notification_stack_scroller),
+                commandQueue, notificationStackScrollLayoutController,
                 notificationPanelViewController,
+                statusBarView.findViewById(R.id.heads_up_status_bar_view),
                 statusBarView.findViewById(R.id.clock),
                 statusBarView.findViewById(R.id.operator_name_frame),
                 statusBarView.findViewById(R.id.centered_icon_area));
@@ -119,9 +118,9 @@
             NotificationWakeUpCoordinator wakeUpCoordinator,
             KeyguardStateController keyguardStateController,
             CommandQueue commandQueue,
-            HeadsUpStatusBarView headsUpStatusBarView,
-            NotificationStackScrollLayout stackScroller,
+            NotificationStackScrollLayoutController stackScrollerController,
             NotificationPanelViewController notificationPanelViewController,
+            HeadsUpStatusBarView headsUpStatusBarView,
             View clockView,
             View operatorNameView,
             View centeredIconView) {
@@ -132,14 +131,14 @@
         mCenteredIconView = centeredIconView;
         headsUpStatusBarView.setOnDrawingRectChangedListener(
                 () -> updateIsolatedIconLocation(true /* requireUpdate */));
-        mStackScroller = stackScroller;
+        mStackScrollerController = stackScrollerController;
         mNotificationPanelViewController = notificationPanelViewController;
         notificationPanelViewController.addTrackingHeadsUpListener(mSetTrackingHeadsUp);
         notificationPanelViewController.addVerticalTranslationListener(mUpdatePanelTranslation);
         notificationPanelViewController.setHeadsUpAppearanceController(this);
-        mStackScroller.addOnExpandedHeightChangedListener(mSetExpandedHeight);
-        mStackScroller.addOnLayoutChangeListener(mStackScrollLayoutChangeListener);
-        mStackScroller.setHeadsUpAppearanceController(this);
+        mStackScrollerController.addOnExpandedHeightChangedListener(mSetExpandedHeight);
+        mStackScrollerController.addOnLayoutChangeListener(mStackScrollLayoutChangeListener);
+        mStackScrollerController.setHeadsUpAppearanceController(this);
         mClockView = clockView;
         mOperatorNameView = operatorNameView;
         mDarkIconDispatcher = Dependency.get(DarkIconDispatcher.class);
@@ -153,7 +152,7 @@
                     updateTopEntry();
 
                     // trigger scroller to notify the latest panel translation
-                    mStackScroller.requestLayout();
+                    mStackScrollerController.requestLayout();
                 }
                 mHeadsUpStatusBarView.removeOnLayoutChangeListener(this);
             }
@@ -174,8 +173,8 @@
         mNotificationPanelViewController.removeTrackingHeadsUpListener(mSetTrackingHeadsUp);
         mNotificationPanelViewController.removeVerticalTranslationListener(mUpdatePanelTranslation);
         mNotificationPanelViewController.setHeadsUpAppearanceController(null);
-        mStackScroller.removeOnExpandedHeightChangedListener(mSetExpandedHeight);
-        mStackScroller.removeOnLayoutChangeListener(mStackScrollLayoutChangeListener);
+        mStackScrollerController.removeOnExpandedHeightChangedListener(mSetExpandedHeight);
+        mStackScrollerController.removeOnLayoutChangeListener(mStackScrollLayoutChangeListener);
         mDarkIconDispatcher.removeDarkReceiver(this);
     }
 
@@ -219,12 +218,12 @@
         }
 
         int realDisplaySize = 0;
-        if (mStackScroller.getDisplay() != null) {
-            mStackScroller.getDisplay().getRealSize(mPoint);
+        if (mStackScrollerController.getDisplay() != null) {
+            mStackScrollerController.getDisplay().getRealSize(mPoint);
             realDisplaySize = mPoint.x;
         }
 
-        WindowInsets windowInset = mStackScroller.getRootWindowInsets();
+        WindowInsets windowInset = mStackScrollerController.getRootWindowInsets();
         DisplayCutout cutout = (windowInset != null) ? windowInset.getDisplayCutout() : null;
         int sysWinLeft = (windowInset != null) ? windowInset.getStableInsetLeft() : 0;
         int sysWinRight = (windowInset != null) ? windowInset.getStableInsetRight() : 0;
@@ -233,17 +232,17 @@
         int leftInset = Math.max(sysWinLeft, cutoutLeft);
         int rightInset = Math.max(sysWinRight, cutoutRight);
 
-        return leftInset + mStackScroller.getRight() + rightInset - realDisplaySize;
+        return leftInset + mStackScrollerController.getRight() + rightInset - realDisplaySize;
     }
 
     public void updatePanelTranslation() {
         float newTranslation;
-        if (mStackScroller.isLayoutRtl()) {
+        if (mStackScrollerController.isLayoutRtl()) {
             newTranslation = getRtlTranslation();
         } else {
-            newTranslation = mStackScroller.getLeft();
+            newTranslation = mStackScrollerController.getLeft();
         }
-        newTranslation += mStackScroller.getTranslationX();
+        newTranslation += mStackScrollerController.getTranslationX();
         mHeadsUpStatusBarView.setPanelTranslation(newTranslation);
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java
index 1d82e08..8092cb9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java
@@ -31,8 +31,8 @@
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.plugins.statusbar.StatusBarStateController.StateListener;
 import com.android.systemui.statusbar.StatusBarState;
-import com.android.systemui.statusbar.notification.VisualStabilityManager;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
+import com.android.systemui.statusbar.notification.collection.legacy.VisualStabilityManager;
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
 import com.android.systemui.statusbar.policy.ConfigurationController;
 import com.android.systemui.statusbar.policy.HeadsUpManager;
@@ -58,6 +58,7 @@
     private final NotificationGroupManager mGroupManager;
     private final List<OnHeadsUpPhoneListenerChange> mHeadsUpPhoneListeners = new ArrayList<>();
     private final int mAutoHeadsUpNotificationDecay;
+    // TODO (b/162832756): remove visual stability manager when migrating to new pipeline
     private VisualStabilityManager mVisualStabilityManager;
     private boolean mReleaseOnExpandFinish;
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBypassController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBypassController.kt
index 0827511..242bd0a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBypassController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBypassController.kt
@@ -21,6 +21,7 @@
 import android.hardware.biometrics.BiometricSourceType
 import android.provider.Settings
 import com.android.systemui.Dumpable
+import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dump.DumpManager
 import com.android.systemui.plugins.statusbar.StatusBarStateController
 import com.android.systemui.statusbar.NotificationLockscreenUserManager
@@ -30,9 +31,8 @@
 import java.io.FileDescriptor
 import java.io.PrintWriter
 import javax.inject.Inject
-import javax.inject.Singleton
 
-@Singleton
+@SysUISingleton
 open class KeyguardBypassController : Dumpable {
 
     private val mKeyguardStateController: KeyguardStateController
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardDismissUtil.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardDismissUtil.java
index 834d2a5..c0181f4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardDismissUtil.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardDismissUtil.java
@@ -18,16 +18,16 @@
 
 import android.util.Log;
 
+import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.plugins.ActivityStarter.OnDismissAction;
 
 import javax.inject.Inject;
-import javax.inject.Singleton;
 
 /**
  * Executes actions that require the screen to be unlocked. Delegates the actual handling to an
  * implementation passed via {@link #setDismissHandler}.
  */
-@Singleton
+@SysUISingleton
 public class KeyguardDismissUtil implements KeyguardDismissHandler {
     private static final String TAG = "KeyguardDismissUtil";
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardEnvironmentImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardEnvironmentImpl.java
index e763496..817b86b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardEnvironmentImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardEnvironmentImpl.java
@@ -21,14 +21,14 @@
 import android.util.Log;
 
 import com.android.systemui.Dependency;
+import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.statusbar.NotificationLockscreenUserManager;
 import com.android.systemui.statusbar.notification.NotificationEntryManager.KeyguardEnvironment;
 import com.android.systemui.statusbar.policy.DeviceProvisionedController;
 
 import javax.inject.Inject;
-import javax.inject.Singleton;
 
-@Singleton
+@SysUISingleton
 public class KeyguardEnvironmentImpl implements KeyguardEnvironment {
 
     private static final String TAG = "KeyguardEnvironmentImpl";
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarController.java
index 3e5eb5f..24c9021 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarController.java
@@ -32,6 +32,8 @@
 import com.android.internal.view.AppearanceRegion;
 import com.android.systemui.Dumpable;
 import com.android.systemui.R;
+import com.android.systemui.dagger.SysUISingleton;
+import com.android.systemui.navigationbar.NavigationModeController;
 import com.android.systemui.plugins.DarkIconDispatcher;
 import com.android.systemui.shared.system.QuickStepContract;
 import com.android.systemui.statusbar.policy.BatteryController;
@@ -40,12 +42,11 @@
 import java.io.PrintWriter;
 
 import javax.inject.Inject;
-import javax.inject.Singleton;
 
 /**
  * Controls how light status bar flag applies to the icons.
  */
-@Singleton
+@SysUISingleton
 public class LightBarController implements BatteryController.BatteryStateChangeCallback, Dumpable {
 
     private static final float NAV_BAR_INVERSION_SCRIM_ALPHA_THRESHOLD = 0.1f;
@@ -125,7 +126,7 @@
         updateStatus();
     }
 
-    void onNavigationBarAppearanceChanged(@Appearance int appearance, boolean nbModeChanged,
+    public void onNavigationBarAppearanceChanged(@Appearance int appearance, boolean nbModeChanged,
             int navigationBarMode, boolean navbarColorManagedByIme) {
         int diff = appearance ^ mAppearance;
         if ((diff & APPEARANCE_LIGHT_NAVIGATION_BARS) != 0 || nbModeChanged) {
@@ -144,7 +145,7 @@
         mNavbarColorManagedByIme = navbarColorManagedByIme;
     }
 
-    void onNavigationBarModeChanged(int newBarMode) {
+    public void onNavigationBarModeChanged(int newBarMode) {
         mHasLightNavigationBar = isLight(mAppearance, newBarMode, APPEARANCE_LIGHT_NAVIGATION_BARS);
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightsOutNotifController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightsOutNotifController.java
index 8e192c5..d27a3d5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightsOutNotifController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightsOutNotifController.java
@@ -29,13 +29,13 @@
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.statusbar.NotificationVisibility;
 import com.android.internal.view.AppearanceRegion;
+import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.statusbar.CommandQueue;
 import com.android.systemui.statusbar.notification.NotificationEntryListener;
 import com.android.systemui.statusbar.notification.NotificationEntryManager;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 
 import javax.inject.Inject;
-import javax.inject.Singleton;
 
 /**
  * Apps can request a low profile mode {@link View.SYSTEM_UI_FLAG_LOW_PROFILE}
@@ -45,7 +45,7 @@
  * This controller shows and hides the notification dot in the status bar to indicate
  * whether there are notifications when the device is in {@link View.SYSTEM_UI_FLAG_LOW_PROFILE}.
  */
-@Singleton
+@SysUISingleton
 public class LightsOutNotifController {
     private final CommandQueue mCommandQueue;
     private final NotificationEntryManager mEntryManager;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockscreenGestureLogger.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockscreenGestureLogger.java
index 0d6597f..094ebb9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockscreenGestureLogger.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockscreenGestureLogger.java
@@ -27,15 +27,15 @@
 import com.android.systemui.Dependency;
 import com.android.systemui.EventLogConstants;
 import com.android.systemui.EventLogTags;
+import com.android.systemui.dagger.SysUISingleton;
 
 import javax.inject.Inject;
-import javax.inject.Singleton;
 
 /**
  * Wrapper that emits both new- and old-style gesture logs.
  * TODO: delete this once the old logs are no longer needed.
  */
-@Singleton
+@SysUISingleton
 public class LockscreenGestureLogger {
 
     /**
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockscreenLockIconController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockscreenLockIconController.java
index 1dc0f07..11ceedf 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockscreenLockIconController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockscreenLockIconController.java
@@ -37,6 +37,7 @@
 import com.android.keyguard.KeyguardUpdateMonitor;
 import com.android.keyguard.KeyguardUpdateMonitorCallback;
 import com.android.systemui.R;
+import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.dock.DockManager;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
@@ -54,10 +55,9 @@
 import java.util.Optional;
 
 import javax.inject.Inject;
-import javax.inject.Singleton;
 
 /** Controls the {@link LockIcon} in the lockscreen. */
-@Singleton
+@SysUISingleton
 public class LockscreenLockIconController {
 
     private final LockscreenGestureLogger mLockscreenGestureLogger;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockscreenWallpaper.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockscreenWallpaper.java
index 04211df..78fcd82 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockscreenWallpaper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockscreenWallpaper.java
@@ -42,8 +42,10 @@
 import com.android.internal.util.IndentingPrintWriter;
 import com.android.keyguard.KeyguardUpdateMonitor;
 import com.android.systemui.Dumpable;
+import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.dump.DumpManager;
+import com.android.systemui.keyguard.FaceAuthScreenBrightnessController;
 import com.android.systemui.statusbar.NotificationMediaManager;
 
 import libcore.io.IoUtils;
@@ -51,14 +53,14 @@
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
 import java.util.Objects;
+import java.util.Optional;
 
 import javax.inject.Inject;
-import javax.inject.Singleton;
 
 /**
  * Manages the lockscreen wallpaper.
  */
-@Singleton
+@SysUISingleton
 public class LockscreenWallpaper extends IWallpaperManagerCallback.Stub implements Runnable,
         Dumpable {
 
@@ -68,6 +70,7 @@
     private final WallpaperManager mWallpaperManager;
     private final KeyguardUpdateMonitor mUpdateMonitor;
     private final Handler mH;
+    private final Optional<FaceAuthScreenBrightnessController> mFaceAuthScreenBrightnessController;
 
     private boolean mCached;
     private Bitmap mCache;
@@ -83,12 +86,14 @@
             KeyguardUpdateMonitor keyguardUpdateMonitor,
             DumpManager dumpManager,
             NotificationMediaManager mediaManager,
+            Optional<FaceAuthScreenBrightnessController> faceAuthScreenBrightnessController,
             @Main Handler mainHandler) {
         dumpManager.registerDumpable(getClass().getSimpleName(), this);
         mWallpaperManager = wallpaperManager;
         mCurrentUserId = ActivityManager.getCurrentUser();
         mUpdateMonitor = keyguardUpdateMonitor;
         mMediaManager = mediaManager;
+        mFaceAuthScreenBrightnessController = faceAuthScreenBrightnessController;
         mH = mainHandler;
 
         if (iWallpaperManager != null) {
@@ -128,6 +133,14 @@
             return LoaderResult.success(null);
         }
 
+        Bitmap faceAuthWallpaper = null;
+        if (mFaceAuthScreenBrightnessController.isPresent()) {
+            faceAuthWallpaper = mFaceAuthScreenBrightnessController.get().getFaceAuthWallpaper();
+            if (faceAuthWallpaper != null) {
+                return LoaderResult.success(faceAuthWallpaper);
+            }
+        }
+
         // Prefer the selected user (when specified) over the current user for the FLAG_SET_LOCK
         // wallpaper.
         final int lockWallpaperUserId =
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ManagedProfileControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ManagedProfileControllerImpl.java
index 07e9f94..94d1bf4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ManagedProfileControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ManagedProfileControllerImpl.java
@@ -24,18 +24,20 @@
 import android.os.UserHandle;
 import android.os.UserManager;
 
+import androidx.annotation.NonNull;
+
 import com.android.systemui.broadcast.BroadcastDispatcher;
+import com.android.systemui.dagger.SysUISingleton;
 
 import java.util.ArrayList;
 import java.util.LinkedList;
 import java.util.List;
 
 import javax.inject.Inject;
-import javax.inject.Singleton;
 
 /**
  */
-@Singleton
+@SysUISingleton
 public class ManagedProfileControllerImpl implements ManagedProfileController {
 
     private final List<Callback> mCallbacks = new ArrayList<>();
@@ -57,7 +59,8 @@
         mProfiles = new LinkedList<UserInfo>();
     }
 
-    public void addCallback(Callback callback) {
+    @Override
+    public void addCallback(@NonNull Callback callback) {
         mCallbacks.add(callback);
         if (mCallbacks.size() == 1) {
             setListening(true);
@@ -65,7 +68,8 @@
         callback.onManagedProfileChanged();
     }
 
-    public void removeCallback(Callback callback) {
+    @Override
+    public void removeCallback(@NonNull Callback callback) {
         if (mCallbacks.remove(callback) && mCallbacks.size() == 0) {
             setListening(false);
         }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationGroupManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationGroupManager.java
index 80785db..c44c59c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationGroupManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationGroupManager.java
@@ -23,6 +23,7 @@
 
 import com.android.systemui.Dependency;
 import com.android.systemui.bubbles.BubbleController;
+import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.plugins.statusbar.StatusBarStateController.StateListener;
 import com.android.systemui.statusbar.StatusBarState;
@@ -40,14 +41,13 @@
 import java.util.Objects;
 
 import javax.inject.Inject;
-import javax.inject.Singleton;
 
 import dagger.Lazy;
 
 /**
  * A class to handle notifications and their corresponding groups.
  */
-@Singleton
+@SysUISingleton
 public class NotificationGroupManager implements OnHeadsUpChangedListener, StateListener {
 
     private static final String TAG = "NotificationGroupManager";
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaController.java
index 9d3e915..bda35fb 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaController.java
@@ -5,9 +5,9 @@
 import android.content.res.Resources;
 import android.graphics.Color;
 import android.graphics.Rect;
+import android.os.Bundle;
 import android.view.LayoutInflater;
 import android.view.View;
-import android.view.ViewGroup;
 import android.widget.FrameLayout;
 
 import androidx.annotation.NonNull;
@@ -20,31 +20,40 @@
 import com.android.systemui.Interpolators;
 import com.android.systemui.R;
 import com.android.systemui.bubbles.BubbleController;
+import com.android.systemui.dagger.SysUISingleton;
+import com.android.systemui.demomode.DemoMode;
+import com.android.systemui.demomode.DemoModeController;
 import com.android.systemui.plugins.DarkIconDispatcher;
 import com.android.systemui.plugins.DarkIconDispatcher.DarkReceiver;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.statusbar.CrossFadeHelper;
 import com.android.systemui.statusbar.NotificationListener;
 import com.android.systemui.statusbar.NotificationMediaManager;
-import com.android.systemui.statusbar.NotificationShelf;
+import com.android.systemui.statusbar.NotificationShelfController;
 import com.android.systemui.statusbar.StatusBarIconView;
 import com.android.systemui.statusbar.StatusBarState;
 import com.android.systemui.statusbar.notification.NotificationUtils;
 import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator;
+import com.android.systemui.statusbar.notification.collection.ListEntry;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
-import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
 
 import java.util.ArrayList;
+import java.util.List;
 import java.util.Objects;
 import java.util.function.Function;
 
+import javax.inject.Inject;
+
 /**
  * A controller for the space in the status bar to the left of the system icons. This area is
  * normally reserved for notifications.
  */
-public class NotificationIconAreaController implements DarkReceiver,
+@SysUISingleton
+public class NotificationIconAreaController implements
+        DarkReceiver,
         StatusBarStateController.StateListener,
-        NotificationWakeUpCoordinator.WakeUpListener {
+        NotificationWakeUpCoordinator.WakeUpListener,
+        DemoMode {
 
     public static final String HIGH_PRIORITY = "high_priority";
     private static final long AOD_ICONS_APPEAR_DURATION = 200;
@@ -57,13 +66,14 @@
     private final KeyguardBypassController mBypassController;
     private final DozeParameters mDozeParameters;
     private final BubbleController mBubbleController;
+    private final StatusBarWindowController mStatusBarWindowController;
 
     private int mIconSize;
     private int mIconHPadding;
     private int mIconTint = Color.WHITE;
     private int mCenteredIconTint = Color.WHITE;
 
-    private StatusBar mStatusBar;
+    private List<ListEntry> mNotificationEntries = List.of();
     protected View mNotificationIconArea;
     private NotificationIconContainer mNotificationIcons;
     private NotificationIconContainer mShelfIcons;
@@ -72,8 +82,10 @@
     private NotificationIconContainer mAodIcons;
     private StatusBarIconView mCenteredIconView;
     private final Rect mTintArea = new Rect();
-    private ViewGroup mNotificationScrollLayout;
     private Context mContext;
+
+    private final DemoModeController mDemoModeController;
+
     private int mAodIconAppearTranslation;
 
     private boolean mAnimationsEnabled;
@@ -88,24 +100,24 @@
             new NotificationListener.NotificationSettingsListener() {
                 @Override
                 public void onStatusBarIconsBehaviorChanged(boolean hideSilentStatusIcons) {
-                        mShowLowPriority = !hideSilentStatusIcons;
-                        if (mNotificationScrollLayout != null) {
-                            updateStatusBarIcons();
-                        }
+                    mShowLowPriority = !hideSilentStatusIcons;
+                    updateStatusBarIcons();
                 }
             };
 
+    @Inject
     public NotificationIconAreaController(
             Context context,
-            StatusBar statusBar,
             StatusBarStateController statusBarStateController,
             NotificationWakeUpCoordinator wakeUpCoordinator,
             KeyguardBypassController keyguardBypassController,
             NotificationMediaManager notificationMediaManager,
             NotificationListener notificationListener,
             DozeParameters dozeParameters,
-            BubbleController bubbleController) {
-        mStatusBar = statusBar;
+            BubbleController bubbleController,
+            DemoModeController demoModeController,
+            DarkIconDispatcher darkIconDispatcher,
+            StatusBarWindowController statusBarWindowController) {
         mContrastColorUtil = ContrastColorUtil.getInstance(context);
         mContext = context;
         mStatusBarStateController = statusBarStateController;
@@ -116,10 +128,14 @@
         wakeUpCoordinator.addListener(this);
         mBypassController = keyguardBypassController;
         mBubbleController = bubbleController;
+        mDemoModeController = demoModeController;
+        mDemoModeController.addCallback(this);
+        mStatusBarWindowController = statusBarWindowController;
         notificationListener.addNotificationSettingsListener(mSettingsListener);
 
         initializeNotificationAreaViews(context);
         reloadAodColor();
+        darkIconDispatcher.addDarkReceiver(this);
     }
 
     protected View inflateIconArea(LayoutInflater inflater) {
@@ -136,22 +152,21 @@
         mNotificationIconArea = inflateIconArea(layoutInflater);
         mNotificationIcons = mNotificationIconArea.findViewById(R.id.notificationIcons);
 
-        mNotificationScrollLayout = mStatusBar.getNotificationScrollLayout();
-
         mCenteredIconArea = layoutInflater.inflate(R.layout.center_icon_area, null);
         mCenteredIcon = mCenteredIconArea.findViewById(R.id.centeredIcon);
-
-        initAodIcons();
     }
 
-    public void initAodIcons() {
+    /**
+     * Called by the StatusBar. The StatusBar passes the NotificationIconContainer which holds
+     * the aod icons.
+     */
+    void setupAodIcons(@NonNull NotificationIconContainer aodIcons) {
         boolean changed = mAodIcons != null;
         if (changed) {
             mAodIcons.setAnimationsEnabled(false);
             mAodIcons.removeAllViews();
         }
-        mAodIcons = mStatusBar.getNotificationShadeWindowView().findViewById(
-                R.id.clock_notification_icon_container);
+        mAodIcons = aodIcons;
         mAodIcons.setOnLockScreen(true);
         updateAodIconsVisibility(false /* animate */);
         updateAnimations();
@@ -160,9 +175,9 @@
         }
     }
 
-    public void setupShelf(NotificationShelf shelf) {
-        mShelfIcons = shelf.getShelfIcons();
-        shelf.setCollapsedIcons(mNotificationIcons);
+    public void setupShelf(NotificationShelfController notificationShelfController) {
+        mShelfIcons = notificationShelfController.getShelfIcons();
+        notificationShelfController.setCollapsedIcons(mNotificationIcons);
     }
 
     public void onDensityOrFontScaleChanged(Context context) {
@@ -189,7 +204,7 @@
     @NonNull
     private FrameLayout.LayoutParams generateIconLayoutParams() {
         return new FrameLayout.LayoutParams(
-                mIconSize + 2 * mIconHPadding, getHeight());
+                mIconSize + 2 * mIconHPadding, mStatusBarWindowController.getStatusBarHeight());
     }
 
     private void reloadDimens(Context context) {
@@ -228,29 +243,17 @@
             mTintArea.set(tintArea);
         }
 
-        if (mNotificationIconArea != null) {
-            if (DarkIconDispatcher.isInArea(tintArea, mNotificationIconArea)) {
-                mIconTint = iconTint;
-            }
-        } else {
+        if (DarkIconDispatcher.isInArea(tintArea, mNotificationIconArea)) {
             mIconTint = iconTint;
         }
 
-        if (mCenteredIconArea != null) {
-            if (DarkIconDispatcher.isInArea(tintArea, mCenteredIconArea)) {
-                mCenteredIconTint = iconTint;
-            }
-        } else {
+        if (DarkIconDispatcher.isInArea(tintArea, mCenteredIconArea)) {
             mCenteredIconTint = iconTint;
         }
 
         applyNotificationIconsTint();
     }
 
-    protected int getHeight() {
-        return mStatusBar.getStatusBarHeight();
-    }
-
     protected boolean shouldShowNotificationIcon(NotificationEntry entry,
             boolean showAmbient, boolean showLowPriority, boolean hideDismissed,
             boolean hideRepliedMessages, boolean hideCurrentMedia, boolean hideCenteredIcon,
@@ -300,11 +303,15 @@
         }
         return true;
     }
-
     /**
      * Updates the notifications with the given list of notifications to display.
      */
-    public void updateNotificationIcons() {
+    public void updateNotificationIcons(List<ListEntry> entries) {
+        mNotificationEntries = entries;
+        updateNotificationIcons();
+    }
+
+    private void updateNotificationIcons() {
         updateStatusBarIcons();
         updateShelfIcons();
         updateCenterIcon();
@@ -381,18 +388,15 @@
             NotificationIconContainer hostLayout, boolean showAmbient, boolean showLowPriority,
             boolean hideDismissed, boolean hideRepliedMessages, boolean hideCurrentMedia,
             boolean hideCenteredIcon, boolean hidePulsing, boolean onlyShowCenteredIcon) {
-        ArrayList<StatusBarIconView> toShow = new ArrayList<>(
-                mNotificationScrollLayout.getChildCount());
-
+        ArrayList<StatusBarIconView> toShow = new ArrayList<>(mNotificationEntries.size());
         // Filter out ambient notifications and notification children.
-        for (int i = 0; i < mNotificationScrollLayout.getChildCount(); i++) {
-            View view = mNotificationScrollLayout.getChildAt(i);
-            if (view instanceof ExpandableNotificationRow) {
-                NotificationEntry ent = ((ExpandableNotificationRow) view).getEntry();
-                if (shouldShowNotificationIcon(ent, showAmbient, showLowPriority, hideDismissed,
+        for (int i = 0; i < mNotificationEntries.size(); i++) {
+            NotificationEntry entry = mNotificationEntries.get(i).getRepresentativeEntry();
+            if (entry != null && entry.getRow() != null) {
+                if (shouldShowNotificationIcon(entry, showAmbient, showLowPriority, hideDismissed,
                         hideRepliedMessages, hideCurrentMedia, hideCenteredIcon, hidePulsing,
                         onlyShowCenteredIcon)) {
-                    StatusBarIconView iconView = function.apply(ent);
+                    StatusBarIconView iconView = function.apply(entry);
                     if (iconView != null) {
                         toShow.add(iconView);
                     }
@@ -597,13 +601,16 @@
         mAodIconTint = Utils.getColorAttrDefaultColor(mContext,
                 R.attr.wallpaperTextColor);
     }
+
     private void updateAodIconColors() {
-        for (int i = 0; i < mAodIcons.getChildCount(); i++) {
-            final StatusBarIconView iv = (StatusBarIconView) mAodIcons.getChildAt(i);
-            if (iv.getWidth() != 0) {
-                updateTintForIcon(iv, mAodIconTint);
-            } else {
-                iv.executeOnLayout(() -> updateTintForIcon(iv, mAodIconTint));
+        if (mAodIcons != null) {
+            for (int i = 0; i < mAodIcons.getChildCount(); i++) {
+                final StatusBarIconView iv = (StatusBarIconView) mAodIcons.getChildAt(i);
+                if (iv.getWidth() != 0) {
+                    updateTintForIcon(iv, mAodIconTint);
+                } else {
+                    iv.executeOnLayout(() -> updateTintForIcon(iv, mAodIconTint));
+                }
             }
         }
     }
@@ -666,4 +673,27 @@
             }
         }
     }
+
+    @Override
+    public List<String> demoCommands() {
+        ArrayList<String> commands = new ArrayList<>();
+        commands.add(DemoMode.COMMAND_NOTIFICATIONS);
+        return commands;
+    }
+
+    @Override
+    public void dispatchDemoCommand(String command, Bundle args) {
+        if (mNotificationIconArea != null) {
+            String visible = args.getString("visible");
+            int vis = "false".equals(visible) ? View.INVISIBLE : View.VISIBLE;
+            mNotificationIconArea.setVisibility(vis);
+        }
+    }
+
+    @Override
+    public void onDemoModeFinished() {
+        if (mNotificationIconArea != null) {
+            mNotificationIconArea.setVisibility(View.VISIBLE);
+        }
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java
index 99cb476..5974a53 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java
@@ -86,6 +86,7 @@
 import com.android.systemui.statusbar.KeyguardIndicationController;
 import com.android.systemui.statusbar.NotificationLockscreenUserManager;
 import com.android.systemui.statusbar.NotificationShelf;
+import com.android.systemui.statusbar.NotificationShelfController;
 import com.android.systemui.statusbar.PulseExpansionHandler;
 import com.android.systemui.statusbar.RemoteInputController;
 import com.android.systemui.statusbar.StatusBarState;
@@ -99,12 +100,14 @@
 import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator;
 import com.android.systemui.statusbar.notification.PropertyAnimator;
 import com.android.systemui.statusbar.notification.ViewGroupFadeHelper;
+import com.android.systemui.statusbar.notification.collection.ListEntry;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 import com.android.systemui.statusbar.notification.row.ActivatableNotificationView;
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
 import com.android.systemui.statusbar.notification.row.ExpandableView;
 import com.android.systemui.statusbar.notification.stack.AnimationProperties;
 import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout;
+import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController;
 import com.android.systemui.statusbar.notification.stack.StackStateAnimator;
 import com.android.systemui.statusbar.phone.LockscreenGestureLogger.LockscreenUiEvent;
 import com.android.systemui.statusbar.phone.dagger.StatusBarComponent;
@@ -174,6 +177,8 @@
     private final ZenModeController mZenModeController;
     private final ConfigurationController mConfigurationController;
     private final FlingAnimationUtils.Builder mFlingAnimationUtilsBuilder;
+    private final NotificationStackScrollLayoutController mNotificationStackScrollLayoutController;
+    private final NotificationIconAreaController mNotificationIconAreaController;
 
     // Cap and total height of Roboto font. Needs to be adjusted when font for the big clock is
     // changed.
@@ -264,7 +269,6 @@
     private KeyguardStatusView mKeyguardStatusView;
     private View mQsNavbarScrim;
     private NotificationsQuickSettingsContainer mNotificationContainerParent;
-    private NotificationStackScrollLayout mNotificationStackScroller;
     private boolean mAnimateNextPositionUpdate;
 
     private int mTrackingPointer;
@@ -454,6 +458,7 @@
 
     private boolean mAnimatingQS;
     private int mOldLayoutDirection;
+    private NotificationShelfController mNotificationShelfController;
 
     private View.AccessibilityDelegate mAccessibilityDelegate = new View.AccessibilityDelegate() {
         @Override
@@ -498,7 +503,9 @@
             MediaHierarchyManager mediaHierarchyManager,
             BiometricUnlockController biometricUnlockController,
             StatusBarKeyguardViewManager statusBarKeyguardViewManager,
-            Provider<KeyguardClockSwitchController> keyguardClockSwitchControllerProvider) {
+            Provider<KeyguardClockSwitchController> keyguardClockSwitchControllerProvider,
+            NotificationStackScrollLayoutController notificationStackScrollLayoutController,
+            NotificationIconAreaController notificationIconAreaController) {
         super(view, falsingManager, dozeLog, keyguardStateController,
                 (SysuiStatusBarStateController) statusBarStateController, vibratorHelper,
                 latencyTracker, flingAnimationUtilsBuilder, statusBarTouchableRegionManager);
@@ -511,6 +518,8 @@
         mMediaHierarchyManager = mediaHierarchyManager;
         mStatusBarKeyguardViewManager = statusBarKeyguardViewManager;
         mKeyguardClockSwitchControllerProvider = keyguardClockSwitchControllerProvider;
+        mNotificationStackScrollLayoutController = notificationStackScrollLayoutController;
+        mNotificationIconAreaController = notificationIconAreaController;
         mView.setWillNotDraw(!DEBUG);
         mInjectionInflationController = injectionInflationController;
         mFalsingManager = falsingManager;
@@ -590,21 +599,26 @@
         keyguardClockSwitchController.setBigClockContainer(mBigClockContainer);
 
         mNotificationContainerParent = mView.findViewById(R.id.notification_container_parent);
-        mNotificationStackScroller = mView.findViewById(R.id.notification_stack_scroller);
-        mNotificationStackScroller.setOnHeightChangedListener(mOnHeightChangedListener);
-        mNotificationStackScroller.setOverscrollTopChangedListener(mOnOverscrollTopChangedListener);
-        mNotificationStackScroller.setOnEmptySpaceClickListener(mOnEmptySpaceClickListener);
-        addTrackingHeadsUpListener(mNotificationStackScroller::setTrackingHeadsUp);
+        NotificationStackScrollLayout stackScrollLayout = mView.findViewById(
+                R.id.notification_stack_scroller);
+        mNotificationStackScrollLayoutController.attach(stackScrollLayout);
+        mNotificationStackScrollLayoutController.setOnHeightChangedListener(
+                mOnHeightChangedListener);
+        mNotificationStackScrollLayoutController.setOverscrollTopChangedListener(
+                mOnOverscrollTopChangedListener);
+        mNotificationStackScrollLayoutController.setOnEmptySpaceClickListener(
+                mOnEmptySpaceClickListener);
+        addTrackingHeadsUpListener(mNotificationStackScrollLayoutController::setTrackingHeadsUp);
         mKeyguardBottomArea = mView.findViewById(R.id.keyguard_bottom_area);
         mQsNavbarScrim = mView.findViewById(R.id.qs_navbar_scrim);
         mLastOrientation = mResources.getConfiguration().orientation;
 
         initBottomArea();
 
-        mWakeUpCoordinator.setStackScroller(mNotificationStackScroller);
+        mWakeUpCoordinator.setStackScroller(mNotificationStackScrollLayoutController);
         mQsFrame = mView.findViewById(R.id.qs_frame);
         mPulseExpansionHandler.setUp(
-                mNotificationStackScroller, mExpansionCallback, mShadeController);
+                mNotificationStackScrollLayoutController, mExpansionCallback, mShadeController);
         mWakeUpCoordinator.addListener(new NotificationWakeUpCoordinator.WakeUpListener() {
             @Override
             public void onFullyHiddenChanged(boolean isFullyHidden) {
@@ -688,11 +702,11 @@
         }
 
         int panelWidth = mResources.getDimensionPixelSize(R.dimen.notification_panel_width);
-        lp = (FrameLayout.LayoutParams) mNotificationStackScroller.getLayoutParams();
+        lp = mNotificationStackScrollLayoutController.getLayoutParams();
         if (lp.width != panelWidth || lp.gravity != panelGravity) {
             lp.width = panelWidth;
             lp.gravity = panelGravity;
-            mNotificationStackScroller.setLayoutParams(lp);
+            mNotificationStackScrollLayoutController.setLayoutParams(lp);
         }
     }
 
@@ -770,7 +784,7 @@
 
     private void setIsFullWidth(boolean isFullWidth) {
         mIsFullWidth = isFullWidth;
-        mNotificationStackScroller.setIsFullWidth(isFullWidth);
+        mNotificationStackScrollLayoutController.setIsFullWidth(isFullWidth);
     }
 
     private void startQsSizeChangeAnimation(int oldHeight, final int newHeight) {
@@ -804,7 +818,7 @@
      * showing.
      */
     private void positionClockAndNotifications() {
-        boolean animate = mNotificationStackScroller.isAddOrRemoveAnimationPending();
+        boolean animate = mNotificationStackScrollLayoutController.isAddOrRemoveAnimationPending();
         boolean animateClock = animate || mAnimateNextPositionUpdate;
         int stackScrollerPadding;
         if (mBarState != StatusBarState.KEYGUARD) {
@@ -814,12 +828,12 @@
             int bottomPadding = Math.max(mIndicationBottomPadding, mAmbientIndicationBottomPadding);
             int clockPreferredY = mKeyguardStatusView.getClockPreferredY(totalHeight);
             boolean bypassEnabled = mKeyguardBypassController.getBypassEnabled();
-            final boolean
-                    hasVisibleNotifications =
-                    !bypassEnabled && mNotificationStackScroller.getVisibleNotificationCount() != 0;
+            final boolean hasVisibleNotifications = !bypassEnabled
+                    && mNotificationStackScrollLayoutController.getVisibleNotificationCount() != 0;
             mKeyguardStatusView.setHasVisibleNotifications(hasVisibleNotifications);
             mClockPositionAlgorithm.setup(mStatusBarMinHeight, totalHeight - bottomPadding,
-                    mNotificationStackScroller.getIntrinsicContentHeight(), getExpandedFraction(),
+                    mNotificationStackScrollLayoutController.getIntrinsicContentHeight(),
+                    getExpandedFraction(),
                     totalHeight, (int) (mKeyguardStatusView.getHeight() - mShelfHeight / 2.0f
                             - mDarkIconSize / 2.0f), clockPreferredY, hasCustomClock(),
                     hasVisibleNotifications, mInterpolatedDarkAmount, mEmptyDragAmount,
@@ -833,7 +847,7 @@
             updateClock();
             stackScrollerPadding = mClockPositionResult.stackScrollerPaddingExpanded;
         }
-        mNotificationStackScroller.setIntrinsicPadding(stackScrollerPadding);
+        mNotificationStackScrollLayoutController.setIntrinsicPadding(stackScrollerPadding);
         mKeyguardBottomArea.setAntiBurnInOffsetX(mClockPositionResult.clockX);
 
         mStackScrollerMeasuringPass++;
@@ -858,36 +872,51 @@
         float minPadding = mClockPositionAlgorithm.getMinStackScrollerPadding();
         int notificationPadding = Math.max(
                 1, mResources.getDimensionPixelSize(R.dimen.notification_divider_height));
-        NotificationShelf shelf = mNotificationStackScroller.getNotificationShelf();
+        NotificationShelf shelf = mNotificationShelfController.getView();
         float
                 shelfSize =
                 shelf.getVisibility() == View.GONE ? 0
                         : shelf.getIntrinsicHeight() + notificationPadding;
         float
                 availableSpace =
-                mNotificationStackScroller.getHeight() - minPadding - shelfSize - Math.max(
-                        mIndicationBottomPadding, mAmbientIndicationBottomPadding)
+                mNotificationStackScrollLayoutController.getHeight() - minPadding - shelfSize
+                        - Math.max(mIndicationBottomPadding, mAmbientIndicationBottomPadding)
                         - mKeyguardStatusView.getLogoutButtonHeight();
         int count = 0;
         ExpandableView previousView = null;
-        for (int i = 0; i < mNotificationStackScroller.getChildCount(); i++) {
-            ExpandableView child = (ExpandableView) mNotificationStackScroller.getChildAt(i);
+        for (int i = 0; i < mNotificationStackScrollLayoutController.getChildCount(); i++) {
+            ExpandableView child = mNotificationStackScrollLayoutController.getChildAt(i);
+            if (!(child instanceof ExpandableNotificationRow)) {
+                continue;
+            }
+            ExpandableNotificationRow row = (ExpandableNotificationRow) child;
+            boolean
+                    suppressedSummary =
+                    mGroupManager != null && mGroupManager.isSummaryOfSuppressedGroup(
+                            row.getEntry().getSbn());
+            if (suppressedSummary) {
+                continue;
+            }
             if (!canShowViewOnLockscreen(child)) {
                 continue;
             }
+            if (row.isRemoved()) {
+                continue;
+            }
             availableSpace -= child.getMinHeight(true /* ignoreTemporaryStates */);
             availableSpace -= count == 0 ? 0 : notificationPadding;
-            availableSpace -= mNotificationStackScroller.calculateGapHeight(previousView, child,
-                    count);
+            availableSpace -= mNotificationStackScrollLayoutController
+                    .calculateGapHeight(previousView, child, count);
             previousView = child;
             if (availableSpace >= 0 && count < maximum) {
                 count++;
             } else if (availableSpace > -shelfSize) {
                 // if we are exactly the last view, then we can show us still!
-                for (int j = i + 1; j < mNotificationStackScroller.getChildCount(); j++) {
-                    ExpandableView view = (ExpandableView) mNotificationStackScroller.getChildAt(j);
-                    if (view instanceof ExpandableNotificationRow &&
-                            canShowViewOnLockscreen(view)) {
+                int childCount = mNotificationStackScrollLayoutController.getChildCount();
+                for (int j = i + 1; j < childCount; j++) {
+                    ExpandableView view = mNotificationStackScrollLayoutController.getChildAt(j);
+                    if (view instanceof ExpandableNotificationRow
+                            && canShowViewOnLockscreen(view)) {
                         return count;
                     }
                 }
@@ -952,7 +981,7 @@
     }
 
     public void animateToFullShade(long delay) {
-        mNotificationStackScroller.goToFullShade(delay);
+        mNotificationStackScrollLayoutController.goToFullShade(delay);
         mView.requestLayout();
         mAnimateNextPositionUpdate = true;
     }
@@ -978,9 +1007,9 @@
         } else {
             closeQs();
         }
-        mNotificationStackScroller.setOverScrollAmount(0f, true /* onTop */, animate,
+        mNotificationStackScrollLayoutController.setOverScrollAmount(0f, true /* onTop */, animate,
                 !animate /* cancelAnimators */);
-        mNotificationStackScroller.resetScrollPosition();
+        mNotificationStackScrollLayoutController.resetScrollPosition();
     }
 
     @Override
@@ -991,7 +1020,7 @@
 
         if (mQsExpanded) {
             mQsExpandImmediate = true;
-            mNotificationStackScroller.setShouldShowShelfOnly(true);
+            mNotificationStackScrollLayoutController.setShouldShowShelfOnly(true);
         }
         super.collapse(delayed, speedUpFactor);
     }
@@ -1027,7 +1056,7 @@
     public void expandWithQs() {
         if (mQsExpansionEnabled) {
             mQsExpandImmediate = true;
-            mNotificationStackScroller.setShouldShowShelfOnly(true);
+            mNotificationStackScrollLayoutController.setShouldShowShelfOnly(true);
         }
         if (isFullyCollapsed()) {
             expand(true /* animate */);
@@ -1088,7 +1117,7 @@
                     onQsExpansionStarted();
                     mInitialHeightOnTouch = mQsExpansionHeight;
                     mQsTracking = true;
-                    mNotificationStackScroller.cancelLongPress();
+                    mNotificationStackScrollLayoutController.cancelLongPress();
                 }
                 break;
             case MotionEvent.ACTION_POINTER_UP:
@@ -1124,7 +1153,7 @@
                     mInitialHeightOnTouch = mQsExpansionHeight;
                     mInitialTouchY = y;
                     mInitialTouchX = x;
-                    mNotificationStackScroller.cancelLongPress();
+                    mNotificationStackScrollLayoutController.cancelLongPress();
                     return true;
                 }
                 break;
@@ -1144,9 +1173,11 @@
 
     @Override
     protected boolean isInContentBounds(float x, float y) {
-        float stackScrollerX = mNotificationStackScroller.getX();
-        return !mNotificationStackScroller.isBelowLastNotification(x - stackScrollerX, y)
-                && stackScrollerX < x && x < stackScrollerX + mNotificationStackScroller.getWidth();
+        float stackScrollerX = mNotificationStackScrollLayoutController.getX();
+        return !mNotificationStackScrollLayoutController
+                .isBelowLastNotification(x - stackScrollerX, y)
+                && stackScrollerX < x
+                && x < stackScrollerX + mNotificationStackScrollLayoutController.getWidth();
     }
 
     private void initDownStates(MotionEvent event) {
@@ -1254,7 +1285,7 @@
 
     @Override
     protected float getOpeningHeight() {
-        return mNotificationStackScroller.getOpeningHeight();
+        return mNotificationStackScrollLayoutController.getOpeningHeight();
     }
 
 
@@ -1290,7 +1321,7 @@
                 < mStatusBarMinHeight) {
             mMetricsLogger.count(COUNTER_PANEL_OPEN_QS, 1);
             mQsExpandImmediate = true;
-            mNotificationStackScroller.setShouldShowShelfOnly(true);
+            mNotificationStackScrollLayoutController.setShouldShowShelfOnly(true);
             requestPanelHeightUpdate();
 
             // Normally, we start listening when the panel is expanded, but here we need to start
@@ -1302,7 +1333,7 @@
 
     private boolean isInQsArea(float x, float y) {
         return (x >= mQsFrame.getX() && x <= mQsFrame.getX() + mQsFrame.getWidth()) && (
-                y <= mNotificationStackScroller.getBottomMostNotificationBottom()
+                y <= mNotificationStackScrollLayoutController.getBottomMostNotificationBottom()
                         || y <= mQs.getView().getY() + mQs.getView().getHeight());
     }
 
@@ -1492,7 +1523,7 @@
         float height = mQsExpansionHeight - overscrollAmount;
         setQsExpansion(height);
         requestPanelHeightUpdate();
-        mNotificationStackScroller.checkSnoozeLeavebehind();
+        mNotificationStackScrollLayoutController.checkSnoozeLeavebehind();
 
         // When expanding QS, let's authenticate the user if possible,
         // this will speed up notification actions.
@@ -1665,8 +1696,8 @@
     }
 
     private void updateQsState() {
-        mNotificationStackScroller.setQsExpanded(mQsExpanded);
-        mNotificationStackScroller.setScrollingEnabled(
+        mNotificationStackScrollLayoutController.setQsExpanded(mQsExpanded);
+        mNotificationStackScrollLayoutController.setScrollingEnabled(
                 mBarState != StatusBarState.KEYGUARD && (!mQsExpanded
                         || mQsExpansionFromOverscroll));
         updateEmptyShadeView();
@@ -1726,7 +1757,7 @@
         float qsExpansionFraction = getQsExpansionFraction();
         mQs.setQsExpansion(qsExpansionFraction, getHeaderTranslation());
         mMediaHierarchyManager.setQsExpansion(qsExpansionFraction);
-        mNotificationStackScroller.setQsExpansionFraction(qsExpansionFraction);
+        mNotificationStackScrollLayoutController.setQsExpansionFraction(qsExpansionFraction);
     }
 
     private String determineAccessibilityPaneTitle() {
@@ -1785,18 +1816,18 @@
             return mClockPositionResult.stackScrollerPadding;
         }
         int collapsedPosition = mHeadsUpInset;
-        if (!mNotificationStackScroller.isPulseExpanding()) {
+        if (!mNotificationStackScrollLayoutController.isPulseExpanding()) {
             return collapsedPosition;
         } else {
             int expandedPosition = mClockPositionResult.stackScrollerPadding;
             return (int) MathUtils.lerp(collapsedPosition, expandedPosition,
-                    mNotificationStackScroller.calculateAppearFractionBypass());
+                    mNotificationStackScrollLayoutController.calculateAppearFractionBypass());
         }
     }
 
 
     protected void requestScrollerTopPaddingUpdate(boolean animate) {
-        mNotificationStackScroller.updateTopPadding(calculateQsTopPadding(), animate);
+        mNotificationStackScrollLayoutController.updateTopPadding(calculateQsTopPadding(), animate);
         if (mKeyguardShowing && mKeyguardBypassController.getBypassEnabled()) {
             // update the position of the header
             updateQsExpansion();
@@ -1808,7 +1839,7 @@
         if (mQs != null) {
             mQs.setShowCollapsedOnKeyguard(
                     mKeyguardShowing && mKeyguardBypassController.getBypassEnabled()
-                            && mNotificationStackScroller.isPulseExpanding());
+                            && mNotificationStackScrollLayoutController.isPulseExpanding());
         }
     }
 
@@ -1904,7 +1935,7 @@
             public void onAnimationEnd(Animator animation) {
                 mAnimatingQS = false;
                 notifyExpandingFinished();
-                mNotificationStackScroller.resetCheckSnoozeLeavebehind();
+                mNotificationStackScrollLayoutController.resetCheckSnoozeLeavebehind();
                 mQsExpansionAnimator = null;
                 if (onFinishRunnable != null) {
                     onFinishRunnable.run();
@@ -1943,8 +1974,8 @@
     protected boolean canCollapsePanelOnTouch() {
         if (!isInSettings()) {
             return mBarState == StatusBarState.KEYGUARD
-                    || mNotificationStackScroller.isScrolledToBottom()
-                    || mIsPanelCollapseOnQQS;
+                    || mIsPanelCollapseOnQQS
+                    || mNotificationStackScrollLayoutController.isScrolledToBottom();
         } else {
             return true;
         }
@@ -1962,7 +1993,7 @@
     private int getMaxPanelHeightNonBypass() {
         int min = mStatusBarMinHeight;
         if (!(mBarState == StatusBarState.KEYGUARD)
-                && mNotificationStackScroller.getNotGoneChildCount() == 0) {
+                && mNotificationStackScrollLayoutController.getNotGoneChildCount() == 0) {
             int minHeight = (int) (mQsMinExpansionHeight + getOverExpansionAmount());
             min = Math.max(min, minHeight);
         }
@@ -1988,7 +2019,7 @@
         int position =
                 mClockPositionAlgorithm.getExpandedClockPosition()
                         + mKeyguardStatusView.getHeight();
-        if (mNotificationStackScroller.getVisibleNotificationCount() != 0) {
+        if (mNotificationStackScrollLayoutController.getVisibleNotificationCount() != 0) {
             position += mShelfHeight / 2.0f + mDarkIconSize / 2.0f;
         }
         return position;
@@ -2027,8 +2058,8 @@
                 // minimum QS expansion + minStackHeight
                 float
                         panelHeightQsCollapsed =
-                        mNotificationStackScroller.getIntrinsicPadding()
-                                + mNotificationStackScroller.getLayoutMinHeight();
+                        mNotificationStackScrollLayoutController.getIntrinsicPadding()
+                                + mNotificationStackScrollLayoutController.getLayoutMinHeight();
                 float panelHeightQsExpanded = calculatePanelHeightQsExpanded();
                 t =
                         (expandedHeight - panelHeightQsCollapsed) / (panelHeightQsExpanded
@@ -2060,16 +2091,16 @@
     }
 
     private int calculatePanelHeightShade() {
-        int emptyBottomMargin = mNotificationStackScroller.getEmptyBottomMargin();
-        int maxHeight = mNotificationStackScroller.getHeight() - emptyBottomMargin;
-        maxHeight += mNotificationStackScroller.getTopPaddingOverflow();
+        int emptyBottomMargin = mNotificationStackScrollLayoutController.getEmptyBottomMargin();
+        int maxHeight = mNotificationStackScrollLayoutController.getHeight() - emptyBottomMargin;
+        maxHeight += mNotificationStackScrollLayoutController.getTopPaddingOverflow();
 
         if (mBarState == StatusBarState.KEYGUARD) {
             int
                     minKeyguardPanelBottom =
                     mClockPositionAlgorithm.getExpandedClockPosition()
                             + mKeyguardStatusView.getHeight()
-                            + mNotificationStackScroller.getIntrinsicContentHeight();
+                            + mNotificationStackScrollLayoutController.getIntrinsicContentHeight();
             return Math.max(maxHeight, minKeyguardPanelBottom);
         } else {
             return maxHeight;
@@ -2079,15 +2110,16 @@
     private int calculatePanelHeightQsExpanded() {
         float
                 notificationHeight =
-                mNotificationStackScroller.getHeight()
-                        - mNotificationStackScroller.getEmptyBottomMargin()
-                        - mNotificationStackScroller.getTopPadding();
+                mNotificationStackScrollLayoutController.getHeight()
+                        - mNotificationStackScrollLayoutController.getEmptyBottomMargin()
+                        - mNotificationStackScrollLayoutController.getTopPadding();
 
         // When only empty shade view is visible in QS collapsed state, simulate that we would have
         // it in expanded QS state as well so we don't run into troubles when fading the view in/out
         // and expanding/collapsing the whole panel from/to quick settings.
-        if (mNotificationStackScroller.getNotGoneChildCount() == 0 && mShowEmptyShadeView) {
-            notificationHeight = mNotificationStackScroller.getEmptyShadeViewHeight();
+        if (mNotificationStackScrollLayoutController.getNotGoneChildCount() == 0
+                && mShowEmptyShadeView) {
+            notificationHeight = mNotificationStackScrollLayoutController.getEmptyShadeViewHeight();
         }
         int maxQsHeight = mQsMaxExpansionHeight;
 
@@ -2102,12 +2134,13 @@
         float totalHeight = Math.max(maxQsHeight,
                 mBarState == StatusBarState.KEYGUARD ? mClockPositionResult.stackScrollerPadding
                         : 0) + notificationHeight
-                + mNotificationStackScroller.getTopPaddingOverflow();
-        if (totalHeight > mNotificationStackScroller.getHeight()) {
+                + mNotificationStackScrollLayoutController.getTopPaddingOverflow();
+        if (totalHeight > mNotificationStackScrollLayoutController.getHeight()) {
             float
                     fullyCollapsedHeight =
-                    maxQsHeight + mNotificationStackScroller.getLayoutMinHeight();
-            totalHeight = Math.max(fullyCollapsedHeight, mNotificationStackScroller.getHeight());
+                    maxQsHeight + mNotificationStackScrollLayoutController.getLayoutMinHeight();
+            totalHeight = Math.max(fullyCollapsedHeight,
+                    mNotificationStackScrollLayoutController.getHeight());
         }
         return (int) totalHeight;
     }
@@ -2122,7 +2155,7 @@
                 && !mKeyguardBypassController.getBypassEnabled()) {
             alpha *= mClockPositionResult.clockAlpha;
         }
-        mNotificationStackScroller.setAlpha(alpha);
+        mNotificationStackScrollLayoutController.setAlpha(alpha);
     }
 
     private float getFadeoutAlpha() {
@@ -2138,7 +2171,8 @@
 
     @Override
     protected float getOverExpansionAmount() {
-        float result = mNotificationStackScroller.getCurrentOverScrollAmount(true /* top */);
+        float result = mNotificationStackScrollLayoutController
+                .getCurrentOverScrollAmount(true /* top */);
         if (isNaN(result)) {
             Log.wtf(TAG, "OverExpansionAmount is NaN!");
         }
@@ -2148,7 +2182,8 @@
 
     @Override
     protected float getOverExpansionPixels() {
-        return mNotificationStackScroller.getCurrentOverScrolledPixels(true /* top */);
+        return mNotificationStackScrollLayoutController
+                .getCurrentOverScrolledPixels(true /* top */);
     }
 
     /**
@@ -2165,17 +2200,19 @@
         if (mBarState == StatusBarState.KEYGUARD && !mKeyguardBypassController.getBypassEnabled()) {
             return -mQs.getQsMinExpansionHeight();
         }
-        float appearAmount = mNotificationStackScroller.calculateAppearFraction(mExpandedHeight);
+        float appearAmount = mNotificationStackScrollLayoutController
+                .calculateAppearFraction(mExpandedHeight);
         float startHeight = -mQsExpansionHeight;
         if (mKeyguardBypassController.getBypassEnabled() && isOnKeyguard()
-                && mNotificationStackScroller.isPulseExpanding()) {
+                && mNotificationStackScrollLayoutController.isPulseExpanding()) {
             if (!mPulseExpansionHandler.isExpanding()
                     && !mPulseExpansionHandler.getLeavingLockscreen()) {
                 // If we aborted the expansion we need to make sure the header doesn't reappear
                 // again after the header has animated away
                 appearAmount = 0;
             } else {
-                appearAmount = mNotificationStackScroller.calculateAppearFractionBypass();
+                appearAmount = mNotificationStackScrollLayoutController
+                        .calculateAppearFractionBypass();
             }
             startHeight = -mQs.getQsMinExpansionHeight();
         }
@@ -2264,7 +2301,7 @@
     @Override
     protected void onExpandingStarted() {
         super.onExpandingStarted();
-        mNotificationStackScroller.onExpansionStarted();
+        mNotificationStackScrollLayoutController.onExpansionStarted();
         mIsExpanding = true;
         mQsExpandedWhenExpandingStarted = mQsFullyExpanded;
         mMediaHierarchyManager.setCollapsingShadeFromQS(mQsExpandedWhenExpandingStarted &&
@@ -2282,7 +2319,7 @@
     @Override
     protected void onExpandingFinished() {
         super.onExpandingFinished();
-        mNotificationStackScroller.onExpansionStopped();
+        mNotificationStackScrollLayoutController.onExpansionStopped();
         mHeadsUpManager.onExpandingFinished();
         mConversationNotificationManager.onNotificationPanelExpandStateChanged(isFullyCollapsed());
         mIsExpanding = false;
@@ -2308,7 +2345,7 @@
             setListening(true);
         }
         mQsExpandImmediate = false;
-        mNotificationStackScroller.setShouldShowShelfOnly(false);
+        mNotificationStackScrollLayoutController.setShouldShowShelfOnly(false);
         mTwoFingerQsExpandPossible = false;
         notifyListenersTrackingHeadsUp(null);
         mExpandingFromHeadsUp = false;
@@ -2340,15 +2377,16 @@
             return;
         }
         if (mBarState != StatusBarState.KEYGUARD) {
-            mNotificationStackScroller.setOnHeightChangedListener(null);
+            mNotificationStackScrollLayoutController.setOnHeightChangedListener(null);
             if (isPixels) {
-                mNotificationStackScroller.setOverScrolledPixels(overExpansion, true /* onTop */,
-                        false /* animate */);
+                mNotificationStackScrollLayoutController.setOverScrolledPixels(
+                        overExpansion, true /* onTop */, false /* animate */);
             } else {
-                mNotificationStackScroller.setOverScrollAmount(overExpansion, true /* onTop */,
-                        false /* animate */);
+                mNotificationStackScrollLayoutController.setOverScrollAmount(
+                        overExpansion, true /* onTop */, false /* animate */);
             }
-            mNotificationStackScroller.setOnHeightChangedListener(mOnHeightChangedListener);
+            mNotificationStackScrollLayoutController
+                    .setOnHeightChangedListener(mOnHeightChangedListener);
         }
     }
 
@@ -2358,12 +2396,12 @@
         super.onTrackingStarted();
         if (mQsFullyExpanded) {
             mQsExpandImmediate = true;
-            mNotificationStackScroller.setShouldShowShelfOnly(true);
+            mNotificationStackScrollLayoutController.setShouldShowShelfOnly(true);
         }
         if (mBarState == StatusBarState.KEYGUARD || mBarState == StatusBarState.SHADE_LOCKED) {
             mAffordanceHelper.animateHideLeftRightIcon();
         }
-        mNotificationStackScroller.onPanelTrackingStarted();
+        mNotificationStackScrollLayoutController.onPanelTrackingStarted();
     }
 
     @Override
@@ -2371,10 +2409,10 @@
         mFalsingManager.onTrackingStopped();
         super.onTrackingStopped(expand);
         if (expand) {
-            mNotificationStackScroller.setOverScrolledPixels(0.0f, true /* onTop */,
+            mNotificationStackScrollLayoutController.setOverScrolledPixels(0.0f, true /* onTop */,
                     true /* animate */);
         }
-        mNotificationStackScroller.onPanelTrackingStopped();
+        mNotificationStackScrollLayoutController.onPanelTrackingStopped();
         if (expand && (mBarState == StatusBarState.KEYGUARD
                 || mBarState == StatusBarState.SHADE_LOCKED)) {
             if (!mHintAnimationRunning) {
@@ -2384,7 +2422,8 @@
     }
 
     private void updateMaxHeadsUpTranslation() {
-        mNotificationStackScroller.setHeadsUpBoundaries(getHeight(), mNavigationBarBottomHeight);
+        mNotificationStackScrollLayoutController.setHeadsUpBoundaries(
+                getHeight(), mNavigationBarBottomHeight);
     }
 
     @Override
@@ -2400,19 +2439,19 @@
     @Override
     protected void onUnlockHintFinished() {
         super.onUnlockHintFinished();
-        mNotificationStackScroller.setUnlockHintRunning(false);
+        mNotificationStackScrollLayoutController.setUnlockHintRunning(false);
     }
 
     @Override
     protected void onUnlockHintStarted() {
         super.onUnlockHintStarted();
-        mNotificationStackScroller.setUnlockHintRunning(true);
+        mNotificationStackScrollLayoutController.setUnlockHintRunning(true);
     }
 
     @Override
     protected float getPeekHeight() {
-        if (mNotificationStackScroller.getNotGoneChildCount() > 0) {
-            return mNotificationStackScroller.getPeekHeight();
+        if (mNotificationStackScrollLayoutController.getNotGoneChildCount() > 0) {
+            return mNotificationStackScrollLayoutController.getPeekHeight();
         } else {
             return mQsMinExpansionHeight;
         }
@@ -2426,7 +2465,8 @@
         }
         // Let's make sure we're not appearing but the animation will end below the appear.
         // Otherwise quick settings would jump at the end of the animation.
-        float fraction = mNotificationStackScroller.calculateAppearFraction(targetHeight);
+        float fraction = mNotificationStackScrollLayoutController
+                .calculateAppearFraction(targetHeight);
         return fraction >= 1.0f;
     }
 
@@ -2438,18 +2478,19 @@
 
     @Override
     protected boolean fullyExpandedClearAllVisible() {
-        return mNotificationStackScroller.isFooterViewNotGone()
-                && mNotificationStackScroller.isScrolledToBottom() && !mQsExpandImmediate;
+        return mNotificationStackScrollLayoutController.isFooterViewNotGone()
+                && mNotificationStackScrollLayoutController.isScrolledToBottom()
+                && !mQsExpandImmediate;
     }
 
     @Override
     protected boolean isClearAllVisible() {
-        return mNotificationStackScroller.isFooterViewContentVisible();
+        return mNotificationStackScrollLayoutController.isFooterViewContentVisible();
     }
 
     @Override
     protected int getClearAllHeightWithPadding() {
-        return mNotificationStackScroller.getFooterViewHeightWithPadding();
+        return mNotificationStackScrollLayoutController.getFooterViewHeightWithPadding();
     }
 
     @Override
@@ -2500,7 +2541,8 @@
 
     private void updateEmptyShadeView() {
         // Hide "No notifications" in QS.
-        mNotificationStackScroller.updateEmptyShadeView(mShowEmptyShadeView && !mQsExpanded);
+        mNotificationStackScrollLayoutController.updateEmptyShadeView(
+                mShowEmptyShadeView && !mQsExpanded);
     }
 
     public void setQsScrimEnabled(boolean qsScrimEnabled) {
@@ -2591,7 +2633,7 @@
 
     public void setHeadsUpAnimatingAway(boolean headsUpAnimatingAway) {
         mHeadsUpAnimatingAway = headsUpAnimatingAway;
-        mNotificationStackScroller.setHeadsUpAnimatingAway(headsUpAnimatingAway);
+        mNotificationStackScrollLayoutController.setHeadsUpAnimatingAway(headsUpAnimatingAway);
         updateHeadsUpVisibility();
     }
 
@@ -2603,7 +2645,7 @@
     public void setHeadsUpManager(HeadsUpManagerPhone headsUpManager) {
         super.setHeadsUpManager(headsUpManager);
         mHeadsUpTouchHelper = new HeadsUpTouchHelper(headsUpManager,
-                mNotificationStackScroller.getHeadsUpCallback(),
+                mNotificationStackScrollLayoutController.getHeadsUpCallback(),
                 NotificationPanelViewController.this);
     }
 
@@ -2620,11 +2662,12 @@
         super.onClosingFinished();
         resetHorizontalPanelPosition();
         setClosingWithAlphaFadeout(false);
+        mMediaHierarchyManager.closeGuts();
     }
 
     private void setClosingWithAlphaFadeout(boolean closing) {
         mClosingWithAlphaFadeOut = closing;
-        mNotificationStackScroller.forceNoOverlappingRendering(closing);
+        mNotificationStackScrollLayoutController.forceNoOverlappingRendering(closing);
     }
 
     /**
@@ -2634,22 +2677,24 @@
      * @param x the x-coordinate the touch event
      */
     protected void updateVerticalPanelPosition(float x) {
-        if (mNotificationStackScroller.getWidth() * 1.75f > mView.getWidth()) {
+        if (mNotificationStackScrollLayoutController.getWidth() * 1.75f > mView.getWidth()) {
             resetHorizontalPanelPosition();
             return;
         }
-        float leftMost = mPositionMinSideMargin + mNotificationStackScroller.getWidth() / 2;
+        float leftMost = mPositionMinSideMargin
+                + mNotificationStackScrollLayoutController.getWidth() / 2;
         float
                 rightMost =
                 mView.getWidth() - mPositionMinSideMargin
-                        - mNotificationStackScroller.getWidth() / 2;
-        if (Math.abs(x - mView.getWidth() / 2) < mNotificationStackScroller.getWidth() / 4) {
+                        - mNotificationStackScrollLayoutController.getWidth() / 2;
+        if (Math.abs(x - mView.getWidth() / 2)
+                < mNotificationStackScrollLayoutController.getWidth() / 4) {
             x = mView.getWidth() / 2;
         }
         x = Math.min(rightMost, Math.max(leftMost, x));
         float
-                center =
-                mNotificationStackScroller.getLeft() + mNotificationStackScroller.getWidth() / 2;
+                center = mNotificationStackScrollLayoutController.getLeft()
+                + mNotificationStackScrollLayoutController.getWidth() / 2;
         setHorizontalPanelTranslation(x - center);
     }
 
@@ -2658,7 +2703,7 @@
     }
 
     protected void setHorizontalPanelTranslation(float translation) {
-        mNotificationStackScroller.setTranslationX(translation);
+        mNotificationStackScrollLayoutController.setTranslationX(translation);
         mQsFrame.setTranslationX(translation);
         int size = mVerticalTranslationListener.size();
         for (int i = 0; i < size; i++) {
@@ -2668,13 +2713,14 @@
 
     protected void updateExpandedHeight(float expandedHeight) {
         if (mTracking) {
-            mNotificationStackScroller.setExpandingVelocity(getCurrentExpandVelocity());
+            mNotificationStackScrollLayoutController
+                    .setExpandingVelocity(getCurrentExpandVelocity());
         }
         if (mKeyguardBypassController.getBypassEnabled() && isOnKeyguard()) {
             // The expandedHeight is always the full panel Height when bypassing
             expandedHeight = getMaxPanelHeightNonBypass();
         }
-        mNotificationStackScroller.setExpandedHeight(expandedHeight);
+        mNotificationStackScrollLayoutController.setExpandedHeight(expandedHeight);
         updateKeyguardBottomAreaAlpha();
         updateBigClockAlpha();
         updateStatusBarIcons();
@@ -2834,7 +2880,7 @@
                             mHeightListener.onQsHeightChanged();
                         }
                     });
-            mNotificationStackScroller.setQsContainer((ViewGroup) mQs.getView());
+            mNotificationStackScrollLayoutController.setQsContainer((ViewGroup) mQs.getView());
             if (mQs instanceof QSFragment) {
                 mKeyguardStatusBar.setQSPanel(((QSFragment) mQs).getQsPanel());
             }
@@ -2858,7 +2904,7 @@
         if (disabled && mAffordanceHelper.isSwipingInProgress() && !mIsLaunchTransitionRunning) {
             mAffordanceHelper.reset(false /* animate */);
         }
-        mNotificationStackScroller.setAnimationsEnabled(!disabled);
+        mNotificationStackScrollLayoutController.setAnimationsEnabled(!disabled);
     }
 
     /**
@@ -2872,7 +2918,7 @@
         if (dozing == mDozing) return;
         mView.setDozing(dozing);
         mDozing = dozing;
-        mNotificationStackScroller.setDozing(mDozing, animate, wakeUpTouchLocation);
+        mNotificationStackScrollLayoutController.setDozing(mDozing, animate, wakeUpTouchLocation);
         mKeyguardBottomArea.setDozing(mDozing, animate);
 
         if (dozing) {
@@ -2900,7 +2946,7 @@
         if (!mPulsing && !mDozing) {
             mAnimateNextPositionUpdate = false;
         }
-        mNotificationStackScroller.setPulsing(pulsing, animatePulse);
+        mNotificationStackScrollLayoutController.setPulsing(pulsing, animatePulse);
         mKeyguardStatusView.setPulsing(pulsing);
     }
 
@@ -3005,7 +3051,7 @@
     }
 
     public boolean hasActiveClearableNotifications() {
-        return mNotificationStackScroller.hasActiveClearableNotifications(ROWS_ALL);
+        return mNotificationStackScrollLayoutController.hasActiveClearableNotifications(ROWS_ALL);
     }
 
     private void updateShowEmptyShadeView() {
@@ -3016,54 +3062,71 @@
     }
 
     public RemoteInputController.Delegate createRemoteInputDelegate() {
-        return mNotificationStackScroller.createDelegate();
+        return mNotificationStackScrollLayoutController.createDelegate();
     }
 
     void updateNotificationViews(String reason) {
-        mNotificationStackScroller.updateSectionBoundaries(reason);
-        mNotificationStackScroller.updateSpeedBumpIndex();
-        mNotificationStackScroller.updateFooter();
+        mNotificationStackScrollLayoutController.updateSectionBoundaries(reason);
+        mNotificationStackScrollLayoutController.updateSpeedBumpIndex();
+        mNotificationStackScrollLayoutController.updateFooter();
         updateShowEmptyShadeView();
-        mNotificationStackScroller.updateIconAreaViews();
+        mNotificationIconAreaController.updateNotificationIcons(createVisibleEntriesList());
+    }
+
+    private List<ListEntry> createVisibleEntriesList() {
+        List<ListEntry> entries = new ArrayList<>(
+                mNotificationStackScrollLayoutController.getChildCount());
+        for (int i = 0; i < mNotificationStackScrollLayoutController.getChildCount(); i++) {
+            View view = mNotificationStackScrollLayoutController.getChildAt(i);
+            if (view instanceof ExpandableNotificationRow) {
+                entries.add(((ExpandableNotificationRow) view).getEntry());
+            }
+        }
+        return entries;
     }
 
     public void onUpdateRowStates() {
-        mNotificationStackScroller.onUpdateRowStates();
+        mNotificationStackScrollLayoutController.onUpdateRowStates();
     }
 
     public boolean hasPulsingNotifications() {
-        return mNotificationStackScroller.hasPulsingNotifications();
+        return mNotificationStackScrollLayoutController
+                .getNotificationListContainer().hasPulsingNotifications();
     }
 
     public ActivatableNotificationView getActivatedChild() {
-        return mNotificationStackScroller.getActivatedChild();
+        return mNotificationStackScrollLayoutController.getActivatedChild();
     }
 
     public void setActivatedChild(ActivatableNotificationView o) {
-        mNotificationStackScroller.setActivatedChild(o);
+        mNotificationStackScrollLayoutController.setActivatedChild(o);
     }
 
     public void runAfterAnimationFinished(Runnable r) {
-        mNotificationStackScroller.runAfterAnimationFinished(r);
+        mNotificationStackScrollLayoutController.runAfterAnimationFinished(r);
     }
 
     public void setScrollingEnabled(boolean b) {
-        mNotificationStackScroller.setScrollingEnabled(b);
+        mNotificationStackScrollLayoutController.setScrollingEnabled(b);
     }
 
-    public void initDependencies(StatusBar statusBar, NotificationGroupManager groupManager,
-            NotificationShelf notificationShelf,
-            NotificationIconAreaController notificationIconAreaController,
+    /**
+     * Initialize objects instead of injecting to avoid circular dependencies.
+     */
+    public void initDependencies(
+            StatusBar statusBar,
+            NotificationGroupManager groupManager,
+            NotificationShelfController notificationShelfController,
             ScrimController scrimController) {
         setStatusBar(statusBar);
         setGroupManager(mGroupManager);
-        mNotificationStackScroller.setNotificationPanelController(this);
-        mNotificationStackScroller.setIconAreaController(notificationIconAreaController);
-        mNotificationStackScroller.setStatusBar(statusBar);
-        mNotificationStackScroller.setGroupManager(groupManager);
-        mNotificationStackScroller.setShelf(notificationShelf);
-        mNotificationStackScroller.setScrimController(scrimController);
+        mNotificationStackScrollLayoutController.setNotificationPanelController(this);
+        mNotificationStackScrollLayoutController.setStatusBar(statusBar);
+        mNotificationStackScrollLayoutController.setGroupManager(groupManager);
+        mNotificationStackScrollLayoutController.setShelfController(notificationShelfController);
+        mNotificationStackScrollLayoutController.setScrimController(scrimController);
         updateShowEmptyShadeView();
+        mNotificationShelfController = notificationShelfController;
     }
 
     public void showTransientIndication(int id) {
@@ -3215,6 +3278,10 @@
         return new OnConfigurationChangedListener();
     }
 
+    public NotificationStackScrollLayoutController getNotificationStackScrollLayoutController() {
+        return mNotificationStackScrollLayoutController;
+    }
+
     private class OnHeightChangedListener implements ExpandableView.OnHeightChangedListener {
         @Override
         public void onHeightChanged(ExpandableView view, boolean needsAnimation) {
@@ -3227,7 +3294,8 @@
             if (needsAnimation && mInterpolatedDarkAmount == 0) {
                 mAnimateNextPositionUpdate = true;
             }
-            ExpandableView firstChildNotGone = mNotificationStackScroller.getFirstChildNotGone();
+            ExpandableView firstChildNotGone =
+                    mNotificationStackScrollLayoutController.getFirstChildNotGone();
             ExpandableNotificationRow
                     firstRow =
                     firstChildNotGone instanceof ExpandableNotificationRow
@@ -3453,13 +3521,13 @@
     private class MyOnHeadsUpChangedListener implements OnHeadsUpChangedListener {
         @Override
         public void onHeadsUpPinnedModeChanged(final boolean inPinnedMode) {
-            mNotificationStackScroller.setInHeadsUpPinnedMode(inPinnedMode);
+            mNotificationStackScrollLayoutController.setInHeadsUpPinnedMode(inPinnedMode);
             if (inPinnedMode) {
                 mHeadsUpExistenceChangedRunnable.run();
                 updateNotificationTranslucency();
             } else {
                 setHeadsUpAnimatingAway(true);
-                mNotificationStackScroller.runAfterAnimationFinished(
+                mNotificationStackScrollLayoutController.runAfterAnimationFinished(
                         mHeadsUpExistenceChangedRunnable);
             }
             updateGestureExclusionRect();
@@ -3471,8 +3539,8 @@
         @Override
         public void onHeadsUpPinned(NotificationEntry entry) {
             if (!isOnKeyguard()) {
-                mNotificationStackScroller.generateHeadsUpAnimation(entry.getHeadsUpAnimationView(),
-                        true);
+                mNotificationStackScrollLayoutController.generateHeadsUpAnimation(
+                        entry.getHeadsUpAnimationView(), true);
             }
         }
 
@@ -3484,7 +3552,7 @@
             // notification
             // will stick to the top without any interaction.
             if (isFullyCollapsed() && entry.isRowHeadsUp() && !isOnKeyguard()) {
-                mNotificationStackScroller.generateHeadsUpAnimation(
+                mNotificationStackScrollLayoutController.generateHeadsUpAnimation(
                         entry.getHeadsUpAnimationView(), false);
                 entry.setHeadsUpIsVisible();
             }
@@ -3492,7 +3560,7 @@
 
         @Override
         public void onHeadsUpStateChanged(NotificationEntry entry, boolean isHeadsUp) {
-            mNotificationStackScroller.generateHeadsUpAnimation(entry, isHeadsUp);
+            mNotificationStackScrollLayoutController.generateHeadsUpAnimation(entry, isHeadsUp);
         }
     }
 
@@ -3507,7 +3575,7 @@
             if (mAccessibilityManager.isEnabled()) {
                 mView.setAccessibilityPaneTitle(determineAccessibilityPaneTitle());
             }
-            mNotificationStackScroller.setMaxTopPadding(
+            mNotificationStackScrollLayoutController.setMaxTopPadding(
                     mQsMaxExpansionHeight + mQsNotificationTopPadding);
         }
     }
@@ -3569,7 +3637,7 @@
             } else if (oldState == StatusBarState.SHADE_LOCKED
                     && statusBarState == StatusBarState.KEYGUARD) {
                 animateKeyguardStatusBarIn(StackStateAnimator.ANIMATION_DURATION_STANDARD);
-                mNotificationStackScroller.resetScrollPosition();
+                mNotificationStackScrollLayoutController.resetScrollPosition();
                 // Only animate header if the header is visible. If not, it will partially
                 // animate out
                 // the top of QS
@@ -3645,7 +3713,7 @@
                 int oldTop, int oldRight, int oldBottom) {
             DejankUtils.startDetectingBlockingIpcs("NVP#onLayout");
             super.onLayoutChange(v, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom);
-            setIsFullWidth(mNotificationStackScroller.getWidth() == mView.getWidth());
+            setIsFullWidth(mNotificationStackScrollLayoutController.getWidth() == mView.getWidth());
 
             // Update Clock Pivot
             mKeyguardStatusView.setPivotX(mView.getWidth() / 2);
@@ -3661,7 +3729,7 @@
                     mQsExpansionHeight = mQsMinExpansionHeight;
                 }
                 mQsMaxExpansionHeight = mQs.getDesiredHeight();
-                mNotificationStackScroller.setMaxTopPadding(
+                mNotificationStackScrollLayoutController.setMaxTopPadding(
                         mQsMaxExpansionHeight + mQsNotificationTopPadding);
             }
             positionClockAndNotifications();
@@ -3723,7 +3791,7 @@
                     0, calculateQsTopPadding(), mView.getWidth(), calculateQsTopPadding(), p);
             p.setColor(Color.CYAN);
             canvas.drawLine(0, mClockPositionResult.stackScrollerPadding, mView.getWidth(),
-                    mNotificationStackScroller.getTopPadding(), p);
+                    mNotificationStackScrollLayoutController.getTopPadding(), p);
             p.setColor(Color.GRAY);
             canvas.drawLine(0, mClockPositionResult.clockY, mView.getWidth(),
                     mClockPositionResult.clockY, p);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowControllerImpl.java
similarity index 95%
rename from packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowController.java
rename to packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowControllerImpl.java
index bc73be1..5abc4261 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowControllerImpl.java
@@ -43,11 +43,12 @@
 import com.android.systemui.Dumpable;
 import com.android.systemui.R;
 import com.android.systemui.colorextraction.SysuiColorExtractor;
+import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dump.DumpManager;
 import com.android.systemui.keyguard.KeyguardViewMediator;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.plugins.statusbar.StatusBarStateController.StateListener;
-import com.android.systemui.statusbar.RemoteInputController.Callback;
+import com.android.systemui.statusbar.NotificationShadeWindowController;
 import com.android.systemui.statusbar.StatusBarState;
 import com.android.systemui.statusbar.SysuiStatusBarStateController;
 import com.android.systemui.statusbar.policy.ConfigurationController;
@@ -66,14 +67,13 @@
 import java.util.function.Consumer;
 
 import javax.inject.Inject;
-import javax.inject.Singleton;
 
 /**
  * Encapsulates all logic for the notification shade window state management.
  */
-@Singleton
-public class NotificationShadeWindowController implements Callback, Dumpable,
-        ConfigurationListener {
+@SysUISingleton
+public class NotificationShadeWindowControllerImpl implements NotificationShadeWindowController,
+        Dumpable, ConfigurationListener {
 
     private static final String TAG = "NotificationShadeWindowController";
     private static final boolean DEBUG = false;
@@ -101,9 +101,10 @@
             mCallbacks = Lists.newArrayList();
 
     private final SysuiColorExtractor mColorExtractor;
+    private float mFaceAuthDisplayBrightness = LayoutParams.BRIGHTNESS_OVERRIDE_NONE;
 
     @Inject
-    public NotificationShadeWindowController(Context context, WindowManager windowManager,
+    public NotificationShadeWindowControllerImpl(Context context, WindowManager windowManager,
             IActivityManager activityManager, DozeParameters dozeParameters,
             StatusBarStateController statusBarStateController,
             ConfigurationController configurationController,
@@ -147,6 +148,7 @@
     /**
      * Register to receive notifications about status bar window state changes.
      */
+    @Override
     public void registerCallback(StatusBarWindowCallback callback) {
         // Prevent adding duplicate callbacks
         for (int i = 0; i < mCallbacks.size(); i++) {
@@ -161,6 +163,7 @@
      * Register a listener to monitor scrims visibility
      * @param listener A listener to monitor scrims visibility
      */
+    @Override
     public void setScrimsVisibilityListener(Consumer<Integer> listener) {
         if (listener != null && mScrimsVisibilityListener != listener) {
             mScrimsVisibilityListener = listener;
@@ -176,6 +179,7 @@
     /**
      * Adds the notification shade view to the window manager.
      */
+    @Override
     public void attach() {
         // Now that the notification shade encompasses the sliding panel and its
         // translucent backdrop, the entire thing is made TRANSLUCENT and is
@@ -214,18 +218,27 @@
         }
     }
 
+    @Override
     public void setNotificationShadeView(ViewGroup view) {
         mNotificationShadeView = view;
     }
 
+    @Override
     public ViewGroup getNotificationShadeView() {
         return mNotificationShadeView;
     }
 
+    @Override
     public void setDozeScreenBrightness(int value) {
         mScreenBrightnessDoze = value / 255f;
     }
 
+    @Override
+    public void setFaceAuthDisplayBrightness(float brightness) {
+        mFaceAuthDisplayBrightness = brightness;
+        apply(mCurrentState);
+    }
+
     private void setKeyguardDark(boolean dark) {
         int vis = mNotificationShadeView.getSystemUiVisibility();
         if (dark) {
@@ -406,6 +419,7 @@
         notifyStateChangedCallbacks();
     }
 
+    @Override
     public void notifyStateChangedCallbacks() {
         for (int i = 0; i < mCallbacks.size(); i++) {
             StatusBarWindowCallback cb = mCallbacks.get(i).get();
@@ -429,7 +443,7 @@
         if (state.mForceDozeBrightness) {
             mLpChanged.screenBrightness = mScreenBrightnessDoze;
         } else {
-            mLpChanged.screenBrightness = LayoutParams.BRIGHTNESS_OVERRIDE_NONE;
+            mLpChanged.screenBrightness = mFaceAuthDisplayBrightness;
         }
     }
 
@@ -445,62 +459,74 @@
         }
     }
 
+    @Override
     public void setKeyguardShowing(boolean showing) {
         mCurrentState.mKeyguardShowing = showing;
         apply(mCurrentState);
     }
 
+    @Override
     public void setKeyguardOccluded(boolean occluded) {
         mCurrentState.mKeyguardOccluded = occluded;
         apply(mCurrentState);
     }
 
+    @Override
     public void setKeyguardNeedsInput(boolean needsInput) {
         mCurrentState.mKeyguardNeedsInput = needsInput;
         apply(mCurrentState);
     }
 
+    @Override
     public void setPanelVisible(boolean visible) {
         mCurrentState.mPanelVisible = visible;
         mCurrentState.mNotificationShadeFocusable = visible;
         apply(mCurrentState);
     }
 
+    @Override
     public void setNotificationShadeFocusable(boolean focusable) {
         mCurrentState.mNotificationShadeFocusable = focusable;
         apply(mCurrentState);
     }
 
+    @Override
     public void setBouncerShowing(boolean showing) {
         mCurrentState.mBouncerShowing = showing;
         apply(mCurrentState);
     }
 
+    @Override
     public void setBackdropShowing(boolean showing) {
         mCurrentState.mBackdropShowing = showing;
         apply(mCurrentState);
     }
 
+    @Override
     public void setKeyguardFadingAway(boolean keyguardFadingAway) {
         mCurrentState.mKeyguardFadingAway = keyguardFadingAway;
         apply(mCurrentState);
     }
 
+    @Override
     public void setQsExpanded(boolean expanded) {
         mCurrentState.mQsExpanded = expanded;
         apply(mCurrentState);
     }
 
+    @Override
     public void setForceUserActivity(boolean forceUserActivity) {
         mCurrentState.mForceUserActivity = forceUserActivity;
         apply(mCurrentState);
     }
 
-    void setLaunchingActivity(boolean launching) {
+    @Override
+    public void setLaunchingActivity(boolean launching) {
         mCurrentState.mLaunchingActivity = launching;
         apply(mCurrentState);
     }
 
+    @Override
     public void setScrimsVisibility(int scrimsVisibility) {
         mCurrentState.mScrimsVisibility = scrimsVisibility;
         apply(mCurrentState);
@@ -512,6 +538,7 @@
      * {@link com.android.systemui.statusbar.NotificationShadeDepthController}.
      * @param backgroundBlurRadius Radius in pixels.
      */
+    @Override
     public void setBackgroundBlurRadius(int backgroundBlurRadius) {
         if (mCurrentState.mBackgroundBlurRadius == backgroundBlurRadius) {
             return;
@@ -520,11 +547,13 @@
         apply(mCurrentState);
     }
 
+    @Override
     public void setHeadsUpShowing(boolean showing) {
         mCurrentState.mHeadsUpShowing = showing;
         apply(mCurrentState);
     }
 
+    @Override
     public void setWallpaperSupportsAmbientMode(boolean supportsAmbientMode) {
         mCurrentState.mWallpaperSupportsAmbientMode = supportsAmbientMode;
         apply(mCurrentState);
@@ -543,11 +572,13 @@
      * Used for when a heads-up comes in but we still need to wait for the touchable regions to
      * be computed.
      */
+    @Override
     public void setForceWindowCollapsed(boolean force) {
         mCurrentState.mForceCollapsed = force;
         apply(mCurrentState);
     }
 
+    @Override
     public void setPanelExpanded(boolean isExpanded) {
         mCurrentState.mPanelExpanded = isExpanded;
         apply(mCurrentState);
@@ -563,16 +594,19 @@
      * Set whether the screen brightness is forced to the value we use for doze mode by the status
      * bar window.
      */
+    @Override
     public void setForceDozeBrightness(boolean forceDozeBrightness) {
         mCurrentState.mForceDozeBrightness = forceDozeBrightness;
         apply(mCurrentState);
     }
 
+    @Override
     public void setDozing(boolean dozing) {
         mCurrentState.mDozing = dozing;
         apply(mCurrentState);
     }
 
+    @Override
     public void setForcePluginOpen(boolean forcePluginOpen) {
         mCurrentState.mForcePluginOpen = forcePluginOpen;
         apply(mCurrentState);
@@ -584,10 +618,12 @@
     /**
      * The forcePluginOpen state for the status bar.
      */
+    @Override
     public boolean getForcePluginOpen() {
         return mCurrentState.mForcePluginOpen;
     }
 
+    @Override
     public void setNotTouchable(boolean notTouchable) {
         mCurrentState.mNotTouchable = notTouchable;
         apply(mCurrentState);
@@ -596,24 +632,29 @@
     /**
      * Whether the status bar panel is expanded or not.
      */
+    @Override
     public boolean getPanelExpanded() {
         return mCurrentState.mPanelExpanded;
     }
 
+    @Override
     public void setStateListener(OtherwisedCollapsedListener listener) {
         mListener = listener;
     }
 
+    @Override
     public void setForcePluginOpenListener(ForcePluginOpenListener listener) {
         mForcePluginOpenListener = listener;
     }
 
+    @Override
     public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
         pw.println(TAG + ":");
         pw.println("  mKeyguardDisplayMode=" + mKeyguardDisplayMode);
         pw.println(mCurrentState);
     }
 
+    @Override
     public boolean isShowingWallpaper() {
         return !mCurrentState.mBackdropShowing;
     }
@@ -632,6 +673,7 @@
     /**
      * When keyguard will be dismissed but didn't start animation yet.
      */
+    @Override
     public void setKeyguardGoingAway(boolean goingAway) {
         mCurrentState.mKeyguardGoingAway = goingAway;
         apply(mCurrentState);
@@ -642,6 +684,7 @@
      * animation is performed, the component should remove itself from the list of features that
      * are forcing SystemUI to be top-ui.
      */
+    @Override
     public void setRequestTopUi(boolean requestTopUi, String componentTag) {
         if (requestTopUi) {
             mCurrentState.mComponentsForcingTopUi.add(componentTag);
@@ -665,6 +708,7 @@
         boolean mHeadsUpShowing;
         boolean mForceCollapsed;
         boolean mForceDozeBrightness;
+        int mFaceAuthDisplayBrightness;
         boolean mForceUserActivity;
         boolean mLaunchingActivity;
         boolean mBackdropShowing;
@@ -725,23 +769,4 @@
             setDozing(isDozing);
         }
     };
-
-    /**
-     * Custom listener to pipe data back to plugins about whether or not the status bar would be
-     * collapsed if not for the plugin.
-     * TODO: Find cleaner way to do this.
-     */
-    public interface OtherwisedCollapsedListener {
-        void setWouldOtherwiseCollapse(boolean otherwiseCollapse);
-    }
-
-    /**
-     * Listener to indicate forcePluginOpen has changed
-     */
-    public interface ForcePluginOpenListener {
-        /**
-         * Called when mState.forcePluginOpen is changed
-         */
-        void onChange(boolean forceOpen);
-    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowViewController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowViewController.java
index 42222d7..53cc267 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowViewController.java
@@ -44,6 +44,7 @@
 import com.android.systemui.statusbar.DragDownHelper;
 import com.android.systemui.statusbar.NotificationLockscreenUserManager;
 import com.android.systemui.statusbar.NotificationShadeDepthController;
+import com.android.systemui.statusbar.NotificationShadeWindowController;
 import com.android.systemui.statusbar.PulseExpansionHandler;
 import com.android.systemui.statusbar.SuperStatusBarViewFactory;
 import com.android.systemui.statusbar.SysuiStatusBarStateController;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java
index b2cfcea..e42c3dc 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java
@@ -284,13 +284,22 @@
                 mResources.getString(R.string.accessibility_data_saver_on));
         mIconController.setIconVisibility(mSlotDataSaver, false);
 
+
         // privacy items
+        String microphoneString = mResources.getString(PrivacyType.TYPE_MICROPHONE.getNameId());
+        String microphoneDesc = mResources.getString(
+                R.string.ongoing_privacy_chip_content_multiple_apps, microphoneString);
         mIconController.setIcon(mSlotMicrophone, PrivacyType.TYPE_MICROPHONE.getIconId(),
-                mResources.getString(PrivacyType.TYPE_MICROPHONE.getNameId()));
+                microphoneDesc);
         mIconController.setIconVisibility(mSlotMicrophone, false);
+
+        String cameraString = mResources.getString(PrivacyType.TYPE_CAMERA.getNameId());
+        String cameraDesc = mResources.getString(
+                R.string.ongoing_privacy_chip_content_multiple_apps, cameraString);
         mIconController.setIcon(mSlotCamera, PrivacyType.TYPE_CAMERA.getIconId(),
-                mResources.getString(PrivacyType.TYPE_CAMERA.getNameId()));
+                cameraDesc);
         mIconController.setIconVisibility(mSlotCamera, false);
+
         mIconController.setIcon(mSlotLocation, LOCATION_STATUS_ICON_ID,
                 mResources.getString(R.string.accessibility_location_active));
         mIconController.setIconVisibility(mSlotLocation, false);
@@ -662,16 +671,18 @@
 
         mIconController.setIconVisibility(mSlotCamera, showCamera);
         mIconController.setIconVisibility(mSlotMicrophone, showMicrophone);
-        mIconController.setIconVisibility(mSlotLocation, showLocation);
+        if (mPrivacyItemController.getAllIndicatorsAvailable()) {
+            mIconController.setIconVisibility(mSlotLocation, showLocation);
+        }
     }
 
     @Override
     public void onLocationActiveChanged(boolean active) {
-        if (!mPrivacyItemController.getIndicatorsAvailable()) updateLocation();
+        if (!mPrivacyItemController.getAllIndicatorsAvailable()) updateLocationFromController();
     }
 
     // Updates the status view based on the current state of location requests.
-    private void updateLocation() {
+    private void updateLocationFromController() {
         if (mLocationController.isLocationActive()) {
             mIconController.setIconVisibility(mSlotLocation, true);
         } else {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
index 686b871..11d0583 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
@@ -46,6 +46,7 @@
 import com.android.systemui.Dumpable;
 import com.android.systemui.R;
 import com.android.systemui.colorextraction.SysuiColorExtractor;
+import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dock.DockManager;
 import com.android.systemui.statusbar.BlurUtils;
 import com.android.systemui.statusbar.ScrimView;
@@ -62,13 +63,12 @@
 import java.util.function.Consumer;
 
 import javax.inject.Inject;
-import javax.inject.Singleton;
 
 /**
  * Controls both the scrim behind the notifications and in front of the notifications (when a
  * security method gets shown).
  */
-@Singleton
+@SysUISingleton
 public class ScrimController implements ViewTreeObserver.OnPreDrawListener, OnColorsChangedListener,
         Dumpable {
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ShadeControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ShadeControllerImpl.java
index 3330615..1ce2219 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ShadeControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ShadeControllerImpl.java
@@ -23,20 +23,21 @@
 
 import com.android.systemui.assist.AssistManager;
 import com.android.systemui.bubbles.BubbleController;
+import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.statusbar.CommandQueue;
 import com.android.systemui.statusbar.NotificationPresenter;
+import com.android.systemui.statusbar.NotificationShadeWindowController;
 import com.android.systemui.statusbar.StatusBarState;
 
 import java.util.ArrayList;
 
 import javax.inject.Inject;
-import javax.inject.Singleton;
 
 import dagger.Lazy;
 
 /** An implementation of {@link com.android.systemui.statusbar.phone.ShadeController}. */
-@Singleton
+@SysUISingleton
 public class ShadeControllerImpl implements ShadeController {
 
     private static final String TAG = "ShadeControllerImpl";
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
index eb62628..9391104 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
@@ -138,14 +138,12 @@
 import com.android.systemui.ActivityIntentHelper;
 import com.android.systemui.AutoReinflateContainer;
 import com.android.systemui.DejankUtils;
-import com.android.systemui.DemoMode;
 import com.android.systemui.Dumpable;
 import com.android.systemui.EventLogTags;
 import com.android.systemui.InitController;
 import com.android.systemui.Prefs;
 import com.android.systemui.R;
 import com.android.systemui.SystemUI;
-import com.android.systemui.SystemUIFactory;
 import com.android.systemui.assist.AssistManager;
 import com.android.systemui.broadcast.BroadcastDispatcher;
 import com.android.systemui.bubbles.BubbleController;
@@ -153,12 +151,17 @@
 import com.android.systemui.classifier.FalsingLog;
 import com.android.systemui.colorextraction.SysuiColorExtractor;
 import com.android.systemui.dagger.qualifiers.UiBackground;
+import com.android.systemui.demomode.DemoMode;
+import com.android.systemui.demomode.DemoModeCommandReceiver;
+import com.android.systemui.demomode.DemoModeController;
 import com.android.systemui.fragments.ExtensionFragmentListener;
 import com.android.systemui.fragments.FragmentHostManager;
 import com.android.systemui.keyguard.DismissCallbackRegistry;
 import com.android.systemui.keyguard.KeyguardViewMediator;
 import com.android.systemui.keyguard.ScreenLifecycle;
 import com.android.systemui.keyguard.WakefulnessLifecycle;
+import com.android.systemui.navigationbar.NavigationBarController;
+import com.android.systemui.navigationbar.NavigationBarView;
 import com.android.systemui.plugins.ActivityStarter;
 import com.android.systemui.plugins.DarkIconDispatcher;
 import com.android.systemui.plugins.FalsingManager;
@@ -174,22 +177,21 @@
 import com.android.systemui.recents.ScreenPinningRequest;
 import com.android.systemui.shared.plugins.PluginManager;
 import com.android.systemui.shared.system.WindowManagerWrapper;
-import com.android.systemui.stackdivider.Divider;
+import com.android.systemui.stackdivider.SplitScreenController;
 import com.android.systemui.statusbar.AutoHideUiElement;
 import com.android.systemui.statusbar.BackDropView;
 import com.android.systemui.statusbar.CommandQueue;
 import com.android.systemui.statusbar.CrossFadeHelper;
-import com.android.systemui.statusbar.EmptyShadeView;
 import com.android.systemui.statusbar.GestureRecorder;
 import com.android.systemui.statusbar.KeyboardShortcuts;
 import com.android.systemui.statusbar.KeyguardIndicationController;
-import com.android.systemui.statusbar.NavigationBarController;
 import com.android.systemui.statusbar.NotificationLockscreenUserManager;
 import com.android.systemui.statusbar.NotificationMediaManager;
 import com.android.systemui.statusbar.NotificationPresenter;
 import com.android.systemui.statusbar.NotificationRemoteInputManager;
 import com.android.systemui.statusbar.NotificationShadeDepthController;
-import com.android.systemui.statusbar.NotificationShelf;
+import com.android.systemui.statusbar.NotificationShadeWindowController;
+import com.android.systemui.statusbar.NotificationShelfController;
 import com.android.systemui.statusbar.NotificationViewHierarchyManager;
 import com.android.systemui.statusbar.PulseExpansionHandler;
 import com.android.systemui.statusbar.ScrimView;
@@ -201,8 +203,8 @@
 import com.android.systemui.statusbar.notification.DynamicPrivacyController;
 import com.android.systemui.statusbar.notification.NotificationActivityStarter;
 import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator;
-import com.android.systemui.statusbar.notification.VisualStabilityManager;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
+import com.android.systemui.statusbar.notification.collection.legacy.VisualStabilityManager;
 import com.android.systemui.statusbar.notification.init.NotificationsController;
 import com.android.systemui.statusbar.notification.interruption.BypassHeadsUpNotifier;
 import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProvider;
@@ -210,6 +212,8 @@
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
 import com.android.systemui.statusbar.notification.row.NotificationGutsManager;
 import com.android.systemui.statusbar.notification.stack.NotificationListContainer;
+import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout;
+import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController;
 import com.android.systemui.statusbar.phone.dagger.StatusBarComponent;
 import com.android.systemui.statusbar.phone.dagger.StatusBarPhoneModule;
 import com.android.systemui.statusbar.policy.BatteryController;
@@ -231,6 +235,8 @@
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
 import java.io.StringWriter;
+import java.util.ArrayList;
+import java.util.List;
 import java.util.Map;
 import java.util.Optional;
 import java.util.concurrent.Executor;
@@ -382,19 +388,20 @@
     private final Lazy<BiometricUnlockController> mBiometricUnlockControllerLazy;
     private final Provider<StatusBarComponent.Builder> mStatusBarComponentBuilder;
     private final PluginManager mPluginManager;
-    private final Optional<Divider> mDividerOptional;
+    private final Optional<SplitScreenController> mSplitScreenControllerOptional;
     private final StatusBarNotificationActivityStarter.Builder
             mStatusBarNotificationActivityStarterBuilder;
     private final ShadeController mShadeController;
     private final SuperStatusBarViewFactory mSuperStatusBarViewFactory;
     private final LightsOutNotifController mLightsOutNotifController;
     private final InitController mInitController;
-    private final DarkIconDispatcher mDarkIconDispatcher;
+
     private final PluginDependencyProvider mPluginDependencyProvider;
     private final KeyguardDismissUtil mKeyguardDismissUtil;
     private final ExtensionController mExtensionController;
     private final UserInfoControllerImpl mUserInfoControllerImpl;
     private final DismissCallbackRegistry mDismissCallbackRegistry;
+    private final DemoModeController mDemoModeController;
     private NotificationsController mNotificationsController;
 
     // expanded notifications
@@ -601,7 +608,7 @@
     private UiModeManager mUiModeManager;
     protected boolean mIsKeyguard;
     private LogMaker mStatusBarStateLog;
-    protected NotificationIconAreaController mNotificationIconAreaController;
+    protected final NotificationIconAreaController mNotificationIconAreaController;
     @Nullable private View mAmbientIndicationContainer;
     private final SysuiColorExtractor mColorExtractor;
     private final ScreenLifecycle mScreenLifecycle;
@@ -646,6 +653,7 @@
     private final BubbleController.BubbleExpandListener mBubbleExpandListener;
 
     private ActivityIntentHelper mActivityIntentHelper;
+    private NotificationStackScrollLayoutController mStackScrollerController;
 
     /**
      * Public constructor for StatusBar.
@@ -713,7 +721,7 @@
             Optional<Recents> recentsOptional,
             Provider<StatusBarComponent.Builder> statusBarComponentBuilder,
             PluginManager pluginManager,
-            Optional<Divider> dividerOptional,
+            Optional<SplitScreenController> splitScreenControllerOptional,
             LightsOutNotifController lightsOutNotifController,
             StatusBarNotificationActivityStarter.Builder
                     statusBarNotificationActivityStarterBuilder,
@@ -722,7 +730,6 @@
             StatusBarKeyguardViewManager statusBarKeyguardViewManager,
             ViewMediatorCallback viewMediatorCallback,
             InitController initController,
-            DarkIconDispatcher darkIconDispatcher,
             @Named(TIME_TICK_HANDLER_NAME) Handler timeTickHandler,
             PluginDependencyProvider pluginDependencyProvider,
             KeyguardDismissUtil keyguardDismissUtil,
@@ -731,8 +738,10 @@
             PhoneStatusBarPolicy phoneStatusBarPolicy,
             KeyguardIndicationController keyguardIndicationController,
             DismissCallbackRegistry dismissCallbackRegistry,
+            DemoModeController demoModeController,
             Lazy<NotificationShadeDepthController> notificationShadeDepthControllerLazy,
-            StatusBarTouchableRegionManager statusBarTouchableRegionManager) {
+            StatusBarTouchableRegionManager statusBarTouchableRegionManager,
+            NotificationIconAreaController notificationIconAreaController) {
         super(context);
         mNotificationsController = notificationsController;
         mLightBarController = lightBarController;
@@ -794,7 +803,7 @@
         mRecentsOptional = recentsOptional;
         mStatusBarComponentBuilder = statusBarComponentBuilder;
         mPluginManager = pluginManager;
-        mDividerOptional = dividerOptional;
+        mSplitScreenControllerOptional = splitScreenControllerOptional;
         mStatusBarNotificationActivityStarterBuilder = statusBarNotificationActivityStarterBuilder;
         mShadeController = shadeController;
         mSuperStatusBarViewFactory = superStatusBarViewFactory;
@@ -802,13 +811,14 @@
         mStatusBarKeyguardViewManager = statusBarKeyguardViewManager;
         mKeyguardViewMediatorCallback = viewMediatorCallback;
         mInitController = initController;
-        mDarkIconDispatcher = darkIconDispatcher;
         mPluginDependencyProvider = pluginDependencyProvider;
         mKeyguardDismissUtil = keyguardDismissUtil;
         mExtensionController = extensionController;
         mUserInfoControllerImpl = userInfoControllerImpl;
         mIconPolicy = phoneStatusBarPolicy;
         mDismissCallbackRegistry = dismissCallbackRegistry;
+        mDemoModeController = demoModeController;
+        mNotificationIconAreaController = notificationIconAreaController;
 
         mBubbleExpandListener =
                 (isExpanding, key) -> {
@@ -863,6 +873,9 @@
         // Connect in to the status bar manager service
         mCommandQueue.addCallback(this);
 
+        // Listen for demo mode changes
+        mDemoModeController.addCallback(this);
+
         RegisterStatusBarResult result = null;
         try {
             result = mBarService.registerStatusBar(mCommandQueue);
@@ -939,9 +952,12 @@
         startKeyguard();
 
         mKeyguardUpdateMonitor.registerCallback(mUpdateCallback);
-        mDozeServiceHost.initialize(this, mNotificationIconAreaController,
-                mStatusBarKeyguardViewManager, mNotificationShadeWindowViewController,
-                mNotificationPanelViewController, mAmbientIndicationContainer);
+        mDozeServiceHost.initialize(
+                this,
+                mStatusBarKeyguardViewManager,
+                mNotificationShadeWindowViewController,
+                mNotificationPanelViewController,
+                mAmbientIndicationContainer);
 
         mConfigurationController.addCallback(this);
 
@@ -1016,25 +1032,19 @@
 
         // TODO: Deal with the ugliness that comes from having some of the statusbar broken out
         // into fragments, but the rest here, it leaves some awkward lifecycle and whatnot.
-        mStackScroller = mNotificationShadeWindowView.findViewById(
-                R.id.notification_stack_scroller);
-        NotificationListContainer notifListContainer = (NotificationListContainer) mStackScroller;
+        mStackScrollerController =
+                mNotificationPanelViewController.getNotificationStackScrollLayoutController();
+        mStackScroller = mStackScrollerController.getView();
+        NotificationListContainer notifListContainer =
+                mStackScrollerController.getNotificationListContainer();
         mNotificationLogger.setUpWithContainer(notifListContainer);
 
-        // TODO: make this injectable. Currently that would create a circular dependency between
-        // NotificationIconAreaController and StatusBar.
-        mNotificationIconAreaController = SystemUIFactory.getInstance()
-                .createNotificationIconAreaController(context, this,
-                        mWakeUpCoordinator, mKeyguardBypassController,
-                        mStatusBarStateController);
-        mWakeUpCoordinator.setIconAreaController(mNotificationIconAreaController);
+        updateAodIconArea();
         inflateShelf();
-        mNotificationIconAreaController.setupShelf(mNotificationShelf);
-        mNotificationPanelViewController.setOnReinflationListener(
-                mNotificationIconAreaController::initAodIcons);
+        mNotificationIconAreaController.setupShelf(mNotificationShelfController);
+        mNotificationPanelViewController.setOnReinflationListener(this::updateAodIconArea);
         mNotificationPanelViewController.addExpansionListener(mWakeUpCoordinator);
 
-        mDarkIconDispatcher.addDarkReceiver(mNotificationIconAreaController);
         // Allow plugins to reference DarkIconDispatcher and StatusBarStateController
         mPluginDependencyProvider.allowPluginDependency(DarkIconDispatcher.class);
         mPluginDependencyProvider.allowPluginDependency(StatusBarStateController.class);
@@ -1076,7 +1086,7 @@
                     // TODO (b/136993073) Separate notification shade and status bar
                     mHeadsUpAppearanceController = new HeadsUpAppearanceController(
                             mNotificationIconAreaController, mHeadsUpManager,
-                            mNotificationShadeWindowView,
+                            mStackScroller.getController(),
                             mStatusBarStateController, mKeyguardBypassController,
                             mKeyguardStateController, mWakeUpCoordinator, mCommandQueue,
                             mNotificationPanelViewController, mStatusBarView);
@@ -1147,8 +1157,11 @@
         });
         mScrimController.attachViews(scrimBehind, scrimInFront, scrimForBubble);
 
-        mNotificationPanelViewController.initDependencies(this, mGroupManager, mNotificationShelf,
-                mNotificationIconAreaController, mScrimController);
+        mNotificationPanelViewController.initDependencies(
+                this,
+                mGroupManager,
+                mNotificationShelfController,
+                mScrimController);
 
         BackDropView backdrop = mNotificationShadeWindowView.findViewById(R.id.backdrop);
         mMediaManager.setup(backdrop, backdrop.findViewById(R.id.backdrop_front),
@@ -1248,7 +1261,6 @@
         if (DEBUG_MEDIA_FAKE_ARTWORK) {
             demoFilter.addAction(ACTION_FAKE_ARTWORK);
         }
-        demoFilter.addAction(ACTION_DEMO);
         context.registerReceiverAsUser(mDemoReceiver, UserHandle.ALL, demoFilter,
                 android.Manifest.permission.DUMP, null);
 
@@ -1263,6 +1275,12 @@
         ThreadedRenderer.overrideProperty("ambientRatio", String.valueOf(1.5f));
     }
 
+    private void updateAodIconArea() {
+        mNotificationIconAreaController.setupAodIcons(
+                getNotificationShadeWindowView()
+                        .findViewById(R.id.clock_notification_icon_container));
+    }
+
     @NonNull
     @Override
     public Lifecycle getLifecycle() {
@@ -1300,17 +1318,19 @@
         mActivityLaunchAnimator = new ActivityLaunchAnimator(
                 mNotificationShadeWindowViewController, this, mNotificationPanelViewController,
                 mNotificationShadeDepthControllerLazy.get(),
-                (NotificationListContainer) mStackScroller, mContext.getMainExecutor());
+                mStackScrollerController.getNotificationListContainer(),
+                mContext.getMainExecutor());
 
         // TODO: inject this.
         mPresenter = new StatusBarNotificationPresenter(mContext, mNotificationPanelViewController,
-                mHeadsUpManager, mNotificationShadeWindowView, mStackScroller, mDozeScrimController,
-                mScrimController, mActivityLaunchAnimator, mDynamicPrivacyController,
-                mKeyguardStateController, mKeyguardIndicationController,
+                mHeadsUpManager, mNotificationShadeWindowView, mStackScrollerController,
+                mDozeScrimController, mScrimController, mActivityLaunchAnimator,
+                mDynamicPrivacyController, mKeyguardStateController,
+                mKeyguardIndicationController,
                 this /* statusBar */, mShadeController, mCommandQueue, mInitController,
                 mNotificationInterruptStateProvider);
 
-        mNotificationShelf.setOnActivatedListener(mPresenter);
+        mNotificationShelfController.setOnActivatedListener(mPresenter);
         mRemoteInputManager.getController().addCallback(mNotificationShadeWindowController);
 
         mNotificationActivityStarter =
@@ -1320,16 +1340,13 @@
                         .setNotificationPresenter(mPresenter)
                         .setNotificationPanelViewController(mNotificationPanelViewController)
                         .build();
-
-        ((NotificationListContainer) mStackScroller)
-                .setNotificationActivityStarter(mNotificationActivityStarter);
-
+        mStackScroller.setNotificationActivityStarter(mNotificationActivityStarter);
         mGutsManager.setNotificationActivityStarter(mNotificationActivityStarter);
 
         mNotificationsController.initialize(
                 this,
                 mPresenter,
-                (NotificationListContainer) mStackScroller,
+                mStackScrollerController.getNotificationListContainer(),
                 mNotificationActivityStarter,
                 mPresenter);
     }
@@ -1386,8 +1403,9 @@
     }
 
     private void inflateShelf() {
-        mNotificationShelf = mSuperStatusBarViewFactory.getNotificationShelf(mStackScroller);
-        mNotificationShelf.setOnClickListener(mGoToLockedShadeListener);
+        mNotificationShelfController = mSuperStatusBarViewFactory
+                .getNotificationShelfController(mStackScroller);
+        mNotificationShelfController.setOnClickListener(mGoToLockedShadeListener);
     }
 
     @Override
@@ -1458,6 +1476,34 @@
     protected void startKeyguard() {
         Trace.beginSection("StatusBar#startKeyguard");
         mBiometricUnlockController = mBiometricUnlockControllerLazy.get();
+        mBiometricUnlockController.setBiometricModeListener(
+                new BiometricUnlockController.BiometricModeListener() {
+                    @Override
+                    public void onResetMode() {
+                        setWakeAndUnlocking(false);
+                    }
+
+                    @Override
+                    public void onModeChanged(int mode) {
+                        switch (mode) {
+                            case BiometricUnlockController.MODE_WAKE_AND_UNLOCK_FROM_DREAM:
+                            case BiometricUnlockController.MODE_WAKE_AND_UNLOCK_PULSING:
+                            case BiometricUnlockController.MODE_WAKE_AND_UNLOCK:
+                                setWakeAndUnlocking(true);
+                        }
+                    }
+
+                    @Override
+                    public void notifyBiometricAuthModeChanged() {
+                        StatusBar.this.notifyBiometricAuthModeChanged();
+                    }
+
+                    private void setWakeAndUnlocking(boolean wakeAndUnlocking) {
+                        if (getNavigationBarView() != null) {
+                            getNavigationBarView().setWakeAndUnlocking(wakeAndUnlocking);
+                        }
+                    }
+                });
         mStatusBarKeyguardViewManager.registerStatusBar(
                 /* statusBar= */ this, getBouncerContainer(),
                 mNotificationPanelViewController, mBiometricUnlockController,
@@ -1500,35 +1546,36 @@
         return mStatusBarWindowController.getStatusBarHeight();
     }
 
-    protected boolean toggleSplitScreenMode(int metricsDockAction, int metricsUndockAction) {
+    public boolean toggleSplitScreenMode(int metricsDockAction, int metricsUndockAction) {
         if (!mRecentsOptional.isPresent()) {
             return false;
         }
-        Divider divider = null;
-        if (mDividerOptional.isPresent()) {
-            divider = mDividerOptional.get();
-        }
-        if (divider == null || !divider.isDividerVisible()) {
-            final int navbarPos = WindowManagerWrapper.getInstance().getNavBarPosition(mDisplayId);
-            if (navbarPos == NAV_BAR_POS_INVALID) {
-                return false;
-            }
-            int createMode = navbarPos == NAV_BAR_POS_LEFT
-                    ? SPLIT_SCREEN_CREATE_MODE_BOTTOM_OR_RIGHT
-                    : SPLIT_SCREEN_CREATE_MODE_TOP_OR_LEFT;
-            return mRecentsOptional.get().splitPrimaryTask(createMode, null, metricsDockAction);
-        } else {
-            if (divider.isMinimized() && !divider.isHomeStackResizable()) {
-                // Undocking from the minimized state is not supported
-                return false;
-            } else {
-                divider.onUndockingTask();
-                if (metricsUndockAction != -1) {
-                    mMetricsLogger.action(metricsUndockAction);
+
+        if (mSplitScreenControllerOptional.isPresent()) {
+            SplitScreenController splitScreenController = mSplitScreenControllerOptional.get();
+            if (splitScreenController.isDividerVisible()) {
+                if (splitScreenController.isMinimized()
+                        && !splitScreenController.isHomeStackResizable()) {
+                    // Undocking from the minimized state is not supported
+                    return false;
+                } else {
+                    splitScreenController.onUndockingTask();
+                    if (metricsUndockAction != -1) {
+                        mMetricsLogger.action(metricsUndockAction);
+                    }
                 }
+                return true;
             }
         }
-        return true;
+
+        final int navbarPos = WindowManagerWrapper.getInstance().getNavBarPosition(mDisplayId);
+        if (navbarPos == NAV_BAR_POS_INVALID) {
+            return false;
+        }
+        int createMode = navbarPos == NAV_BAR_POS_LEFT
+                ? SPLIT_SCREEN_CREATE_MODE_BOTTOM_OR_RIGHT
+                : SPLIT_SCREEN_CREATE_MODE_TOP_OR_LEFT;
+        return mRecentsOptional.get().splitPrimaryTask(createMode, null, metricsDockAction);
     }
 
     /**
@@ -1803,7 +1850,7 @@
         mPanelExpanded = isExpanded;
         updateHideIconsForBouncer(false /* animate */);
         mNotificationShadeWindowController.setPanelExpanded(isExpanded);
-        mVisualStabilityManager.setPanelExpanded(isExpanded);
+        mStatusBarStateController.setPanelExpanded(isExpanded);
         if (isExpanded && mStatusBarStateController.getState() != StatusBarState.KEYGUARD) {
             if (DEBUG) {
                 Log.v(TAG, "clearing notification effects from setExpandedHeight");
@@ -2024,7 +2071,7 @@
                     mVibratorHelper.vibrate(VibrationEffect.EFFECT_TICK);
                 }
                 mNotificationPanelViewController.expand(true /* animate */);
-                ((NotificationListContainer) mStackScroller).setWillExpand(true);
+                mStackScroller.setWillExpand(true);
                 mHeadsUpManager.unpinAll(true /* userUnpinned */);
                 mMetricsLogger.count(NotificationPanelView.COUNTER_PANEL_OPEN, 1);
             } else if (!mNotificationPanelViewController.isInSettings()
@@ -2433,8 +2480,8 @@
         return mNotificationShadeWindowViewController.getBarTransitions();
     }
 
-    void checkBarModes() {
-        if (mDemoMode) return;
+    public void checkBarModes() {
+        if (mDemoModeController.isInDemoMode()) return;
         if (mNotificationShadeWindowViewController != null && getStatusBarTransitions() != null) {
             checkBarMode(mStatusBarMode, mStatusBarWindowState, getStatusBarTransitions());
         }
@@ -2443,7 +2490,7 @@
     }
 
     // Called by NavigationBarFragment
-    void setQsScrimEnabled(boolean scrimEnabled) {
+    public void setQsScrimEnabled(boolean scrimEnabled) {
         mNotificationPanelViewController.setQsScrimEnabled(scrimEnabled);
     }
 
@@ -2616,7 +2663,7 @@
         }
     }
 
-    static void dumpBarTransitions(PrintWriter pw, String var, BarTransitions transitions) {
+    public static void dumpBarTransitions(PrintWriter pw, String var, BarTransitions transitions) {
         pw.print("  "); pw.print(var); pw.print(".BarTransitions.mMode=");
         pw.println(BarTransitions.modeToString(transitions.getMode()));
     }
@@ -2807,19 +2854,7 @@
         public void onReceive(Context context, Intent intent) {
             if (DEBUG) Log.v(TAG, "onReceive: " + intent);
             String action = intent.getAction();
-            if (ACTION_DEMO.equals(action)) {
-                Bundle bundle = intent.getExtras();
-                if (bundle != null) {
-                    String command = bundle.getString("command", "").trim().toLowerCase();
-                    if (command.length() > 0) {
-                        try {
-                            dispatchDemoCommand(command, bundle);
-                        } catch (Throwable t) {
-                            Log.w(TAG, "Error running demo command, intent=" + intent, t);
-                        }
-                    }
-                }
-            } else if (ACTION_FAKE_ARTWORK.equals(action)) {
+            if (ACTION_FAKE_ARTWORK.equals(action)) {
                 if (DEBUG_MEDIA_FAKE_ARTWORK) {
                     mPresenter.updateMediaMetaData(true, true);
                 }
@@ -3083,50 +3118,34 @@
         startActivityDismissingKeyguard(intent, onlyProvisioned, true /* dismissShade */);
     }
 
-    private boolean mDemoModeAllowed;
-    private boolean mDemoMode;
+    @Override
+    public List<String> demoCommands() {
+        List<String> s = new ArrayList<>();
+        s.add(DemoMode.COMMAND_BARS);
+        s.add(DemoMode.COMMAND_CLOCK);
+        s.add(DemoMode.COMMAND_OPERATOR);
+        return s;
+    }
 
     @Override
-    public void dispatchDemoCommand(String command, Bundle args) {
-        if (!mDemoModeAllowed) {
-            mDemoModeAllowed = Settings.Global.getInt(mContext.getContentResolver(),
-                    DEMO_MODE_ALLOWED, 0) != 0;
-        }
-        if (!mDemoModeAllowed) return;
-        if (command.equals(COMMAND_ENTER)) {
-            mDemoMode = true;
-        } else if (command.equals(COMMAND_EXIT)) {
-            mDemoMode = false;
-            checkBarModes();
-        } else if (!mDemoMode) {
-            // automatically enter demo mode on first demo command
-            dispatchDemoCommand(COMMAND_ENTER, new Bundle());
-        }
-        boolean modeChange = command.equals(COMMAND_ENTER) || command.equals(COMMAND_EXIT);
-        if ((modeChange || command.equals(COMMAND_VOLUME)) && mVolumeComponent != null) {
-            mVolumeComponent.dispatchDemoCommand(command, args);
-        }
-        if (modeChange || command.equals(COMMAND_CLOCK)) {
+    public void onDemoModeStarted() {
+        // Must send this message to any view that we delegate to via dispatchDemoCommandToView
+        dispatchDemoModeStartedToView(R.id.clock);
+        dispatchDemoModeStartedToView(R.id.operator_name);
+    }
+
+    @Override
+    public void onDemoModeFinished() {
+        dispatchDemoModeFinishedToView(R.id.clock);
+        dispatchDemoModeFinishedToView(R.id.operator_name);
+        checkBarModes();
+    }
+
+    @Override
+    public void dispatchDemoCommand(String command, @NonNull Bundle args) {
+        if (command.equals(COMMAND_CLOCK)) {
             dispatchDemoCommandToView(command, args, R.id.clock);
         }
-        if (modeChange || command.equals(COMMAND_BATTERY)) {
-            mBatteryController.dispatchDemoCommand(command, args);
-        }
-        if (modeChange || command.equals(COMMAND_STATUS)) {
-            ((StatusBarIconControllerImpl) mIconController).dispatchDemoCommand(command, args);
-        }
-        if (mNetworkController != null && (modeChange || command.equals(COMMAND_NETWORK))) {
-            mNetworkController.dispatchDemoCommand(command, args);
-        }
-        if (modeChange || command.equals(COMMAND_NOTIFICATIONS)) {
-            View notifications = mStatusBarView == null ? null
-                    : mStatusBarView.findViewById(R.id.notification_icon_area);
-            if (notifications != null) {
-                String visible = args.getString("visible");
-                int vis = mDemoMode && "false".equals(visible) ? View.INVISIBLE : View.VISIBLE;
-                notifications.setVisibility(vis);
-            }
-        }
         if (command.equals(COMMAND_BARS)) {
             String mode = args.getString("mode");
             int barMode = "opaque".equals(mode) ? MODE_OPAQUE :
@@ -3145,16 +3164,33 @@
                 mNavigationBarController.transitionTo(mDisplayId, barMode, animate);
             }
         }
-        if (modeChange || command.equals(COMMAND_OPERATOR)) {
+        if (command.equals(COMMAND_OPERATOR)) {
             dispatchDemoCommandToView(command, args, R.id.operator_name);
         }
     }
 
+    //TODO: these should have controllers, and this method should be removed
     private void dispatchDemoCommandToView(String command, Bundle args, int id) {
         if (mStatusBarView == null) return;
         View v = mStatusBarView.findViewById(id);
-        if (v instanceof DemoMode) {
-            ((DemoMode)v).dispatchDemoCommand(command, args);
+        if (v instanceof DemoModeCommandReceiver) {
+            ((DemoModeCommandReceiver) v).dispatchDemoCommand(command, args);
+        }
+    }
+
+    private void dispatchDemoModeStartedToView(int id) {
+        if (mStatusBarView == null) return;
+        View v = mStatusBarView.findViewById(id);
+        if (v instanceof DemoModeCommandReceiver) {
+            ((DemoModeCommandReceiver) v).onDemoModeStarted();
+        }
+    }
+
+    private void dispatchDemoModeFinishedToView(int id) {
+        if (mStatusBarView == null) return;
+        View v = mStatusBarView.findViewById(id);
+        if (v instanceof DemoModeCommandReceiver) {
+            ((DemoModeCommandReceiver) v).onDemoModeFinished();
         }
     }
 
@@ -3752,7 +3788,6 @@
             mDeviceInteractive = false;
             mWakeUpComingFromTouch = false;
             mWakeUpTouchLocation = null;
-            mVisualStabilityManager.setScreenOn(false);
             updateVisibleToUser();
 
             updateNotificationPanelTouchState();
@@ -3789,7 +3824,6 @@
             if (!mKeyguardBypassController.getBypassEnabled()) {
                 mHeadsUpManager.releaseAllImmediately();
             }
-            mVisualStabilityManager.setScreenOn(true);
             updateVisibleToUser();
             updateIsKeyguard();
             mDozeServiceHost.stopDozing();
@@ -3883,14 +3917,16 @@
     @Override
     public void appTransitionCancelled(int displayId) {
         if (displayId == mDisplayId) {
-            mDividerOptional.ifPresent(Divider::onAppTransitionFinished);
+            mSplitScreenControllerOptional.ifPresent(
+                    splitScreen -> splitScreen.onAppTransitionFinished());
         }
     }
 
     @Override
     public void appTransitionFinished(int displayId) {
         if (displayId == mDisplayId) {
-            mDividerOptional.ifPresent(Divider::onAppTransitionFinished);
+            mSplitScreenControllerOptional.ifPresent(
+                    splitScreen -> splitScreen.onAppTransitionFinished());
         }
     }
 
@@ -4049,7 +4085,7 @@
     protected IStatusBarService mBarService;
 
     // all notifications
-    protected ViewGroup mStackScroller;
+    protected NotificationStackScrollLayout mStackScroller;
 
     private final NotificationGroupManager mGroupManager;
 
@@ -4085,8 +4121,7 @@
 
     private final Optional<Recents> mRecentsOptional;
 
-    protected NotificationShelf mNotificationShelf;
-    protected EmptyShadeView mEmptyShadeView;
+    protected NotificationShelfController mNotificationShelfController;
 
     private final Lazy<AssistManager> mAssistManagerLazy;
 
@@ -4131,7 +4166,7 @@
         toggleSplitScreenMode(-1 /* metricsDockAction */, -1 /* metricsUndockAction */);
     }
 
-    void awakenDreams() {
+    public void awakenDreams() {
         mUiBgExecutor.execute(() -> {
             try {
                 mDreamManager.awaken();
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 b89cb21..f0efed3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java
@@ -32,9 +32,9 @@
 import androidx.annotation.VisibleForTesting;
 
 import com.android.internal.statusbar.StatusBarIcon;
-import com.android.systemui.DemoMode;
 import com.android.systemui.Dependency;
 import com.android.systemui.R;
+import com.android.systemui.demomode.DemoModeCommandReceiver;
 import com.android.systemui.plugins.DarkIconDispatcher;
 import com.android.systemui.plugins.DarkIconDispatcher.DarkReceiver;
 import com.android.systemui.statusbar.CommandQueue;
@@ -86,7 +86,7 @@
     static ArraySet<String> getIconHideList(Context context, String hideListStr) {
         ArraySet<String> ret = new ArraySet<>();
         String[] hideList = hideListStr == null
-            ? context.getResources().getStringArray(R.array.config_statusBarIconBlackList)
+            ? context.getResources().getStringArray(R.array.config_statusBarIconsToExclude)
             : hideListStr.split(",");
         for (String slot : hideList) {
             if (!TextUtils.isEmpty(slot)) {
@@ -198,7 +198,7 @@
     /**
      * Turns info from StatusBarIconController into ImageViews in a ViewGroup.
      */
-    public static class IconManager implements DemoMode {
+    class IconManager implements DemoModeCommandReceiver {
         protected final ViewGroup mGroup;
         protected final Context mContext;
         protected final int mIconSize;
@@ -390,18 +390,24 @@
                 return;
             }
 
-            if (command.equals(COMMAND_EXIT)) {
-                if (mDemoStatusIcons != null) {
-                    mDemoStatusIcons.dispatchDemoCommand(command, args);
-                    exitDemoMode();
-                }
+            mDemoStatusIcons.dispatchDemoCommand(command, args);
+        }
+
+        @Override
+        public void onDemoModeStarted() {
+            mIsInDemoMode = true;
+            if (mDemoStatusIcons == null) {
+                mDemoStatusIcons = createDemoStatusIcons();
+            }
+            mDemoStatusIcons.onDemoModeStarted();
+        }
+
+        @Override
+        public void onDemoModeFinished() {
+            if (mDemoStatusIcons != null) {
+                mDemoStatusIcons.onDemoModeFinished();
+                exitDemoMode();
                 mIsInDemoMode = false;
-            } else {
-                if (mDemoStatusIcons == null) {
-                    mIsInDemoMode = true;
-                    mDemoStatusIcons = createDemoStatusIcons();
-                }
-                mDemoStatusIcons.dispatchDemoCommand(command, args);
             }
         }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconControllerImpl.java
index 21e1d31..2870152 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconControllerImpl.java
@@ -29,6 +29,9 @@
 import com.android.systemui.Dependency;
 import com.android.systemui.Dumpable;
 import com.android.systemui.R;
+import com.android.systemui.dagger.SysUISingleton;
+import com.android.systemui.demomode.DemoMode;
+import com.android.systemui.demomode.DemoModeController;
 import com.android.systemui.statusbar.CommandQueue;
 import com.android.systemui.statusbar.StatusIconDisplayable;
 import com.android.systemui.statusbar.phone.StatusBarSignalPolicy.MobileIconState;
@@ -45,31 +48,28 @@
 import java.util.List;
 
 import javax.inject.Inject;
-import javax.inject.Singleton;
 
 /**
  * Receives the callbacks from CommandQueue related to icons and tracks the state of
  * all the icons. Dispatches this state to any IconManagers that are currently
  * registered with it.
  */
-@Singleton
+@SysUISingleton
 public class StatusBarIconControllerImpl extends StatusBarIconList implements Tunable,
-        ConfigurationListener, Dumpable, CommandQueue.Callbacks, StatusBarIconController {
+        ConfigurationListener, Dumpable, CommandQueue.Callbacks, StatusBarIconController, DemoMode {
 
     private static final String TAG = "StatusBarIconController";
 
     private final ArrayList<IconManager> mIconGroups = new ArrayList<>();
     private final ArraySet<String> mIconHideList = new ArraySet<>();
 
-    // Points to light or dark context depending on the... context?
     private Context mContext;
-    private Context mLightContext;
-    private Context mDarkContext;
-
-    private boolean mIsDark = false;
 
     @Inject
-    public StatusBarIconControllerImpl(Context context, CommandQueue commandQueue) {
+    public StatusBarIconControllerImpl(
+            Context context,
+            CommandQueue commandQueue,
+            DemoModeController demoModeController) {
         super(context.getResources().getStringArray(
                 com.android.internal.R.array.config_statusBarIcons));
         Dependency.get(ConfigurationController.class).addCallback(this);
@@ -80,6 +80,7 @@
 
         commandQueue.addCallback(this);
         Dependency.get(TunerService.class).addTunable(this, ICON_HIDE_LIST);
+        demoModeController.addCallback(this);
     }
 
     @Override
@@ -339,6 +340,25 @@
         super.dump(pw);
     }
 
+    @Override
+    public void onDemoModeStarted() {
+        for (IconManager manager : mIconGroups) {
+            if (manager.isDemoable()) {
+                manager.onDemoModeStarted();
+            }
+        }
+    }
+
+    @Override
+    public void onDemoModeFinished() {
+        for (IconManager manager : mIconGroups) {
+            if (manager.isDemoable()) {
+                manager.onDemoModeFinished();
+            }
+        }
+    }
+
+    @Override
     public void dispatchDemoCommand(String command, Bundle args) {
         for (IconManager manager : mIconGroups) {
             if (manager.isDemoable()) {
@@ -348,6 +368,13 @@
     }
 
     @Override
+    public List<String> demoCommands() {
+        List<String> s = new ArrayList<>();
+        s.add(DemoMode.COMMAND_STATUS);
+        return s;
+    }
+
+    @Override
     public void onDensityOrFontScaleChanged() {
         loadDimens();
     }
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 81d0699..777bf3f7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
@@ -47,14 +47,18 @@
 import com.android.settingslib.animation.AppearAnimationUtils;
 import com.android.systemui.DejankUtils;
 import com.android.systemui.SystemUIFactory;
+import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dock.DockManager;
 import com.android.systemui.keyguard.DismissCallbackRegistry;
+import com.android.systemui.keyguard.FaceAuthScreenBrightnessController;
+import com.android.systemui.navigationbar.NavigationModeController;
 import com.android.systemui.plugins.FalsingManager;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.shared.system.QuickStepContract;
 import com.android.systemui.shared.system.SysUiStatsLog;
 import com.android.systemui.statusbar.CrossFadeHelper;
 import com.android.systemui.statusbar.NotificationMediaManager;
+import com.android.systemui.statusbar.NotificationShadeWindowController;
 import com.android.systemui.statusbar.RemoteInputController;
 import com.android.systemui.statusbar.StatusBarState;
 import com.android.systemui.statusbar.SysuiStatusBarStateController;
@@ -65,9 +69,9 @@
 
 import java.io.PrintWriter;
 import java.util.ArrayList;
+import java.util.Optional;
 
 import javax.inject.Inject;
-import javax.inject.Singleton;
 
 /**
  * Manages creating, showing, hiding and resetting the keyguard within the status bar. Calls back
@@ -75,7 +79,7 @@
  * which is in turn, reported to this class by the current
  * {@link com.android.keyguard.KeyguardViewBase}.
  */
-@Singleton
+@SysUISingleton
 public class StatusBarKeyguardViewManager implements RemoteInputController.Callback,
         StatusBarStateController.StateListener, ConfigurationController.ConfigurationListener,
         PanelExpansionListener, NavigationModeController.ModeChangedListener,
@@ -101,6 +105,7 @@
     private final ConfigurationController mConfigurationController;
     private final NavigationModeController mNavigationModeController;
     private final NotificationShadeWindowController mNotificationShadeWindowController;
+    private final Optional<FaceAuthScreenBrightnessController> mFaceAuthScreenBrightnessController;
     private final BouncerExpansionCallback mExpansionCallback = new BouncerExpansionCallback() {
         @Override
         public void onFullyShown() {
@@ -210,6 +215,7 @@
             DockManager dockManager,
             NotificationShadeWindowController notificationShadeWindowController,
             KeyguardStateController keyguardStateController,
+            Optional<FaceAuthScreenBrightnessController> faceAuthScreenBrightnessController,
             NotificationMediaManager notificationMediaManager) {
         mContext = context;
         mViewMediatorCallback = callback;
@@ -222,6 +228,7 @@
         mKeyguardUpdateManager = keyguardUpdateMonitor;
         mStatusBarStateController = sysuiStatusBarStateController;
         mDockManager = dockManager;
+        mFaceAuthScreenBrightnessController = faceAuthScreenBrightnessController;
     }
 
     @Override
@@ -246,6 +253,11 @@
         notificationPanelViewController.addExpansionListener(this);
         mBypassController = bypassController;
         mNotificationContainer = notificationContainer;
+        mFaceAuthScreenBrightnessController.ifPresent((it) -> {
+            View overlay = new View(mContext);
+            container.addView(overlay);
+            it.attach(overlay);
+        });
 
         registerListeners();
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java
index 4de6484..de11c90 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java
@@ -49,7 +49,7 @@
 import com.android.systemui.EventLogTags;
 import com.android.systemui.assist.AssistManager;
 import com.android.systemui.bubbles.BubbleController;
-import com.android.systemui.dagger.qualifiers.Background;
+import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.dagger.qualifiers.UiBackground;
 import com.android.systemui.plugins.ActivityStarter;
@@ -73,14 +73,13 @@
 import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProvider;
 import com.android.systemui.statusbar.notification.logging.NotificationLogger;
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
-import com.android.systemui.statusbar.notification.row.OnDismissCallback;
+import com.android.systemui.statusbar.notification.row.OnUserInteractionCallback;
 import com.android.systemui.statusbar.policy.HeadsUpUtil;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
 
 import java.util.concurrent.Executor;
 
 import javax.inject.Inject;
-import javax.inject.Singleton;
 
 import dagger.Lazy;
 
@@ -93,7 +92,6 @@
 
     private final CommandQueue mCommandQueue;
     private final Handler mMainThreadHandler;
-    private final Handler mBackgroundHandler;
     private final Executor mUiBgExecutor;
 
     private final NotificationEntryManager mEntryManager;
@@ -126,7 +124,7 @@
     private final NotificationPresenter mPresenter;
     private final NotificationPanelViewController mNotificationPanel;
     private final ActivityLaunchAnimator mActivityLaunchAnimator;
-    private final OnDismissCallback mOnDismissCallback;
+    private final OnUserInteractionCallback mOnUserInteractionCallback;
 
     private boolean mIsCollapsingToShowActivityOverLockscreen;
 
@@ -134,7 +132,6 @@
             Context context,
             CommandQueue commandQueue,
             Handler mainThreadHandler,
-            Handler backgroundHandler,
             Executor uiBgExecutor,
             NotificationEntryManager entryManager,
             NotifPipeline notifPipeline,
@@ -161,7 +158,7 @@
             FeatureFlags featureFlags,
             MetricsLogger metricsLogger,
             StatusBarNotificationActivityStarterLogger logger,
-            OnDismissCallback onDismissCallback,
+            OnUserInteractionCallback onUserInteractionCallback,
 
             StatusBar statusBar,
             NotificationPresenter presenter,
@@ -170,7 +167,6 @@
         mContext = context;
         mCommandQueue = commandQueue;
         mMainThreadHandler = mainThreadHandler;
-        mBackgroundHandler = backgroundHandler;
         mUiBgExecutor = uiBgExecutor;
         mEntryManager = entryManager;
         mNotifPipeline = notifPipeline;
@@ -197,7 +193,7 @@
         mFeatureFlags = featureFlags;
         mMetricsLogger = metricsLogger;
         mLogger = logger;
-        mOnDismissCallback = onDismissCallback;
+        mOnUserInteractionCallback = onUserInteractionCallback;
 
         // TODO: use KeyguardStateController#isOccluded to remove this dependency
         mStatusBar = statusBar;
@@ -307,7 +303,7 @@
             mStatusBarKeyguardViewManager.addAfterKeyguardGoneRunnable(runnable);
             mShadeController.collapsePanel();
         } else {
-            mBackgroundHandler.postAtFrontOfQueue(runnable);
+            runnable.run();
         }
         return !mNotificationPanel.isFullyCollapsed();
     }
@@ -579,10 +575,10 @@
                 // To avoid lags we're only performing the remove
                 // after the shade was collapsed
                 mShadeController.addPostCollapseAction(
-                        () -> mOnDismissCallback.onDismiss(entry, REASON_CLICK)
+                        () -> mOnUserInteractionCallback.onDismiss(entry, REASON_CLICK)
                 );
             } else {
-                mOnDismissCallback.onDismiss(entry, REASON_CLICK);
+                mOnUserInteractionCallback.onDismiss(entry, REASON_CLICK);
             }
         });
     }
@@ -600,12 +596,12 @@
     /**
      * Public builder for {@link StatusBarNotificationActivityStarter}.
      */
-    @Singleton
+    @SysUISingleton
     public static class Builder {
         private final Context mContext;
         private final CommandQueue mCommandQueue;
         private final Handler mMainThreadHandler;
-        private final Handler mBackgroundHandler;
+
         private final Executor mUiBgExecutor;
         private final NotificationEntryManager mEntryManager;
         private final NotifPipeline mNotifPipeline;
@@ -632,7 +628,7 @@
         private final FeatureFlags mFeatureFlags;
         private final MetricsLogger mMetricsLogger;
         private final StatusBarNotificationActivityStarterLogger mLogger;
-        private final OnDismissCallback mOnDismissCallback;
+        private final OnUserInteractionCallback mOnUserInteractionCallback;
 
         private StatusBar mStatusBar;
         private NotificationPresenter mNotificationPresenter;
@@ -644,7 +640,6 @@
                 Context context,
                 CommandQueue commandQueue,
                 @Main Handler mainThreadHandler,
-                @Background Handler backgroundHandler,
                 @UiBackground Executor uiBgExecutor,
                 NotificationEntryManager entryManager,
                 NotifPipeline notifPipeline,
@@ -671,12 +666,11 @@
                 FeatureFlags featureFlags,
                 MetricsLogger metricsLogger,
                 StatusBarNotificationActivityStarterLogger logger,
-                OnDismissCallback onDismissCallback) {
+                OnUserInteractionCallback onUserInteractionCallback) {
 
             mContext = context;
             mCommandQueue = commandQueue;
             mMainThreadHandler = mainThreadHandler;
-            mBackgroundHandler = backgroundHandler;
             mUiBgExecutor = uiBgExecutor;
             mEntryManager = entryManager;
             mNotifPipeline = notifPipeline;
@@ -703,7 +697,7 @@
             mFeatureFlags = featureFlags;
             mMetricsLogger = metricsLogger;
             mLogger = logger;
-            mOnDismissCallback = onDismissCallback;
+            mOnUserInteractionCallback = onUserInteractionCallback;
         }
 
         /** Sets the status bar to use as {@link StatusBar}. */
@@ -734,7 +728,6 @@
                     mContext,
                     mCommandQueue,
                     mMainThreadHandler,
-                    mBackgroundHandler,
                     mUiBgExecutor,
                     mEntryManager,
                     mNotifPipeline,
@@ -761,7 +754,7 @@
                     mFeatureFlags,
                     mMetricsLogger,
                     mLogger,
-                    mOnDismissCallback,
+                    mOnUserInteractionCallback,
 
                     mStatusBar,
                     mNotificationPresenter,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java
index 45f0c49..67adaaa 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java
@@ -30,7 +30,6 @@
 import android.util.Log;
 import android.util.Slog;
 import android.view.View;
-import android.view.ViewGroup;
 import android.view.accessibility.AccessibilityManager;
 import android.widget.TextView;
 
@@ -53,6 +52,7 @@
 import com.android.systemui.statusbar.NotificationMediaManager;
 import com.android.systemui.statusbar.NotificationPresenter;
 import com.android.systemui.statusbar.NotificationRemoteInputManager;
+import com.android.systemui.statusbar.NotificationShadeWindowController;
 import com.android.systemui.statusbar.NotificationViewHierarchyManager;
 import com.android.systemui.statusbar.StatusBarState;
 import com.android.systemui.statusbar.SysuiStatusBarStateController;
@@ -61,9 +61,9 @@
 import com.android.systemui.statusbar.notification.DynamicPrivacyController;
 import com.android.systemui.statusbar.notification.NotificationEntryListener;
 import com.android.systemui.statusbar.notification.NotificationEntryManager;
-import com.android.systemui.statusbar.notification.VisualStabilityManager;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 import com.android.systemui.statusbar.notification.collection.inflation.NotificationRowBinderImpl;
+import com.android.systemui.statusbar.notification.collection.legacy.VisualStabilityManager;
 import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProvider;
 import com.android.systemui.statusbar.notification.interruption.NotificationInterruptSuppressor;
 import com.android.systemui.statusbar.notification.row.ActivatableNotificationView;
@@ -71,7 +71,7 @@
 import com.android.systemui.statusbar.notification.row.NotificationGutsManager;
 import com.android.systemui.statusbar.notification.row.NotificationGutsManager.OnSettingsClickListener;
 import com.android.systemui.statusbar.notification.row.NotificationInfo.CheckSaveListener;
-import com.android.systemui.statusbar.notification.stack.NotificationListContainer;
+import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController;
 import com.android.systemui.statusbar.phone.LockscreenGestureLogger.LockscreenUiEvent;
 import com.android.systemui.statusbar.policy.ConfigurationController;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
@@ -133,7 +133,7 @@
             NotificationPanelViewController panel,
             HeadsUpManagerPhone headsUp,
             NotificationShadeWindowView statusBarWindow,
-            ViewGroup stackScroller,
+            NotificationStackScrollLayoutController stackScrollerController,
             DozeScrimController dozeScrimController,
             ScrimController scrimController,
             ActivityLaunchAnimator activityLaunchAnimator,
@@ -155,7 +155,7 @@
         mStatusBar = statusBar;
         mShadeController = shadeController;
         mCommandQueue = commandQueue;
-        mAboveShelfObserver = new AboveShelfObserver(stackScroller);
+        mAboveShelfObserver = new AboveShelfObserver(stackScrollerController.getView());
         mActivityLaunchAnimator = activityLaunchAnimator;
         mAboveShelfObserver.setListener(statusBarWindow.findViewById(
                 R.id.notification_container_parent));
@@ -190,7 +190,6 @@
         remoteInputManager.getController().addCallback(
                 Dependency.get(NotificationShadeWindowController.class));
 
-        NotificationListContainer notifListContainer = (NotificationListContainer) stackScroller;
         initController.addPostInitTask(() -> {
             NotificationEntryListener notificationEntryListener = new NotificationEntryListener() {
                 @Override
@@ -207,7 +206,8 @@
                 }
             };
 
-            mViewHierarchyManager.setUpWithPresenter(this, notifListContainer);
+            mViewHierarchyManager.setUpWithPresenter(this,
+                    stackScrollerController.getNotificationListContainer());
             mEntryManager.setUpWithPresenter(this);
             mEntryManager.addNotificationEntryListener(notificationEntryListener);
             mEntryManager.addNotificationLifetimeExtender(mHeadsUpManager);
@@ -217,9 +217,9 @@
             notificationInterruptStateProvider.addSuppressor(mInterruptSuppressor);
             mLockscreenUserManager.setUpWithPresenter(this);
             mMediaManager.setUpWithPresenter(this);
-            mVisualStabilityManager.setUpWithPresenter(this);
             mGutsManager.setUpWithPresenter(this,
-                    notifListContainer, mCheckSaveListener, mOnSettingsClickListener);
+                    stackScrollerController.getNotificationListContainer(), mCheckSaveListener,
+                    mOnSettingsClickListener);
             // ForegroundServiceNotificationListener adds its listener in its constructor
             // but we need to request it here in order for it to be instantiated.
             // TODO: figure out how to do this correctly once Dependency.get() is gone.
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarRemoteInputCallback.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarRemoteInputCallback.java
index 72395e6..8a89429 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarRemoteInputCallback.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarRemoteInputCallback.java
@@ -34,6 +34,7 @@
 import android.view.ViewParent;
 
 import com.android.systemui.ActivityIntentHelper;
+import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.plugins.ActivityStarter;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.statusbar.ActionClickLogger;
@@ -49,11 +50,10 @@
 import com.android.systemui.statusbar.policy.KeyguardStateController;
 
 import javax.inject.Inject;
-import javax.inject.Singleton;
 
 /**
  */
-@Singleton
+@SysUISingleton
 public class StatusBarRemoteInputCallback implements Callback, Callbacks,
         StatusBarStateController.StateListener {
 
@@ -244,9 +244,10 @@
 
     @Override
     public boolean handleRemoteViewClick(View view, PendingIntent pendingIntent,
+            boolean appRequestedAuth,
             NotificationRemoteInputManager.ClickHandler defaultHandler) {
         final boolean isActivity = pendingIntent.isActivity();
-        if (isActivity) {
+        if (isActivity || appRequestedAuth) {
             mActionClickLogger.logWaitingToCloseKeyguard(pendingIntent);
             final boolean afterKeyguardGone = mActivityIntentHelper.wouldLaunchResolverActivity(
                     pendingIntent.getIntent(), mLockscreenUserManager.getCurrentUserId());
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarTouchableRegionManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarTouchableRegionManager.java
index 9c7f490..b859250 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarTouchableRegionManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarTouchableRegionManager.java
@@ -32,6 +32,8 @@
 import com.android.systemui.Dumpable;
 import com.android.systemui.R;
 import com.android.systemui.ScreenDecorations;
+import com.android.systemui.dagger.SysUISingleton;
+import com.android.systemui.statusbar.NotificationShadeWindowController;
 import com.android.systemui.statusbar.policy.ConfigurationController;
 import com.android.systemui.statusbar.policy.ConfigurationController.ConfigurationListener;
 import com.android.systemui.statusbar.policy.OnHeadsUpChangedListener;
@@ -40,14 +42,13 @@
 import java.io.PrintWriter;
 
 import javax.inject.Inject;
-import javax.inject.Singleton;
 
 /**
  * Manages what parts of the status bar are touchable. Clients are primarily UI that display in the
  * status bar even though the UI doesn't look like part of the status bar. Currently this consists
  * of HeadsUpNotifications.
  */
-@Singleton
+@SysUISingleton
 public final class StatusBarTouchableRegionManager implements Dumpable {
     private static final String TAG = "TouchableRegionManager";
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowController.java
index d4e1aa4..2f7278b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowController.java
@@ -29,16 +29,16 @@
 import android.view.ViewGroup;
 import android.view.WindowManager;
 
+import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.statusbar.SuperStatusBarViewFactory;
 
 import javax.inject.Inject;
-import javax.inject.Singleton;
 
 /**
  * Encapsulates all logic for the status bar window state management.
  */
-@Singleton
+@SysUISingleton
 public class StatusBarWindowController {
     private static final String TAG = "StatusBarWindowController";
     private static final boolean DEBUG = false;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneDependenciesModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneDependenciesModule.java
index 69c6814..79d72b3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneDependenciesModule.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneDependenciesModule.java
@@ -16,12 +16,11 @@
 
 package com.android.systemui.statusbar.phone.dagger;
 
+import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.statusbar.notification.row.RowContentBindStage;
 import com.android.systemui.statusbar.phone.NotificationGroupAlertTransferHelper;
 import com.android.systemui.statusbar.phone.StatusBar;
 
-import javax.inject.Singleton;
-
 import dagger.Module;
 import dagger.Provides;
 
@@ -34,7 +33,7 @@
 public interface StatusBarPhoneDependenciesModule {
 
     /** */
-    @Singleton
+    @SysUISingleton
     @Provides
     static NotificationGroupAlertTransferHelper provideNotificationGroupAlertTransferHelper(
             RowContentBindStage bindStage) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneModule.java
index 02e0312..024a776 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneModule.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneModule.java
@@ -33,25 +33,27 @@
 import com.android.systemui.broadcast.BroadcastDispatcher;
 import com.android.systemui.bubbles.BubbleController;
 import com.android.systemui.colorextraction.SysuiColorExtractor;
+import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dagger.qualifiers.UiBackground;
+import com.android.systemui.demomode.DemoModeController;
 import com.android.systemui.keyguard.DismissCallbackRegistry;
 import com.android.systemui.keyguard.KeyguardViewMediator;
 import com.android.systemui.keyguard.ScreenLifecycle;
 import com.android.systemui.keyguard.WakefulnessLifecycle;
-import com.android.systemui.plugins.DarkIconDispatcher;
+import com.android.systemui.navigationbar.NavigationBarController;
 import com.android.systemui.plugins.FalsingManager;
 import com.android.systemui.plugins.PluginDependencyProvider;
 import com.android.systemui.recents.Recents;
 import com.android.systemui.recents.ScreenPinningRequest;
 import com.android.systemui.shared.plugins.PluginManager;
-import com.android.systemui.stackdivider.Divider;
+import com.android.systemui.stackdivider.SplitScreenController;
 import com.android.systemui.statusbar.CommandQueue;
 import com.android.systemui.statusbar.KeyguardIndicationController;
-import com.android.systemui.statusbar.NavigationBarController;
 import com.android.systemui.statusbar.NotificationLockscreenUserManager;
 import com.android.systemui.statusbar.NotificationMediaManager;
 import com.android.systemui.statusbar.NotificationRemoteInputManager;
 import com.android.systemui.statusbar.NotificationShadeDepthController;
+import com.android.systemui.statusbar.NotificationShadeWindowController;
 import com.android.systemui.statusbar.NotificationViewHierarchyManager;
 import com.android.systemui.statusbar.PulseExpansionHandler;
 import com.android.systemui.statusbar.SuperStatusBarViewFactory;
@@ -59,7 +61,7 @@
 import com.android.systemui.statusbar.VibratorHelper;
 import com.android.systemui.statusbar.notification.DynamicPrivacyController;
 import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator;
-import com.android.systemui.statusbar.notification.VisualStabilityManager;
+import com.android.systemui.statusbar.notification.collection.legacy.VisualStabilityManager;
 import com.android.systemui.statusbar.notification.init.NotificationsController;
 import com.android.systemui.statusbar.notification.interruption.BypassHeadsUpNotifier;
 import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProvider;
@@ -79,7 +81,7 @@
 import com.android.systemui.statusbar.phone.LockscreenLockIconController;
 import com.android.systemui.statusbar.phone.LockscreenWallpaper;
 import com.android.systemui.statusbar.phone.NotificationGroupManager;
-import com.android.systemui.statusbar.phone.NotificationShadeWindowController;
+import com.android.systemui.statusbar.phone.NotificationIconAreaController;
 import com.android.systemui.statusbar.phone.PhoneStatusBarPolicy;
 import com.android.systemui.statusbar.phone.ScrimController;
 import com.android.systemui.statusbar.phone.ShadeController;
@@ -104,7 +106,6 @@
 
 import javax.inject.Named;
 import javax.inject.Provider;
-import javax.inject.Singleton;
 
 import dagger.Lazy;
 import dagger.Module;
@@ -119,7 +120,7 @@
      * Provides our instance of StatusBar which is considered optional.
      */
     @Provides
-    @Singleton
+    @SysUISingleton
     static StatusBar provideStatusBar(
             Context context,
             NotificationsController notificationsController,
@@ -179,7 +180,7 @@
             Optional<Recents> recentsOptional,
             Provider<StatusBarComponent.Builder> statusBarComponentBuilder,
             PluginManager pluginManager,
-            Optional<Divider> dividerOptional,
+            Optional<SplitScreenController> splitScreenControllerOptional,
             LightsOutNotifController lightsOutNotifController,
             StatusBarNotificationActivityStarter.Builder
                     statusBarNotificationActivityStarterBuilder,
@@ -188,7 +189,6 @@
             StatusBarKeyguardViewManager statusBarKeyguardViewManager,
             ViewMediatorCallback viewMediatorCallback,
             InitController initController,
-            DarkIconDispatcher darkIconDispatcher,
             @Named(TIME_TICK_HANDLER_NAME) Handler timeTickHandler,
             PluginDependencyProvider pluginDependencyProvider,
             KeyguardDismissUtil keyguardDismissUtil,
@@ -196,9 +196,11 @@
             UserInfoControllerImpl userInfoControllerImpl,
             PhoneStatusBarPolicy phoneStatusBarPolicy,
             KeyguardIndicationController keyguardIndicationController,
+            DemoModeController demoModeController,
             Lazy<NotificationShadeDepthController> notificationShadeDepthController,
             DismissCallbackRegistry dismissCallbackRegistry,
-            StatusBarTouchableRegionManager statusBarTouchableRegionManager) {
+            StatusBarTouchableRegionManager statusBarTouchableRegionManager,
+            NotificationIconAreaController notificationIconAreaController) {
         return new StatusBar(
                 context,
                 notificationsController,
@@ -258,7 +260,7 @@
                 recentsOptional,
                 statusBarComponentBuilder,
                 pluginManager,
-                dividerOptional,
+                splitScreenControllerOptional,
                 lightsOutNotifController,
                 statusBarNotificationActivityStarterBuilder,
                 shadeController,
@@ -266,7 +268,6 @@
                 statusBarKeyguardViewManager,
                 viewMediatorCallback,
                 initController,
-                darkIconDispatcher,
                 timeTickHandler,
                 pluginDependencyProvider,
                 keyguardDismissUtil,
@@ -275,7 +276,9 @@
                 phoneStatusBarPolicy,
                 keyguardIndicationController,
                 dismissCallbackRegistry,
+                demoModeController,
                 notificationShadeDepthController,
-                statusBarTouchableRegionManager);
+                statusBarTouchableRegionManager,
+                notificationIconAreaController);
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/AccessibilityController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/AccessibilityController.java
index ebfdb3f..ad49c79 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/AccessibilityController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/AccessibilityController.java
@@ -19,16 +19,17 @@
 import android.content.Context;
 import android.view.accessibility.AccessibilityManager;
 
+import com.android.systemui.dagger.SysUISingleton;
+
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
 import java.util.ArrayList;
 
 import javax.inject.Inject;
-import javax.inject.Singleton;
 
 /**
  */
-@Singleton
+@SysUISingleton
 public class AccessibilityController implements
         AccessibilityManager.AccessibilityStateChangeListener,
         AccessibilityManager.TouchExplorationStateChangeListener {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/AccessibilityManagerWrapper.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/AccessibilityManagerWrapper.java
index 1395e13..d38284a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/AccessibilityManagerWrapper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/AccessibilityManagerWrapper.java
@@ -19,13 +19,16 @@
 import android.view.accessibility.AccessibilityManager;
 import android.view.accessibility.AccessibilityManager.AccessibilityServicesStateChangeListener;
 
+import androidx.annotation.NonNull;
+
+import com.android.systemui.dagger.SysUISingleton;
+
 import javax.inject.Inject;
-import javax.inject.Singleton;
 
 /**
  * For mocking because AccessibilityManager is final for some reason...
  */
-@Singleton
+@SysUISingleton
 public class AccessibilityManagerWrapper implements
         CallbackController<AccessibilityServicesStateChangeListener> {
 
@@ -37,12 +40,12 @@
     }
 
     @Override
-    public void addCallback(AccessibilityServicesStateChangeListener listener) {
+    public void addCallback(@NonNull AccessibilityServicesStateChangeListener listener) {
         mAccessibilityManager.addAccessibilityServicesStateChangeListener(listener, null);
     }
 
     @Override
-    public void removeCallback(AccessibilityServicesStateChangeListener listener) {
+    public void removeCallback(@NonNull AccessibilityServicesStateChangeListener listener) {
         mAccessibilityManager.removeAccessibilityServicesStateChangeListener(listener);
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryController.java
index 673549a..06e4731 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryController.java
@@ -18,8 +18,8 @@
 
 import android.annotation.Nullable;
 
-import com.android.systemui.DemoMode;
 import com.android.systemui.Dumpable;
+import com.android.systemui.demomode.DemoMode;
 import com.android.systemui.statusbar.policy.BatteryController.BatteryStateChangeCallback;
 
 import java.io.FileDescriptor;
@@ -79,6 +79,13 @@
     default void setReverseState(boolean isReverse) {}
 
     /**
+     * Returns {@code true} if extreme battery saver is on.
+     */
+    default boolean isExtremeSaverOn() {
+        return false;
+    }
+
+    /**
      * A listener that will be notified whenever a change in battery level or power save mode has
      * occurred.
      */
@@ -92,6 +99,9 @@
 
         default void onReverseChanged(boolean isReverse, int level, String name) {
         }
+
+        default void onExtremeBatterySaverChanged(boolean isExtreme) {
+        }
     }
 
     /**
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryControllerImpl.java
index 88a6263..57ac85e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryControllerImpl.java
@@ -27,6 +27,7 @@
 import android.os.PowerSaveState;
 import android.util.Log;
 
+import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 
 import com.android.internal.annotations.VisibleForTesting;
@@ -34,22 +35,25 @@
 import com.android.settingslib.fuelgauge.Estimate;
 import com.android.settingslib.utils.PowerUtil;
 import com.android.systemui.broadcast.BroadcastDispatcher;
+import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dagger.qualifiers.Background;
 import com.android.systemui.dagger.qualifiers.Main;
+import com.android.systemui.demomode.DemoMode;
+import com.android.systemui.demomode.DemoModeController;
 import com.android.systemui.power.EnhancedEstimates;
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
 import java.util.ArrayList;
+import java.util.List;
 
 import javax.inject.Inject;
-import javax.inject.Singleton;
 
 /**
  * Default implementation of a {@link BatteryController}. This controller monitors for battery
  * level change events that are broadcasted by the system.
  */
-@Singleton
+@SysUISingleton
 public class BatteryControllerImpl extends BroadcastReceiver implements BatteryController {
     private static final String TAG = "BatteryController";
 
@@ -63,6 +67,7 @@
             mChangeCallbacks = new ArrayList<>();
     private final ArrayList<EstimateFetchCompletion> mFetchCallbacks = new ArrayList<>();
     private final PowerManager mPowerManager;
+    private final DemoModeController mDemoModeController;
     private final Handler mMainHandler;
     private final Handler mBgHandler;
     protected final Context mContext;
@@ -82,15 +87,21 @@
 
     @VisibleForTesting
     @Inject
-    public BatteryControllerImpl(Context context, EnhancedEstimates enhancedEstimates,
-            PowerManager powerManager, BroadcastDispatcher broadcastDispatcher,
-            @Main Handler mainHandler, @Background Handler bgHandler) {
+    public BatteryControllerImpl(
+            Context context,
+            EnhancedEstimates enhancedEstimates,
+            PowerManager powerManager,
+            BroadcastDispatcher broadcastDispatcher,
+            DemoModeController demoModeController,
+            @Main Handler mainHandler,
+            @Background Handler bgHandler) {
         mContext = context;
         mMainHandler = mainHandler;
         mBgHandler = bgHandler;
         mPowerManager = powerManager;
         mEstimates = enhancedEstimates;
         mBroadcastDispatcher = broadcastDispatcher;
+        mDemoModeController = demoModeController;
     }
 
     private void registerReceiver() {
@@ -114,6 +125,7 @@
                 onReceive(mContext, intent);
             }
         }
+        mDemoModeController.addCallback(this);
         updatePowerSave();
         updateEstimate();
     }
@@ -134,7 +146,7 @@
     }
 
     @Override
-    public void addCallback(BatteryController.BatteryStateChangeCallback cb) {
+    public void addCallback(@NonNull BatteryController.BatteryStateChangeCallback cb) {
         synchronized (mChangeCallbacks) {
             mChangeCallbacks.add(cb);
         }
@@ -144,7 +156,7 @@
     }
 
     @Override
-    public void removeCallback(BatteryController.BatteryStateChangeCallback cb) {
+    public void removeCallback(@NonNull BatteryController.BatteryStateChangeCallback cb) {
         synchronized (mChangeCallbacks) {
             mChangeCallbacks.remove(cb);
         }
@@ -325,32 +337,43 @@
         }
     }
 
-    private boolean mDemoMode;
-
     @Override
     public void dispatchDemoCommand(String command, Bundle args) {
-        if (!mDemoMode && command.equals(COMMAND_ENTER)) {
-            mDemoMode = true;
-            mBroadcastDispatcher.unregisterReceiver(this);
-        } else if (mDemoMode && command.equals(COMMAND_EXIT)) {
-            mDemoMode = false;
-            registerReceiver();
-            updatePowerSave();
-        } else if (mDemoMode && command.equals(COMMAND_BATTERY)) {
-            String level = args.getString("level");
-            String plugged = args.getString("plugged");
-            String powerSave = args.getString("powersave");
-            if (level != null) {
-                mLevel = Math.min(Math.max(Integer.parseInt(level), 0), 100);
-            }
-            if (plugged != null) {
-                mPluggedIn = Boolean.parseBoolean(plugged);
-            }
-            if (powerSave != null) {
-                mPowerSave = powerSave.equals("true");
-                firePowerSaveChanged();
-            }
-            fireBatteryLevelChanged();
+        if (!mDemoModeController.isInDemoMode()) {
+            return;
         }
+
+        String level = args.getString("level");
+        String plugged = args.getString("plugged");
+        String powerSave = args.getString("powersave");
+        if (level != null) {
+            mLevel = Math.min(Math.max(Integer.parseInt(level), 0), 100);
+        }
+        if (plugged != null) {
+            mPluggedIn = Boolean.parseBoolean(plugged);
+        }
+        if (powerSave != null) {
+            mPowerSave = powerSave.equals("true");
+            firePowerSaveChanged();
+        }
+        fireBatteryLevelChanged();
+    }
+
+    @Override
+    public List<String> demoCommands() {
+        List<String> s = new ArrayList<>();
+        s.add(DemoMode.COMMAND_BATTERY);
+        return s;
+    }
+
+    @Override
+    public void onDemoModeStarted() {
+        mBroadcastDispatcher.unregisterReceiver(this);
+    }
+
+    @Override
+    public void onDemoModeFinished() {
+        registerReceiver();
+        updatePowerSave();
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothControllerImpl.java
index 0fc3d84..33b1a4a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothControllerImpl.java
@@ -29,11 +29,14 @@
 import android.os.UserManager;
 import android.util.Log;
 
+import androidx.annotation.NonNull;
+
 import com.android.settingslib.bluetooth.BluetoothCallback;
 import com.android.settingslib.bluetooth.CachedBluetoothDevice;
 import com.android.settingslib.bluetooth.LocalBluetoothManager;
 import com.android.settingslib.bluetooth.LocalBluetoothProfile;
 import com.android.settingslib.bluetooth.LocalBluetoothProfileManager;
+import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dagger.qualifiers.Background;
 import com.android.systemui.dagger.qualifiers.Main;
 
@@ -46,11 +49,10 @@
 import java.util.WeakHashMap;
 
 import javax.inject.Inject;
-import javax.inject.Singleton;
 
 /**
  */
-@Singleton
+@SysUISingleton
 public class BluetoothControllerImpl implements BluetoothController, BluetoothCallback,
         CachedBluetoothDevice.Callback, LocalBluetoothProfileManager.ServiceListener {
     private static final String TAG = "BluetoothController";
@@ -150,13 +152,13 @@
     }
 
     @Override
-    public void addCallback(Callback cb) {
+    public void addCallback(@NonNull Callback cb) {
         mHandler.obtainMessage(H.MSG_ADD_CALLBACK, cb).sendToTarget();
         mHandler.sendEmptyMessage(H.MSG_STATE_CHANGED);
     }
 
     @Override
-    public void removeCallback(Callback cb) {
+    public void removeCallback(@NonNull Callback cb) {
         mHandler.obtainMessage(H.MSG_REMOVE_CALLBACK, cb).sendToTarget();
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BrightnessMirrorController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BrightnessMirrorController.java
index 78111fb..a0b03e1 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BrightnessMirrorController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BrightnessMirrorController.java
@@ -123,13 +123,13 @@
     }
 
     @Override
-    public void addCallback(BrightnessMirrorListener listener) {
+    public void addCallback(@NonNull BrightnessMirrorListener listener) {
         Objects.requireNonNull(listener);
         mBrightnessMirrorListeners.add(listener);
     }
 
     @Override
-    public void removeCallback(BrightnessMirrorListener listener) {
+    public void removeCallback(@NonNull BrightnessMirrorListener listener) {
         mBrightnessMirrorListeners.remove(listener);
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/CallbackController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/CallbackController.java
index 626eef5..047ff75 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/CallbackController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/CallbackController.java
@@ -15,14 +15,19 @@
 
 package com.android.systemui.statusbar.policy;
 
+import androidx.annotation.NonNull;
 import androidx.lifecycle.Lifecycle;
 import androidx.lifecycle.Lifecycle.Event;
 import androidx.lifecycle.LifecycleEventObserver;
 import androidx.lifecycle.LifecycleOwner;
 
 public interface CallbackController<T> {
-    void addCallback(T listener);
-    void removeCallback(T listener);
+
+    /** Add a callback */
+    void addCallback(@NonNull T listener);
+
+    /** Remove a callback */
+    void removeCallback(@NonNull T listener);
 
     /**
      * Wrapper to {@link #addCallback(Object)} when a lifecycle is in the resumed state
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/CastControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/CastControllerImpl.java
index 6106f38..7bde315 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/CastControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/CastControllerImpl.java
@@ -31,10 +31,12 @@
 import android.util.ArrayMap;
 import android.util.Log;
 
+import androidx.annotation.NonNull;
 import androidx.annotation.VisibleForTesting;
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.systemui.R;
+import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dump.DumpManager;
 import com.android.systemui.util.Utils;
 
@@ -46,11 +48,10 @@
 import java.util.UUID;
 
 import javax.inject.Inject;
-import javax.inject.Singleton;
 
 
 /** Platform implementation of the cast controller. **/
-@Singleton
+@SysUISingleton
 public class CastControllerImpl implements CastController {
     private static final String TAG = "CastController";
     private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
@@ -95,7 +96,7 @@
     }
 
     @Override
-    public void addCallback(Callback callback) {
+    public void addCallback(@NonNull Callback callback) {
         synchronized (mCallbacks) {
             mCallbacks.add(callback);
         }
@@ -106,7 +107,7 @@
     }
 
     @Override
-    public void removeCallback(Callback callback) {
+    public void removeCallback(@NonNull Callback callback) {
         synchronized (mCallbacks) {
             mCallbacks.remove(callback);
         }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/Clock.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/Clock.java
index 120a0e3..ef35a3c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/Clock.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/Clock.java
@@ -40,11 +40,11 @@
 import android.widget.TextView;
 
 import com.android.settingslib.Utils;
-import com.android.systemui.DemoMode;
 import com.android.systemui.Dependency;
 import com.android.systemui.FontSizeUtils;
 import com.android.systemui.R;
 import com.android.systemui.broadcast.BroadcastDispatcher;
+import com.android.systemui.demomode.DemoModeCommandReceiver;
 import com.android.systemui.plugins.DarkIconDispatcher;
 import com.android.systemui.plugins.DarkIconDispatcher.DarkReceiver;
 import com.android.systemui.settings.CurrentUserTracker;
@@ -62,7 +62,10 @@
 /**
  * Digital clock for the status bar.
  */
-public class Clock extends TextView implements DemoMode, Tunable, CommandQueue.Callbacks,
+public class Clock extends TextView implements
+        DemoModeCommandReceiver,
+        Tunable,
+        CommandQueue.Callbacks,
         DarkReceiver, ConfigurationListener {
 
     public static final String CLOCK_SECONDS = "clock_seconds";
@@ -467,30 +470,35 @@
 
     @Override
     public void dispatchDemoCommand(String command, Bundle args) {
-        if (!mDemoMode && command.equals(COMMAND_ENTER)) {
-            mDemoMode = true;
-        } else if (mDemoMode && command.equals(COMMAND_EXIT)) {
-            mDemoMode = false;
-            updateClock();
-        } else if (mDemoMode && command.equals(COMMAND_CLOCK)) {
-            String millis = args.getString("millis");
-            String hhmm = args.getString("hhmm");
-            if (millis != null) {
-                mCalendar.setTimeInMillis(Long.parseLong(millis));
-            } else if (hhmm != null && hhmm.length() == 4) {
-                int hh = Integer.parseInt(hhmm.substring(0, 2));
-                int mm = Integer.parseInt(hhmm.substring(2));
-                boolean is24 = DateFormat.is24HourFormat(getContext(), mCurrentUserId);
-                if (is24) {
-                    mCalendar.set(Calendar.HOUR_OF_DAY, hh);
-                } else {
-                    mCalendar.set(Calendar.HOUR, hh);
-                }
-                mCalendar.set(Calendar.MINUTE, mm);
+        // Only registered for COMMAND_CLOCK
+        String millis = args.getString("millis");
+        String hhmm = args.getString("hhmm");
+        if (millis != null) {
+            mCalendar.setTimeInMillis(Long.parseLong(millis));
+        } else if (hhmm != null && hhmm.length() == 4) {
+            int hh = Integer.parseInt(hhmm.substring(0, 2));
+            int mm = Integer.parseInt(hhmm.substring(2));
+            boolean is24 = DateFormat.is24HourFormat(getContext(), mCurrentUserId);
+            if (is24) {
+                mCalendar.set(Calendar.HOUR_OF_DAY, hh);
+            } else {
+                mCalendar.set(Calendar.HOUR, hh);
             }
-            setText(getSmallTime());
-            setContentDescription(mContentDescriptionFormat.format(mCalendar.getTime()));
+            mCalendar.set(Calendar.MINUTE, mm);
         }
+        setText(getSmallTime());
+        setContentDescription(mContentDescriptionFormat.format(mCalendar.getTime()));
+    }
+
+    @Override
+    public void onDemoModeStarted() {
+        mDemoMode = true;
+    }
+
+    @Override
+    public void onDemoModeFinished() {
+        mDemoMode = false;
+        updateClock();
     }
 
     private final BroadcastReceiver mScreenReceiver = new BroadcastReceiver() {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/DataSaverControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/DataSaverControllerImpl.java
index 911715f..8207012 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/DataSaverControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/DataSaverControllerImpl.java
@@ -21,6 +21,8 @@
 import android.os.Looper;
 import android.os.RemoteException;
 
+import androidx.annotation.NonNull;
+
 import java.util.ArrayList;
 
 public class DataSaverControllerImpl implements DataSaverController {
@@ -41,7 +43,8 @@
         }
     }
 
-    public void addCallback(Listener listener) {
+    @Override
+    public void addCallback(@NonNull Listener listener) {
         synchronized (mListeners) {
             mListeners.add(listener);
             if (mListeners.size() == 1) {
@@ -51,7 +54,8 @@
         listener.onDataSaverChanged(isDataSaverEnabled());
     }
 
-    public void removeCallback(Listener listener) {
+    @Override
+    public void removeCallback(@NonNull Listener listener) {
         synchronized (mListeners) {
             mListeners.remove(listener);
             if (mListeners.size() == 0) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceProvisionedControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceProvisionedControllerImpl.java
index 7280a88..9b4e165 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceProvisionedControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceProvisionedControllerImpl.java
@@ -24,18 +24,20 @@
 import android.provider.Settings.Secure;
 import android.util.Log;
 
+import androidx.annotation.NonNull;
+
 import com.android.systemui.broadcast.BroadcastDispatcher;
+import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.settings.CurrentUserTracker;
 
 import java.util.ArrayList;
 
 import javax.inject.Inject;
-import javax.inject.Singleton;
 
 /**
  */
-@Singleton
+@SysUISingleton
 public class DeviceProvisionedControllerImpl extends CurrentUserTracker implements
         DeviceProvisionedController {
 
@@ -87,7 +89,7 @@
     }
 
     @Override
-    public void addCallback(DeviceProvisionedListener listener) {
+    public void addCallback(@NonNull DeviceProvisionedListener listener) {
         mListeners.add(listener);
         if (mListeners.size() == 1) {
             startListening(getCurrentUser());
@@ -97,7 +99,7 @@
     }
 
     @Override
-    public void removeCallback(DeviceProvisionedListener listener) {
+    public void removeCallback(@NonNull DeviceProvisionedListener listener) {
         mListeners.remove(listener);
         if (mListeners.size() == 0) {
             stopListening();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/ExtensionControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ExtensionControllerImpl.java
index eeef726..5011d96 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/ExtensionControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ExtensionControllerImpl.java
@@ -19,6 +19,7 @@
 import android.os.Handler;
 import android.util.ArrayMap;
 
+import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.plugins.Plugin;
 import com.android.systemui.plugins.PluginListener;
 import com.android.systemui.shared.plugins.PluginManager;
@@ -34,11 +35,10 @@
 import java.util.function.Supplier;
 
 import javax.inject.Inject;
-import javax.inject.Singleton;
 
 /**
  */
-@Singleton
+@SysUISingleton
 public class ExtensionControllerImpl implements ExtensionController {
 
     public static final int SORT_ORDER_PLUGIN  = 0;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/FlashlightControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/FlashlightControllerImpl.java
index 41ff9d10..d7c2b96 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/FlashlightControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/FlashlightControllerImpl.java
@@ -30,18 +30,21 @@
 import android.text.TextUtils;
 import android.util.Log;
 
+import androidx.annotation.NonNull;
+
+import com.android.systemui.dagger.SysUISingleton;
+
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
 import java.lang.ref.WeakReference;
 import java.util.ArrayList;
 
 import javax.inject.Inject;
-import javax.inject.Singleton;
 
 /**
  * Manages the flashlight.
  */
-@Singleton
+@SysUISingleton
 public class FlashlightControllerImpl implements FlashlightController {
 
     private static final String TAG = "FlashlightController";
@@ -123,7 +126,8 @@
         return mTorchAvailable;
     }
 
-    public void addCallback(FlashlightListener l) {
+    @Override
+    public void addCallback(@NonNull FlashlightListener l) {
         synchronized (mListeners) {
             if (mCameraId == null) {
                 tryInitCamera();
@@ -135,7 +139,8 @@
         }
     }
 
-    public void removeCallback(FlashlightListener l) {
+    @Override
+    public void removeCallback(@NonNull FlashlightListener l) {
         synchronized (mListeners) {
             cleanUpListenersLocked(l);
         }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HotspotControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HotspotControllerImpl.java
index 60ee75b..99feb18 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HotspotControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HotspotControllerImpl.java
@@ -30,7 +30,10 @@
 import android.os.UserManager;
 import android.util.Log;
 
+import androidx.annotation.NonNull;
+
 import com.android.internal.util.ConcurrentUtils;
+import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dagger.qualifiers.Background;
 import com.android.systemui.dagger.qualifiers.Main;
 
@@ -40,12 +43,11 @@
 import java.util.List;
 
 import javax.inject.Inject;
-import javax.inject.Singleton;
 
 /**
  * Controller used to retrieve information related to a hotspot.
  */
-@Singleton
+@SysUISingleton
 public class HotspotControllerImpl implements HotspotController, WifiManager.SoftApCallback {
 
     private static final String TAG = "HotspotController";
@@ -143,7 +145,7 @@
      * changes. It will immediately trigger the callback added to notify current state.
      */
     @Override
-    public void addCallback(Callback callback) {
+    public void addCallback(@NonNull Callback callback) {
         synchronized (mCallbacks) {
             if (callback == null || mCallbacks.contains(callback)) return;
             if (DEBUG) Log.d(TAG, "addCallback " + callback);
@@ -163,7 +165,7 @@
     }
 
     @Override
-    public void removeCallback(Callback callback) {
+    public void removeCallback(@NonNull Callback callback) {
         if (callback == null) return;
         if (DEBUG) Log.d(TAG, "removeCallback " + callback);
         synchronized (mCallbacks) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateControllerImpl.java
index a7f60d6..7f4eec7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateControllerImpl.java
@@ -31,6 +31,7 @@
 import com.android.keyguard.KeyguardUpdateMonitor;
 import com.android.keyguard.KeyguardUpdateMonitorCallback;
 import com.android.systemui.Dumpable;
+import com.android.systemui.dagger.SysUISingleton;
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
@@ -38,11 +39,10 @@
 import java.util.Objects;
 
 import javax.inject.Inject;
-import javax.inject.Singleton;
 
 /**
  */
-@Singleton
+@SysUISingleton
 public class KeyguardStateControllerImpl implements KeyguardStateController, Dumpable {
 
     private static final boolean DEBUG_AUTH_WITH_ADB = false;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/LocationControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/LocationControllerImpl.java
index adfc14e..0fdc80b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/LocationControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/LocationControllerImpl.java
@@ -33,12 +33,14 @@
 import android.os.UserManager;
 import android.provider.Settings;
 
+import androidx.annotation.NonNull;
 import androidx.annotation.VisibleForTesting;
 
 import com.android.systemui.BootCompleteCache;
 import com.android.systemui.appops.AppOpItem;
 import com.android.systemui.appops.AppOpsController;
 import com.android.systemui.broadcast.BroadcastDispatcher;
+import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dagger.qualifiers.Background;
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.util.Utils;
@@ -47,12 +49,11 @@
 import java.util.List;
 
 import javax.inject.Inject;
-import javax.inject.Singleton;
 
 /**
  * A controller to manage changes of location related states and update the views accordingly.
  */
-@Singleton
+@SysUISingleton
 public class LocationControllerImpl extends BroadcastReceiver implements LocationController,
         AppOpsController.Callback {
 
@@ -87,12 +88,14 @@
     /**
      * Add a callback to listen for changes in location settings.
      */
-    public void addCallback(LocationChangeCallback cb) {
+    @Override
+    public void addCallback(@NonNull LocationChangeCallback cb) {
         mHandler.obtainMessage(H.MSG_ADD_CALLBACK, cb).sendToTarget();
         mHandler.sendEmptyMessage(H.MSG_LOCATION_SETTINGS_CHANGED);
     }
 
-    public void removeCallback(LocationChangeCallback cb) {
+    @Override
+    public void removeCallback(@NonNull LocationChangeCallback cb) {
         mHandler.obtainMessage(H.MSG_REMOVE_CALLBACK, cb).sendToTarget();
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkController.java
index 95a9772..b790c92 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkController.java
@@ -22,7 +22,7 @@
 
 import com.android.settingslib.net.DataUsageController;
 import com.android.settingslib.wifi.AccessPoint;
-import com.android.systemui.DemoMode;
+import com.android.systemui.demomode.DemoMode;
 import com.android.systemui.statusbar.policy.NetworkController.SignalCallback;
 
 import java.util.List;
@@ -30,8 +30,6 @@
 public interface NetworkController extends CallbackController<SignalCallback>, DemoMode {
 
     boolean hasMobileDataFeature();
-    void addCallback(SignalCallback cb);
-    void removeCallback(SignalCallback cb);
     void setWifiEnabled(boolean enabled);
     AccessPointController getAccessPointController();
     DataUsageController getMobileDataController();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java
index 32c4aec..2253ce7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java
@@ -55,14 +55,18 @@
 import android.util.MathUtils;
 import android.util.SparseArray;
 
+import androidx.annotation.NonNull;
+
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.settingslib.net.DataUsageController;
-import com.android.systemui.DemoMode;
 import com.android.systemui.Dumpable;
 import com.android.systemui.R;
 import com.android.systemui.broadcast.BroadcastDispatcher;
+import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dagger.qualifiers.Background;
+import com.android.systemui.demomode.DemoMode;
+import com.android.systemui.demomode.DemoModeController;
 import com.android.systemui.settings.CurrentUserTracker;
 import com.android.systemui.statusbar.policy.DeviceProvisionedController.DeviceProvisionedListener;
 
@@ -76,10 +80,9 @@
 import java.util.Locale;
 
 import javax.inject.Inject;
-import javax.inject.Singleton;
 
 /** Platform implementation of the network controller. **/
-@Singleton
+@SysUISingleton
 public class NetworkControllerImpl extends BroadcastReceiver
         implements NetworkController, DemoMode, DataUsageController.NetworkNameProvider, Dumpable {
     // debug
@@ -104,6 +107,7 @@
     private final DataSaverController mDataSaverController;
     private final CurrentUserTracker mUserTracker;
     private final BroadcastDispatcher mBroadcastDispatcher;
+    private final DemoModeController mDemoModeController;
     private final Object mLock = new Object();
     private Config mConfig;
 
@@ -173,11 +177,16 @@
      * Construct this controller object and register for updates.
      */
     @Inject
-    public NetworkControllerImpl(Context context, @Background Looper bgLooper,
+    public NetworkControllerImpl(
+            Context context,
+            @Background Looper bgLooper,
             DeviceProvisionedController deviceProvisionedController,
-            BroadcastDispatcher broadcastDispatcher, ConnectivityManager connectivityManager,
-            TelephonyManager telephonyManager, @Nullable WifiManager wifiManager,
-            NetworkScoreManager networkScoreManager) {
+            BroadcastDispatcher broadcastDispatcher,
+            ConnectivityManager connectivityManager,
+            TelephonyManager telephonyManager,
+            @Nullable WifiManager wifiManager,
+            NetworkScoreManager networkScoreManager,
+            DemoModeController demoModeController) {
         this(context, connectivityManager,
                 telephonyManager,
                 wifiManager,
@@ -188,7 +197,8 @@
                 new DataUsageController(context),
                 new SubscriptionDefaults(),
                 deviceProvisionedController,
-                broadcastDispatcher);
+                broadcastDispatcher,
+                demoModeController);
         mReceiverHandler.post(mRegisterListeners);
     }
 
@@ -202,7 +212,8 @@
             DataUsageController dataUsageController,
             SubscriptionDefaults defaultsHandler,
             DeviceProvisionedController deviceProvisionedController,
-            BroadcastDispatcher broadcastDispatcher) {
+            BroadcastDispatcher broadcastDispatcher,
+            DemoModeController demoModeController) {
         mContext = context;
         mConfig = config;
         mReceiverHandler = new Handler(bgLooper);
@@ -215,6 +226,7 @@
         mConnectivityManager = connectivityManager;
         mHasMobileDataFeature =
                 mConnectivityManager.isNetworkSupported(ConnectivityManager.TYPE_MOBILE);
+        mDemoModeController = demoModeController;
 
         // telephony
         mPhone = telephonyManager;
@@ -306,6 +318,8 @@
                 doUpdateMobileControllers();
             }
         };
+
+        mDemoModeController.addCallback(this);
     }
 
     private final Runnable mClearForceValidated = () -> {
@@ -514,7 +528,8 @@
         mCallbackHandler.setEmergencyCallsOnly(mIsEmergency);
     }
 
-    public void addCallback(SignalCallback cb) {
+    @Override
+    public void addCallback(@NonNull SignalCallback cb) {
         cb.setSubs(mCurrentSubscriptions);
         cb.setIsAirplaneMode(new IconState(mAirplaneMode,
                 TelephonyIcons.FLIGHT_MODE_ICON, R.string.accessibility_airplane_mode, mContext));
@@ -529,7 +544,7 @@
     }
 
     @Override
-    public void removeCallback(SignalCallback cb) {
+    public void removeCallback(@NonNull SignalCallback cb) {
         mCallbackHandler.setListening(cb, false);
     }
 
@@ -932,203 +947,215 @@
         return "UNKNOWN_SOURCE";
     }
 
-    private boolean mDemoMode;
     private boolean mDemoInetCondition;
     private WifiSignalController.WifiState mDemoWifiState;
 
     @Override
+    public void onDemoModeStarted() {
+        if (DEBUG) Log.d(TAG, "Entering demo mode");
+        unregisterListeners();
+        mDemoInetCondition = mInetCondition;
+        mDemoWifiState = mWifiSignalController.getState();
+        mDemoWifiState.ssid = "DemoMode";
+    }
+
+    @Override
+    public void onDemoModeFinished() {
+        if (DEBUG) Log.d(TAG, "Exiting demo mode");
+        // Update what MobileSignalControllers, because they may change
+        // to set the number of sim slots.
+        updateMobileControllers();
+        for (int i = 0; i < mMobileSignalControllers.size(); i++) {
+            MobileSignalController controller = mMobileSignalControllers.valueAt(i);
+            controller.resetLastState();
+        }
+        mWifiSignalController.resetLastState();
+        mReceiverHandler.post(mRegisterListeners);
+        notifyAllListeners();
+    }
+
+    @Override
     public void dispatchDemoCommand(String command, Bundle args) {
-        if (!mDemoMode && command.equals(COMMAND_ENTER)) {
-            if (DEBUG) Log.d(TAG, "Entering demo mode");
-            unregisterListeners();
-            mDemoMode = true;
-            mDemoInetCondition = mInetCondition;
-            mDemoWifiState = mWifiSignalController.getState();
-            mDemoWifiState.ssid = "DemoMode";
-        } else if (mDemoMode && command.equals(COMMAND_EXIT)) {
-            if (DEBUG) Log.d(TAG, "Exiting demo mode");
-            mDemoMode = false;
-            // Update what MobileSignalControllers, because they may change
-            // to set the number of sim slots.
-            updateMobileControllers();
+        if (!mDemoModeController.isInDemoMode()) {
+            return;
+        }
+
+        String airplane = args.getString("airplane");
+        if (airplane != null) {
+            boolean show = airplane.equals("show");
+            mCallbackHandler.setIsAirplaneMode(new IconState(show,
+                    TelephonyIcons.FLIGHT_MODE_ICON, R.string.accessibility_airplane_mode,
+                    mContext));
+        }
+        String fully = args.getString("fully");
+        if (fully != null) {
+            mDemoInetCondition = Boolean.parseBoolean(fully);
+            BitSet connected = new BitSet();
+
+            if (mDemoInetCondition) {
+                connected.set(mWifiSignalController.mTransportType);
+            }
+            mWifiSignalController.updateConnectivity(connected, connected);
             for (int i = 0; i < mMobileSignalControllers.size(); i++) {
                 MobileSignalController controller = mMobileSignalControllers.valueAt(i);
-                controller.resetLastState();
-            }
-            mWifiSignalController.resetLastState();
-            mReceiverHandler.post(mRegisterListeners);
-            notifyAllListeners();
-        } else if (mDemoMode && command.equals(COMMAND_NETWORK)) {
-            String airplane = args.getString("airplane");
-            if (airplane != null) {
-                boolean show = airplane.equals("show");
-                mCallbackHandler.setIsAirplaneMode(new IconState(show,
-                        TelephonyIcons.FLIGHT_MODE_ICON, R.string.accessibility_airplane_mode,
-                        mContext));
-            }
-            String fully = args.getString("fully");
-            if (fully != null) {
-                mDemoInetCondition = Boolean.parseBoolean(fully);
-                BitSet connected = new BitSet();
-
                 if (mDemoInetCondition) {
-                    connected.set(mWifiSignalController.mTransportType);
+                    connected.set(controller.mTransportType);
                 }
-                mWifiSignalController.updateConnectivity(connected, connected);
+                controller.updateConnectivity(connected, connected);
+            }
+        }
+        String wifi = args.getString("wifi");
+        if (wifi != null) {
+            boolean show = wifi.equals("show");
+            String level = args.getString("level");
+            if (level != null) {
+                mDemoWifiState.level = level.equals("null") ? -1
+                        : Math.min(Integer.parseInt(level), WifiIcons.WIFI_LEVEL_COUNT - 1);
+                mDemoWifiState.connected = mDemoWifiState.level >= 0;
+            }
+            String activity = args.getString("activity");
+            if (activity != null) {
+                switch (activity) {
+                    case "inout":
+                        mWifiSignalController.setActivity(DATA_ACTIVITY_INOUT);
+                        break;
+                    case "in":
+                        mWifiSignalController.setActivity(DATA_ACTIVITY_IN);
+                        break;
+                    case "out":
+                        mWifiSignalController.setActivity(DATA_ACTIVITY_OUT);
+                        break;
+                    default:
+                        mWifiSignalController.setActivity(DATA_ACTIVITY_NONE);
+                        break;
+                }
+            } else {
+                mWifiSignalController.setActivity(DATA_ACTIVITY_NONE);
+            }
+            String ssid = args.getString("ssid");
+            if (ssid != null) {
+                mDemoWifiState.ssid = ssid;
+            }
+            mDemoWifiState.enabled = show;
+            mWifiSignalController.notifyListeners();
+        }
+        String sims = args.getString("sims");
+        if (sims != null) {
+            int num = MathUtils.constrain(Integer.parseInt(sims), 1, 8);
+            List<SubscriptionInfo> subs = new ArrayList<>();
+            if (num != mMobileSignalControllers.size()) {
+                mMobileSignalControllers.clear();
+                int start = mSubscriptionManager.getActiveSubscriptionInfoCountMax();
+                for (int i = start /* get out of normal index range */; i < start + num; i++) {
+                    subs.add(addSignalController(i, i));
+                }
+                mCallbackHandler.setSubs(subs);
                 for (int i = 0; i < mMobileSignalControllers.size(); i++) {
-                    MobileSignalController controller = mMobileSignalControllers.valueAt(i);
-                    if (mDemoInetCondition) {
-                        connected.set(controller.mTransportType);
-                    }
-                    controller.updateConnectivity(connected, connected);
-                }
-            }
-            String wifi = args.getString("wifi");
-            if (wifi != null) {
-                boolean show = wifi.equals("show");
-                String level = args.getString("level");
-                if (level != null) {
-                    mDemoWifiState.level = level.equals("null") ? -1
-                            : Math.min(Integer.parseInt(level), WifiIcons.WIFI_LEVEL_COUNT - 1);
-                    mDemoWifiState.connected = mDemoWifiState.level >= 0;
-                }
-                String activity = args.getString("activity");
-                if (activity != null) {
-                    switch (activity) {
-                        case "inout":
-                            mWifiSignalController.setActivity(DATA_ACTIVITY_INOUT);
-                            break;
-                        case "in":
-                            mWifiSignalController.setActivity(DATA_ACTIVITY_IN);
-                            break;
-                        case "out":
-                            mWifiSignalController.setActivity(DATA_ACTIVITY_OUT);
-                            break;
-                        default:
-                            mWifiSignalController.setActivity(DATA_ACTIVITY_NONE);
-                            break;
-                    }
-                } else {
-                    mWifiSignalController.setActivity(DATA_ACTIVITY_NONE);
-                }
-                String ssid = args.getString("ssid");
-                if (ssid != null) {
-                    mDemoWifiState.ssid = ssid;
-                }
-                mDemoWifiState.enabled = show;
-                mWifiSignalController.notifyListeners();
-            }
-            String sims = args.getString("sims");
-            if (sims != null) {
-                int num = MathUtils.constrain(Integer.parseInt(sims), 1, 8);
-                List<SubscriptionInfo> subs = new ArrayList<>();
-                if (num != mMobileSignalControllers.size()) {
-                    mMobileSignalControllers.clear();
-                    int start = mSubscriptionManager.getActiveSubscriptionInfoCountMax();
-                    for (int i = start /* get out of normal index range */; i < start + num; i++) {
-                        subs.add(addSignalController(i, i));
-                    }
-                    mCallbackHandler.setSubs(subs);
-                    for (int i = 0; i < mMobileSignalControllers.size(); i++) {
-                        int key = mMobileSignalControllers.keyAt(i);
-                        MobileSignalController controller = mMobileSignalControllers.get(key);
-                        controller.notifyListeners();
-                    }
-                }
-            }
-            String nosim = args.getString("nosim");
-            if (nosim != null) {
-                mHasNoSubs = nosim.equals("show");
-                mCallbackHandler.setNoSims(mHasNoSubs, mSimDetected);
-            }
-            String mobile = args.getString("mobile");
-            if (mobile != null) {
-                boolean show = mobile.equals("show");
-                String datatype = args.getString("datatype");
-                String slotString = args.getString("slot");
-                int slot = TextUtils.isEmpty(slotString) ? 0 : Integer.parseInt(slotString);
-                slot = MathUtils.constrain(slot, 0, 8);
-                // Ensure we have enough sim slots
-                List<SubscriptionInfo> subs = new ArrayList<>();
-                while (mMobileSignalControllers.size() <= slot) {
-                    int nextSlot = mMobileSignalControllers.size();
-                    subs.add(addSignalController(nextSlot, nextSlot));
-                }
-                if (!subs.isEmpty()) {
-                    mCallbackHandler.setSubs(subs);
-                }
-                // Hack to index linearly for easy use.
-                MobileSignalController controller = mMobileSignalControllers.valueAt(slot);
-                controller.getState().dataSim = datatype != null;
-                controller.getState().isDefault = datatype != null;
-                controller.getState().dataConnected = datatype != null;
-                if (datatype != null) {
-                    controller.getState().iconGroup =
-                            datatype.equals("1x") ? TelephonyIcons.ONE_X :
-                            datatype.equals("3g") ? TelephonyIcons.THREE_G :
-                            datatype.equals("4g") ? TelephonyIcons.FOUR_G :
-                            datatype.equals("4g+") ? TelephonyIcons.FOUR_G_PLUS :
-                            datatype.equals("5g") ? TelephonyIcons.NR_5G :
-                            datatype.equals("5ge") ? TelephonyIcons.LTE_CA_5G_E :
-                            datatype.equals("5g+") ? TelephonyIcons.NR_5G_PLUS :
-                            datatype.equals("e") ? TelephonyIcons.E :
-                            datatype.equals("g") ? TelephonyIcons.G :
-                            datatype.equals("h") ? TelephonyIcons.H :
-                            datatype.equals("h+") ? TelephonyIcons.H_PLUS :
-                            datatype.equals("lte") ? TelephonyIcons.LTE :
-                            datatype.equals("lte+") ? TelephonyIcons.LTE_PLUS :
-                            datatype.equals("dis") ? TelephonyIcons.DATA_DISABLED :
-                            datatype.equals("not") ? TelephonyIcons.NOT_DEFAULT_DATA :
-                            TelephonyIcons.UNKNOWN;
-                }
-                if (args.containsKey("roam")) {
-                    controller.getState().roaming = "show".equals(args.getString("roam"));
-                }
-                String level = args.getString("level");
-                if (level != null) {
-                    controller.getState().level = level.equals("null") ? -1
-                            : Math.min(Integer.parseInt(level),
-                                    CellSignalStrength.getNumSignalStrengthLevels());
-                    controller.getState().connected = controller.getState().level >= 0;
-                }
-                if (args.containsKey("inflate")) {
-                    for (int i = 0; i < mMobileSignalControllers.size(); i++) {
-                        mMobileSignalControllers.valueAt(i).mInflateSignalStrengths =
-                                "true".equals(args.getString("inflate"));
-                    }
-                }
-                String activity = args.getString("activity");
-                if (activity != null) {
-                    controller.getState().dataConnected = true;
-                    switch (activity) {
-                        case "inout":
-                            controller.setActivity(TelephonyManager.DATA_ACTIVITY_INOUT);
-                            break;
-                        case "in":
-                            controller.setActivity(TelephonyManager.DATA_ACTIVITY_IN);
-                            break;
-                        case "out":
-                            controller.setActivity(TelephonyManager.DATA_ACTIVITY_OUT);
-                            break;
-                        default:
-                            controller.setActivity(TelephonyManager.DATA_ACTIVITY_NONE);
-                            break;
-                    }
-                } else {
-                    controller.setActivity(TelephonyManager.DATA_ACTIVITY_NONE);
-                }
-                controller.getState().enabled = show;
-                controller.notifyListeners();
-            }
-            String carrierNetworkChange = args.getString("carriernetworkchange");
-            if (carrierNetworkChange != null) {
-                boolean show = carrierNetworkChange.equals("show");
-                for (int i = 0; i < mMobileSignalControllers.size(); i++) {
-                    MobileSignalController controller = mMobileSignalControllers.valueAt(i);
-                    controller.setCarrierNetworkChangeMode(show);
+                    int key = mMobileSignalControllers.keyAt(i);
+                    MobileSignalController controller = mMobileSignalControllers.get(key);
+                    controller.notifyListeners();
                 }
             }
         }
+        String nosim = args.getString("nosim");
+        if (nosim != null) {
+            mHasNoSubs = nosim.equals("show");
+            mCallbackHandler.setNoSims(mHasNoSubs, mSimDetected);
+        }
+        String mobile = args.getString("mobile");
+        if (mobile != null) {
+            boolean show = mobile.equals("show");
+            String datatype = args.getString("datatype");
+            String slotString = args.getString("slot");
+            int slot = TextUtils.isEmpty(slotString) ? 0 : Integer.parseInt(slotString);
+            slot = MathUtils.constrain(slot, 0, 8);
+            // Ensure we have enough sim slots
+            List<SubscriptionInfo> subs = new ArrayList<>();
+            while (mMobileSignalControllers.size() <= slot) {
+                int nextSlot = mMobileSignalControllers.size();
+                subs.add(addSignalController(nextSlot, nextSlot));
+            }
+            if (!subs.isEmpty()) {
+                mCallbackHandler.setSubs(subs);
+            }
+            // Hack to index linearly for easy use.
+            MobileSignalController controller = mMobileSignalControllers.valueAt(slot);
+            controller.getState().dataSim = datatype != null;
+            controller.getState().isDefault = datatype != null;
+            controller.getState().dataConnected = datatype != null;
+            if (datatype != null) {
+                controller.getState().iconGroup =
+                        datatype.equals("1x") ? TelephonyIcons.ONE_X :
+                        datatype.equals("3g") ? TelephonyIcons.THREE_G :
+                        datatype.equals("4g") ? TelephonyIcons.FOUR_G :
+                        datatype.equals("4g+") ? TelephonyIcons.FOUR_G_PLUS :
+                        datatype.equals("5g") ? TelephonyIcons.NR_5G :
+                        datatype.equals("5ge") ? TelephonyIcons.LTE_CA_5G_E :
+                        datatype.equals("5g+") ? TelephonyIcons.NR_5G_PLUS :
+                        datatype.equals("e") ? TelephonyIcons.E :
+                        datatype.equals("g") ? TelephonyIcons.G :
+                        datatype.equals("h") ? TelephonyIcons.H :
+                        datatype.equals("h+") ? TelephonyIcons.H_PLUS :
+                        datatype.equals("lte") ? TelephonyIcons.LTE :
+                        datatype.equals("lte+") ? TelephonyIcons.LTE_PLUS :
+                        datatype.equals("dis") ? TelephonyIcons.DATA_DISABLED :
+                        datatype.equals("not") ? TelephonyIcons.NOT_DEFAULT_DATA :
+                        TelephonyIcons.UNKNOWN;
+            }
+            if (args.containsKey("roam")) {
+                controller.getState().roaming = "show".equals(args.getString("roam"));
+            }
+            String level = args.getString("level");
+            if (level != null) {
+                controller.getState().level = level.equals("null") ? -1
+                        : Math.min(Integer.parseInt(level),
+                                CellSignalStrength.getNumSignalStrengthLevels());
+                controller.getState().connected = controller.getState().level >= 0;
+            }
+            if (args.containsKey("inflate")) {
+                for (int i = 0; i < mMobileSignalControllers.size(); i++) {
+                    mMobileSignalControllers.valueAt(i).mInflateSignalStrengths =
+                            "true".equals(args.getString("inflate"));
+                }
+            }
+            String activity = args.getString("activity");
+            if (activity != null) {
+                controller.getState().dataConnected = true;
+                switch (activity) {
+                    case "inout":
+                        controller.setActivity(TelephonyManager.DATA_ACTIVITY_INOUT);
+                        break;
+                    case "in":
+                        controller.setActivity(TelephonyManager.DATA_ACTIVITY_IN);
+                        break;
+                    case "out":
+                        controller.setActivity(TelephonyManager.DATA_ACTIVITY_OUT);
+                        break;
+                    default:
+                        controller.setActivity(TelephonyManager.DATA_ACTIVITY_NONE);
+                        break;
+                }
+            } else {
+                controller.setActivity(TelephonyManager.DATA_ACTIVITY_NONE);
+            }
+            controller.getState().enabled = show;
+            controller.notifyListeners();
+        }
+        String carrierNetworkChange = args.getString("carriernetworkchange");
+        if (carrierNetworkChange != null) {
+            boolean show = carrierNetworkChange.equals("show");
+            for (int i = 0; i < mMobileSignalControllers.size(); i++) {
+                MobileSignalController controller = mMobileSignalControllers.valueAt(i);
+                controller.setCarrierNetworkChangeMode(show);
+            }
+        }
+    }
+
+    @Override
+    public List<String> demoCommands() {
+        List<String> s = new ArrayList<>();
+        s.add(DemoMode.COMMAND_NETWORK);
+        return s;
     }
 
     private SubscriptionInfo addSignalController(int id, int simSlotIndex) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NextAlarmControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NextAlarmControllerImpl.java
index 288b3af..272c494 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NextAlarmControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NextAlarmControllerImpl.java
@@ -23,17 +23,20 @@
 import android.content.IntentFilter;
 import android.os.UserHandle;
 
+import androidx.annotation.NonNull;
+
+import com.android.systemui.dagger.SysUISingleton;
+
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
 import java.util.ArrayList;
 
 import javax.inject.Inject;
-import javax.inject.Singleton;
 
 /**
  * Implementation of {@link NextAlarmController}
  */
-@Singleton
+@SysUISingleton
 public class NextAlarmControllerImpl extends BroadcastReceiver
         implements NextAlarmController {
 
@@ -59,12 +62,14 @@
         pw.print("  mNextAlarm="); pw.println(mNextAlarm);
     }
 
-    public void addCallback(NextAlarmChangeCallback cb) {
+    @Override
+    public void addCallback(@NonNull NextAlarmChangeCallback cb) {
         mChangeCallbacks.add(cb);
         cb.onNextAlarmChanged(mNextAlarm);
     }
 
-    public void removeCallback(NextAlarmChangeCallback cb) {
+    @Override
+    public void removeCallback(@NonNull NextAlarmChangeCallback cb) {
         mChangeCallbacks.remove(cb);
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputQuickSettingsDisabler.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputQuickSettingsDisabler.java
index 7ef9945..ac8b47d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputQuickSettingsDisabler.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputQuickSettingsDisabler.java
@@ -21,17 +21,17 @@
 import android.content.res.Configuration;
 
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.qs.QSFragment;
 import com.android.systemui.statusbar.CommandQueue;
 import com.android.systemui.statusbar.phone.StatusBar;
 
 import javax.inject.Inject;
-import javax.inject.Singleton;
 
 /**
  * Let {@link RemoteInputView} to control the visibility of QuickSetting.
  */
-@Singleton
+@SysUISingleton
 public class RemoteInputQuickSettingsDisabler
         implements ConfigurationController.ConfigurationListener {
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputUriController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputUriController.java
index b503183..03b6122 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputUriController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputUriController.java
@@ -23,17 +23,17 @@
 
 import com.android.internal.statusbar.IStatusBarService;
 import com.android.internal.statusbar.NotificationVisibility;
+import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.statusbar.notification.NotificationEntryListener;
 import com.android.systemui.statusbar.notification.NotificationEntryManager;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 
 import javax.inject.Inject;
-import javax.inject.Singleton;
 
 /**
  * Handles granting and revoking inline URI grants associated with RemoteInputs.
  */
-@Singleton
+@SysUISingleton
 public class RemoteInputUriController {
 
     private final IStatusBarService mStatusBarManagerService;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/RotationLockControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RotationLockControllerImpl.java
index 1f368e1..53d68d0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/RotationLockControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RotationLockControllerImpl.java
@@ -19,15 +19,17 @@
 import android.content.Context;
 import android.os.UserHandle;
 
+import androidx.annotation.NonNull;
+
 import com.android.internal.view.RotationPolicy;
+import com.android.systemui.dagger.SysUISingleton;
 
 import java.util.concurrent.CopyOnWriteArrayList;
 
 import javax.inject.Inject;
-import javax.inject.Singleton;
 
 /** Platform implementation of the rotation lock controller. **/
-@Singleton
+@SysUISingleton
 public final class RotationLockControllerImpl implements RotationLockController {
     private final Context mContext;
     private final CopyOnWriteArrayList<RotationLockControllerCallback> mCallbacks =
@@ -47,12 +49,14 @@
         setListening(true);
     }
 
-    public void addCallback(RotationLockControllerCallback callback) {
+    @Override
+    public void addCallback(@NonNull RotationLockControllerCallback callback) {
         mCallbacks.add(callback);
         notifyChanged(callback);
     }
 
-    public void removeCallback(RotationLockControllerCallback callback) {
+    @Override
+    public void removeCallback(@NonNull RotationLockControllerCallback callback) {
         mCallbacks.remove(callback);
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SecurityControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SecurityControllerImpl.java
index 309d4b0..7e54e8d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SecurityControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SecurityControllerImpl.java
@@ -42,11 +42,14 @@
 import android.util.Pair;
 import android.util.SparseArray;
 
+import androidx.annotation.NonNull;
+
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.net.LegacyVpnInfo;
 import com.android.internal.net.VpnConfig;
 import com.android.systemui.R;
 import com.android.systemui.broadcast.BroadcastDispatcher;
+import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dagger.qualifiers.Background;
 import com.android.systemui.settings.CurrentUserTracker;
 
@@ -56,11 +59,10 @@
 import java.util.concurrent.Executor;
 
 import javax.inject.Inject;
-import javax.inject.Singleton;
 
 /**
  */
-@Singleton
+@SysUISingleton
 public class SecurityControllerImpl extends CurrentUserTracker implements SecurityController {
 
     private static final String TAG = "SecurityController";
@@ -274,7 +276,7 @@
     }
 
     @Override
-    public void removeCallback(SecurityControllerCallback callback) {
+    public void removeCallback(@NonNull SecurityControllerCallback callback) {
         synchronized (mCallbacks) {
             if (callback == null) return;
             if (DEBUG) Log.d(TAG, "removeCallback " + callback);
@@ -283,7 +285,7 @@
     }
 
     @Override
-    public void addCallback(SecurityControllerCallback callback) {
+    public void addCallback(@NonNull SecurityControllerCallback callback) {
         synchronized (mCallbacks) {
             if (callback == null || mCallbacks.contains(callback)) return;
             if (DEBUG) Log.d(TAG, "addCallback " + callback);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SensorPrivacyControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SensorPrivacyControllerImpl.java
index 5db6693..20cc46f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SensorPrivacyControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SensorPrivacyControllerImpl.java
@@ -19,16 +19,19 @@
 import android.content.Context;
 import android.hardware.SensorPrivacyManager;
 
+import androidx.annotation.NonNull;
+
+import com.android.systemui.dagger.SysUISingleton;
+
 import java.util.ArrayList;
 import java.util.List;
 
 import javax.inject.Inject;
-import javax.inject.Singleton;
 
 /**
  * Controls sensor privacy state and notification.
  */
-@Singleton
+@SysUISingleton
 public class SensorPrivacyControllerImpl implements SensorPrivacyController,
         SensorPrivacyManager.OnSensorPrivacyChangedListener {
     private SensorPrivacyManager mSensorPrivacyManager;
@@ -60,7 +63,7 @@
     /**
      * Adds the provided listener for callbacks when sensor privacy state changes.
      */
-    public void addCallback(OnSensorPrivacyChangedListener listener) {
+    public void addCallback(@NonNull OnSensorPrivacyChangedListener listener) {
         synchronized (mLock) {
             mListeners.add(listener);
             notifyListenerLocked(listener);
@@ -70,7 +73,7 @@
     /**
      * Removes the provided listener from callbacks when sensor privacy state changes.
      */
-    public void removeCallback(OnSensorPrivacyChangedListener listener) {
+    public void removeCallback(@NonNull OnSensorPrivacyChangedListener listener) {
         synchronized (mLock) {
             mListeners.remove(listener);
         }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SmartReplyConstants.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SmartReplyConstants.java
index 311e8738..52a6bca 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SmartReplyConstants.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SmartReplyConstants.java
@@ -27,13 +27,13 @@
 
 import com.android.internal.config.sysui.SystemUiDeviceConfigFlags;
 import com.android.systemui.R;
+import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.util.DeviceConfigProxy;
 
 import javax.inject.Inject;
-import javax.inject.Singleton;
 
-@Singleton
+@SysUISingleton
 public final class SmartReplyConstants {
 
     private static final String TAG = "SmartReplyConstants";
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserInfoControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserInfoControllerImpl.java
index 0ca6ff6..9eaee22 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserInfoControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserInfoControllerImpl.java
@@ -34,18 +34,20 @@
 import android.provider.ContactsContract;
 import android.util.Log;
 
+import androidx.annotation.NonNull;
+
 import com.android.internal.util.UserIcons;
 import com.android.settingslib.drawable.UserIconDrawable;
 import com.android.systemui.R;
+import com.android.systemui.dagger.SysUISingleton;
 
 import java.util.ArrayList;
 
 import javax.inject.Inject;
-import javax.inject.Singleton;
 
 /**
  */
-@Singleton
+@SysUISingleton
 public class UserInfoControllerImpl implements UserInfoController {
 
     private static final String TAG = "UserInfoController";
@@ -75,12 +77,14 @@
                 null, null);
     }
 
-    public void addCallback(OnUserInfoChangedListener callback) {
+    @Override
+    public void addCallback(@NonNull OnUserInfoChangedListener callback) {
         mCallbacks.add(callback);
         callback.onUserInfoChanged(mUserName, mUserDrawable, mUserAccount);
     }
 
-    public void removeCallback(OnUserInfoChangedListener callback) {
+    @Override
+    public void removeCallback(@NonNull OnUserInfoChangedListener callback) {
         mCallbacks.remove(callback);
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java
index ce5bb05..f9ac760 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java
@@ -62,6 +62,7 @@
 import com.android.systemui.R;
 import com.android.systemui.SystemUISecondaryUserService;
 import com.android.systemui.broadcast.BroadcastDispatcher;
+import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.plugins.ActivityStarter;
 import com.android.systemui.plugins.qs.DetailAdapter;
@@ -76,12 +77,11 @@
 import java.util.List;
 
 import javax.inject.Inject;
-import javax.inject.Singleton;
 
 /**
  * Keeps a list of all users on the device for user switching.
  */
-@Singleton
+@SysUISingleton
 public class UserSwitcherController implements Dumpable {
 
     public static final float USER_SWITCH_ENABLED_ALPHA = 1.0f;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/WifiSignalController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/WifiSignalController.java
index 5257ce4..4ae9665 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/WifiSignalController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/WifiSignalController.java
@@ -84,7 +84,7 @@
                 R.bool.config_showWifiIndicatorWhenEnabled);
         boolean wifiVisible = mCurrentState.enabled && (
                 (mCurrentState.connected && mCurrentState.inetCondition == 1)
-                        || !mHasMobileDataFeature || mWifiTracker.isDefaultNetwork
+                        || !mHasMobileDataFeature || mCurrentState.isDefault
                         || visibleWhenEnabled);
         String wifiDesc = mCurrentState.connected ? mCurrentState.ssid : null;
         boolean ssidPresent = wifiVisible && mCurrentState.ssid != null;
@@ -107,6 +107,7 @@
     public void fetchInitialState() {
         mWifiTracker.fetchInitialState();
         mCurrentState.enabled = mWifiTracker.enabled;
+        mCurrentState.isDefault = mWifiTracker.isDefaultNetwork;
         mCurrentState.connected = mWifiTracker.connected;
         mCurrentState.ssid = mWifiTracker.ssid;
         mCurrentState.rssi = mWifiTracker.rssi;
@@ -121,6 +122,7 @@
     public void handleBroadcast(Intent intent) {
         mWifiTracker.handleBroadcast(intent);
         mCurrentState.enabled = mWifiTracker.enabled;
+        mCurrentState.isDefault = mWifiTracker.isDefaultNetwork;
         mCurrentState.connected = mWifiTracker.connected;
         mCurrentState.ssid = mWifiTracker.ssid;
         mCurrentState.rssi = mWifiTracker.rssi;
@@ -131,6 +133,7 @@
 
     private void handleStatusUpdated() {
         mCurrentState.statusLabel = mWifiTracker.statusLabel;
+        mCurrentState.isDefault = mWifiTracker.isDefaultNetwork;
         notifyListenersIfNecessary();
     }
 
@@ -156,6 +159,7 @@
     static class WifiState extends SignalController.State {
         String ssid;
         boolean isTransient;
+        boolean isDefault;
         String statusLabel;
 
         @Override
@@ -164,6 +168,7 @@
             WifiState state = (WifiState) s;
             ssid = state.ssid;
             isTransient = state.isTransient;
+            isDefault = state.isDefault;
             statusLabel = state.statusLabel;
         }
 
@@ -172,6 +177,7 @@
             super.toString(builder);
             builder.append(",ssid=").append(ssid)
                 .append(",isTransient=").append(isTransient)
+                .append(",isDefault=").append(isDefault)
                 .append(",statusLabel=").append(statusLabel);
         }
 
@@ -183,6 +189,7 @@
             WifiState other = (WifiState) o;
             return Objects.equals(other.ssid, ssid)
                     && other.isTransient == isTransient
+                    && other.isDefault == isDefault
                     && TextUtils.equals(other.statusLabel, statusLabel);
         }
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/ZenModeControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ZenModeControllerImpl.java
index 4376a01..897a3b8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/ZenModeControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ZenModeControllerImpl.java
@@ -37,9 +37,12 @@
 import android.text.format.DateFormat;
 import android.util.Log;
 
+import androidx.annotation.NonNull;
+
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.systemui.Dumpable;
 import com.android.systemui.broadcast.BroadcastDispatcher;
+import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.qs.GlobalSetting;
 import com.android.systemui.settings.CurrentUserTracker;
@@ -51,10 +54,9 @@
 import java.util.Objects;
 
 import javax.inject.Inject;
-import javax.inject.Singleton;
 
 /** Platform implementation of the zen mode controller. **/
-@Singleton
+@SysUISingleton
 public class ZenModeControllerImpl extends CurrentUserTracker
         implements ZenModeController, Dumpable {
     private static final String TAG = "ZenModeController";
@@ -124,14 +126,14 @@
     }
 
     @Override
-    public void addCallback(Callback callback) {
+    public void addCallback(@NonNull Callback callback) {
         synchronized (mCallbacksLock) {
             mCallbacks.add(callback);
         }
     }
 
     @Override
-    public void removeCallback(Callback callback) {
+    public void removeCallback(@NonNull Callback callback) {
         synchronized (mCallbacksLock) {
             mCallbacks.remove(callback);
         }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/tv/TvStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/tv/TvStatusBar.java
index c0602762..bcfff60 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/tv/TvStatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/tv/TvStatusBar.java
@@ -29,11 +29,11 @@
 import com.android.systemui.R;
 import com.android.systemui.SystemUI;
 import com.android.systemui.assist.AssistManager;
+import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.statusbar.CommandQueue;
 import com.android.systemui.statusbar.tv.micdisclosure.AudioRecordingDisclosureBar;
 
 import javax.inject.Inject;
-import javax.inject.Singleton;
 
 import dagger.Lazy;
 
@@ -46,7 +46,7 @@
  * recording, discloses the responsible applications </li>
  * </ul>
  */
-@Singleton
+@SysUISingleton
 public class TvStatusBar extends SystemUI implements CommandQueue.Callbacks {
 
     private static final String ACTION_OPEN_TV_NOTIFICATIONS_PANEL =
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/tv/micdisclosure/AudioActivityObserver.java b/packages/SystemUI/src/com/android/systemui/statusbar/tv/micdisclosure/AudioActivityObserver.java
index 87b3956..bbab625 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/tv/micdisclosure/AudioActivityObserver.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/tv/micdisclosure/AudioActivityObserver.java
@@ -40,5 +40,9 @@
         mListener = listener;
     }
 
+    abstract void start();
+
+    abstract void stop();
+
     abstract Set<String> getActivePackages();
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/tv/micdisclosure/AudioRecordingDisclosureBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/tv/micdisclosure/AudioRecordingDisclosureBar.java
index 8e4e123..a29db4d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/tv/micdisclosure/AudioRecordingDisclosureBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/tv/micdisclosure/AudioRecordingDisclosureBar.java
@@ -16,20 +16,18 @@
 
 package com.android.systemui.statusbar.tv.micdisclosure;
 
+import static android.provider.DeviceConfig.NAMESPACE_PRIVACY;
 import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
 
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
 import android.animation.AnimatorSet;
 import android.animation.ObjectAnimator;
-import android.animation.PropertyValuesHolder;
 import android.annotation.IntDef;
 import android.annotation.UiThread;
 import android.content.Context;
-import android.content.pm.ApplicationInfo;
-import android.content.pm.PackageManager;
 import android.graphics.PixelFormat;
-import android.provider.Settings;
+import android.provider.DeviceConfig;
 import android.text.TextUtils;
 import android.util.ArraySet;
 import android.util.Log;
@@ -46,8 +44,8 @@
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 import java.util.Arrays;
-import java.util.LinkedList;
-import java.util.Queue;
+import java.util.Collections;
+import java.util.List;
 import java.util.Set;
 
 /**
@@ -65,35 +63,30 @@
     // CtsSystemUiHostTestCases:TvMicrophoneCaptureIndicatorTest
     private static final String LAYOUT_PARAMS_TITLE = "MicrophoneCaptureIndicator";
 
-    private static final String EXEMPT_PACKAGES_LIST = "sysui_mic_disclosure_exempt";
-    private static final String FORCED_PACKAGES_LIST = "sysui_mic_disclosure_forced";
+    private static final String ENABLED_FLAG = "mic_disclosure_enabled";
+    private static final String EXEMPT_PACKAGES_LIST = "mic_disclosure_exempt_packages";
+    private static final String FORCED_PACKAGES_LIST = "mic_disclosure_forced_packages";
 
     @Retention(RetentionPolicy.SOURCE)
     @IntDef(prefix = {"STATE_"}, value = {
+            STATE_STOPPED,
             STATE_NOT_SHOWN,
             STATE_APPEARING,
             STATE_SHOWN,
-            STATE_MINIMIZING,
-            STATE_MINIMIZED,
-            STATE_MAXIMIZING,
             STATE_DISAPPEARING
     })
     public @interface State {}
 
+    private static final int STATE_STOPPED = -1;
     private static final int STATE_NOT_SHOWN = 0;
     private static final int STATE_APPEARING = 1;
     private static final int STATE_SHOWN = 2;
-    private static final int STATE_MINIMIZING = 3;
-    private static final int STATE_MINIMIZED = 4;
-    private static final int STATE_MAXIMIZING = 5;
-    private static final int STATE_DISAPPEARING = 6;
+    private static final int STATE_DISAPPEARING = 3;
 
     private static final int ANIMATION_DURATION = 600;
-    private static final int MAXIMIZED_DURATION = 3000;
-    private static final int PULSE_BIT_DURATION = 1000;
-    private static final float PULSE_SCALE = 1.25f;
 
     private final Context mContext;
+    private boolean mIsEnabled;
 
     private View mIndicatorView;
     private View mIconTextsContainer;
@@ -104,58 +97,82 @@
     private TextView mTextView;
     private boolean mIsLtr;
 
-    @State private int mState = STATE_NOT_SHOWN;
+    @State private int mState = STATE_STOPPED;
 
     /**
      * Array of the observers that monitor different aspects of the system, such as AppOps and
      * microphone foreground services
      */
-    private final AudioActivityObserver[] mAudioActivityObservers;
-    /**
-     * Whether the indicator should expand and show the recording application's label.
-     * If disabled ({@code false}) the "minimized" ({@link #STATE_MINIMIZED}) indicator would appear
-     * on the screen whenever an application is recording, but will not reveal to the user what
-     * application this is.
-     */
-    private final boolean mRevealRecordingPackages;
-    /**
-     * Set of applications that we've notified the user about since the indicator came up. Meaning
-     * that if an application is in this list then at some point since the indicator came up, it
-     * was expanded showing this application's title.
-     * Used not to notify the user about the same application again while the indicator is shown.
-     * We empty this set every time the indicator goes off the screen (we always call {@code
-     * mSessionNotifiedPackages.clear()} before calling {@link #hide()}).
-     */
-    private final Set<String> mSessionNotifiedPackages = new ArraySet<>();
-    /**
-     * If an application starts recording while the TV indicator is neither in {@link
-     * #STATE_NOT_SHOWN} nor in {@link #STATE_MINIMIZED}, then we add the application's package
-     * name to the queue, from which we take packages names one by one to disclose the
-     * corresponding applications' titles to the user, whenever the indicator eventually comes to
-     * one of the two aforementioned states.
-     */
-    private final Queue<String> mPendingNotificationPackages = new LinkedList<>();
+    private AudioActivityObserver[] mAudioActivityObservers;
     /**
      * Set of applications for which we make an exception and do not show the indicator. This gets
      * populated once - in {@link #AudioRecordingDisclosureBar(Context)}.
      */
-    private final Set<String> mExemptPackages;
+    private final Set<String> mExemptPackages = new ArraySet<>();
 
     public AudioRecordingDisclosureBar(Context context) {
         mContext = context;
 
-        mRevealRecordingPackages = mContext.getResources().getBoolean(
-                R.bool.audio_recording_disclosure_reveal_packages);
-        mExemptPackages = new ArraySet<>(
-                Arrays.asList(mContext.getResources().getStringArray(
-                        R.array.audio_recording_disclosure_exempt_apps)));
-        mExemptPackages.addAll(Arrays.asList(getGlobalStringArray(EXEMPT_PACKAGES_LIST)));
-        mExemptPackages.removeAll(Arrays.asList(getGlobalStringArray(FORCED_PACKAGES_LIST)));
+        // Load configs
+        reloadExemptPackages();
 
-        mAudioActivityObservers = new AudioActivityObserver[]{
-                new RecordAudioAppOpObserver(mContext, this),
-                new MicrophoneForegroundServicesObserver(mContext, this),
-        };
+        mIsEnabled = DeviceConfig.getBoolean(NAMESPACE_PRIVACY, ENABLED_FLAG, true);
+        // Start if enabled
+        if (mIsEnabled) {
+            start();
+        }
+
+        // Set up a config change listener
+        DeviceConfig.addOnPropertiesChangedListener(NAMESPACE_PRIVACY, mContext.getMainExecutor(),
+                mConfigChangeListener);
+    }
+
+    private void reloadExemptPackages() {
+        mExemptPackages.clear();
+        mExemptPackages.addAll(Arrays.asList(mContext.getResources().getStringArray(
+                R.array.audio_recording_disclosure_exempt_apps)));
+        mExemptPackages.addAll(
+                splitByComma(
+                        DeviceConfig.getString(NAMESPACE_PRIVACY, EXEMPT_PACKAGES_LIST, null)));
+        mExemptPackages.removeAll(
+                splitByComma(
+                        DeviceConfig.getString(NAMESPACE_PRIVACY, FORCED_PACKAGES_LIST, null)));
+    }
+
+    @UiThread
+    private void start() {
+        if (mState != STATE_STOPPED) {
+            return;
+        }
+        mState = STATE_NOT_SHOWN;
+
+        if (mAudioActivityObservers == null) {
+            mAudioActivityObservers = new AudioActivityObserver[]{
+                    new RecordAudioAppOpObserver(mContext, this),
+                    new MicrophoneForegroundServicesObserver(mContext, this),
+            };
+        }
+
+        for (int i = mAudioActivityObservers.length - 1; i >= 0; i--) {
+            mAudioActivityObservers[i].start();
+        }
+    }
+
+    @UiThread
+    private void stop() {
+        if (mState == STATE_STOPPED) {
+            return;
+        }
+        mState = STATE_STOPPED;
+
+        for (int i = mAudioActivityObservers.length - 1; i >= 0; i--) {
+            mAudioActivityObservers[i].stop();
+        }
+
+        // Remove the view if shown.
+        if (mState != STATE_NOT_SHOWN) {
+            removeIndicatorView();
+        }
     }
 
     @UiThread
@@ -173,70 +190,29 @@
         }
 
         if (active) {
-            showIndicatorForPackageIfNeeded(packageName);
+            showIfNotShown();
         } else {
             hideIndicatorIfNeeded();
         }
     }
 
     @UiThread
-    private void showIndicatorForPackageIfNeeded(String packageName) {
-        if (DEBUG) Log.d(TAG, "showIndicatorForPackageIfNeeded, packageName=" + packageName);
-        if (!mSessionNotifiedPackages.add(packageName)) {
-            // We've already notified user about this app, no need to do it again.
-            if (DEBUG) Log.d(TAG, "   - already notified");
-            return;
-        }
-
-        switch (mState) {
-            case STATE_NOT_SHOWN:
-                show(packageName);
-                break;
-
-            case STATE_MINIMIZED:
-                if (mRevealRecordingPackages) {
-                    expand(packageName);
-                }
-                break;
-
-            case STATE_DISAPPEARING:
-            case STATE_APPEARING:
-            case STATE_MAXIMIZING:
-            case STATE_SHOWN:
-            case STATE_MINIMIZING:
-                // Currently animating or expanded. Thus add to the pending notifications, and it
-                // will be picked up once the indicator comes to the STATE_MINIMIZED.
-                mPendingNotificationPackages.add(packageName);
-                break;
-        }
-    }
-
-    @UiThread
     private void hideIndicatorIfNeeded() {
-        if (DEBUG) Log.d(TAG, "hideIndicatorIfNeeded");
-        // If not MINIMIZED, will check whether the indicator should be hidden when the indicator
-        // comes to the STATE_MINIMIZED eventually.
-        if (mState != STATE_MINIMIZED) return;
+        // If not STATE_APPEARING, will check whether the indicator should be hidden when the
+        // indicator comes to the STATE_SHOWN.
+        // If STATE_DISAPPEARING or STATE_SHOWN - nothing else for us to do here.
+        if (mState != STATE_SHOWN) return;
 
-        // If is in the STATE_MINIMIZED, but there are other active recorders - simply ignore.
-        for (int index = mAudioActivityObservers.length - 1; index >= 0; index--) {
-            for (String activePackage : mAudioActivityObservers[index].getActivePackages()) {
-                if (mExemptPackages.contains(activePackage)) continue;
-                if (DEBUG) Log.d(TAG, "   - there are still ongoing activities");
-                return;
-            }
+        // If is in the STATE_SHOWN and there are no active recorders - hide.
+        if (!hasActiveRecorders()) {
+            hide();
         }
-
-        // Clear the state and hide the indicator.
-        mSessionNotifiedPackages.clear();
-        hide();
     }
 
     @UiThread
-    private void show(String packageName) {
-        if (DEBUG) {
-            Log.d(TAG, "Showing indicator for " + packageName);
-        }
+    private void showIfNotShown() {
+        if (mState != STATE_NOT_SHOWN) return;
+        if (DEBUG) Log.d(TAG, "Showing indicator");
 
         mIsLtr = mContext.getResources().getConfiguration().getLayoutDirection()
                 == View.LAYOUT_DIRECTION_LTR;
@@ -252,30 +228,14 @@
         mTextView = mTextsContainers.findViewById(R.id.text);
         mBgEnd = mIndicatorView.findViewById(R.id.bg_end);
 
-        // Set up the notification text
-        if (mRevealRecordingPackages) {
-            // Swap background drawables depending on layout directions (both drawables have rounded
-            // corners only on one side)
-            if (mIsLtr) {
-                mBgEnd.setBackgroundResource(R.drawable.tv_rect_dark_right_rounded);
-                mIconContainerBg.setBackgroundResource(R.drawable.tv_rect_dark_left_rounded);
-            } else {
-                mBgEnd.setBackgroundResource(R.drawable.tv_rect_dark_left_rounded);
-                mIconContainerBg.setBackgroundResource(R.drawable.tv_rect_dark_right_rounded);
-            }
-
-            final String label = getApplicationLabel(packageName);
-            mTextView.setText(mContext.getString(R.string.app_accessed_mic, label));
-        } else {
-            mTextsContainers.setVisibility(View.GONE);
-            mIconContainerBg.setVisibility(View.GONE);
-            mTextView.setVisibility(View.GONE);
-            mBgEnd.setVisibility(View.GONE);
-            mTextsContainers = null;
-            mIconContainerBg = null;
-            mTextView = null;
-            mBgEnd = null;
-        }
+        mTextsContainers.setVisibility(View.GONE);
+        mIconContainerBg.setVisibility(View.GONE);
+        mTextView.setVisibility(View.GONE);
+        mBgEnd.setVisibility(View.GONE);
+        mTextsContainers = null;
+        mIconContainerBg = null;
+        mTextView = null;
+        mBgEnd = null;
 
         // Initially change the visibility to INVISIBLE, wait until and receives the size and
         // then animate it moving from "off" the screen correctly
@@ -286,6 +246,10 @@
                         new ViewTreeObserver.OnGlobalLayoutListener() {
                             @Override
                             public void onGlobalLayout() {
+                                if (mState == STATE_STOPPED) {
+                                    return;
+                                }
+
                                 // Remove the observer
                                 mIndicatorView.getViewTreeObserver().removeOnGlobalLayoutListener(
                                         this);
@@ -306,18 +270,15 @@
                                             @Override
                                             public void onAnimationStart(Animator animation,
                                                     boolean isReverse) {
+                                                if (mState == STATE_STOPPED) return;
+
                                                 // Indicator is INVISIBLE at the moment, change it.
                                                 mIndicatorView.setVisibility(View.VISIBLE);
                                             }
 
                                             @Override
                                             public void onAnimationEnd(Animator animation) {
-                                                startPulsatingAnimation();
-                                                if (mRevealRecordingPackages) {
-                                                    onExpanded();
-                                                } else {
-                                                    onMinimized();
-                                                }
+                                                onAppeared();
                                             }
                                         });
                                 set.start();
@@ -341,62 +302,9 @@
     }
 
     @UiThread
-    private void expand(String packageName) {
-        assertRevealingRecordingPackages();
-
-        final String label = getApplicationLabel(packageName);
-        if (DEBUG) {
-            Log.d(TAG, "Expanding for " + packageName + " (" + label + ")...");
-        }
-        mTextView.setText(mContext.getString(R.string.app_accessed_mic, label));
-
-        final AnimatorSet set = new AnimatorSet();
-        set.playTogether(
-                ObjectAnimator.ofFloat(mIconTextsContainer, View.TRANSLATION_X, 0),
-                ObjectAnimator.ofFloat(mIconContainerBg, View.ALPHA, 1f),
-                ObjectAnimator.ofFloat(mTextsContainers, View.ALPHA, 1f),
-                ObjectAnimator.ofFloat(mBgEnd, View.ALPHA, 1f));
-        set.setDuration(ANIMATION_DURATION);
-        set.addListener(
-                new AnimatorListenerAdapter() {
-                    @Override
-                    public void onAnimationEnd(Animator animation) {
-                        onExpanded();
-                    }
-                });
-        set.start();
-
-        mState = STATE_MAXIMIZING;
-    }
-
-    @UiThread
-    private void minimize() {
-        assertRevealingRecordingPackages();
-
-        if (DEBUG) Log.d(TAG, "Minimizing...");
-        final int targetOffset = (mIsLtr ? 1 : -1) * mTextsContainers.getWidth();
-        final AnimatorSet set = new AnimatorSet();
-        set.playTogether(
-                ObjectAnimator.ofFloat(mIconTextsContainer, View.TRANSLATION_X, targetOffset),
-                ObjectAnimator.ofFloat(mIconContainerBg, View.ALPHA, 0f),
-                ObjectAnimator.ofFloat(mTextsContainers, View.ALPHA, 0f),
-                ObjectAnimator.ofFloat(mBgEnd, View.ALPHA, 0f));
-        set.setDuration(ANIMATION_DURATION);
-        set.addListener(
-                new AnimatorListenerAdapter() {
-                    @Override
-                    public void onAnimationEnd(Animator animation) {
-                        onMinimized();
-                    }
-                });
-        set.start();
-
-        mState = STATE_MINIMIZING;
-    }
-
-    @UiThread
     private void hide() {
-        if (DEBUG) Log.d(TAG, "Hiding...");
+        if (DEBUG) Log.d(TAG, "Hide indicator");
+
         final int targetOffset = (mIsLtr ? 1 : -1) * (mIndicatorView.getWidth()
                 - (int) mIconTextsContainer.getTranslationX());
         final AnimatorSet set = new AnimatorSet();
@@ -416,35 +324,40 @@
         mState = STATE_DISAPPEARING;
     }
 
-    @UiThread
-    private void onExpanded() {
-        assertRevealingRecordingPackages();
 
-        if (DEBUG) Log.d(TAG, "Expanded");
+    @UiThread
+    private void onAppeared() {
+        if (mState == STATE_STOPPED) return;
+
         mState = STATE_SHOWN;
 
-        mIndicatorView.postDelayed(this::minimize, MAXIMIZED_DURATION);
-    }
-
-    @UiThread
-    private void onMinimized() {
-        if (DEBUG) Log.d(TAG, "Minimized");
-        mState = STATE_MINIMIZED;
-
-        if (mRevealRecordingPackages) {
-            if (!mPendingNotificationPackages.isEmpty()) {
-                // There is a new application that started recording, tell the user about it.
-                expand(mPendingNotificationPackages.poll());
-            } else {
-                hideIndicatorIfNeeded();
-            }
-        }
+        hideIndicatorIfNeeded();
     }
 
     @UiThread
     private void onHidden() {
-        if (DEBUG) Log.d(TAG, "Hidden");
+        if (mState == STATE_STOPPED) return;
 
+        removeIndicatorView();
+        mState = STATE_NOT_SHOWN;
+
+        if (hasActiveRecorders()) {
+            // Got new recorders, show again.
+            showIfNotShown();
+        }
+    }
+
+    private boolean hasActiveRecorders() {
+        for (int index = mAudioActivityObservers.length - 1; index >= 0; index--) {
+            for (String activePackage : mAudioActivityObservers[index].getActivePackages()) {
+                if (mExemptPackages.contains(activePackage)) continue;
+                return true;
+            }
+        }
+        return false;
+    }
+
+    private void removeIndicatorView() {
         final WindowManager windowManager = (WindowManager) mContext.getSystemService(
                 Context.WINDOW_SERVICE);
         windowManager.removeView(mIndicatorView);
@@ -456,52 +369,28 @@
         mTextsContainers = null;
         mTextView = null;
         mBgEnd = null;
-
-        mState = STATE_NOT_SHOWN;
-
-        // Check if anybody started recording while we were in STATE_DISAPPEARING
-        if (!mPendingNotificationPackages.isEmpty()) {
-            // There is a new application that started recording, tell the user about it.
-            show(mPendingNotificationPackages.poll());
-        }
     }
 
-    @UiThread
-    private void startPulsatingAnimation() {
-        final View pulsatingView = mIconTextsContainer.findViewById(R.id.pulsating_circle);
-        final ObjectAnimator animator =
-                ObjectAnimator.ofPropertyValuesHolder(
-                        pulsatingView,
-                        PropertyValuesHolder.ofFloat(View.SCALE_X, PULSE_SCALE),
-                        PropertyValuesHolder.ofFloat(View.SCALE_Y, PULSE_SCALE));
-        animator.setDuration(PULSE_BIT_DURATION);
-        animator.setRepeatCount(ObjectAnimator.INFINITE);
-        animator.setRepeatMode(ObjectAnimator.REVERSE);
-        animator.start();
+    private static List<String> splitByComma(String string) {
+        return TextUtils.isEmpty(string) ? Collections.emptyList() : Arrays.asList(
+                string.split(","));
     }
 
-    private String[] getGlobalStringArray(String setting) {
-        String result = Settings.Global.getString(mContext.getContentResolver(), setting);
-        return TextUtils.isEmpty(result) ? new String[0] : result.split(",");
-    }
+    private final DeviceConfig.OnPropertiesChangedListener mConfigChangeListener =
+            new DeviceConfig.OnPropertiesChangedListener() {
+                @Override
+                public void onPropertiesChanged(DeviceConfig.Properties properties) {
+                    reloadExemptPackages();
 
-    private String getApplicationLabel(String packageName) {
-        assertRevealingRecordingPackages();
-
-        final PackageManager pm = mContext.getPackageManager();
-        final ApplicationInfo appInfo;
-        try {
-            appInfo = pm.getApplicationInfo(packageName, 0);
-        } catch (PackageManager.NameNotFoundException e) {
-            return packageName;
-        }
-        return pm.getApplicationLabel(appInfo).toString();
-    }
-
-    private void assertRevealingRecordingPackages() {
-        if (!mRevealRecordingPackages) {
-            Log.e(TAG, "Not revealing recording packages",
-                    DEBUG ? new RuntimeException("Should not be called") : null);
-        }
-    }
+                    // Check if was enabled/disabled
+                    if (mIsEnabled != properties.getBoolean(ENABLED_FLAG, true)) {
+                        mIsEnabled = !mIsEnabled;
+                        if (mIsEnabled) {
+                            start();
+                        } else {
+                            stop();
+                        }
+                    }
+                }
+            };
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/tv/micdisclosure/MicrophoneForegroundServicesObserver.java b/packages/SystemUI/src/com/android/systemui/statusbar/tv/micdisclosure/MicrophoneForegroundServicesObserver.java
index 1ede88a..8caf95f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/tv/micdisclosure/MicrophoneForegroundServicesObserver.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/tv/micdisclosure/MicrophoneForegroundServicesObserver.java
@@ -30,7 +30,6 @@
 import android.util.Log;
 import android.util.SparseArray;
 
-import java.util.Collections;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
@@ -41,9 +40,8 @@
  */
 class MicrophoneForegroundServicesObserver extends AudioActivityObserver {
     private static final String TAG = "MicrophoneForegroundServicesObserver";
-    private static final boolean ENABLED = true;
 
-    private final IActivityManager mActivityManager;
+    private IActivityManager mActivityManager;
     /**
      * A dictionary that maps PIDs to the package names. We only keep track of the PIDs that are
      * "active" (those that are running FGS with FOREGROUND_SERVICE_TYPE_MICROPHONE flag).
@@ -60,7 +58,10 @@
     MicrophoneForegroundServicesObserver(Context context,
             OnAudioActivityStateChangeListener listener) {
         super(context, listener);
+    }
 
+    @Override
+    void start() {
         mActivityManager = ActivityManager.getService();
         try {
             mActivityManager.registerProcessObserver(mProcessObserver);
@@ -70,8 +71,19 @@
     }
 
     @Override
+    void stop() {
+        try {
+            mActivityManager.unregisterProcessObserver(mProcessObserver);
+        } catch (RemoteException e) {
+            Log.e(TAG, "Couldn't unregister process observer", e);
+        }
+        mActivityManager = null;
+        mPackageToProcessCount.clear();
+    }
+
+    @Override
     Set<String> getActivePackages() {
-        return ENABLED ? mPackageToProcessCount.keySet() : Collections.emptySet();
+        return mPackageToProcessCount.keySet();
     }
 
     @UiThread
@@ -141,13 +153,12 @@
 
     @UiThread
     private void notifyPackageStateChanged(String packageName, boolean active) {
-        if (active) {
-            if (DEBUG) Log.d(TAG, "New microphone fgs detected, package=" + packageName);
-        } else {
-            if (DEBUG) Log.d(TAG, "Microphone fgs is gone, package=" + packageName);
+        if (DEBUG) {
+            Log.d(TAG, (active ? "New microphone fgs detected" : "Microphone fgs is gone")
+                    + ", package=" + packageName);
         }
 
-        if (ENABLED) mListener.onAudioActivityStateChange(active, packageName);
+        mListener.onAudioActivityStateChange(active, packageName);
     }
 
     @UiThread
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/tv/micdisclosure/RecordAudioAppOpObserver.java b/packages/SystemUI/src/com/android/systemui/statusbar/tv/micdisclosure/RecordAudioAppOpObserver.java
index b5b1c2b..9a2b4a9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/tv/micdisclosure/RecordAudioAppOpObserver.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/tv/micdisclosure/RecordAudioAppOpObserver.java
@@ -42,14 +42,33 @@
 
     RecordAudioAppOpObserver(Context context, OnAudioActivityStateChangeListener listener) {
         super(context, listener);
+    }
+
+    @Override
+    void start() {
+        if (DEBUG) {
+            Log.d(TAG, "Start");
+        }
 
         // Register AppOpsManager callback
-        final AppOpsManager appOpsManager = (AppOpsManager) mContext.getSystemService(
-                Context.APP_OPS_SERVICE);
-        appOpsManager.startWatchingActive(
-                new String[]{AppOpsManager.OPSTR_RECORD_AUDIO},
-                mContext.getMainExecutor(),
-                this);
+        mContext.getSystemService(AppOpsManager.class)
+                .startWatchingActive(
+                        new String[]{AppOpsManager.OPSTR_RECORD_AUDIO},
+                        mContext.getMainExecutor(),
+                        this);
+    }
+
+    @Override
+    void stop() {
+        if (DEBUG) {
+            Log.d(TAG, "Stop");
+        }
+
+        // Unregister AppOpsManager callback
+        mContext.getSystemService(AppOpsManager.class).stopWatchingActive(this);
+
+        // Clean up state
+        mActiveAudioRecordingPackages.clear();
     }
 
     @UiThread
diff --git a/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java b/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java
index f31f8eb..132e092 100644
--- a/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java
+++ b/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java
@@ -36,6 +36,7 @@
 import com.android.systemui.R;
 import com.android.systemui.SystemUI;
 import com.android.systemui.broadcast.BroadcastDispatcher;
+import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dagger.qualifiers.Background;
 
 import com.google.android.collect.Sets;
@@ -48,7 +49,6 @@
 import java.util.Set;
 
 import javax.inject.Inject;
-import javax.inject.Singleton;
 
 /**
  * Controls the application of theme overlays across the system for all users.
@@ -59,7 +59,7 @@
  * - Observing work profile changes and applying overlays from the primary user to their
  * associated work profiles
  */
-@Singleton
+@SysUISingleton
 public class ThemeOverlayController extends SystemUI {
     private static final String TAG = "ThemeOverlayController";
     private static final boolean DEBUG = false;
diff --git a/packages/SystemUI/src/com/android/systemui/toast/ToastUI.java b/packages/SystemUI/src/com/android/systemui/toast/ToastUI.java
index 9b465ae..a220373 100644
--- a/packages/SystemUI/src/com/android/systemui/toast/ToastUI.java
+++ b/packages/SystemUI/src/com/android/systemui/toast/ToastUI.java
@@ -33,17 +33,17 @@
 import com.android.internal.R;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.systemui.SystemUI;
+import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.statusbar.CommandQueue;
 
 import java.util.Objects;
 
 import javax.inject.Inject;
-import javax.inject.Singleton;
 
 /**
  * Controls display of text toasts.
  */
-@Singleton
+@SysUISingleton
 public class ToastUI extends SystemUI implements CommandQueue.Callbacks {
     private static final String TAG = "ToastUI";
 
diff --git a/packages/SystemUI/src/com/android/systemui/tracing/ProtoTracer.java b/packages/SystemUI/src/com/android/systemui/tracing/ProtoTracer.java
index 25ae098..8a8f92b 100644
--- a/packages/SystemUI/src/com/android/systemui/tracing/ProtoTracer.java
+++ b/packages/SystemUI/src/com/android/systemui/tracing/ProtoTracer.java
@@ -25,6 +25,7 @@
 import androidx.annotation.NonNull;
 
 import com.android.systemui.Dumpable;
+import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dump.DumpManager;
 import com.android.systemui.shared.tracing.FrameProtoTracer;
 import com.android.systemui.shared.tracing.FrameProtoTracer.ProtoTraceParams;
@@ -42,12 +43,11 @@
 import java.util.Queue;
 
 import javax.inject.Inject;
-import javax.inject.Singleton;
 
 /**
  * Controller for coordinating winscope proto tracing.
  */
-@Singleton
+@SysUISingleton
 public class ProtoTracer implements Dumpable, ProtoTraceParams<MessageNano, SystemUiTraceFileProto,
         SystemUiTraceEntryProto, SystemUiTraceProto> {
 
diff --git a/packages/SystemUI/src/com/android/systemui/tuner/DemoModeFragment.java b/packages/SystemUI/src/com/android/systemui/tuner/DemoModeFragment.java
index 49ada1a..1f44434 100644
--- a/packages/SystemUI/src/com/android/systemui/tuner/DemoModeFragment.java
+++ b/packages/SystemUI/src/com/android/systemui/tuner/DemoModeFragment.java
@@ -15,14 +15,10 @@
  */
 package com.android.systemui.tuner;
 
-import android.content.ContentResolver;
+import android.annotation.SuppressLint;
 import android.content.Context;
 import android.content.Intent;
-import android.database.ContentObserver;
 import android.os.Bundle;
-import android.os.Handler;
-import android.os.Looper;
-import android.provider.Settings;
 import android.view.MenuItem;
 
 import androidx.preference.Preference;
@@ -33,13 +29,13 @@
 
 import com.android.internal.logging.MetricsLogger;
 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
-import com.android.systemui.DemoMode;
 import com.android.systemui.R;
+import com.android.systemui.demomode.DemoMode;
+import com.android.systemui.demomode.DemoModeAvailabilityTracker;
+import com.android.systemui.demomode.DemoModeController;
 
 public class DemoModeFragment extends PreferenceFragment implements OnPreferenceChangeListener {
 
-    private static final String DEMO_MODE_ON = "sysui_tuner_demo_on";
-
     private static final String[] STATUS_ICONS = {
         "volume",
         "bluetooth",
@@ -57,6 +53,17 @@
     private SwitchPreference mEnabledSwitch;
     private SwitchPreference mOnSwitch;
 
+    private DemoModeController mDemoModeController;
+    private Tracker mDemoModeTracker;
+
+    // We are the only ones who ever call this constructor, so don't worry about the warning
+    @SuppressLint("ValidFragment")
+    public DemoModeFragment(DemoModeController demoModeController) {
+        super();
+        mDemoModeController = demoModeController;
+    }
+
+
     @Override
     public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
         Context context = getContext();
@@ -73,13 +80,11 @@
         screen.addPreference(mOnSwitch);
         setPreferenceScreen(screen);
 
+        mDemoModeTracker = new Tracker(context);
+        mDemoModeTracker.startTracking();
         updateDemoModeEnabled();
         updateDemoModeOn();
-        ContentResolver contentResolver = getContext().getContentResolver();
-        contentResolver.registerContentObserver(Settings.Global.getUriFor(
-                DemoMode.DEMO_MODE_ALLOWED), false, mDemoModeObserver);
-        contentResolver.registerContentObserver(Settings.Global.getUriFor(DEMO_MODE_ON), false,
-                mDemoModeObserver);
+
         setHasOptionsMenu(true);
     }
 
@@ -107,21 +112,17 @@
 
     @Override
     public void onDestroy() {
-        getContext().getContentResolver().unregisterContentObserver(mDemoModeObserver);
+        mDemoModeTracker.stopTracking();
         super.onDestroy();
     }
 
     private void updateDemoModeEnabled() {
-        boolean enabled = Settings.Global.getInt(getContext().getContentResolver(),
-                DemoMode.DEMO_MODE_ALLOWED, 0) != 0;
-        mEnabledSwitch.setChecked(enabled);
-        mOnSwitch.setEnabled(enabled);
+        mEnabledSwitch.setChecked(mDemoModeTracker.isDemoModeAvailable());
+        mOnSwitch.setEnabled(mDemoModeTracker.isDemoModeAvailable());
     }
 
     private void updateDemoModeOn() {
-        boolean enabled = Settings.Global.getInt(getContext().getContentResolver(),
-                DEMO_MODE_ON, 0) != 0;
-        mOnSwitch.setChecked(enabled);
+        mOnSwitch.setChecked(mDemoModeTracker.isInDemoMode());
     }
 
     @Override
@@ -134,7 +135,7 @@
                 stopDemoMode();
             }
             MetricsLogger.action(getContext(), MetricsEvent.TUNER_DEMO_MODE_ENABLED, enabled);
-            setGlobal(DemoMode.DEMO_MODE_ALLOWED, enabled ? 1 : 0);
+            mDemoModeController.requestSetDemoModeAllowed(enabled);
         } else if (preference == mOnSwitch) {
             MetricsLogger.action(getContext(), MetricsEvent.TUNER_DEMO_MODE_ON, enabled);
             if (enabled) {
@@ -151,11 +152,11 @@
     private void startDemoMode() {
         Intent intent = new Intent(DemoMode.ACTION_DEMO);
 
-        intent.putExtra(DemoMode.EXTRA_COMMAND, DemoMode.COMMAND_ENTER);
-        getContext().sendBroadcast(intent);
+        mDemoModeController.requestStartDemoMode();
 
         intent.putExtra(DemoMode.EXTRA_COMMAND, DemoMode.COMMAND_CLOCK);
 
+        //TODO: everything below should move to DemoModeController, or some `initialState` command
         String demoTime = "1010"; // 10:10, a classic choice of horologists
         try {
             String[] versionParts = android.os.Build.VERSION.RELEASE_OR_CODENAME.split("\\.");
@@ -194,25 +195,31 @@
         intent.putExtra("visible", "false");
         getContext().sendBroadcast(intent);
 
-        setGlobal(DEMO_MODE_ON, 1);
     }
 
     private void stopDemoMode() {
-        Intent intent = new Intent(DemoMode.ACTION_DEMO);
-        intent.putExtra(DemoMode.EXTRA_COMMAND, DemoMode.COMMAND_EXIT);
-        getContext().sendBroadcast(intent);
-        setGlobal(DEMO_MODE_ON, 0);
+        mDemoModeController.requestFinishDemoMode();
     }
 
-    private void setGlobal(String key, int value) {
-        Settings.Global.putInt(getContext().getContentResolver(), key, value);
-    }
+    private class Tracker extends DemoModeAvailabilityTracker {
+        Tracker(Context context) {
+            super(context);
+        }
 
-    private final ContentObserver mDemoModeObserver =
-            new ContentObserver(new Handler(Looper.getMainLooper())) {
-        public void onChange(boolean selfChange) {
+        @Override
+        public void onDemoModeAvailabilityChanged() {
             updateDemoModeEnabled();
             updateDemoModeOn();
-        };
+        }
+
+        @Override
+        public void onDemoModeStarted() {
+            updateDemoModeOn();
+        }
+
+        @Override
+        public void onDemoModeFinished() {
+            updateDemoModeOn();
+        }
     };
 }
diff --git a/packages/SystemUI/src/com/android/systemui/tuner/NavBarTuner.java b/packages/SystemUI/src/com/android/systemui/tuner/NavBarTuner.java
index fa531b5..87d2063 100644
--- a/packages/SystemUI/src/com/android/systemui/tuner/NavBarTuner.java
+++ b/packages/SystemUI/src/com/android/systemui/tuner/NavBarTuner.java
@@ -14,18 +14,18 @@
 
 package com.android.systemui.tuner;
 
-import static com.android.systemui.statusbar.phone.NavigationBarInflaterView.KEY;
-import static com.android.systemui.statusbar.phone.NavigationBarInflaterView.KEY_CODE_END;
-import static com.android.systemui.statusbar.phone.NavigationBarInflaterView.KEY_CODE_START;
-import static com.android.systemui.statusbar.phone.NavigationBarInflaterView.KEY_IMAGE_DELIM;
-import static com.android.systemui.statusbar.phone.NavigationBarInflaterView.MENU_IME_ROTATE;
-import static com.android.systemui.statusbar.phone.NavigationBarInflaterView.NAVSPACE;
-import static com.android.systemui.statusbar.phone.NavigationBarInflaterView.NAV_BAR_LEFT;
-import static com.android.systemui.statusbar.phone.NavigationBarInflaterView.NAV_BAR_RIGHT;
-import static com.android.systemui.statusbar.phone.NavigationBarInflaterView.NAV_BAR_VIEWS;
-import static com.android.systemui.statusbar.phone.NavigationBarInflaterView.extractButton;
-import static com.android.systemui.statusbar.phone.NavigationBarInflaterView.extractImage;
-import static com.android.systemui.statusbar.phone.NavigationBarInflaterView.extractKeycode;
+import static com.android.systemui.navigationbar.NavigationBarInflaterView.KEY;
+import static com.android.systemui.navigationbar.NavigationBarInflaterView.KEY_CODE_END;
+import static com.android.systemui.navigationbar.NavigationBarInflaterView.KEY_CODE_START;
+import static com.android.systemui.navigationbar.NavigationBarInflaterView.KEY_IMAGE_DELIM;
+import static com.android.systemui.navigationbar.NavigationBarInflaterView.MENU_IME_ROTATE;
+import static com.android.systemui.navigationbar.NavigationBarInflaterView.NAVSPACE;
+import static com.android.systemui.navigationbar.NavigationBarInflaterView.NAV_BAR_LEFT;
+import static com.android.systemui.navigationbar.NavigationBarInflaterView.NAV_BAR_RIGHT;
+import static com.android.systemui.navigationbar.NavigationBarInflaterView.NAV_BAR_VIEWS;
+import static com.android.systemui.navigationbar.NavigationBarInflaterView.extractButton;
+import static com.android.systemui.navigationbar.NavigationBarInflaterView.extractImage;
+import static com.android.systemui.navigationbar.NavigationBarInflaterView.extractKeycode;
 
 import android.annotation.Nullable;
 import android.app.AlertDialog;
@@ -51,6 +51,7 @@
 
 import java.util.ArrayList;
 
+@Deprecated
 public class NavBarTuner extends TunerPreferenceFragment {
 
     private static final String LAYOUT = "layout";
diff --git a/packages/SystemUI/src/com/android/systemui/tuner/TunablePadding.java b/packages/SystemUI/src/com/android/systemui/tuner/TunablePadding.java
index 8f3a8f6..d54c07c 100644
--- a/packages/SystemUI/src/com/android/systemui/tuner/TunablePadding.java
+++ b/packages/SystemUI/src/com/android/systemui/tuner/TunablePadding.java
@@ -19,10 +19,10 @@
 import android.view.WindowManager;
 
 import com.android.systemui.Dependency;
+import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.tuner.TunerService.Tunable;
 
 import javax.inject.Inject;
-import javax.inject.Singleton;
 
 /**
  * Version of Space that can be resized by a tunable setting.
@@ -77,7 +77,7 @@
     /**
      * Exists for easy injecting in tests.
      */
-    @Singleton
+    @SysUISingleton
     public static class TunablePaddingService {
 
         private final TunerService mTunerService;
diff --git a/packages/SystemUI/src/com/android/systemui/tuner/TunerActivity.java b/packages/SystemUI/src/com/android/systemui/tuner/TunerActivity.java
index 453c2f7..78341ed 100644
--- a/packages/SystemUI/src/com/android/systemui/tuner/TunerActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/tuner/TunerActivity.java
@@ -31,6 +31,7 @@
 
 import com.android.systemui.Dependency;
 import com.android.systemui.R;
+import com.android.systemui.demomode.DemoModeController;
 import com.android.systemui.fragments.FragmentService;
 
 import javax.inject.Inject;
@@ -41,9 +42,12 @@
 
     private static final String TAG_TUNER = "tuner";
 
+    private final DemoModeController mDemoModeController;
+
     @Inject
-    TunerActivity() {
+    TunerActivity(DemoModeController demoModeController) {
         super();
+        mDemoModeController = demoModeController;
     }
 
     protected void onCreate(Bundle savedInstanceState) {
@@ -61,7 +65,8 @@
             final String action = getIntent().getAction();
             boolean showDemoMode = action != null && action.equals(
                     "com.android.settings.action.DEMO_MODE");
-            final PreferenceFragment fragment = showDemoMode ? new DemoModeFragment()
+            final PreferenceFragment fragment = showDemoMode
+                    ? new DemoModeFragment(mDemoModeController)
                     : new TunerFragment();
             getFragmentManager().beginTransaction().replace(R.id.content_frame,
                     fragment, TAG_TUNER).commit();
diff --git a/packages/SystemUI/src/com/android/systemui/tuner/TunerServiceImpl.java b/packages/SystemUI/src/com/android/systemui/tuner/TunerServiceImpl.java
index 644f758..d9727a7 100644
--- a/packages/SystemUI/src/com/android/systemui/tuner/TunerServiceImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/tuner/TunerServiceImpl.java
@@ -18,7 +18,6 @@
 import android.app.ActivityManager;
 import android.content.ContentResolver;
 import android.content.Context;
-import android.content.Intent;
 import android.content.pm.UserInfo;
 import android.database.ContentObserver;
 import android.net.Uri;
@@ -33,9 +32,10 @@
 
 import com.android.internal.util.ArrayUtils;
 import com.android.systemui.DejankUtils;
-import com.android.systemui.DemoMode;
 import com.android.systemui.broadcast.BroadcastDispatcher;
+import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dagger.qualifiers.Main;
+import com.android.systemui.demomode.DemoModeController;
 import com.android.systemui.qs.QSTileHost;
 import com.android.systemui.settings.CurrentUserTracker;
 import com.android.systemui.statusbar.phone.StatusBarIconController;
@@ -46,14 +46,14 @@
 import java.util.concurrent.ConcurrentHashMap;
 
 import javax.inject.Inject;
-import javax.inject.Singleton;
 
 
 /**
  */
-@Singleton
+@SysUISingleton
 public class TunerServiceImpl extends TunerService {
 
+    private static final String TAG = "TunerService";
     private static final String TUNER_VERSION = "sysui_tuner_version";
 
     private static final int CURRENT_TUNER_VERSION = 4;
@@ -63,7 +63,8 @@
     private static final String[] RESET_EXCEPTION_LIST = new String[] {
             QSTileHost.TILES_SETTING,
             Settings.Secure.DOZE_ALWAYS_ON,
-            Settings.Secure.MEDIA_CONTROLS_RESUME
+            Settings.Secure.MEDIA_CONTROLS_RESUME,
+            Secure.MEDIA_CONTROLS_RESUME_BLOCKED
     };
 
     private final Observer mObserver = new Observer();
@@ -76,6 +77,7 @@
     private final HashSet<Tunable> mTunables = LeakDetector.ENABLED ? new HashSet<>() : null;
     private final Context mContext;
     private final LeakDetector mLeakDetector;
+    private final DemoModeController mDemoModeController;
 
     private ContentResolver mContentResolver;
     private int mCurrentUser;
@@ -84,11 +86,16 @@
     /**
      */
     @Inject
-    public TunerServiceImpl(Context context, @Main Handler mainHandler,
-            LeakDetector leakDetector, BroadcastDispatcher broadcastDispatcher) {
+    public TunerServiceImpl(
+            Context context,
+            @Main Handler mainHandler,
+            LeakDetector leakDetector,
+            DemoModeController demoModeController,
+            BroadcastDispatcher broadcastDispatcher) {
         mContext = context;
         mContentResolver = mContext.getContentResolver();
         mLeakDetector = leakDetector;
+        mDemoModeController = demoModeController;
 
         for (UserInfo user : UserManager.get(mContext).getUsers()) {
             mCurrentUser = user.getUserHandle().getIdentifier();
@@ -244,12 +251,11 @@
     }
 
     public void clearAllFromUser(int user) {
-        // A couple special cases.
-        Settings.Global.putString(mContentResolver, DemoMode.DEMO_MODE_ALLOWED, null);
-        Intent intent = new Intent(DemoMode.ACTION_DEMO);
-        intent.putExtra(DemoMode.EXTRA_COMMAND, DemoMode.COMMAND_EXIT);
-        mContext.sendBroadcast(intent);
+        // Turn off demo mode
+        mDemoModeController.requestFinishDemoMode();
+        mDemoModeController.requestSetDemoModeAllowed(false);
 
+        // A couple special cases.
         for (String key : mTunableLookup.keySet()) {
             if (ArrayUtils.contains(RESET_EXCEPTION_LIST, key)) {
                 continue;
diff --git a/packages/SystemUI/src/com/android/systemui/tv/TvGlobalRootComponent.java b/packages/SystemUI/src/com/android/systemui/tv/TvGlobalRootComponent.java
new file mode 100644
index 0000000..37aac11
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/tv/TvGlobalRootComponent.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.tv;
+
+import com.android.systemui.dagger.GlobalRootComponent;
+
+import javax.inject.Singleton;
+
+import dagger.Component;
+
+/**
+ * Root component for Dagger injection.
+ */
+@Singleton
+@Component(modules = {TvSysUIComponentModule.class})
+public interface TvGlobalRootComponent extends GlobalRootComponent {
+    /**
+     * Component Builder interface. This allows to bind Context instance in the component
+     */
+    @Component.Builder
+    interface Builder extends GlobalRootComponent.Builder {
+        TvGlobalRootComponent build();
+    }
+
+    @Override
+    TvSysUIComponent.Builder getSysUIComponent();
+}
diff --git a/packages/SystemUI/src/com/android/systemui/tv/TvSystemUIRootComponent.java b/packages/SystemUI/src/com/android/systemui/tv/TvSysUIComponent.java
similarity index 65%
rename from packages/SystemUI/src/com/android/systemui/tv/TvSystemUIRootComponent.java
rename to packages/SystemUI/src/com/android/systemui/tv/TvSysUIComponent.java
index dce38c1..302301d 100644
--- a/packages/SystemUI/src/com/android/systemui/tv/TvSystemUIRootComponent.java
+++ b/packages/SystemUI/src/com/android/systemui/tv/TvSysUIComponent.java
@@ -19,37 +19,34 @@
 import com.android.systemui.dagger.DefaultComponentBinder;
 import com.android.systemui.dagger.DependencyBinder;
 import com.android.systemui.dagger.DependencyProvider;
+import com.android.systemui.dagger.SysUIComponent;
+import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dagger.SystemServicesModule;
 import com.android.systemui.dagger.SystemUIBinder;
-import com.android.systemui.dagger.SystemUIDefaultModule;
 import com.android.systemui.dagger.SystemUIModule;
-import com.android.systemui.dagger.SystemUIRootComponent;
-import com.android.systemui.onehanded.dagger.OneHandedModule;
 
-import javax.inject.Singleton;
-
-import dagger.Component;
+import dagger.Subcomponent;
 
 /**
- * Root component for Dagger injection.
+ * Dagger Subcomponent for Core SysUI.
  */
-@Singleton
-@Component(modules = {
+@SysUISingleton
+@Subcomponent(modules = {
         DefaultComponentBinder.class,
         DependencyProvider.class,
         DependencyBinder.class,
-        OneHandedModule.class,
         SystemServicesModule.class,
         SystemUIBinder.class,
         SystemUIModule.class,
-        SystemUIDefaultModule.class,
+        TvSystemUIModule.class,
         TvSystemUIBinder.class})
-public interface TvSystemUIRootComponent extends SystemUIRootComponent {
+public interface TvSysUIComponent extends SysUIComponent {
+
     /**
-     * Component Builder interface. This allows to bind Context instance in the component
+     * Builder for a SysUIComponent.
      */
-    @Component.Builder
-    interface Builder extends SystemUIRootComponent.Builder {
-        TvSystemUIRootComponent build();
+    @Subcomponent.Builder
+    interface Builder extends SysUIComponent.Builder {
+        TvSysUIComponent build();
     }
 }
diff --git a/tests/AutoVerify/app1/src/com/android/test/autoverify/MainActivity.java b/packages/SystemUI/src/com/android/systemui/tv/TvSysUIComponentModule.java
similarity index 73%
copy from tests/AutoVerify/app1/src/com/android/test/autoverify/MainActivity.java
copy to packages/SystemUI/src/com/android/systemui/tv/TvSysUIComponentModule.java
index 09ef472..334bb01 100644
--- a/tests/AutoVerify/app1/src/com/android/test/autoverify/MainActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/tv/TvSysUIComponentModule.java
@@ -13,3 +13,14 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
+
+package com.android.systemui.tv;
+
+import dagger.Module;
+
+/**
+ * Dagger module for including the WMComponent.
+ */
+@Module(subcomponents = {TvSysUIComponent.class})
+public abstract class TvSysUIComponentModule {
+}
diff --git a/packages/SystemUI/src/com/android/systemui/tv/TvSystemUIBinder.java b/packages/SystemUI/src/com/android/systemui/tv/TvSystemUIBinder.java
index be30a4a..9a44bf1 100644
--- a/packages/SystemUI/src/com/android/systemui/tv/TvSystemUIBinder.java
+++ b/packages/SystemUI/src/com/android/systemui/tv/TvSystemUIBinder.java
@@ -16,7 +16,7 @@
 
 package com.android.systemui.tv;
 
-import com.android.systemui.dagger.SystemUIRootComponent;
+import com.android.systemui.dagger.GlobalRootComponent;
 import com.android.systemui.pip.tv.dagger.PipModule;
 
 import dagger.Binds;
@@ -25,5 +25,5 @@
 @Module(includes = {PipModule.class})
 interface TvSystemUIBinder {
     @Binds
-    SystemUIRootComponent bindSystemUIRootComponent(TvSystemUIRootComponent systemUIRootComponent);
+    GlobalRootComponent bindGlobalRootComponent(TvGlobalRootComponent globalRootComponent);
 }
diff --git a/packages/SystemUI/src/com/android/systemui/tv/TvSystemUIFactory.java b/packages/SystemUI/src/com/android/systemui/tv/TvSystemUIFactory.java
index 7d3ec67..c99ad23 100644
--- a/packages/SystemUI/src/com/android/systemui/tv/TvSystemUIFactory.java
+++ b/packages/SystemUI/src/com/android/systemui/tv/TvSystemUIFactory.java
@@ -19,16 +19,16 @@
 import android.content.Context;
 
 import com.android.systemui.SystemUIFactory;
-import com.android.systemui.dagger.SystemUIRootComponent;
+import com.android.systemui.dagger.GlobalRootComponent;
 
 /**
- * TV variant {@link SystemUIFactory}, that substitutes default {@link SystemUIRootComponent} for
- * {@link TvSystemUIRootComponent}
+ * TV variant {@link SystemUIFactory}, that substitutes default {@link GlobalRootComponent} for
+ * {@link TvGlobalRootComponent}
  */
 public class TvSystemUIFactory extends SystemUIFactory {
     @Override
-    protected SystemUIRootComponent buildSystemUIRootComponent(Context context) {
-        return DaggerTvSystemUIRootComponent.builder()
+    protected GlobalRootComponent buildGlobalRootComponent(Context context) {
+        return DaggerTvGlobalRootComponent.builder()
                 .context(context)
                 .build();
     }
diff --git a/packages/SystemUI/src/com/android/systemui/tv/TvSystemUIModule.java b/packages/SystemUI/src/com/android/systemui/tv/TvSystemUIModule.java
new file mode 100644
index 0000000..e7c10f1
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/tv/TvSystemUIModule.java
@@ -0,0 +1,169 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.tv;
+
+import static com.android.systemui.Dependency.ALLOW_NOTIFICATION_LONG_PRESS_NAME;
+import static com.android.systemui.Dependency.LEAK_REPORT_EMAIL_NAME;
+
+import android.content.Context;
+import android.os.Handler;
+import android.os.PowerManager;
+
+import androidx.annotation.Nullable;
+
+import com.android.keyguard.KeyguardViewController;
+import com.android.systemui.broadcast.BroadcastDispatcher;
+import com.android.systemui.dagger.SysUISingleton;
+import com.android.systemui.dagger.qualifiers.Background;
+import com.android.systemui.dagger.qualifiers.Main;
+import com.android.systemui.demomode.DemoModeController;
+import com.android.systemui.dock.DockManager;
+import com.android.systemui.dock.DockManagerImpl;
+import com.android.systemui.doze.DozeHost;
+import com.android.systemui.plugins.qs.QSFactory;
+import com.android.systemui.plugins.statusbar.StatusBarStateController;
+import com.android.systemui.power.EnhancedEstimates;
+import com.android.systemui.power.EnhancedEstimatesImpl;
+import com.android.systemui.qs.dagger.QSModule;
+import com.android.systemui.qs.tileimpl.QSFactoryImpl;
+import com.android.systemui.recents.Recents;
+import com.android.systemui.recents.RecentsImplementation;
+import com.android.systemui.statusbar.CommandQueue;
+import com.android.systemui.statusbar.NotificationLockscreenUserManager;
+import com.android.systemui.statusbar.NotificationLockscreenUserManagerImpl;
+import com.android.systemui.statusbar.NotificationShadeWindowController;
+import com.android.systemui.statusbar.notification.NotificationEntryManager;
+import com.android.systemui.statusbar.phone.DozeServiceHost;
+import com.android.systemui.statusbar.phone.HeadsUpManagerPhone;
+import com.android.systemui.statusbar.phone.KeyguardBypassController;
+import com.android.systemui.statusbar.phone.KeyguardEnvironmentImpl;
+import com.android.systemui.statusbar.phone.NotificationGroupManager;
+import com.android.systemui.statusbar.phone.NotificationShadeWindowControllerImpl;
+import com.android.systemui.statusbar.phone.ShadeController;
+import com.android.systemui.statusbar.phone.ShadeControllerImpl;
+import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
+import com.android.systemui.statusbar.policy.BatteryController;
+import com.android.systemui.statusbar.policy.BatteryControllerImpl;
+import com.android.systemui.statusbar.policy.ConfigurationController;
+import com.android.systemui.statusbar.policy.DeviceProvisionedController;
+import com.android.systemui.statusbar.policy.DeviceProvisionedControllerImpl;
+import com.android.systemui.statusbar.policy.HeadsUpManager;
+import com.android.systemui.wmshell.WMShellModule;
+
+import javax.inject.Named;
+
+import dagger.Binds;
+import dagger.Module;
+import dagger.Provides;
+
+/**
+ * A dagger module for injecting default implementations of components of System UI that may be
+ * overridden by the System UI implementation.
+ */
+@Module(includes = {
+            QSModule.class,
+            WMShellModule.class
+        },
+        subcomponents = {
+        })
+public abstract class TvSystemUIModule {
+
+    @SysUISingleton
+    @Provides
+    @Named(LEAK_REPORT_EMAIL_NAME)
+    @Nullable
+    static String provideLeakReportEmail() {
+        return null;
+    }
+
+    @Binds
+    abstract EnhancedEstimates bindEnhancedEstimates(EnhancedEstimatesImpl enhancedEstimates);
+
+    @Binds
+    abstract NotificationLockscreenUserManager bindNotificationLockscreenUserManager(
+            NotificationLockscreenUserManagerImpl notificationLockscreenUserManager);
+
+    @Provides
+    @SysUISingleton
+    static BatteryController provideBatteryController(Context context,
+            EnhancedEstimates enhancedEstimates, PowerManager powerManager,
+            BroadcastDispatcher broadcastDispatcher, DemoModeController demoModeController,
+            @Main Handler mainHandler, @Background Handler bgHandler) {
+        BatteryController bC = new BatteryControllerImpl(context, enhancedEstimates, powerManager,
+                broadcastDispatcher, demoModeController, mainHandler, bgHandler);
+        bC.init();
+        return bC;
+    }
+
+    @Binds
+    @SysUISingleton
+    abstract QSFactory bindQSFactory(QSFactoryImpl qsFactoryImpl);
+
+    @Binds
+    abstract DockManager bindDockManager(DockManagerImpl dockManager);
+
+    @Binds
+    abstract NotificationEntryManager.KeyguardEnvironment bindKeyguardEnvironment(
+            KeyguardEnvironmentImpl keyguardEnvironment);
+
+    @Binds
+    abstract ShadeController provideShadeController(ShadeControllerImpl shadeController);
+
+    @SysUISingleton
+    @Provides
+    @Named(ALLOW_NOTIFICATION_LONG_PRESS_NAME)
+    static boolean provideAllowNotificationLongPress() {
+        return true;
+    }
+
+    @SysUISingleton
+    @Provides
+    static HeadsUpManagerPhone provideHeadsUpManagerPhone(
+            Context context,
+            StatusBarStateController statusBarStateController,
+            KeyguardBypassController bypassController,
+            NotificationGroupManager groupManager,
+            ConfigurationController configurationController) {
+        return new HeadsUpManagerPhone(context, statusBarStateController, bypassController,
+                groupManager, configurationController);
+    }
+
+    @Binds
+    abstract HeadsUpManager bindHeadsUpManagerPhone(HeadsUpManagerPhone headsUpManagerPhone);
+
+    @Provides
+    @SysUISingleton
+    static Recents provideRecents(Context context, RecentsImplementation recentsImplementation,
+            CommandQueue commandQueue) {
+        return new Recents(context, recentsImplementation, commandQueue);
+    }
+
+    @Binds
+    abstract DeviceProvisionedController bindDeviceProvisionedController(
+            DeviceProvisionedControllerImpl deviceProvisionedController);
+
+    @Binds
+    abstract KeyguardViewController bindKeyguardViewController(
+            StatusBarKeyguardViewManager statusBarKeyguardViewManager);
+
+    @Binds
+    abstract NotificationShadeWindowController bindNotificationShadeController(
+            NotificationShadeWindowControllerImpl notificationShadeWindowController);
+
+    @Binds
+    abstract DozeHost provideDozeHost(DozeServiceHost dozeServiceHost);
+}
diff --git a/packages/SystemUI/src/com/android/systemui/usb/UsbAccessoryUriActivity.java b/packages/SystemUI/src/com/android/systemui/usb/UsbAccessoryUriActivity.java
index b5f98ad..89297fd 100644
--- a/packages/SystemUI/src/com/android/systemui/usb/UsbAccessoryUriActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/usb/UsbAccessoryUriActivity.java
@@ -54,7 +54,7 @@
         String uriString = intent.getStringExtra("uri");
         mUri = (uriString == null ? null : Uri.parse(uriString));
 
-        // sanity check before displaying dialog
+        // Exception check before displaying dialog
         if (mUri == null) {
             Log.e(TAG, "could not parse Uri " + uriString);
             finish();
diff --git a/packages/SystemUI/src/com/android/systemui/util/DeviceConfigProxy.java b/packages/SystemUI/src/com/android/systemui/util/DeviceConfigProxy.java
index 3a2172a..66f8f74 100644
--- a/packages/SystemUI/src/com/android/systemui/util/DeviceConfigProxy.java
+++ b/packages/SystemUI/src/com/android/systemui/util/DeviceConfigProxy.java
@@ -25,13 +25,11 @@
 
 import java.util.concurrent.Executor;
 
-import javax.inject.Inject;
-
 /**
  * Wrapper around DeviceConfig useful for testing.
  */
 public class DeviceConfigProxy {
-    @Inject
+
     public DeviceConfigProxy() {
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/util/FloatingContentCoordinator.kt b/packages/SystemUI/src/com/android/systemui/util/FloatingContentCoordinator.kt
index 242f7cd..bcfb2af 100644
--- a/packages/SystemUI/src/com/android/systemui/util/FloatingContentCoordinator.kt
+++ b/packages/SystemUI/src/com/android/systemui/util/FloatingContentCoordinator.kt
@@ -2,10 +2,9 @@
 
 import android.graphics.Rect
 import android.util.Log
+import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.util.FloatingContentCoordinator.FloatingContent
 import java.util.HashMap
-import javax.inject.Inject
-import javax.inject.Singleton
 
 /** Tag for debug logging. */
 private const val TAG = "FloatingCoordinator"
@@ -20,9 +19,9 @@
  * other content out of the way. [onContentRemoved] should be called when the content is removed or
  * no longer visible.
  */
-@Singleton
-class FloatingContentCoordinator @Inject constructor() {
 
+@SysUISingleton
+class FloatingContentCoordinator constructor() {
     /**
      * Represents a piece of floating content, such as PIP or the Bubbles stack. Provides methods
      * that allow the [FloatingContentCoordinator] to determine the current location of the content,
diff --git a/packages/SystemUI/src/com/android/systemui/util/InjectionInflationController.java b/packages/SystemUI/src/com/android/systemui/util/InjectionInflationController.java
index 551b7b4..d278905 100644
--- a/packages/SystemUI/src/com/android/systemui/util/InjectionInflationController.java
+++ b/packages/SystemUI/src/com/android/systemui/util/InjectionInflationController.java
@@ -25,13 +25,12 @@
 
 import com.android.keyguard.KeyguardMessageArea;
 import com.android.keyguard.KeyguardSliceView;
-import com.android.systemui.dagger.SystemUIRootComponent;
+import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.qs.QSFooterImpl;
 import com.android.systemui.qs.QSPanel;
 import com.android.systemui.qs.QuickQSPanel;
 import com.android.systemui.qs.QuickStatusBarHeader;
 import com.android.systemui.qs.customize.QSCustomizer;
-import com.android.systemui.statusbar.NotificationShelf;
 import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout;
 
 import java.lang.reflect.InvocationTargetException;
@@ -40,38 +39,28 @@
 
 import javax.inject.Inject;
 import javax.inject.Named;
-import javax.inject.Singleton;
 
-import dagger.Module;
-import dagger.Provides;
+import dagger.BindsInstance;
 import dagger.Subcomponent;
 
 /**
  * Manages inflation that requires dagger injection.
  * See docs/dagger.md for details.
  */
-@Singleton
+@SysUISingleton
 public class InjectionInflationController {
 
     public static final String VIEW_CONTEXT = "view_context";
-    private final ViewCreator mViewCreator;
     private final ArrayMap<String, Method> mInjectionMap = new ArrayMap<>();
     private final LayoutInflater.Factory2 mFactory = new InjectionFactory();
+    private final ViewInstanceCreator.Factory mViewInstanceCreatorFactory;
 
     @Inject
-    public InjectionInflationController(SystemUIRootComponent rootComponent) {
-        mViewCreator = rootComponent.createViewCreator();
+    public InjectionInflationController(ViewInstanceCreator.Factory viewInstanceCreatorFactory) {
+        mViewInstanceCreatorFactory = viewInstanceCreatorFactory;
         initInjectionMap();
     }
 
-    ArrayMap<String, Method> getInjectionMap() {
-        return mInjectionMap;
-    }
-
-    ViewCreator getFragmentCreator() {
-        return mViewCreator;
-    }
-
     /**
      * Wraps a {@link LayoutInflater} to support creating dagger injected views.
      * See docs/dagger.md for details.
@@ -92,25 +81,19 @@
     }
 
     /**
-     * The subcomponent of dagger that holds all views that need injection.
+     * Subcomponent that actually creates injected views.
      */
     @Subcomponent
-    public interface ViewCreator {
-        /**
-         * Creates another subcomponent to actually generate the view.
-         */
-        ViewInstanceCreator createInstanceCreator(ViewAttributeProvider attributeProvider);
-    }
-
-    /**
-     * Secondary sub-component that actually creates the views.
-     *
-     * Having two subcomponents lets us hide the complexity of providing the named context
-     * and AttributeSet from the SystemUIRootComponent, instead we have one subcomponent that
-     * creates a new ViewInstanceCreator any time we need to inflate a view.
-     */
-    @Subcomponent(modules = ViewAttributeProvider.class)
     public interface ViewInstanceCreator {
+
+        /** Factory for creating a ViewInstanceCreator. */
+        @Subcomponent.Factory
+        interface Factory {
+            ViewInstanceCreator build(
+                    @BindsInstance @Named(VIEW_CONTEXT) Context context,
+                    @BindsInstance AttributeSet attributeSet);
+        }
+
         /**
          * Creates the QuickStatusBarHeader.
          */
@@ -126,11 +109,6 @@
         NotificationStackScrollLayout createNotificationStackScrollLayout();
 
         /**
-         * Creates the Shelf.
-         */
-        NotificationShelf creatNotificationShelf();
-
-        /**
          * Creates the KeyguardSliceView.
          */
         KeyguardSliceView createKeyguardSliceView();
@@ -156,36 +134,6 @@
         QSCustomizer createQSCustomizer();
     }
 
-    /**
-     * Module for providing view-specific constructor objects.
-     */
-    @Module
-    public class ViewAttributeProvider {
-        private final Context mContext;
-        private final AttributeSet mAttrs;
-
-        private ViewAttributeProvider(Context context, AttributeSet attrs) {
-            mContext = context;
-            mAttrs = attrs;
-        }
-
-        /**
-         * Provides the view-themed context (as opposed to the global sysui application context).
-         */
-        @Provides
-        @Named(VIEW_CONTEXT)
-        public Context provideContext() {
-            return mContext;
-        }
-
-        /**
-         * Provides the AttributeSet for the current view being inflated.
-         */
-        @Provides
-        public AttributeSet provideAttributeSet() {
-            return mAttrs;
-        }
-    }
 
     private class InjectionFactory implements LayoutInflater.Factory2 {
 
@@ -193,10 +141,9 @@
         public View onCreateView(String name, Context context, AttributeSet attrs) {
             Method creationMethod = mInjectionMap.get(name);
             if (creationMethod != null) {
-                ViewAttributeProvider provider = new ViewAttributeProvider(context, attrs);
                 try {
                     return (View) creationMethod.invoke(
-                            mViewCreator.createInstanceCreator(provider));
+                            mViewInstanceCreatorFactory.build(context, attrs));
                 } catch (IllegalAccessException e) {
                     throw new InflateException("Could not inflate " + name, e);
                 } catch (InvocationTargetException e) {
diff --git a/packages/SystemUI/src/com/android/systemui/util/RingerModeTrackerImpl.kt b/packages/SystemUI/src/com/android/systemui/util/RingerModeTrackerImpl.kt
index 58684c3..7513241 100644
--- a/packages/SystemUI/src/com/android/systemui/util/RingerModeTrackerImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/util/RingerModeTrackerImpl.kt
@@ -25,12 +25,12 @@
 import androidx.lifecycle.LiveData
 import androidx.lifecycle.MutableLiveData
 import com.android.systemui.broadcast.BroadcastDispatcher
+import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Background
 import java.util.concurrent.Executor
 import javax.inject.Inject
-import javax.inject.Singleton
 
-@Singleton
+@SysUISingleton
 class RingerModeTrackerImpl @Inject constructor(
     audioManager: AudioManager,
     broadcastDispatcher: BroadcastDispatcher,
diff --git a/packages/SystemUI/src/com/android/systemui/util/Utils.java b/packages/SystemUI/src/com/android/systemui/util/Utils.java
index e5f30cf..72f1f22 100644
--- a/packages/SystemUI/src/com/android/systemui/util/Utils.java
+++ b/packages/SystemUI/src/com/android/systemui/util/Utils.java
@@ -21,12 +21,15 @@
 import android.content.Intent;
 import android.content.pm.PackageManager;
 import android.provider.Settings;
+import android.text.TextUtils;
 import android.view.View;
 
 import com.android.systemui.shared.system.QuickStepContract;
 import com.android.systemui.statusbar.CommandQueue;
 
+import java.util.HashSet;
 import java.util.List;
+import java.util.Set;
 import java.util.function.Consumer;
 
 public class Utils {
@@ -143,4 +146,21 @@
                 Settings.Secure.MEDIA_CONTROLS_RESUME, 1);
         return useQsMediaPlayer(context) && flag > 0;
     }
+
+    /**
+     * Get the set of apps for which the user has manually disabled resumption.
+     */
+    public static Set<String> getBlockedMediaApps(Context context) {
+        String list = Settings.Secure.getString(context.getContentResolver(),
+                Settings.Secure.MEDIA_CONTROLS_RESUME_BLOCKED);
+        if (TextUtils.isEmpty(list)) {
+            return new HashSet<>();
+        }
+        String[] names = list.split(":");
+        Set<String> apps = new HashSet<>(names.length);
+        for (String s : names) {
+            apps.add(s);
+        }
+        return apps;
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/util/concurrency/ConcurrencyModule.java b/packages/SystemUI/src/com/android/systemui/util/concurrency/ConcurrencyModule.java
index bf22a98..628c808 100644
--- a/packages/SystemUI/src/com/android/systemui/util/concurrency/ConcurrencyModule.java
+++ b/packages/SystemUI/src/com/android/systemui/util/concurrency/ConcurrencyModule.java
@@ -22,6 +22,7 @@
 import android.os.Looper;
 import android.os.Process;
 
+import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dagger.qualifiers.Background;
 import com.android.systemui.dagger.qualifiers.LongRunning;
 import com.android.systemui.dagger.qualifiers.Main;
@@ -30,8 +31,6 @@
 import java.util.concurrent.Executor;
 import java.util.concurrent.Executors;
 
-import javax.inject.Singleton;
-
 import dagger.Binds;
 import dagger.Module;
 import dagger.Provides;
@@ -43,7 +42,7 @@
 public abstract class ConcurrencyModule {
     /** Background Looper */
     @Provides
-    @Singleton
+    @SysUISingleton
     @Background
     public static Looper provideBgLooper() {
         HandlerThread thread = new HandlerThread("SysUiBg",
@@ -54,7 +53,7 @@
 
     /** Long running tasks Looper */
     @Provides
-    @Singleton
+    @SysUISingleton
     @LongRunning
     public static Looper provideLongRunningLooper() {
         HandlerThread thread = new HandlerThread("SysUiLng",
@@ -96,7 +95,7 @@
      * Provide a Background-Thread Executor by default.
      */
     @Provides
-    @Singleton
+    @SysUISingleton
     public static Executor provideExecutor(@Background Looper looper) {
         return new ExecutorImpl(looper);
     }
@@ -105,7 +104,7 @@
      * Provide a Long running Executor by default.
      */
     @Provides
-    @Singleton
+    @SysUISingleton
     @LongRunning
     public static Executor provideLongRunningExecutor(@LongRunning Looper looper) {
         return new ExecutorImpl(looper);
@@ -115,7 +114,7 @@
      * Provide a Background-Thread Executor.
      */
     @Provides
-    @Singleton
+    @SysUISingleton
     @Background
     public static Executor provideBackgroundExecutor(@Background Looper looper) {
         return new ExecutorImpl(looper);
@@ -134,7 +133,7 @@
      * Provide a Background-Thread Executor by default.
      */
     @Provides
-    @Singleton
+    @SysUISingleton
     public static DelayableExecutor provideDelayableExecutor(@Background Looper looper) {
         return new ExecutorImpl(looper);
     }
@@ -143,7 +142,7 @@
      * Provide a Background-Thread Executor.
      */
     @Provides
-    @Singleton
+    @SysUISingleton
     @Background
     public static DelayableExecutor provideBackgroundDelayableExecutor(@Background Looper looper) {
         return new ExecutorImpl(looper);
@@ -153,7 +152,7 @@
      * Provide a Main-Thread Executor.
      */
     @Provides
-    @Singleton
+    @SysUISingleton
     @Main
     public static DelayableExecutor provideMainDelayableExecutor(@Main Looper looper) {
         return new ExecutorImpl(looper);
@@ -163,7 +162,7 @@
      * Provide a Background-Thread Executor by default.
      */
     @Provides
-    @Singleton
+    @SysUISingleton
     public static RepeatableExecutor provideRepeatableExecutor(@Background DelayableExecutor exec) {
         return new RepeatableExecutorImpl(exec);
     }
@@ -172,7 +171,7 @@
      * Provide a Background-Thread Executor.
      */
     @Provides
-    @Singleton
+    @SysUISingleton
     @Background
     public static RepeatableExecutor provideBackgroundRepeatableExecutor(
             @Background DelayableExecutor exec) {
@@ -183,7 +182,7 @@
      * Provide a Main-Thread Executor.
      */
     @Provides
-    @Singleton
+    @SysUISingleton
     @Main
     public static RepeatableExecutor provideMainRepeatableExecutor(@Main DelayableExecutor exec) {
         return new RepeatableExecutorImpl(exec);
@@ -195,7 +194,7 @@
      * Keep submitted runnables short and to the point, just as with any other UI code.
      */
     @Provides
-    @Singleton
+    @SysUISingleton
     @UiBackground
     public static Executor provideUiBackgroundExecutor() {
         return Executors.newSingleThreadExecutor();
diff --git a/packages/SystemUI/src/com/android/systemui/util/io/Files.java b/packages/SystemUI/src/com/android/systemui/util/io/Files.java
index 7d633a7..a2d309c 100644
--- a/packages/SystemUI/src/com/android/systemui/util/io/Files.java
+++ b/packages/SystemUI/src/com/android/systemui/util/io/Files.java
@@ -18,6 +18,8 @@
 
 import androidx.annotation.NonNull;
 
+import com.android.systemui.dagger.SysUISingleton;
+
 import java.io.BufferedWriter;
 import java.io.IOException;
 import java.nio.charset.StandardCharsets;
@@ -28,12 +30,11 @@
 import java.util.stream.Stream;
 
 import javax.inject.Inject;
-import javax.inject.Singleton;
 
 /**
  * Wrapper around {@link java.nio.file.Files} that can be mocked in tests.
  */
-@Singleton
+@SysUISingleton
 public class Files {
     @Inject
     public Files() { }
diff --git a/tests/AutoVerify/app1/src/com/android/test/autoverify/MainActivity.java b/packages/SystemUI/src/com/android/systemui/util/kotlin/nullability.kt
similarity index 73%
copy from tests/AutoVerify/app1/src/com/android/test/autoverify/MainActivity.java
copy to packages/SystemUI/src/com/android/systemui/util/kotlin/nullability.kt
index 09ef472..92c73a4 100644
--- a/tests/AutoVerify/app1/src/com/android/test/autoverify/MainActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/util/kotlin/nullability.kt
@@ -13,3 +13,10 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
+
+package com.android.systemui.util.kotlin
+
+/**
+ * If [value] is not null, then returns block(value). Otherwise returns null.
+ */
+inline fun <T : Any, R> transform(value: T?, block: (T) -> R): R? = value?.let(block)
diff --git a/packages/SystemUI/src/com/android/systemui/util/leak/GarbageMonitor.java b/packages/SystemUI/src/com/android/systemui/util/leak/GarbageMonitor.java
index d1805af..ba58ed2 100644
--- a/packages/SystemUI/src/com/android/systemui/util/leak/GarbageMonitor.java
+++ b/packages/SystemUI/src/com/android/systemui/util/leak/GarbageMonitor.java
@@ -48,6 +48,7 @@
 import com.android.systemui.Dumpable;
 import com.android.systemui.R;
 import com.android.systemui.SystemUI;
+import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dagger.qualifiers.Background;
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.plugins.ActivityStarter;
@@ -63,14 +64,13 @@
 import java.util.List;
 
 import javax.inject.Inject;
-import javax.inject.Singleton;
 
 /**
  * Suite of tools to periodically inspect the System UI heap and possibly prompt the user to
  * capture heap dumps and report them. Includes the implementation of the "Dump SysUI Heap"
  * quick settings tile.
  */
-@Singleton
+@SysUISingleton
 public class GarbageMonitor implements Dumpable {
     // Feature switches
     // ================
@@ -552,7 +552,7 @@
     }
 
     /** */
-    @Singleton
+    @SysUISingleton
     public static class Service extends SystemUI implements Dumpable {
         private final GarbageMonitor mGarbageMonitor;
 
diff --git a/packages/SystemUI/src/com/android/systemui/util/leak/LeakReporter.java b/packages/SystemUI/src/com/android/systemui/util/leak/LeakReporter.java
index b25df5f..f0a4195 100644
--- a/packages/SystemUI/src/com/android/systemui/util/leak/LeakReporter.java
+++ b/packages/SystemUI/src/com/android/systemui/util/leak/LeakReporter.java
@@ -34,6 +34,8 @@
 
 import androidx.core.content.FileProvider;
 
+import com.android.systemui.dagger.SysUISingleton;
+
 import com.google.android.collect.Lists;
 
 import java.io.File;
@@ -44,12 +46,11 @@
 
 import javax.inject.Inject;
 import javax.inject.Named;
-import javax.inject.Singleton;
 
 /**
  * Dumps data to debug leaks and posts a notification to share the data.
  */
-@Singleton
+@SysUISingleton
 public class LeakReporter {
 
     static final String TAG = "LeakReporter";
@@ -104,9 +105,13 @@
                     .setContentText(String.format(
                             "SystemUI has detected %d leaked objects. Tap to send", garbageCount))
                     .setSmallIcon(com.android.internal.R.drawable.stat_sys_adb)
-                    .setContentIntent(PendingIntent.getActivityAsUser(mContext, 0,
+                    .setContentIntent(PendingIntent.getActivityAsUser(
+                            mContext,
+                            0,
                             getIntent(hprofFile, dumpFile),
-                            PendingIntent.FLAG_UPDATE_CURRENT, null, UserHandle.CURRENT));
+                            PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE,
+                            null,
+                            UserHandle.CURRENT));
             notiMan.notify(TAG, 0, builder.build());
         } catch (IOException e) {
             Log.e(TAG, "Couldn't dump heap for leak", e);
diff --git a/packages/SystemUI/src/com/android/systemui/util/sensors/AsyncSensorManager.java b/packages/SystemUI/src/com/android/systemui/util/sensors/AsyncSensorManager.java
index ed4df17..4875982 100644
--- a/packages/SystemUI/src/com/android/systemui/util/sensors/AsyncSensorManager.java
+++ b/packages/SystemUI/src/com/android/systemui/util/sensors/AsyncSensorManager.java
@@ -29,6 +29,7 @@
 import android.util.Log;
 
 import com.android.internal.util.Preconditions;
+import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.plugins.PluginListener;
 import com.android.systemui.plugins.SensorManagerPlugin;
 import com.android.systemui.shared.plugins.PluginManager;
@@ -39,7 +40,6 @@
 import java.util.concurrent.Executor;
 
 import javax.inject.Inject;
-import javax.inject.Singleton;
 
 /**
  * Wrapper around sensor manager that hides potential sources of latency.
@@ -48,7 +48,7 @@
  * without blocking. Note that this means registering listeners now always appears successful even
  * if it is not.
  */
-@Singleton
+@SysUISingleton
 public class AsyncSensorManager extends SensorManager
         implements PluginListener<SensorManagerPlugin> {
 
diff --git a/packages/SystemUI/src/com/android/systemui/util/sensors/ProximitySensor.java b/packages/SystemUI/src/com/android/systemui/util/sensors/ProximitySensor.java
index 9fa03df..06806d0 100644
--- a/packages/SystemUI/src/com/android/systemui/util/sensors/ProximitySensor.java
+++ b/packages/SystemUI/src/com/android/systemui/util/sensors/ProximitySensor.java
@@ -338,7 +338,7 @@
         @Override
         public void run() {
             unregister();
-            mSensor.alertListeners();
+            onProximityEvent(null);
         }
 
         /**
diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeComponent.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeComponent.java
index 4239337..e8c5db7 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/VolumeComponent.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeComponent.java
@@ -18,7 +18,7 @@
 
 import android.content.res.Configuration;
 
-import com.android.systemui.DemoMode;
+import com.android.systemui.demomode.DemoMode;
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogComponent.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogComponent.java
index 1c2a2fa..56f1c09 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogComponent.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogComponent.java
@@ -27,6 +27,9 @@
 
 import com.android.settingslib.applications.InterestingConfigChanges;
 import com.android.systemui.Dependency;
+import com.android.systemui.dagger.SysUISingleton;
+import com.android.systemui.demomode.DemoMode;
+import com.android.systemui.demomode.DemoModeController;
 import com.android.systemui.keyguard.KeyguardViewMediator;
 import com.android.systemui.plugins.ActivityStarter;
 import com.android.systemui.plugins.PluginDependencyProvider;
@@ -38,14 +41,15 @@
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.List;
 
 import javax.inject.Inject;
-import javax.inject.Singleton;
 
 /**
  * Implementation of VolumeComponent backed by the new volume dialog.
  */
-@Singleton
+@SysUISingleton
 public class VolumeDialogComponent implements VolumeComponent, TunerService.Tunable,
         VolumeDialogControllerImpl.UserActivityListener{
 
@@ -72,8 +76,11 @@
     );
 
     @Inject
-    public VolumeDialogComponent(Context context, KeyguardViewMediator keyguardViewMediator,
-            VolumeDialogControllerImpl volumeDialogController) {
+    public VolumeDialogComponent(
+            Context context,
+            KeyguardViewMediator keyguardViewMediator,
+            VolumeDialogControllerImpl volumeDialogController,
+            DemoModeController demoModeController) {
         mContext = context;
         mKeyguardViewMediator = keyguardViewMediator;
         mController = volumeDialogController;
@@ -94,6 +101,7 @@
         applyConfiguration();
         Dependency.get(TunerService.class).addTunable(this, VOLUME_DOWN_SILENT, VOLUME_UP_SILENT,
                 VOLUME_SILENT_DO_NOT_DISTURB);
+        demoModeController.addCallback(this);
     }
 
     protected VolumeDialog createDefault() {
@@ -164,6 +172,13 @@
     }
 
     @Override
+    public List<String> demoCommands() {
+        List<String> s = new ArrayList<>();
+        s.add(DemoMode.COMMAND_VOLUME);
+        return s;
+    }
+
+    @Override
     public void register() {
         mController.register();
         DndTile.setCombinedIcon(mContext, true);
diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogControllerImpl.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogControllerImpl.java
index f19c49c..d8eecef 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogControllerImpl.java
@@ -62,6 +62,7 @@
 import com.android.systemui.Dumpable;
 import com.android.systemui.R;
 import com.android.systemui.broadcast.BroadcastDispatcher;
+import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.keyguard.WakefulnessLifecycle;
 import com.android.systemui.plugins.VolumeDialogController;
 import com.android.systemui.qs.tiles.DndTile;
@@ -77,7 +78,6 @@
 import java.util.Optional;
 
 import javax.inject.Inject;
-import javax.inject.Singleton;
 
 import dagger.Lazy;
 
@@ -88,7 +88,7 @@
  *
  *  Methods ending in "W" must be called on the worker thread.
  */
-@Singleton
+@SysUISingleton
 public class VolumeDialogControllerImpl implements VolumeDialogController, Dumpable {
     private static final String TAG = Util.logTag(VolumeDialogControllerImpl.class);
 
diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
index 3455ff4..51ad30e 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
@@ -150,6 +150,8 @@
     private boolean mShowing;
     private boolean mShowA11yStream;
 
+    private final boolean mShowLowMediaVolumeIcon;
+
     private int mActiveStream;
     private int mPrevActiveStream;
     private boolean mAutomute = VolumePrefs.DEFAULT_ENABLE_AUTOMUTE;
@@ -166,7 +168,7 @@
 
     public VolumeDialogImpl(Context context) {
         mContext =
-                new ContextThemeWrapper(context, R.style.qs_theme);
+                new ContextThemeWrapper(context, R.style.volume_dialog_theme);
         mController = Dependency.get(VolumeDialogController.class);
         mKeyguard = (KeyguardManager) mContext.getSystemService(Context.KEYGUARD_SERVICE);
         mActivityManager = (ActivityManager) mContext.getSystemService(Context.ACTIVITY_SERVICE);
@@ -175,6 +177,8 @@
         mShowActiveStreamOnly = showActiveStreamOnly();
         mHasSeenODICaptionsTooltip =
                 Prefs.getBoolean(context, Prefs.Key.HAS_SEEN_ODI_CAPTIONS_TOOLTIP, false);
+        mShowLowMediaVolumeIcon =
+            mContext.getResources().getBoolean(R.bool.config_showLowMediaVolumeIcon);
     }
 
     @Override
@@ -421,6 +425,7 @@
         row.dndIcon = row.view.findViewById(R.id.dnd_icon);
         row.slider = row.view.findViewById(R.id.volume_row_slider);
         row.slider.setOnSeekBarChangeListener(new VolumeSeekBarChangeListener(row));
+        row.number = row.view.findViewById(R.id.volume_number);
 
         row.anim = null;
 
@@ -935,6 +940,7 @@
     protected void onStateChangedH(State state) {
         if (D.BUG) Log.d(TAG, "onStateChangedH() state: " + state.toString());
         if (mState != null && state != null
+                && mState.ringerModeInternal != -1
                 && mState.ringerModeInternal != state.ringerModeInternal
                 && state.ringerModeInternal == AudioManager.RINGER_MODE_VIBRATE) {
             mController.vibrate(VibrationEffect.get(VibrationEffect.EFFECT_HEAVY_CLICK));
@@ -1024,19 +1030,28 @@
         final boolean iconEnabled = (mAutomute || ss.muteSupported) && !zenMuted;
         row.icon.setEnabled(iconEnabled);
         row.icon.setAlpha(iconEnabled ? 1 : 0.5f);
-        final int iconRes =
-                isRingVibrate ? R.drawable.ic_volume_ringer_vibrate
-                        : isRingSilent || zenMuted ? row.iconMuteRes
-                                : ss.routedToBluetooth
-                                        ? isStreamMuted(ss) ? R.drawable.ic_volume_media_bt_mute
-                                                : R.drawable.ic_volume_media_bt
-                                        : isStreamMuted(ss) ? row.iconMuteRes : row.iconRes;
+        final int iconRes;
+        if (isRingVibrate) {
+            iconRes = R.drawable.ic_volume_ringer_vibrate;
+        } else if (isRingSilent || zenMuted) {
+            iconRes = row.iconMuteRes;
+        } else if (ss.routedToBluetooth) {
+            iconRes = isStreamMuted(ss) ? R.drawable.ic_volume_media_bt_mute
+                                        : R.drawable.ic_volume_media_bt;
+        } else if (isStreamMuted(ss)) {
+            iconRes = row.iconMuteRes;
+        } else {
+            iconRes = mShowLowMediaVolumeIcon && ss.level * 2 < (ss.levelMax + ss.levelMin)
+                      ? R.drawable.ic_volume_media_low : row.iconRes;
+        }
+
         row.icon.setImageResource(iconRes);
         row.iconState =
                 iconRes == R.drawable.ic_volume_ringer_vibrate ? Events.ICON_STATE_VIBRATE
                 : (iconRes == R.drawable.ic_volume_media_bt_mute || iconRes == row.iconMuteRes)
                         ? Events.ICON_STATE_MUTE
-                : (iconRes == R.drawable.ic_volume_media_bt || iconRes == row.iconRes)
+                : (iconRes == R.drawable.ic_volume_media_bt || iconRes == row.iconRes
+                        || iconRes == R.drawable.ic_volume_media_low)
                         ? Events.ICON_STATE_UNMUTE
                 : Events.ICON_STATE_UNKNOWN;
         if (iconEnabled) {
@@ -1090,6 +1105,7 @@
         final int vlevel = row.ss.muted && (!isRingStream && !zenMuted) ? 0
                 : row.ss.level;
         updateVolumeRowSliderH(row, enableSlider, vlevel);
+        if (row.number != null) row.number.setText(Integer.toString(vlevel));
     }
 
     private boolean isStreamMuted(final StreamState streamState) {
@@ -1115,6 +1131,10 @@
         row.icon.setImageTintList(tint);
         row.icon.setImageAlpha(alpha);
         row.cachedTint = tint;
+        if (row.number != null) {
+            row.number.setTextColor(tint);
+            row.number.setAlpha(alpha);
+        }
     }
 
     private void updateVolumeRowSliderH(VolumeRow row, boolean enable, int vlevel) {
@@ -1346,7 +1366,7 @@
 
     private final class CustomDialog extends Dialog implements DialogInterface {
         public CustomDialog(Context context) {
-            super(context, R.style.qs_theme);
+            super(context, R.style.volume_dialog_theme);
         }
 
         @Override
@@ -1458,6 +1478,7 @@
         private TextView header;
         private ImageButton icon;
         private SeekBar slider;
+        private TextView number;
         private int stream;
         private StreamState ss;
         private long userAttempt;  // last user-driven slider change
diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeUI.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeUI.java
index c0b8041..c378e3b 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/VolumeUI.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeUI.java
@@ -23,15 +23,15 @@
 
 import com.android.systemui.R;
 import com.android.systemui.SystemUI;
+import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.qs.tiles.DndTile;
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
 
 import javax.inject.Inject;
-import javax.inject.Singleton;
 
-@Singleton
+@SysUISingleton
 public class VolumeUI extends SystemUI {
     private static final String TAG = "VolumeUI";
     private static boolean LOGD = Log.isLoggable(TAG, Log.DEBUG);
diff --git a/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java b/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java
new file mode 100644
index 0000000..b7d6903
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java
@@ -0,0 +1,112 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.wmshell;
+
+import static com.android.systemui.shared.system.WindowManagerWrapper.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY;
+
+import android.app.ActivityManager;
+import android.content.Context;
+
+import com.android.keyguard.KeyguardUpdateMonitor;
+import com.android.keyguard.KeyguardUpdateMonitorCallback;
+import com.android.systemui.SystemUI;
+import com.android.systemui.dagger.SysUISingleton;
+import com.android.systemui.shared.system.ActivityManagerWrapper;
+import com.android.systemui.shared.system.TaskStackChangeListener;
+import com.android.systemui.stackdivider.SplitScreenController;
+import com.android.wm.shell.common.DisplayImeController;
+
+import java.util.Optional;
+
+import javax.inject.Inject;
+
+/**
+ * Proxy in SysUiScope to delegate events to controllers in WM Shell library.
+ */
+@SysUISingleton
+public final class WMShell extends SystemUI {
+    private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
+    private final DisplayImeController mDisplayImeController;
+    private final Optional<SplitScreenController> mSplitScreenControllerOptional;
+
+    @Inject
+    WMShell(Context context, KeyguardUpdateMonitor keyguardUpdateMonitor,
+            DisplayImeController displayImeController,
+            Optional<SplitScreenController> splitScreenControllerOptional) {
+        super(context);
+        mKeyguardUpdateMonitor = keyguardUpdateMonitor;
+        mDisplayImeController = displayImeController;
+        mSplitScreenControllerOptional = splitScreenControllerOptional;
+    }
+
+    @Override
+    public void start() {
+        // This is to prevent circular init problem by separating registration step out of its
+        // constructor. And make sure the initialization of DisplayImeController won't depend on
+        // specific feature anymore.
+        mDisplayImeController.startMonitorDisplays();
+
+        mSplitScreenControllerOptional.ifPresent(this::initSplitScreenController);
+    }
+
+    private void initSplitScreenController(SplitScreenController splitScreenController) {
+        mKeyguardUpdateMonitor.registerCallback(new KeyguardUpdateMonitorCallback() {
+            @Override
+            public void onKeyguardVisibilityChanged(boolean showing) {
+                // Hide the divider when keyguard is showing. Even though keyguard/statusbar is
+                // above everything, it is actually transparent except for notifications, so
+                // we still need to hide any surfaces that are below it.
+                // TODO(b/148906453): Figure out keyguard dismiss animation for divider view.
+                splitScreenController.onKeyguardVisibilityChanged(showing);
+            }
+        });
+
+        ActivityManagerWrapper.getInstance().registerTaskStackListener(
+                new TaskStackChangeListener() {
+                    @Override
+                    public void onActivityRestartAttempt(ActivityManager.RunningTaskInfo task,
+                            boolean homeTaskVisible, boolean clearedTask, boolean wasVisible) {
+                        if (!wasVisible || task.configuration.windowConfiguration.getWindowingMode()
+                                != WINDOWING_MODE_SPLIT_SCREEN_PRIMARY
+                                || !splitScreenController.isSplitScreenSupported()) {
+                            return;
+                        }
+
+                        if (splitScreenController.isMinimized()) {
+                            splitScreenController.onUndockingTask();
+                        }
+                    }
+
+                    @Override
+                    public void onActivityForcedResizable(String packageName, int taskId,
+                            int reason) {
+                        splitScreenController
+                                .onActivityForcedResizable(packageName, taskId, reason);
+                    }
+
+                    @Override
+                    public void onActivityDismissingDockedStack() {
+                        splitScreenController.onActivityDismissingSplitScreen();
+                    }
+
+                    @Override
+                    public void onActivityLaunchOnSecondaryDisplayFailed() {
+                        splitScreenController.onActivityLaunchOnSecondaryDisplayFailed();
+                    }
+                });
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/wmshell/WMShellBaseModule.java b/packages/SystemUI/src/com/android/systemui/wmshell/WMShellBaseModule.java
new file mode 100644
index 0000000..3111b13
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/wmshell/WMShellBaseModule.java
@@ -0,0 +1,94 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.wmshell;
+
+import android.content.Context;
+import android.os.Handler;
+import android.view.IWindowManager;
+
+import com.android.internal.logging.UiEventLogger;
+import com.android.systemui.dagger.SysUISingleton;
+import com.android.systemui.dagger.qualifiers.Main;
+import com.android.systemui.pip.PipUiEventLogger;
+import com.android.systemui.stackdivider.SplitScreenController;
+import com.android.systemui.util.DeviceConfigProxy;
+import com.android.systemui.util.FloatingContentCoordinator;
+import com.android.wm.shell.ShellTaskOrganizer;
+import com.android.wm.shell.common.DisplayController;
+import com.android.wm.shell.common.SystemWindows;
+import com.android.wm.shell.common.TransactionPool;
+
+import dagger.BindsOptionalOf;
+import dagger.Module;
+import dagger.Provides;
+
+/**
+ * Provides basic dependencies from {@link com.android.wm.shell}, the dependencies declared here
+ * should be shared among different branches of SystemUI.
+ */
+// TODO(b/162923491): Move most of these dependencies into WMSingleton scope.
+@Module
+public abstract class WMShellBaseModule {
+    @SysUISingleton
+    @Provides
+    static TransactionPool provideTransactionPool() {
+        return new TransactionPool();
+    }
+
+    @SysUISingleton
+    @Provides
+    static DisplayController provideDisplayController(Context context, @Main Handler handler,
+            IWindowManager wmService) {
+        return new DisplayController(context, handler, wmService);
+    }
+
+    @SysUISingleton
+    @Provides
+    static DeviceConfigProxy provideDeviceConfigProxy() {
+        return new DeviceConfigProxy();
+    }
+
+    @SysUISingleton
+    @Provides
+    static FloatingContentCoordinator provideFloatingContentCoordinator() {
+        return new FloatingContentCoordinator();
+    }
+
+    @SysUISingleton
+    @Provides
+    static PipUiEventLogger providePipUiEventLogger(UiEventLogger uiEventLogger) {
+        return new PipUiEventLogger(uiEventLogger);
+    }
+
+    @SysUISingleton
+    @Provides
+    static SystemWindows provideSystemWindows(DisplayController displayController,
+            IWindowManager wmService) {
+        return new SystemWindows(displayController, wmService);
+    }
+
+    @SysUISingleton
+    @Provides
+    static ShellTaskOrganizer provideShellTaskOrganizer() {
+        ShellTaskOrganizer organizer = new ShellTaskOrganizer();
+        organizer.registerOrganizer();
+        return organizer;
+    }
+
+    @BindsOptionalOf
+    abstract SplitScreenController optionalSplitScreenController();
+}
diff --git a/packages/SystemUI/src/com/android/systemui/wmshell/WMShellModule.java b/packages/SystemUI/src/com/android/systemui/wmshell/WMShellModule.java
new file mode 100644
index 0000000..ceb6d59
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/wmshell/WMShellModule.java
@@ -0,0 +1,69 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.wmshell;
+
+import android.content.Context;
+import android.os.Handler;
+import android.view.IWindowManager;
+
+import com.android.systemui.dagger.SysUISingleton;
+import com.android.systemui.dagger.qualifiers.Main;
+import com.android.systemui.pip.phone.PipMenuActivity;
+import com.android.systemui.pip.phone.dagger.PipMenuActivityClass;
+import com.android.systemui.stackdivider.SplitScreenController;
+import com.android.wm.shell.ShellTaskOrganizer;
+import com.android.wm.shell.common.DisplayController;
+import com.android.wm.shell.common.DisplayImeController;
+import com.android.wm.shell.common.SystemWindows;
+import com.android.wm.shell.common.TransactionPool;
+
+import dagger.Module;
+import dagger.Provides;
+
+/**
+ * Provides dependencies from {@link com.android.wm.shell} which could be customized among different
+ * branches of SystemUI.
+ */
+// TODO(b/162923491): Move most of these dependencies into WMSingleton scope.
+@Module(includes = WMShellBaseModule.class)
+public class WMShellModule {
+    @SysUISingleton
+    @Provides
+    static DisplayImeController provideDisplayImeController(IWindowManager wmService,
+            DisplayController displayController, @Main Handler mainHandler,
+            TransactionPool transactionPool) {
+        return new DisplayImeController(wmService, displayController, mainHandler, transactionPool);
+    }
+
+    /** TODO(b/150319024): PipMenuActivity will move to a Window */
+    @SysUISingleton
+    @PipMenuActivityClass
+    @Provides
+    static Class<?> providePipMenuActivityClass() {
+        return PipMenuActivity.class;
+    }
+
+    @SysUISingleton
+    @Provides
+    static SplitScreenController provideSplitScreenController(Context context,
+            DisplayController displayController, SystemWindows systemWindows,
+            DisplayImeController displayImeController, @Main Handler handler,
+            TransactionPool transactionPool, ShellTaskOrganizer shellTaskOrganizer) {
+        return new SplitScreenController(context, displayController, systemWindows,
+                displayImeController, handler, transactionPool, shellTaskOrganizer);
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/wmshell/WindowManagerShellModule.java b/packages/SystemUI/src/com/android/systemui/wmshell/WindowManagerShellModule.java
deleted file mode 100644
index 5b2c39d..0000000
--- a/packages/SystemUI/src/com/android/systemui/wmshell/WindowManagerShellModule.java
+++ /dev/null
@@ -1,68 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.wmshell;
-
-import android.content.Context;
-import android.os.Handler;
-import android.view.IWindowManager;
-
-import com.android.systemui.dagger.qualifiers.Main;
-import com.android.wm.shell.common.DisplayController;
-import com.android.wm.shell.common.DisplayImeController;
-import com.android.wm.shell.common.SystemWindows;
-import com.android.wm.shell.common.TransactionPool;
-
-import javax.inject.Singleton;
-
-import dagger.Module;
-import dagger.Provides;
-
-/**
- * Provides dependencies from {@link com.android.wm.shell}.
- */
-@Module
-// TODO(b/161116823) Clean up dependencies after wm shell migration finished.
-public class WindowManagerShellModule {
-    @Singleton
-    @Provides
-    static TransactionPool provideTransactionPool() {
-        return new TransactionPool();
-    }
-
-    @Singleton
-    @Provides
-    static DisplayController provideDisplayController(Context context, @Main Handler handler,
-            IWindowManager wmService) {
-        return new DisplayController(context, handler, wmService);
-    }
-
-    @Singleton
-    @Provides
-    static SystemWindows provideSystemWindows(DisplayController displayController,
-            IWindowManager wmService) {
-        return new SystemWindows(displayController, wmService);
-    }
-
-    @Singleton
-    @Provides
-    static DisplayImeController provideDisplayImeController(IWindowManager wmService,
-            DisplayController displayController, @Main Handler mainHandler,
-            TransactionPool transactionPool) {
-        return new DisplayImeController.Builder(wmService, displayController, mainHandler,
-                transactionPool).build();
-    }
-}
diff --git a/packages/SystemUI/tests/AndroidManifest.xml b/packages/SystemUI/tests/AndroidManifest.xml
index c6331682..db87845 100644
--- a/packages/SystemUI/tests/AndroidManifest.xml
+++ b/packages/SystemUI/tests/AndroidManifest.xml
@@ -80,6 +80,8 @@
                   android:excludeFromRecents="true"
                   />
 
+        <activity android:name="com.android.systemui.screenshot.ScrollViewActivity"
+                  android:exported="false" />
         <provider
             android:name="androidx.lifecycle.ProcessLifecycleOwnerInitializer"
             tools:replace="android:authorities"
diff --git a/packages/SystemUI/tests/res/values/strings.xml b/packages/SystemUI/tests/res/values/strings.xml
new file mode 100644
index 0000000..b9f24c8
--- /dev/null
+++ b/packages/SystemUI/tests/res/values/strings.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2020 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+
+    <string name="test_content">Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aenean ultrices condimentum ultricies. Sed elementum at massa id sagittis. Nullam dictum massa lorem, nec ornare nunc pharetra vitae. Duis ultrices, felis eu condimentum congue, erat orci efficitur purus, ac rutrum odio lacus sed sapien. Suspendisse erat augue, eleifend eget auctor sagittis, porta eget nibh. Mauris pulvinar urna non justo condimentum, ut vehicula sapien finibus. Aliquam nibh magna, tincidunt ut viverra sed, placerat et turpis. Nam placerat, dui sed tincidunt consectetur, ante velit posuere mauris, tincidunt finibus velit lectus ac tortor. Cras eget lectus feugiat, porttitor velit nec, malesuada massa.</string>
+
+</resources>
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchTest.java
index 4c0762e..5999e2c 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchTest.java
@@ -79,7 +79,9 @@
                 .thenReturn(mMockKeyguardSliceView);
 
         InjectionInflationController inflationController = new InjectionInflationController(
-                SystemUIFactory.getInstance().getRootComponent());
+                SystemUIFactory.getInstance()
+                        .getSysUIComponent()
+                        .createViewInstanceCreatorFactory());
         LayoutInflater layoutInflater = inflationController
                 .injectable(LayoutInflater.from(getContext()));
         layoutInflater.setPrivateFactory(new LayoutInflater.Factory2() {
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPresentationTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPresentationTest.java
index e6c2ddc..446b122 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPresentationTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPresentationTest.java
@@ -65,7 +65,9 @@
         allowTestableLooperAsMainThread();
 
         InjectionInflationController inflationController = new InjectionInflationController(
-                SystemUIFactory.getInstance().getRootComponent());
+                SystemUIFactory.getInstance()
+                        .getSysUIComponent()
+                        .createViewInstanceCreatorFactory());
         mLayoutInflater = inflationController.injectable(LayoutInflater.from(mContext));
         mLayoutInflater.setPrivateFactory(new LayoutInflater.Factory2() {
 
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewTest.java
index bc3c3d9..0bf1376 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewTest.java
@@ -51,7 +51,9 @@
         allowTestableLooperAsMainThread();
         mDependency.injectMockDependency(KeyguardUpdateMonitor.class);
         InjectionInflationController inflationController = new InjectionInflationController(
-                SystemUIFactory.getInstance().getRootComponent());
+                SystemUIFactory.getInstance()
+                        .getSysUIComponent()
+                        .createViewInstanceCreatorFactory());
         LayoutInflater layoutInflater = inflationController
                 .injectable(LayoutInflater.from(getContext()));
         mKeyguardStatusView =
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
index a0e5f73..1192023 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
@@ -182,7 +182,7 @@
         // IBiometricsFace@1.0 does not support detection, only authentication.
         when(mFaceSensorProperties.isEmpty()).thenReturn(false);
         when(mFaceSensorProperties.get(anyInt())).thenReturn(new FaceSensorProperties(0 /* id */,
-                false /* supportsFaceDetection */));
+                false /* supportsFaceDetection */, true /* supportsSelfIllumination */));
 
         when(mFingerprintManager.isHardwareDetected()).thenReturn(true);
         when(mFingerprintManager.hasEnrolledTemplates(anyInt())).thenReturn(true);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/DependencyTest.java b/packages/SystemUI/tests/src/com/android/systemui/DependencyTest.java
index 35be496..5d8e435 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/DependencyTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/DependencyTest.java
@@ -47,7 +47,7 @@
     public void testInitDependency() {
         Dependency.clearDependencies();
         Dependency dependency =
-                SystemUIFactory.getInstance().getRootComponent().createDependency();
+                SystemUIFactory.getInstance().getSysUIComponent().createDependency();
         dependency.start();
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/ForegroundServiceControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/ForegroundServiceControllerTest.java
index 60f0cd9..e967a5d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/ForegroundServiceControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/ForegroundServiceControllerTest.java
@@ -82,8 +82,7 @@
         allowTestableLooperAsMainThread();
 
         MockitoAnnotations.initMocks(this);
-        mFsc = new ForegroundServiceController(
-                mEntryManager, mAppOpsController, mMainHandler);
+        mFsc = new ForegroundServiceController(mAppOpsController, mMainHandler);
         mListener = new ForegroundServiceNotificationListener(
                 mContext, mFsc, mEntryManager, mNotifPipeline,
                 mock(ForegroundServiceLifetimeExtender.class), mClock);
@@ -115,85 +114,6 @@
     }
 
     @Test
-    public void testAppOps_appOpChangedBeforeNotificationExists() {
-        // GIVEN app op exists, but notification doesn't exist in NEM yet
-        NotificationEntry entry = createFgEntry();
-        mFsc.onAppOpChanged(
-                AppOpsManager.OP_CAMERA,
-                entry.getSbn().getUid(),
-                entry.getSbn().getPackageName(),
-                true);
-        assertFalse(entry.mActiveAppOps.contains(AppOpsManager.OP_CAMERA));
-
-        // WHEN the notification is added
-        mEntryListener.onPendingEntryAdded(entry);
-
-        // THEN the app op is added to the entry
-        Assert.assertTrue(entry.mActiveAppOps.contains(AppOpsManager.OP_CAMERA));
-    }
-
-    @Test
-    public void testAppOps_appOpAddedToForegroundNotif() {
-        // GIVEN a notification associated with a foreground service
-        NotificationEntry entry = addFgEntry();
-        when(mEntryManager.getPendingOrActiveNotif(entry.getKey())).thenReturn(entry);
-
-        // WHEN we are notified of a new app op for this notification
-        mFsc.onAppOpChanged(
-                AppOpsManager.OP_CAMERA,
-                entry.getSbn().getUid(),
-                entry.getSbn().getPackageName(),
-                true);
-
-        // THEN the app op is added to the entry
-        Assert.assertTrue(entry.mActiveAppOps.contains(AppOpsManager.OP_CAMERA));
-
-        // THEN notification views are updated since the notification is visible
-        verify(mEntryManager, times(1)).updateNotifications(anyString());
-    }
-
-    @Test
-    public void testAppOpsAlreadyAdded() {
-        // GIVEN a foreground service associated notification that already has the correct app op
-        NotificationEntry entry = addFgEntry();
-        entry.mActiveAppOps.add(AppOpsManager.OP_CAMERA);
-        when(mEntryManager.getPendingOrActiveNotif(entry.getKey())).thenReturn(entry);
-
-        // WHEN we are notified of the same app op for this notification
-        mFsc.onAppOpChanged(
-                AppOpsManager.OP_CAMERA,
-                entry.getSbn().getUid(),
-                entry.getSbn().getPackageName(),
-                true);
-
-        // THEN the app op still exists in the notification entry
-        Assert.assertTrue(entry.mActiveAppOps.contains(AppOpsManager.OP_CAMERA));
-
-        // THEN notification views aren't updated since nothing changed
-        verify(mEntryManager, never()).updateNotifications(anyString());
-    }
-
-    @Test
-    public void testAppOps_appOpNotAddedToUnrelatedNotif() {
-        // GIVEN no notification entries correspond to the newly updated appOp
-        NotificationEntry entry = addFgEntry();
-        when(mEntryManager.getPendingOrActiveNotif(entry.getKey())).thenReturn(null);
-
-        // WHEN a new app op is detected
-        mFsc.onAppOpChanged(
-                AppOpsManager.OP_CAMERA,
-                entry.getSbn().getUid(),
-                entry.getSbn().getPackageName(),
-                true);
-
-        // THEN we won't see appOps on the entry
-        Assert.assertFalse(entry.mActiveAppOps.contains(AppOpsManager.OP_CAMERA));
-
-        // THEN notification views aren't updated since nothing changed
-        verify(mEntryManager, never()).updateNotifications(anyString());
-    }
-
-    @Test
     public void testAppOpsCRUD() {
         // no crash on remove that doesn't exist
         mFsc.onAppOpChanged(9, 1000, "pkg1", false);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/SysuiBaseFragmentTest.java b/packages/SystemUI/tests/src/com/android/systemui/SysuiBaseFragmentTest.java
index 3687b4c..53bae86 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/SysuiBaseFragmentTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/SysuiBaseFragmentTest.java
@@ -55,7 +55,7 @@
     public void SysuiSetup() {
         SystemUIFactory.createFromConfig(mContext);
         mDependency = new TestableDependency(
-                SystemUIFactory.getInstance().getRootComponent().createDependency());
+                SystemUIFactory.getInstance().getSysUIComponent().createDependency());
         Dependency.setInstance(mDependency);
 
         // TODO: Figure out another way to give reference to a SysuiTestableContext.
diff --git a/packages/SystemUI/tests/src/com/android/systemui/SysuiTestCase.java b/packages/SystemUI/tests/src/com/android/systemui/SysuiTestCase.java
index 08e2784..b7175ea 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/SysuiTestCase.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/SysuiTestCase.java
@@ -74,7 +74,7 @@
     public void SysuiSetup() throws Exception {
         SystemUIFactory.createFromConfig(mContext);
         mDependency = new TestableDependency(
-                SystemUIFactory.getInstance().getRootComponent().createDependency());
+                SystemUIFactory.getInstance().getSysUIComponent().createDependency());
         Dependency.setInstance(mDependency);
         mFakeBroadcastDispatcher = new FakeBroadcastDispatcher(mContext, mock(Looper.class),
                 mock(Executor.class), mock(DumpManager.class),
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/IWindowMagnificationConnectionTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/IWindowMagnificationConnectionTest.java
index fbc8e9d..ac567e0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/IWindowMagnificationConnectionTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/IWindowMagnificationConnectionTest.java
@@ -25,6 +25,7 @@
 import android.os.RemoteException;
 import android.provider.Settings;
 import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
 import android.view.Display;
 import android.view.accessibility.AccessibilityManager;
 import android.view.accessibility.IWindowMagnificationConnection;
@@ -47,6 +48,7 @@
  */
 @SmallTest
 @RunWith(AndroidTestingRunner.class)
+@TestableLooper.RunWithLooper
 public class IWindowMagnificationConnectionTest extends SysuiTestCase {
 
     private static final int TEST_DISPLAY = Display.DEFAULT_DISPLAY;
@@ -57,7 +59,7 @@
     @Mock
     private IWindowMagnificationConnectionCallback mConnectionCallback;
     @Mock
-    private WindowMagnificationController mWindowMagnificationController;
+    private WindowMagnificationAnimationController mWindowMagnificationAnimationController;
     @Mock
     private ModeSwitchesController mModeSwitchesController;
     private IWindowMagnificationConnection mIWindowMagnificationConnection;
@@ -74,7 +76,8 @@
                 any(IWindowMagnificationConnection.class));
         mWindowMagnification = new WindowMagnification(getContext(),
                 getContext().getMainThreadHandler(), mCommandQueue, mModeSwitchesController);
-        mWindowMagnification.mWindowMagnificationController = mWindowMagnificationController;
+        mWindowMagnification.mWindowMagnificationAnimationController =
+                mWindowMagnificationAnimationController;
         mWindowMagnification.requestWindowMagnificationConnection(true);
         assertNotNull(mIWindowMagnificationConnection);
         mIWindowMagnificationConnection.setConnectionCallback(mConnectionCallback);
@@ -86,7 +89,7 @@
                 Float.NaN);
         waitForIdleSync();
 
-        verify(mWindowMagnificationController).enableWindowMagnification(3.0f, Float.NaN,
+        verify(mWindowMagnificationAnimationController).enableWindowMagnification(3.0f, Float.NaN,
                 Float.NaN);
     }
 
@@ -99,7 +102,7 @@
         mIWindowMagnificationConnection.disableWindowMagnification(TEST_DISPLAY);
         waitForIdleSync();
 
-        verify(mWindowMagnificationController).deleteWindowMagnification();
+        verify(mWindowMagnificationAnimationController).deleteWindowMagnification();
     }
 
     @Test
@@ -107,7 +110,7 @@
         mIWindowMagnificationConnection.setScale(TEST_DISPLAY, 3.0f);
         waitForIdleSync();
 
-        verify(mWindowMagnificationController).setScale(3.0f);
+        verify(mWindowMagnificationAnimationController).setScale(3.0f);
     }
 
     @Test
@@ -115,7 +118,7 @@
         mIWindowMagnificationConnection.moveWindowMagnifier(TEST_DISPLAY, 100f, 200f);
         waitForIdleSync();
 
-        verify(mWindowMagnificationController).moveWindowMagnifier(100f, 200f);
+        verify(mWindowMagnificationAnimationController).moveWindowMagnifier(100f, 200f);
     }
 
     @Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationAnimationControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationAnimationControllerTest.java
new file mode 100644
index 0000000..a6dfbbd
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationAnimationControllerTest.java
@@ -0,0 +1,369 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.accessibility;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.anyFloat;
+import static org.mockito.Mockito.atLeast;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+
+import android.animation.ValueAnimator;
+import android.app.Instrumentation;
+import android.content.Context;
+import android.os.Handler;
+import android.os.SystemClock;
+import android.testing.AndroidTestingRunner;
+import android.view.SurfaceControl;
+import android.view.animation.AccelerateInterpolator;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.MediumTest;
+
+import com.android.internal.graphics.SfVsyncFrameCallbackProvider;
+import com.android.systemui.SysuiTestCase;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.MockitoAnnotations;
+
+import java.util.concurrent.atomic.AtomicReference;
+
+
+@MediumTest
+@RunWith(AndroidTestingRunner.class)
+public class WindowMagnificationAnimationControllerTest extends SysuiTestCase {
+
+    private static final float DEFAULT_SCALE = 3.0f;
+    private static final float DEFAULT_CENTER_X = 400.0f;
+    private static final float DEFAULT_CENTER_Y = 500.0f;
+    // The duration couldn't too short, otherwise the ValueAnimator won't work in expectation.
+    private static final long ANIMATION_DURATION_MS = 200;
+
+    private AtomicReference<Float> mCurrentScale = new AtomicReference<>((float) 0);
+    private AtomicReference<Float> mCurrentCenterX = new AtomicReference<>((float) 0);
+    private AtomicReference<Float> mCurrentCenterY = new AtomicReference<>((float) 0);
+    private ArgumentCaptor<Float> mScaleCaptor = ArgumentCaptor.forClass(Float.class);
+    private ArgumentCaptor<Float> mCenterXCaptor = ArgumentCaptor.forClass(Float.class);
+    private ArgumentCaptor<Float> mCenterYCaptor = ArgumentCaptor.forClass(Float.class);
+
+    @Mock
+    Handler mHandler;
+    @Mock
+    SfVsyncFrameCallbackProvider mSfVsyncFrameProvider;
+    @Mock
+    WindowMagnifierCallback mWindowMagnifierCallback;
+
+    private SpyWindowMagnificationController mController;
+    private WindowMagnificationController mSpyController;
+    private WindowMagnificationAnimationController mWindowMagnificationAnimationController;
+    private Instrumentation mInstrumentation;
+    private long mWaitingAnimationPeriod;
+    private long mWaitIntermediateAnimationPeriod;
+
+    @Before
+    public void setUp() throws Exception {
+        MockitoAnnotations.initMocks(this);
+        mInstrumentation = InstrumentationRegistry.getInstrumentation();
+        mWaitingAnimationPeriod = ANIMATION_DURATION_MS + 50;
+        mWaitIntermediateAnimationPeriod = ANIMATION_DURATION_MS / 2;
+        mController = new SpyWindowMagnificationController(mContext, mHandler,
+                mSfVsyncFrameProvider, null, new SurfaceControl.Transaction(),
+                mWindowMagnifierCallback);
+        mSpyController = mController.getSpyController();
+        mWindowMagnificationAnimationController = new WindowMagnificationAnimationController(
+                mContext, mController, newValueAnimator());
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        mInstrumentation.runOnMainSync(() -> mController.deleteWindowMagnification());
+    }
+
+    @Test
+    public void enableWindowMagnification_disabled_expectedStartAndEndValues() {
+        enableWindowMagnificationAndWaitAnimating(mWaitingAnimationPeriod);
+
+        verify(mSpyController, atLeast(2)).enableWindowMagnification(
+                mScaleCaptor.capture(),
+                mCenterXCaptor.capture(), mCenterYCaptor.capture());
+        verifyStartValue(mScaleCaptor, 1.0f);
+        verifyStartValue(mCenterXCaptor, DEFAULT_CENTER_X);
+        verifyStartValue(mCenterYCaptor, DEFAULT_CENTER_Y);
+        verifyFinalSpec(DEFAULT_SCALE, DEFAULT_CENTER_X, DEFAULT_CENTER_Y);
+    }
+
+    @Test
+    public void enableWindowMagnification_enabling_expectedStartAndEndValues() {
+        enableWindowMagnificationAndWaitAnimating(mWaitIntermediateAnimationPeriod);
+        final float targetScale = DEFAULT_SCALE + 1.0f;
+        final float targetCenterX = DEFAULT_CENTER_X + 100;
+        final float targetCenterY = DEFAULT_CENTER_Y + 100;
+
+        mInstrumentation.runOnMainSync(() -> {
+            Mockito.reset(mSpyController);
+            mWindowMagnificationAnimationController.enableWindowMagnification(targetScale,
+                    targetCenterX, targetCenterY);
+            mCurrentScale.set(mController.getScale());
+            mCurrentCenterX.set(mController.getCenterX());
+            mCurrentCenterY.set(mController.getCenterY());
+        });
+
+        SystemClock.sleep(mWaitingAnimationPeriod);
+
+        verify(mSpyController, atLeast(2)).enableWindowMagnification(mScaleCaptor.capture(),
+                mCenterXCaptor.capture(), mCenterYCaptor.capture());
+        verifyStartValue(mScaleCaptor, mCurrentScale.get());
+        verifyStartValue(mCenterXCaptor, mCurrentCenterX.get());
+        verifyStartValue(mCenterYCaptor, mCurrentCenterY.get());
+        verifyFinalSpec(targetScale, targetCenterX, targetCenterY);
+    }
+
+    @Test
+    public void enableWindowMagnification_disabling_expectedStartAndEndValues() {
+        enableWindowMagnificationAndWaitAnimating(mWaitingAnimationPeriod);
+        deleteWindowMagnificationAndWaitAnimating(mWaitIntermediateAnimationPeriod);
+        final float targetScale = DEFAULT_SCALE + 1.0f;
+        final float targetCenterX = DEFAULT_CENTER_X + 100;
+        final float targetCenterY = DEFAULT_CENTER_Y + 100;
+
+        mInstrumentation.runOnMainSync(
+                () -> {
+                    Mockito.reset(mSpyController);
+                    mWindowMagnificationAnimationController.enableWindowMagnification(targetScale,
+                            targetCenterX, targetCenterY);
+                    mCurrentScale.set(mController.getScale());
+                    mCurrentCenterX.set(mController.getCenterX());
+                    mCurrentCenterY.set(mController.getCenterY());
+                });
+        SystemClock.sleep(mWaitingAnimationPeriod);
+
+        verify(mSpyController, atLeast(2)).enableWindowMagnification(
+                mScaleCaptor.capture(),
+                mCenterXCaptor.capture(), mCenterYCaptor.capture());
+        //Animating in reverse, so we only check if the start values are greater than current.
+        assertTrue(mScaleCaptor.getAllValues().get(0) > mCurrentScale.get());
+        assertEquals(targetScale, mScaleCaptor.getValue(), 0f);
+        assertTrue(mCenterXCaptor.getAllValues().get(0) > mCurrentCenterX.get());
+        assertEquals(targetCenterX, mCenterXCaptor.getValue(), 0f);
+        assertTrue(mCenterYCaptor.getAllValues().get(0) > mCurrentCenterY.get());
+        assertEquals(targetCenterY, mCenterYCaptor.getValue(), 0f);
+        verifyFinalSpec(targetScale, targetCenterX, targetCenterY);
+    }
+
+    @Test
+    public void enableWindowMagnificationWithSameScale_doNothing() {
+        enableWindowMagnificationAndWaitAnimating(mWaitingAnimationPeriod);
+
+        enableWindowMagnificationAndWaitAnimating(mWaitingAnimationPeriod);
+
+        verify(mSpyController, never()).enableWindowMagnification(anyFloat(), anyFloat(),
+                anyFloat());
+    }
+
+    @Test
+    public void setScale_enabled_expectedScale() {
+        enableWindowMagnificationAndWaitAnimating(mWaitingAnimationPeriod);
+
+        mInstrumentation.runOnMainSync(
+                () -> mWindowMagnificationAnimationController.setScale(DEFAULT_SCALE + 1));
+
+        verify(mSpyController).setScale(DEFAULT_SCALE + 1);
+        verifyFinalSpec(DEFAULT_SCALE + 1, DEFAULT_CENTER_X, DEFAULT_CENTER_Y);
+    }
+
+    @Test
+    public void deleteWindowMagnification_enabled_expectedStartAndEndValues() {
+        enableWindowMagnificationAndWaitAnimating(mWaitingAnimationPeriod);
+
+        deleteWindowMagnificationAndWaitAnimating(mWaitingAnimationPeriod);
+
+        verify(mSpyController, atLeast(2)).enableWindowMagnification(mScaleCaptor.capture(),
+                mCenterXCaptor.capture(), mCenterYCaptor.capture());
+        verify(mSpyController).deleteWindowMagnification();
+        verifyStartValue(mScaleCaptor, DEFAULT_SCALE);
+        verifyStartValue(mCenterXCaptor, Float.NaN);
+        verifyStartValue(mCenterYCaptor, Float.NaN);
+        verifyFinalSpec(Float.NaN, Float.NaN, Float.NaN);
+    }
+
+    @Test
+    public void deleteWindowMagnification_disabled_doNothing() {
+        deleteWindowMagnificationAndWaitAnimating(mWaitingAnimationPeriod);
+
+        Mockito.verifyNoMoreInteractions(mSpyController);
+    }
+
+    @Test
+    public void deleteWindowMagnification_enabling_checkStartAndEndValues() {
+        enableWindowMagnificationAndWaitAnimating(mWaitIntermediateAnimationPeriod);
+
+        //It just reverse the animation, so we don't need to wait the whole duration.
+        mInstrumentation.runOnMainSync(
+                () -> {
+                    Mockito.reset(mSpyController);
+                    mWindowMagnificationAnimationController.deleteWindowMagnification();
+                    mCurrentScale.set(mController.getScale());
+                    mCurrentCenterX.set(mController.getCenterX());
+                    mCurrentCenterY.set(mController.getCenterY());
+                });
+        SystemClock.sleep(mWaitingAnimationPeriod);
+
+        verify(mSpyController, atLeast(2)).enableWindowMagnification(mScaleCaptor.capture(),
+                mCenterXCaptor.capture(), mCenterYCaptor.capture());
+        verify(mSpyController).deleteWindowMagnification();
+
+        //The animation is in verse, so we only check the start values should no be greater than
+        // the current one.
+        assertTrue(mScaleCaptor.getAllValues().get(0) <= mCurrentScale.get());
+        assertEquals(1.0f, mScaleCaptor.getValue(), 0f);
+        verifyStartValue(mCenterXCaptor, Float.NaN);
+        verifyStartValue(mCenterYCaptor, Float.NaN);
+        verifyFinalSpec(Float.NaN, Float.NaN, Float.NaN);
+    }
+
+    @Test
+    public void deleteWindowMagnification_disabling_checkStartAndValues() {
+        enableWindowMagnificationAndWaitAnimating(mWaitingAnimationPeriod);
+        deleteWindowMagnificationAndWaitAnimating(mWaitIntermediateAnimationPeriod);
+
+        deleteWindowMagnificationAndWaitAnimating(mWaitingAnimationPeriod);
+
+        verify(mSpyController, atLeast(2)).enableWindowMagnification(mScaleCaptor.capture(),
+                mCenterXCaptor.capture(), mCenterYCaptor.capture());
+        verify(mSpyController).deleteWindowMagnification();
+        assertEquals(1.0f, mScaleCaptor.getValue(), 0f);
+        verifyFinalSpec(Float.NaN, Float.NaN, Float.NaN);
+    }
+
+    @Test
+    public void moveWindowMagnifier_enabled() {
+        enableWindowMagnificationAndWaitAnimating(mWaitingAnimationPeriod);
+
+        mInstrumentation.runOnMainSync(
+                () -> mWindowMagnificationAnimationController.moveWindowMagnifier(100f, 200f));
+
+        verify(mSpyController).moveWindowMagnifier(100f, 200f);
+        verifyFinalSpec(DEFAULT_SCALE, DEFAULT_CENTER_X + 100f, DEFAULT_CENTER_Y + 100f);
+    }
+
+    @Test
+    public void onConfigurationChanged_passThrough() {
+        mWindowMagnificationAnimationController.onConfigurationChanged(100);
+
+        verify(mSpyController).onConfigurationChanged(100);
+    }
+    private void verifyFinalSpec(float expectedScale, float expectedCenterX,
+            float expectedCenterY) {
+        assertEquals(expectedScale, mController.getScale(), 0f);
+        assertEquals(expectedCenterX, mController.getCenterX(), 0f);
+        assertEquals(expectedCenterY, mController.getCenterY(), 0f);
+    }
+
+    private void enableWindowMagnificationAndWaitAnimating(long duration) {
+        mInstrumentation.runOnMainSync(
+                () -> {
+                    Mockito.reset(mSpyController);
+                    mWindowMagnificationAnimationController.enableWindowMagnification(DEFAULT_SCALE,
+                            DEFAULT_CENTER_X, DEFAULT_CENTER_Y);
+                });
+        SystemClock.sleep(duration);
+    }
+
+    private void deleteWindowMagnificationAndWaitAnimating(long duration) {
+        mInstrumentation.runOnMainSync(
+                () -> {
+                    resetMockObjects();
+                    mWindowMagnificationAnimationController.deleteWindowMagnification();
+                });
+        SystemClock.sleep(duration);
+    }
+
+    private void verifyStartValue(ArgumentCaptor<Float> captor, float startValue) {
+        assertEquals(startValue, captor.getAllValues().get(0), 0f);
+    }
+
+    private void resetMockObjects() {
+        Mockito.reset(mSpyController);
+    }
+
+    /**
+     * It observes the methods in {@link WindowMagnificationController} since we couldn't spy it
+     * directly.
+     */
+    private static class SpyWindowMagnificationController extends WindowMagnificationController {
+        private WindowMagnificationController mSpyController;
+
+        SpyWindowMagnificationController(Context context, Handler handler,
+                SfVsyncFrameCallbackProvider sfVsyncFrameProvider,
+                MirrorWindowControl mirrorWindowControl, SurfaceControl.Transaction transaction,
+                WindowMagnifierCallback callback) {
+            super(context, handler, sfVsyncFrameProvider, mirrorWindowControl, transaction,
+                    callback);
+            mSpyController = Mockito.mock(WindowMagnificationController.class);
+        }
+
+        WindowMagnificationController getSpyController() {
+            return mSpyController;
+        }
+
+        @Override
+        void enableWindowMagnification(float scale, float centerX, float centerY) {
+            super.enableWindowMagnification(scale, centerX, centerY);
+            mSpyController.enableWindowMagnification(scale, centerX, centerY);
+        }
+
+        @Override
+        void deleteWindowMagnification() {
+            super.deleteWindowMagnification();
+            mSpyController.deleteWindowMagnification();
+        }
+
+        @Override
+        void moveWindowMagnifier(float offsetX, float offsetY) {
+            super.moveWindowMagnifier(offsetX, offsetX);
+            mSpyController.moveWindowMagnifier(offsetX, offsetY);
+        }
+
+        @Override
+        void setScale(float scale) {
+            super.setScale(scale);
+            mSpyController.setScale(scale);
+        }
+
+        @Override
+        void onConfigurationChanged(int configDiff) {
+            super.onConfigurationChanged(configDiff);
+            mSpyController.onConfigurationChanged(configDiff);
+        }
+
+    }
+
+    private static ValueAnimator newValueAnimator() {
+        final ValueAnimator valueAnimator = new ValueAnimator();
+        valueAnimator.setDuration(ANIMATION_DURATION_MS);
+        valueAnimator.setInterpolator(new AccelerateInterpolator(2.5f));
+        valueAnimator.setFloatValues(0.0f, 1.0f);
+        return valueAnimator;
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationControllerTest.java
index 2007fbb..f1f394e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationControllerTest.java
@@ -18,6 +18,7 @@
 
 import static android.view.Choreographer.FrameCallback;
 
+import static org.junit.Assert.assertEquals;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.Mockito.atLeastOnce;
@@ -26,8 +27,12 @@
 import static org.mockito.Mockito.when;
 
 import android.app.Instrumentation;
+import android.content.Context;
+import android.content.pm.ActivityInfo;
 import android.os.Handler;
 import android.testing.AndroidTestingRunner;
+import android.view.Display;
+import android.view.Surface;
 import android.view.SurfaceControl;
 
 import androidx.test.InstrumentationRegistry;
@@ -41,6 +46,7 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.Mock;
+import org.mockito.Mockito;
 import org.mockito.MockitoAnnotations;
 
 @SmallTest
@@ -57,12 +63,14 @@
     WindowMagnifierCallback mWindowMagnifierCallback;
     @Mock
     SurfaceControl.Transaction mTransaction;
+    private Context mContext;
     private WindowMagnificationController mWindowMagnificationController;
     private Instrumentation mInstrumentation;
 
     @Before
     public void setUp() {
         MockitoAnnotations.initMocks(this);
+        mContext  = Mockito.spy(getContext());
         mInstrumentation = InstrumentationRegistry.getInstrumentation();
         doAnswer(invocation -> {
             FrameCallback callback = invocation.getArgument(0);
@@ -73,8 +81,7 @@
         when(mTransaction.remove(any())).thenReturn(mTransaction);
         when(mTransaction.setGeometry(any(), any(), any(),
                 anyInt())).thenReturn(mTransaction);
-
-        mWindowMagnificationController = new WindowMagnificationController(getContext(),
+        mWindowMagnificationController = new WindowMagnificationController(mContext,
                 mHandler, mSfVsyncFrameProvider,
                 mMirrorWindowControl, mTransaction, mWindowMagnifierCallback);
         verify(mMirrorWindowControl).setWindowDelegate(
@@ -83,9 +90,8 @@
 
     @After
     public void tearDown() {
-        mInstrumentation.runOnMainSync(() -> {
-            mWindowMagnificationController.deleteWindowMagnification();
-        });
+        mInstrumentation.runOnMainSync(
+                () -> mWindowMagnificationController.deleteWindowMagnification());
     }
 
     @Test
@@ -121,4 +127,27 @@
 
         verify(mSfVsyncFrameProvider, atLeastOnce()).postFrameCallback(any());
     }
+
+    @Test
+    public void setScale_enabled_expectedValue() {
+        mInstrumentation.runOnMainSync(
+                () -> mWindowMagnificationController.enableWindowMagnification(Float.NaN, Float.NaN,
+                        Float.NaN));
+
+        mInstrumentation.runOnMainSync(() -> mWindowMagnificationController.setScale(3.0f));
+
+        assertEquals(3.0f, mWindowMagnificationController.getScale(), 0);
+    }
+
+    @Test
+    public void onConfigurationChanged_disabled_withoutException() {
+        Display display = Mockito.spy(mContext.getDisplay());
+        when(display.getRotation()).thenReturn(Surface.ROTATION_90);
+        when(mContext.getDisplay()).thenReturn(display);
+
+        mInstrumentation.runOnMainSync(() -> {
+            mWindowMagnificationController.onConfigurationChanged(ActivityInfo.CONFIG_DENSITY);
+            mWindowMagnificationController.onConfigurationChanged(ActivityInfo.CONFIG_ORIENTATION);
+        });
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationTest.java
index 4136013..936558b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationTest.java
@@ -26,6 +26,7 @@
 import android.graphics.Rect;
 import android.os.RemoteException;
 import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
 import android.view.Display;
 import android.view.accessibility.AccessibilityManager;
 import android.view.accessibility.IWindowMagnificationConnection;
@@ -45,6 +46,7 @@
 
 @SmallTest
 @RunWith(AndroidTestingRunner.class)
+@TestableLooper.RunWithLooper
 public class WindowMagnificationTest extends SysuiTestCase {
 
     @Mock
diff --git a/packages/SystemUI/tests/src/com/android/systemui/appops/AppOpsControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/appops/AppOpsControllerTest.java
index 4fdc06e..8f082c1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/appops/AppOpsControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/appops/AppOpsControllerTest.java
@@ -27,6 +27,9 @@
 import static org.mockito.ArgumentMatchers.anyLong;
 import static org.mockito.ArgumentMatchers.anyString;
 import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.inOrder;
+import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
@@ -34,6 +37,8 @@
 
 import android.app.AppOpsManager;
 import android.content.pm.PackageManager;
+import android.media.AudioManager;
+import android.media.AudioRecordingConfiguration;
 import android.os.Looper;
 import android.os.UserHandle;
 import android.testing.AndroidTestingRunner;
@@ -47,9 +52,11 @@
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.mockito.InOrder;
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 
+import java.util.Collections;
 import java.util.List;
 
 @SmallTest
@@ -73,6 +80,12 @@
     private PermissionFlagsCache mFlagsCache;
     @Mock
     private PackageManager mPackageManager;
+    @Mock(stubOnly = true)
+    private AudioManager mAudioManager;
+    @Mock(stubOnly = true)
+    private AudioManager.AudioRecordingCallback mRecordingCallback;
+    @Mock(stubOnly = true)
+    private AudioRecordingConfiguration mPausedMockRecording;
 
     private AppOpsControllerImpl mController;
     private TestableLooper mTestableLooper;
@@ -94,11 +107,20 @@
         when(mFlagsCache.getPermissionFlags(anyString(), anyString(),
                 eq(TEST_UID_NON_USER_SENSITIVE))).thenReturn(0);
 
+        doAnswer((invocation) -> mRecordingCallback = invocation.getArgument(0))
+                .when(mAudioManager).registerAudioRecordingCallback(any(), any());
+        when(mPausedMockRecording.getClientUid()).thenReturn(TEST_UID);
+        when(mPausedMockRecording.isClientSilenced()).thenReturn(true);
+
+        when(mAudioManager.getActiveRecordingConfigurations())
+                .thenReturn(List.of(mPausedMockRecording));
+
         mController = new AppOpsControllerImpl(
                 mContext,
                 mTestableLooper.getLooper(),
                 mDumpManager,
-                mFlagsCache
+                mFlagsCache,
+                mAudioManager
         );
     }
 
@@ -363,6 +385,89 @@
                 AppOpsManager.OP_FINE_LOCATION, TEST_UID, TEST_PACKAGE_NAME, true);
     }
 
+    @Test
+    public void testPausedRecordingIsRetrievedOnCreation() {
+        mController.addCallback(new int[]{AppOpsManager.OP_RECORD_AUDIO}, mCallback);
+        mTestableLooper.processAllMessages();
+
+        mController.onOpActiveChanged(
+                AppOpsManager.OP_RECORD_AUDIO, TEST_UID, TEST_PACKAGE_NAME, true);
+        mTestableLooper.processAllMessages();
+
+        verify(mCallback, never())
+                .onActiveStateChanged(anyInt(), anyInt(), anyString(), anyBoolean());
+    }
+
+    @Test
+    public void testPausedRecordingFilteredOut() {
+        mController.addCallback(new int[]{AppOpsManager.OP_RECORD_AUDIO}, mCallback);
+        mTestableLooper.processAllMessages();
+
+        mController.onOpActiveChanged(
+                AppOpsManager.OP_RECORD_AUDIO, TEST_UID, TEST_PACKAGE_NAME, true);
+        mTestableLooper.processAllMessages();
+
+        assertTrue(mController.getActiveAppOps().isEmpty());
+    }
+
+    @Test
+    public void testOnlyRecordAudioPaused() {
+        mController.addCallback(new int[]{
+                AppOpsManager.OP_RECORD_AUDIO,
+                AppOpsManager.OP_CAMERA
+        }, mCallback);
+        mTestableLooper.processAllMessages();
+
+        mController.onOpActiveChanged(
+                AppOpsManager.OP_CAMERA, TEST_UID, TEST_PACKAGE_NAME, true);
+        mTestableLooper.processAllMessages();
+
+        verify(mCallback).onActiveStateChanged(
+                AppOpsManager.OP_CAMERA, TEST_UID, TEST_PACKAGE_NAME, true);
+        List<AppOpItem> list = mController.getActiveAppOps();
+
+        assertEquals(1, list.size());
+        assertEquals(AppOpsManager.OP_CAMERA, list.get(0).getCode());
+    }
+
+    @Test
+    public void testUnpausedRecordingSentActive() {
+        mController.addCallback(new int[]{AppOpsManager.OP_RECORD_AUDIO}, mCallback);
+        mTestableLooper.processAllMessages();
+        mController.onOpActiveChanged(
+                AppOpsManager.OP_RECORD_AUDIO, TEST_UID, TEST_PACKAGE_NAME, true);
+
+        mTestableLooper.processAllMessages();
+        mRecordingCallback.onRecordingConfigChanged(Collections.emptyList());
+
+        mTestableLooper.processAllMessages();
+
+        verify(mCallback).onActiveStateChanged(
+                AppOpsManager.OP_RECORD_AUDIO, TEST_UID, TEST_PACKAGE_NAME, true);
+    }
+
+    @Test
+    public void testAudioPausedSentInactive() {
+        mController.addCallback(new int[]{AppOpsManager.OP_RECORD_AUDIO}, mCallback);
+        mTestableLooper.processAllMessages();
+        mController.onOpActiveChanged(
+                AppOpsManager.OP_RECORD_AUDIO, TEST_UID_OTHER, TEST_PACKAGE_NAME, true);
+        mTestableLooper.processAllMessages();
+
+        AudioRecordingConfiguration mockARC = mock(AudioRecordingConfiguration.class);
+        when(mockARC.getClientUid()).thenReturn(TEST_UID_OTHER);
+        when(mockARC.isClientSilenced()).thenReturn(true);
+
+        mRecordingCallback.onRecordingConfigChanged(List.of(mockARC));
+        mTestableLooper.processAllMessages();
+
+        InOrder inOrder = inOrder(mCallback);
+        inOrder.verify(mCallback).onActiveStateChanged(
+                AppOpsManager.OP_RECORD_AUDIO, TEST_UID_OTHER, TEST_PACKAGE_NAME, true);
+        inOrder.verify(mCallback).onActiveStateChanged(
+                AppOpsManager.OP_RECORD_AUDIO, TEST_UID_OTHER, TEST_PACKAGE_NAME, false);
+    }
+
     private class TestHandler extends AppOpsControllerImpl.H {
         TestHandler(Looper looper) {
             mController.super(looper);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/assist/AssistHandleBehaviorControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/assist/AssistHandleBehaviorControllerTest.java
index afcd441..7567f0c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/assist/AssistHandleBehaviorControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/assist/AssistHandleBehaviorControllerTest.java
@@ -41,7 +41,7 @@
 import com.android.internal.config.sysui.SystemUiDeviceConfigFlags;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.dump.DumpManager;
-import com.android.systemui.statusbar.phone.NavigationModeController;
+import com.android.systemui.navigationbar.NavigationModeController;
 
 import org.junit.After;
 import org.junit.Before;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthControllerTest.java
index d4a94c5..c8566c5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthControllerTest.java
@@ -54,6 +54,7 @@
 
 import com.android.internal.R;
 import com.android.systemui.SysuiTestCase;
+import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.statusbar.CommandQueue;
 
 import org.junit.Before;
@@ -103,8 +104,8 @@
         when(mDialog1.isAllowDeviceCredentials()).thenReturn(false);
         when(mDialog2.isAllowDeviceCredentials()).thenReturn(false);
 
-        mAuthController = new TestableAuthController(
-                context, mock(CommandQueue.class), new MockInjector());
+        mAuthController = new TestableAuthController(context, mock(CommandQueue.class),
+                mock(StatusBarStateController.class), new MockInjector());
 
         mAuthController.start();
     }
@@ -502,8 +503,9 @@
         private int mBuildCount = 0;
         private PromptInfo mLastBiometricPromptInfo;
 
-        TestableAuthController(Context context, CommandQueue commandQueue, Injector injector) {
-            super(context, commandQueue, injector);
+        TestableAuthController(Context context, CommandQueue commandQueue,
+                StatusBarStateController statusBarStateController, Injector injector) {
+            super(context, commandQueue, statusBarStateController, injector);
         }
 
         @Override
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleControllerTest.java
index b758953..a7808ad 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleControllerTest.java
@@ -79,10 +79,12 @@
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
 import com.android.systemui.statusbar.notification.row.NotificationTestHelper;
+import com.android.systemui.statusbar.notification.row.dagger.NotificationShelfComponent;
 import com.android.systemui.statusbar.phone.DozeParameters;
 import com.android.systemui.statusbar.phone.KeyguardBypassController;
+import com.android.systemui.statusbar.phone.LockscreenLockIconController;
 import com.android.systemui.statusbar.phone.NotificationGroupManager;
-import com.android.systemui.statusbar.phone.NotificationShadeWindowController;
+import com.android.systemui.statusbar.phone.NotificationShadeWindowControllerImpl;
 import com.android.systemui.statusbar.phone.NotificationShadeWindowView;
 import com.android.systemui.statusbar.phone.ShadeController;
 import com.android.systemui.statusbar.policy.BatteryController;
@@ -153,7 +155,7 @@
     private ArgumentCaptor<NotificationRemoveInterceptor> mRemoveInterceptorCaptor;
 
     private TestableBubbleController mBubbleController;
-    private NotificationShadeWindowController mNotificationShadeWindowController;
+    private NotificationShadeWindowControllerImpl mNotificationShadeWindowController;
     private NotificationEntryListener mEntryListener;
     private NotificationRemoveInterceptor mRemoveInterceptor;
 
@@ -174,6 +176,8 @@
     @Mock
     private ShadeController mShadeController;
     @Mock
+    private NotificationShelfComponent mNotificationShelfComponent;
+    @Mock
     private NotifPipeline mNotifPipeline;
     @Mock
     private FeatureFlags mFeatureFlagsOldPipeline;
@@ -185,6 +189,7 @@
     private IStatusBarService mStatusBarService;
     @Mock
     private LauncherApps mLauncherApps;
+    @Mock private LockscreenLockIconController mLockIconController;
 
     private BubbleData mBubbleData;
 
@@ -199,7 +204,8 @@
         mContext.addMockSystemService(FaceManager.class, mFaceManager);
         when(mColorExtractor.getNeutralColors()).thenReturn(mGradientColors);
 
-        mNotificationShadeWindowController = new NotificationShadeWindowController(mContext,
+        // Bubbles get added to status bar window view
+        mNotificationShadeWindowController = new NotificationShadeWindowControllerImpl(mContext,
                 mWindowManager, mActivityManager, mDozeParameters, mStatusBarStateController,
                 mConfigurationController, mKeyguardViewMediator, mKeyguardBypassController,
                 mColorExtractor, mDumpManager);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bubbles/NewNotifPipelineBubbleControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/bubbles/NewNotifPipelineBubbleControllerTest.java
index 43bf191..4936360 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bubbles/NewNotifPipelineBubbleControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/bubbles/NewNotifPipelineBubbleControllerTest.java
@@ -58,6 +58,7 @@
 
 import com.android.internal.colorextraction.ColorExtractor;
 import com.android.internal.statusbar.IStatusBarService;
+import com.android.systemui.SystemUIFactory;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.colorextraction.SysuiColorExtractor;
 import com.android.systemui.dump.DumpManager;
@@ -66,7 +67,9 @@
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.statusbar.FeatureFlags;
 import com.android.systemui.statusbar.NotificationLockscreenUserManager;
+import com.android.systemui.statusbar.NotificationShelf;
 import com.android.systemui.statusbar.RankingBuilder;
+import com.android.systemui.statusbar.SuperStatusBarViewFactory;
 import com.android.systemui.statusbar.SysuiStatusBarStateController;
 import com.android.systemui.statusbar.notification.NotificationEntryManager;
 import com.android.systemui.statusbar.notification.NotificationFilter;
@@ -75,12 +78,12 @@
 import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener;
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
 import com.android.systemui.statusbar.notification.row.NotificationTestHelper;
-import com.android.systemui.statusbar.notification.row.dagger.NotificationRowComponent;
+import com.android.systemui.statusbar.notification.row.dagger.NotificationShelfComponent;
 import com.android.systemui.statusbar.phone.DozeParameters;
 import com.android.systemui.statusbar.phone.KeyguardBypassController;
 import com.android.systemui.statusbar.phone.LockscreenLockIconController;
 import com.android.systemui.statusbar.phone.NotificationGroupManager;
-import com.android.systemui.statusbar.phone.NotificationShadeWindowController;
+import com.android.systemui.statusbar.phone.NotificationShadeWindowControllerImpl;
 import com.android.systemui.statusbar.phone.NotificationShadeWindowView;
 import com.android.systemui.statusbar.phone.ShadeController;
 import com.android.systemui.statusbar.policy.BatteryController;
@@ -88,6 +91,7 @@
 import com.android.systemui.statusbar.policy.HeadsUpManager;
 import com.android.systemui.statusbar.policy.ZenModeController;
 import com.android.systemui.util.FloatingContentCoordinator;
+import com.android.systemui.util.InjectionInflationController;
 
 import org.junit.Before;
 import org.junit.Ignore;
@@ -149,7 +153,7 @@
     @Captor
     private ArgumentCaptor<NotifCollectionListener> mNotifListenerCaptor;
     private TestableBubbleController mBubbleController;
-    private NotificationShadeWindowController mNotificationShadeWindowController;
+    private NotificationShadeWindowControllerImpl mNotificationShadeWindowController;
     private NotifCollectionListener mEntryListener;
     private NotificationTestHelper mNotificationTestHelper;
     private ExpandableNotificationRow mRow;
@@ -168,7 +172,7 @@
     @Mock
     private ShadeController mShadeController;
     @Mock
-    private NotificationRowComponent mNotificationRowComponent;
+    private NotificationShelfComponent mNotificationShelfComponent;
     @Mock
     private NotifPipeline mNotifPipeline;
     @Mock
@@ -185,6 +189,7 @@
     private BubbleData mBubbleData;
 
     private TestableLooper mTestableLooper;
+    private SuperStatusBarViewFactory mSuperStatusBarViewFactory;
 
     @Before
     public void setUp() throws Exception {
@@ -195,8 +200,25 @@
         mContext.addMockSystemService(FaceManager.class, mFaceManager);
         when(mColorExtractor.getNeutralColors()).thenReturn(mGradientColors);
 
+        mSuperStatusBarViewFactory = new SuperStatusBarViewFactory(mContext,
+                new InjectionInflationController(SystemUIFactory.getInstance().getSysUIComponent()
+                        .createViewInstanceCreatorFactory()),
+                new NotificationShelfComponent.Builder() {
+                    @Override
+                    public NotificationShelfComponent.Builder notificationShelf(
+                            NotificationShelf view) {
+                        return this;
+                    }
+
+                    @Override
+                    public NotificationShelfComponent build() {
+                        return mNotificationShelfComponent;
+                    }
+                },
+                mLockIconController);
+
         // Bubbles get added to status bar window view
-        mNotificationShadeWindowController = new NotificationShadeWindowController(mContext,
+        mNotificationShadeWindowController = new NotificationShadeWindowControllerImpl(mContext,
                 mWindowManager, mActivityManager, mDozeParameters, mStatusBarStateController,
                 mConfigurationController, mKeyguardViewMediator, mKeyguardBypassController,
                 mColorExtractor, mDumpManager);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bubbles/TestableBubbleController.java b/packages/SystemUI/tests/src/com/android/systemui/bubbles/TestableBubbleController.java
index 0a6d071..51ca2a4 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bubbles/TestableBubbleController.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/bubbles/TestableBubbleController.java
@@ -27,11 +27,11 @@
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.statusbar.FeatureFlags;
 import com.android.systemui.statusbar.NotificationLockscreenUserManager;
+import com.android.systemui.statusbar.NotificationShadeWindowController;
 import com.android.systemui.statusbar.notification.NotificationEntryManager;
 import com.android.systemui.statusbar.notification.collection.NotifPipeline;
 import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProvider;
 import com.android.systemui.statusbar.phone.NotificationGroupManager;
-import com.android.systemui.statusbar.phone.NotificationShadeWindowController;
 import com.android.systemui.statusbar.phone.ShadeController;
 import com.android.systemui.statusbar.policy.ConfigurationController;
 import com.android.systemui.statusbar.policy.ZenModeController;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/CustomIconCacheTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/CustomIconCacheTest.kt
new file mode 100644
index 0000000..4d0f2ed
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/controls/CustomIconCacheTest.kt
@@ -0,0 +1,101 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.controls
+
+import android.content.ComponentName
+import android.graphics.drawable.Icon
+import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import org.junit.Assert.assertNull
+import org.junit.Assert.assertTrue
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+class CustomIconCacheTest : SysuiTestCase() {
+
+    companion object {
+        private val TEST_COMPONENT1 = ComponentName.unflattenFromString("pkg/.cls1")!!
+        private val TEST_COMPONENT2 = ComponentName.unflattenFromString("pkg/.cls2")!!
+        private const val CONTROL_ID_1 = "TEST_CONTROL_1"
+        private const val CONTROL_ID_2 = "TEST_CONTROL_2"
+    }
+
+    @Mock(stubOnly = true)
+    private lateinit var icon1: Icon
+    @Mock(stubOnly = true)
+    private lateinit var icon2: Icon
+    private lateinit var customIconCache: CustomIconCache
+
+    @Before
+    fun setUp() {
+        MockitoAnnotations.initMocks(this)
+
+        customIconCache = CustomIconCache()
+    }
+
+    @Test
+    fun testIconStoredCorrectly() {
+        customIconCache.store(TEST_COMPONENT1, CONTROL_ID_1, icon1)
+
+        assertTrue(icon1 === customIconCache.retrieve(TEST_COMPONENT1, CONTROL_ID_1))
+    }
+
+    @Test
+    fun testIconNotStoredReturnsNull() {
+        customIconCache.store(TEST_COMPONENT1, CONTROL_ID_1, icon1)
+
+        assertNull(customIconCache.retrieve(TEST_COMPONENT1, CONTROL_ID_2))
+    }
+
+    @Test
+    fun testWrongComponentReturnsNull() {
+        customIconCache.store(TEST_COMPONENT1, CONTROL_ID_1, icon1)
+
+        assertNull(customIconCache.retrieve(TEST_COMPONENT2, CONTROL_ID_1))
+    }
+
+    @Test
+    fun testChangeComponentOldComponentIsRemoved() {
+        customIconCache.store(TEST_COMPONENT1, CONTROL_ID_1, icon1)
+        customIconCache.store(TEST_COMPONENT2, CONTROL_ID_2, icon2)
+
+        assertNull(customIconCache.retrieve(TEST_COMPONENT1, CONTROL_ID_1))
+        assertNull(customIconCache.retrieve(TEST_COMPONENT1, CONTROL_ID_2))
+    }
+
+    @Test
+    fun testChangeComponentCorrectIconRetrieved() {
+        customIconCache.store(TEST_COMPONENT1, CONTROL_ID_1, icon1)
+        customIconCache.store(TEST_COMPONENT2, CONTROL_ID_1, icon2)
+
+        assertTrue(icon2 === customIconCache.retrieve(TEST_COMPONENT2, CONTROL_ID_1))
+    }
+
+    @Test
+    fun testStoreNull() {
+        customIconCache.store(TEST_COMPONENT1, CONTROL_ID_1, icon1)
+        customIconCache.store(TEST_COMPONENT1, CONTROL_ID_1, null)
+
+        assertNull(customIconCache.retrieve(TEST_COMPONENT1, CONTROL_ID_1))
+    }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsFavoritePersistenceWrapperTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsFavoritePersistenceWrapperTest.kt
index 861c620..690b9a7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsFavoritePersistenceWrapperTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsFavoritePersistenceWrapperTest.kt
@@ -25,6 +25,7 @@
 import com.android.systemui.util.time.FakeSystemClock
 import org.junit.After
 import org.junit.Assert.assertEquals
+import org.junit.Assert.assertFalse
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -78,4 +79,15 @@
 
         assertEquals(list, wrapper.readFavorites())
     }
+
+    @Test
+    fun testSaveEmptyOnNonExistingFile() {
+        if (file.exists()) {
+            file.delete()
+        }
+
+        wrapper.storeFavorites(emptyList())
+
+        assertFalse(file.exists())
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/management/FavoritesModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/management/FavoritesModelTest.kt
index ce33a8d..f0003ed 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/controls/management/FavoritesModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/controls/management/FavoritesModelTest.kt
@@ -22,6 +22,7 @@
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.controls.ControlInterface
+import com.android.systemui.controls.CustomIconCache
 import com.android.systemui.controls.controller.ControlInfo
 import com.android.systemui.util.mockito.any
 import com.android.systemui.util.mockito.eq
@@ -57,6 +58,8 @@
     private lateinit var callback: FavoritesModel.FavoritesModelCallback
     @Mock
     private lateinit var adapter: RecyclerView.Adapter<*>
+    @Mock
+    private lateinit var customIconCache: CustomIconCache
     private lateinit var model: FavoritesModel
     private lateinit var dividerWrapper: DividerWrapper
 
@@ -64,7 +67,7 @@
     fun setUp() {
         MockitoAnnotations.initMocks(this)
 
-        model = FavoritesModel(TEST_COMPONENT, INITIAL_FAVORITES, callback)
+        model = FavoritesModel(customIconCache, TEST_COMPONENT, INITIAL_FAVORITES, callback)
         model.attachAdapter(adapter)
         dividerWrapper = model.elements.first { it is DividerWrapper } as DividerWrapper
     }
@@ -89,7 +92,7 @@
     @Test
     fun testInitialElements() {
         val expected = INITIAL_FAVORITES.map {
-            ControlInfoWrapper(TEST_COMPONENT, it, true)
+            ControlInfoWrapper(TEST_COMPONENT, it, true, customIconCache::retrieve)
         } + DividerWrapper()
         assertEquals(expected, model.elements)
     }
@@ -287,5 +290,13 @@
         verify(callback).onFirstChange()
     }
 
+    @Test
+    fun testCacheCalledWhenGettingCustomIcon() {
+        val wrapper = model.elements[0] as ControlInfoWrapper
+        wrapper.customIcon
+
+        verify(customIconCache).retrieve(TEST_COMPONENT, wrapper.controlId)
+    }
+
     private fun getDividerPosition(): Int = model.elements.indexOf(dividerWrapper)
 }
\ No newline at end of file
diff --git a/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogTest.java b/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogTest.java
index ac8c671..c8e0f49 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogTest.java
@@ -71,7 +71,7 @@
 import com.android.systemui.plugins.GlobalActionsPanelPlugin;
 import com.android.systemui.settings.CurrentUserContextTracker;
 import com.android.systemui.statusbar.NotificationShadeDepthController;
-import com.android.systemui.statusbar.phone.NotificationShadeWindowController;
+import com.android.systemui.statusbar.NotificationShadeWindowController;
 import com.android.systemui.statusbar.policy.ConfigurationController;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
 import com.android.systemui.util.RingerModeLiveData;
@@ -88,7 +88,7 @@
 
 @SmallTest
 @RunWith(AndroidTestingRunner.class)
-@TestableLooper.RunWithLooper()
+@TestableLooper.RunWithLooper(setAsMainLooper = true)
 public class GlobalActionsDialogTest extends SysuiTestCase {
     private GlobalActionsDialog mGlobalActionsDialog;
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/FaceAuthScreenBrightnessControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/FaceAuthScreenBrightnessControllerTest.kt
new file mode 100644
index 0000000..58cb032
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/FaceAuthScreenBrightnessControllerTest.kt
@@ -0,0 +1,135 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard
+
+import android.animation.ValueAnimator
+import android.content.res.Resources
+import android.hardware.biometrics.BiometricSourceType
+import android.os.Handler
+import android.provider.Settings.System.SCREEN_BRIGHTNESS_FLOAT
+import android.testing.AndroidTestingRunner
+import android.util.TypedValue
+import android.view.View
+import androidx.test.filters.SmallTest
+import com.android.keyguard.KeyguardUpdateMonitor
+import com.android.keyguard.KeyguardUpdateMonitorCallback
+import com.android.systemui.Dumpable
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.dump.DumpManager
+import com.android.systemui.statusbar.NotificationShadeWindowController
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.capture
+import com.android.systemui.util.mockito.eq
+import com.android.systemui.util.settings.GlobalSettings
+import com.android.systemui.util.settings.SystemSettings
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentCaptor
+import org.mockito.Captor
+import org.mockito.Mock
+import org.mockito.Mockito.`when`
+import org.mockito.Mockito.anyInt
+import org.mockito.Mockito.anyString
+import org.mockito.Mockito.clearInvocations
+import org.mockito.Mockito.never
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+
+const val INITIAL_BRIGHTNESS = 0.5f
+
+@RunWith(AndroidTestingRunner::class)
+@SmallTest
+class FaceAuthScreenBrightnessControllerTest : SysuiTestCase() {
+
+    @Mock
+    lateinit var whiteOverlay: View
+    @Mock
+    lateinit var dumpManager: DumpManager
+    @Mock
+    lateinit var resources: Resources
+    @Mock
+    lateinit var mainHandler: Handler
+    @Mock
+    lateinit var globalSettings: GlobalSettings
+    @Mock
+    lateinit var systemSettings: SystemSettings
+    @Mock
+    lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor
+    @Mock
+    lateinit var notificationShadeWindowController: NotificationShadeWindowController
+    @Mock
+    lateinit var animator: ValueAnimator
+    @Captor
+    lateinit var keyguardUpdateCallback: ArgumentCaptor<KeyguardUpdateMonitorCallback>
+    lateinit var faceAuthScreenBrightnessController: FaceAuthScreenBrightnessController
+
+    @Before
+    fun setup() {
+        MockitoAnnotations.initMocks(this)
+        faceAuthScreenBrightnessController = object : FaceAuthScreenBrightnessController(
+                notificationShadeWindowController, keyguardUpdateMonitor, resources, globalSettings,
+                systemSettings, mainHandler, dumpManager) {
+            override fun createAnimator(start: Float, end: Float) = animator
+        }
+        `when`(systemSettings.getFloat(eq(SCREEN_BRIGHTNESS_FLOAT))).thenReturn(INITIAL_BRIGHTNESS)
+        faceAuthScreenBrightnessController.attach(whiteOverlay)
+        verify(keyguardUpdateMonitor).registerCallback(capture(keyguardUpdateCallback))
+    }
+
+    @Test
+    fun init_registersDumpManager() {
+        verify(dumpManager).registerDumpable(anyString(), any(Dumpable::class.java))
+    }
+
+    @Test
+    fun init_registersKeyguardCallback() {
+        verify(keyguardUpdateMonitor)
+                .registerCallback(any(KeyguardUpdateMonitorCallback::class.java))
+    }
+
+    @Test
+    fun onBiometricRunningChanged_animatesBrightness() {
+        clearInvocations(whiteOverlay)
+        keyguardUpdateCallback.value
+                .onBiometricRunningStateChanged(true, BiometricSourceType.FACE)
+        verify(whiteOverlay).visibility = eq(View.VISIBLE)
+        verify(animator).start()
+    }
+
+    @Test
+    fun faceAuthWallpaper_whenFaceIsDisabledForUser() {
+        faceAuthScreenBrightnessController.useFaceAuthWallpaper = true
+        faceAuthScreenBrightnessController.faceAuthWallpaper
+        verify(resources, never()).openRawResource(anyInt(), any(TypedValue::class.java))
+    }
+
+    @Test
+    fun faceAuthWallpaper_whenFaceFlagIsDisabled() {
+        faceAuthScreenBrightnessController.useFaceAuthWallpaper = true
+        faceAuthScreenBrightnessController.faceAuthWallpaper
+        verify(resources, never()).openRawResource(anyInt(), any(TypedValue::class.java))
+    }
+
+    @Test
+    fun faceAuthWallpaper_whenFaceIsEnabledForUser() {
+        faceAuthScreenBrightnessController.useFaceAuthWallpaper = true
+        `when`(keyguardUpdateMonitor.isFaceAuthEnabledForUser(anyInt())).thenReturn(true)
+        faceAuthScreenBrightnessController.faceAuthWallpaper
+        verify(resources).openRawResource(anyInt(), any(TypedValue::class.java))
+    }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardSliceProviderTest.java b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardSliceProviderTest.java
index f70fb4f..d1f505b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardSliceProviderTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardSliceProviderTest.java
@@ -254,16 +254,12 @@
         int mCleanDateFormatInvokations;
         private int mCounter;
 
-        Uri getUri() {
-            return mSliceUri;
-        }
+        TestableKeyguardSliceProvider() {
+            super();
 
-        @Override
-        protected void inject() {
             mAlarmManager = KeyguardSliceProviderTest.this.mAlarmManager;
             mContentResolver = KeyguardSliceProviderTest.this.mContentResolver;
             mZenModeController = KeyguardSliceProviderTest.this.mZenModeController;
-            mMediaWakeLock = KeyguardSliceProviderTest.this.mMediaWakeLock;
             mDozeParameters = KeyguardSliceProviderTest.this.mDozeParameters;
             mNextAlarmController = KeyguardSliceProviderTest.this.mNextAlarmController;
             mStatusBarStateController = KeyguardSliceProviderTest.this.mStatusBarStateController;
@@ -272,6 +268,17 @@
         }
 
         @Override
+        public boolean onCreateSliceProvider() {
+            boolean result = super.onCreateSliceProvider();
+            mMediaWakeLock = KeyguardSliceProviderTest.this.mMediaWakeLock;
+            return result;
+        }
+
+        Uri getUri() {
+            return mSliceUri;
+        }
+
+        @Override
         protected boolean isDndOn() {
             return mIsZenMode;
         }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
index 90e9ef4..c874b1f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
@@ -42,10 +42,11 @@
 import com.android.systemui.broadcast.BroadcastDispatcher;
 import com.android.systemui.classifier.FalsingManagerFake;
 import com.android.systemui.dump.DumpManager;
-import com.android.systemui.statusbar.phone.NavigationModeController;
+import com.android.systemui.navigationbar.NavigationModeController;
 import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
 import com.android.systemui.util.DeviceConfigProxy;
 import com.android.systemui.util.DeviceConfigProxyFake;
+import com.android.systemui.util.InjectionInflationController;
 import com.android.systemui.util.concurrency.FakeExecutor;
 import com.android.systemui.util.time.FakeSystemClock;
 
@@ -71,6 +72,7 @@
     private @Mock PowerManager mPowerManager;
     private @Mock TrustManager mTrustManager;
     private @Mock NavigationModeController mNavigationModeController;
+    private @Mock InjectionInflationController mInjectionInflationController;
     private DeviceConfigProxy mDeviceConfig = new DeviceConfigProxyFake();
     private FakeExecutor mUiBgExecutor = new FakeExecutor(new FakeSystemClock());
 
@@ -88,7 +90,8 @@
                 mContext, mFalsingManager, mLockPatternUtils, mBroadcastDispatcher,
                 () -> mStatusBarKeyguardViewManager,
                 mDismissCallbackRegistry, mUpdateMonitor, mDumpManager, mUiBgExecutor,
-                mPowerManager, mTrustManager, mDeviceConfig, mNavigationModeController);
+                mPowerManager, mTrustManager, mDeviceConfig, mNavigationModeController,
+                mInjectionInflationController);
         mViewMediator.start();
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/MediaControlPanelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/MediaControlPanelTest.kt
index c63781c..8a30b00 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/MediaControlPanelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/MediaControlPanelTest.kt
@@ -16,6 +16,7 @@
 
 package com.android.systemui.media
 
+import android.content.Intent
 import android.content.res.ColorStateList
 import android.graphics.Color
 import android.graphics.drawable.GradientDrawable
@@ -23,6 +24,7 @@
 import android.media.MediaMetadata
 import android.media.session.MediaSession
 import android.media.session.PlaybackState
+import android.provider.Settings.ACTION_MEDIA_CONTROLS_SETTINGS
 import android.testing.AndroidTestingRunner
 import android.testing.TestableLooper
 import android.view.View
@@ -35,24 +37,31 @@
 import androidx.constraintlayout.widget.ConstraintSet
 import androidx.lifecycle.LiveData
 import androidx.test.filters.SmallTest
-import com.android.systemui.R
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.plugins.ActivityStarter
+import com.android.systemui.statusbar.phone.KeyguardDismissUtil
 import com.android.systemui.util.animation.TransitionLayout
 import com.android.systemui.util.concurrency.FakeExecutor
+import com.android.systemui.util.mockito.eq
+import com.android.systemui.util.mockito.any
 import com.android.systemui.util.time.FakeSystemClock
 import com.google.common.truth.Truth.assertThat
+import dagger.Lazy
 import org.junit.After
 import org.junit.Before
 import org.junit.Rule
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.mockito.ArgumentCaptor
+import org.mockito.ArgumentMatchers
+import org.mockito.ArgumentMatchers.anyLong
 import org.mockito.Mock
+import org.mockito.Mockito.anyBoolean
 import org.mockito.Mockito.mock
+import org.mockito.Mockito.never
 import org.mockito.Mockito.verify
-import org.mockito.Mockito.`when` as whenever
 import org.mockito.junit.MockitoJUnit
+import org.mockito.Mockito.`when` as whenever
 
 private const val KEY = "TEST_KEY"
 private const val APP = "APP"
@@ -81,6 +90,8 @@
     @Mock private lateinit var seekBarViewModel: SeekBarViewModel
     @Mock private lateinit var seekBarData: LiveData<SeekBarViewModel.Progress>
     @Mock private lateinit var mediaViewController: MediaViewController
+    @Mock private lateinit var keyguardDismissUtil: KeyguardDismissUtil
+    @Mock private lateinit var mediaDataManager: MediaDataManager
     @Mock private lateinit var expandedSet: ConstraintSet
     @Mock private lateinit var collapsedSet: ConstraintSet
     private lateinit var appIcon: ImageView
@@ -100,6 +111,9 @@
     private lateinit var action2: ImageButton
     private lateinit var action3: ImageButton
     private lateinit var action4: ImageButton
+    private lateinit var settings: View
+    private lateinit var cancel: View
+    private lateinit var dismiss: View
 
     private lateinit var session: MediaSession
     private val device = MediaDeviceData(true, null, DEVICE_NAME)
@@ -114,7 +128,7 @@
         whenever(mediaViewController.collapsedLayout).thenReturn(collapsedSet)
 
         player = MediaControlPanel(context, bgExecutor, activityStarter, mediaViewController,
-                seekBarViewModel)
+                seekBarViewModel, Lazy { mediaDataManager }, keyguardDismissUtil)
         whenever(seekBarViewModel.progress).thenReturn(seekBarData)
 
         // Mock out a view holder for the player to attach to.
@@ -156,6 +170,12 @@
         whenever(holder.action3).thenReturn(action3)
         action4 = ImageButton(context)
         whenever(holder.action4).thenReturn(action4)
+        settings = View(context)
+        whenever(holder.settings).thenReturn(settings)
+        cancel = View(context)
+        whenever(holder.cancel).thenReturn(cancel)
+        dismiss = View(context)
+        whenever(holder.dismiss).thenReturn(dismiss)
 
         // Create media session
         val metadataBuilder = MediaMetadata.Builder().apply {
@@ -254,4 +274,79 @@
         assertThat(seamlessText.getText()).isEqualTo(DEVICE_NAME)
         assertThat(seamless.isEnabled()).isFalse()
     }
+
+    @Test
+    fun longClick_gutsClosed() {
+        player.attach(holder)
+        whenever(mediaViewController.isGutsVisible).thenReturn(false)
+
+        val captor = ArgumentCaptor.forClass(View.OnLongClickListener::class.java)
+        verify(holder.player).setOnLongClickListener(captor.capture())
+
+        captor.value.onLongClick(holder.player)
+        verify(mediaViewController).openGuts()
+    }
+
+    @Test
+    fun longClick_gutsOpen() {
+        player.attach(holder)
+        whenever(mediaViewController.isGutsVisible).thenReturn(true)
+
+        val captor = ArgumentCaptor.forClass(View.OnLongClickListener::class.java)
+        verify(holder.player).setOnLongClickListener(captor.capture())
+
+        captor.value.onLongClick(holder.player)
+        verify(mediaViewController, never()).openGuts()
+    }
+
+    @Test
+    fun cancelButtonClick_animation() {
+        player.attach(holder)
+
+        cancel.callOnClick()
+
+        verify(mediaViewController).closeGuts(false)
+    }
+
+    @Test
+    fun settingsButtonClick() {
+        player.attach(holder)
+
+        settings.callOnClick()
+
+        val captor = ArgumentCaptor.forClass(Intent::class.java)
+        verify(activityStarter).startActivity(captor.capture(), eq(true))
+
+        assertThat(captor.value.action).isEqualTo(ACTION_MEDIA_CONTROLS_SETTINGS)
+    }
+
+    @Test
+    fun dismissButtonClick() {
+        player.attach(holder)
+        val state = MediaData(USER_ID, true, BG_COLOR, APP, null, ARTIST, TITLE, null, emptyList(),
+                emptyList(), PACKAGE, session.getSessionToken(), null, null, true, null,
+                notificationKey = KEY)
+        player.bind(state)
+
+        dismiss.callOnClick()
+        val captor = ArgumentCaptor.forClass(ActivityStarter.OnDismissAction::class.java)
+        verify(keyguardDismissUtil).executeWhenUnlocked(captor.capture(), anyBoolean())
+
+        captor.value.onDismiss()
+        verify(mediaDataManager).dismissMediaData(eq(KEY), anyLong())
+    }
+
+    @Test
+    fun dismissButtonClick_nullNotificationKey() {
+        player.attach(holder)
+        val state = MediaData(USER_ID, true, BG_COLOR, APP, null, ARTIST, TITLE, null, emptyList(),
+                emptyList(), PACKAGE, session.getSessionToken(), null, null, true, null)
+        player.bind(state)
+
+        verify(keyguardDismissUtil, never())
+                .executeWhenUnlocked(
+                        any(ActivityStarter.OnDismissAction::class.java),
+                        ArgumentMatchers.anyBoolean()
+                )
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataCombineLatestTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataCombineLatestTest.java
index 492b33e..89538ac 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataCombineLatestTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataCombineLatestTest.java
@@ -20,7 +20,6 @@
 
 import static org.mockito.Mockito.any;
 import static org.mockito.Mockito.eq;
-import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.reset;
 import static org.mockito.Mockito.verify;
@@ -34,19 +33,23 @@
 import com.android.systemui.SysuiTestCase;
 
 import org.junit.Before;
+import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.ArgumentCaptor;
 import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
 
 import java.util.ArrayList;
-import java.util.Map;
 
 @SmallTest
 @RunWith(AndroidTestingRunner.class)
 @TestableLooper.RunWithLooper
 public class MediaDataCombineLatestTest extends SysuiTestCase {
 
+    @Rule public MockitoRule mockito = MockitoJUnit.rule();
+
     private static final String KEY = "TEST_KEY";
     private static final String OLD_KEY = "TEST_KEY_OLD";
     private static final String APP = "APP";
@@ -59,39 +62,26 @@
 
     private MediaDataCombineLatest mManager;
 
-    @Mock private MediaDataManager mDataSource;
-    @Mock private MediaDeviceManager mDeviceSource;
     @Mock private MediaDataManager.Listener mListener;
 
-    private MediaDataManager.Listener mDataListener;
-    private MediaDeviceManager.Listener mDeviceListener;
-
     private MediaData mMediaData;
     private MediaDeviceData mDeviceData;
 
     @Before
     public void setUp() {
-        mDataSource = mock(MediaDataManager.class);
-        mDeviceSource = mock(MediaDeviceManager.class);
-        mListener = mock(MediaDataManager.Listener.class);
-
-        mManager = new MediaDataCombineLatest(mDataSource, mDeviceSource);
-
-        mDataListener = captureDataListener();
-        mDeviceListener = captureDeviceListener();
-
+        mManager = new MediaDataCombineLatest();
         mManager.addListener(mListener);
 
         mMediaData = new MediaData(USER_ID, true, BG_COLOR, APP, null, ARTIST, TITLE, null,
-                new ArrayList<>(), new ArrayList<>(), PACKAGE, null, null, null, true, null, false,
-                KEY, false);
+                new ArrayList<>(), new ArrayList<>(), PACKAGE, null, null, null, true, null, true,
+                false, KEY, false);
         mDeviceData = new MediaDeviceData(true, null, DEVICE_NAME);
     }
 
     @Test
     public void eventNotEmittedWithoutDevice() {
         // WHEN data source emits an event without device data
-        mDataListener.onMediaDataLoaded(KEY, null, mMediaData);
+        mManager.onMediaDataLoaded(KEY, null, mMediaData);
         // THEN an event isn't emitted
         verify(mListener, never()).onMediaDataLoaded(eq(KEY), any(), any());
     }
@@ -99,7 +89,7 @@
     @Test
     public void eventNotEmittedWithoutMedia() {
         // WHEN device source emits an event without media data
-        mDeviceListener.onMediaDeviceChanged(KEY, null, mDeviceData);
+        mManager.onMediaDeviceChanged(KEY, null, mDeviceData);
         // THEN an event isn't emitted
         verify(mListener, never()).onMediaDataLoaded(eq(KEY), any(), any());
     }
@@ -107,9 +97,9 @@
     @Test
     public void emitEventAfterDeviceFirst() {
         // GIVEN that a device event has already been received
-        mDeviceListener.onMediaDeviceChanged(KEY, null, mDeviceData);
+        mManager.onMediaDeviceChanged(KEY, null, mDeviceData);
         // WHEN media event is received
-        mDataListener.onMediaDataLoaded(KEY, null, mMediaData);
+        mManager.onMediaDataLoaded(KEY, null, mMediaData);
         // THEN the listener receives a combined event
         ArgumentCaptor<MediaData> captor = ArgumentCaptor.forClass(MediaData.class);
         verify(mListener).onMediaDataLoaded(eq(KEY), any(), captor.capture());
@@ -119,9 +109,9 @@
     @Test
     public void emitEventAfterMediaFirst() {
         // GIVEN that media event has already been received
-        mDataListener.onMediaDataLoaded(KEY, null, mMediaData);
+        mManager.onMediaDataLoaded(KEY, null, mMediaData);
         // WHEN device event is received
-        mDeviceListener.onMediaDeviceChanged(KEY, null, mDeviceData);
+        mManager.onMediaDeviceChanged(KEY, null, mDeviceData);
         // THEN the listener receives a combined event
         ArgumentCaptor<MediaData> captor = ArgumentCaptor.forClass(MediaData.class);
         verify(mListener).onMediaDataLoaded(eq(KEY), any(), captor.capture());
@@ -131,11 +121,11 @@
     @Test
     public void migrateKeyMediaFirst() {
         // GIVEN that media and device info has already been received
-        mDataListener.onMediaDataLoaded(OLD_KEY, null, mMediaData);
-        mDeviceListener.onMediaDeviceChanged(OLD_KEY, null, mDeviceData);
+        mManager.onMediaDataLoaded(OLD_KEY, null, mMediaData);
+        mManager.onMediaDeviceChanged(OLD_KEY, null, mDeviceData);
         reset(mListener);
         // WHEN a key migration event is received
-        mDataListener.onMediaDataLoaded(KEY, OLD_KEY, mMediaData);
+        mManager.onMediaDataLoaded(KEY, OLD_KEY, mMediaData);
         // THEN the listener receives a combined event
         ArgumentCaptor<MediaData> captor = ArgumentCaptor.forClass(MediaData.class);
         verify(mListener).onMediaDataLoaded(eq(KEY), eq(OLD_KEY), captor.capture());
@@ -145,11 +135,11 @@
     @Test
     public void migrateKeyDeviceFirst() {
         // GIVEN that media and device info has already been received
-        mDataListener.onMediaDataLoaded(OLD_KEY, null, mMediaData);
-        mDeviceListener.onMediaDeviceChanged(OLD_KEY, null, mDeviceData);
+        mManager.onMediaDataLoaded(OLD_KEY, null, mMediaData);
+        mManager.onMediaDeviceChanged(OLD_KEY, null, mDeviceData);
         reset(mListener);
         // WHEN a key migration event is received
-        mDeviceListener.onMediaDeviceChanged(KEY, OLD_KEY, mDeviceData);
+        mManager.onMediaDeviceChanged(KEY, OLD_KEY, mDeviceData);
         // THEN the listener receives a combined event
         ArgumentCaptor<MediaData> captor = ArgumentCaptor.forClass(MediaData.class);
         verify(mListener).onMediaDataLoaded(eq(KEY), eq(OLD_KEY), captor.capture());
@@ -159,12 +149,12 @@
     @Test
     public void migrateKeyMediaAfter() {
         // GIVEN that media and device info has already been received
-        mDataListener.onMediaDataLoaded(OLD_KEY, null, mMediaData);
-        mDeviceListener.onMediaDeviceChanged(OLD_KEY, null, mDeviceData);
-        mDeviceListener.onMediaDeviceChanged(KEY, OLD_KEY, mDeviceData);
+        mManager.onMediaDataLoaded(OLD_KEY, null, mMediaData);
+        mManager.onMediaDeviceChanged(OLD_KEY, null, mDeviceData);
+        mManager.onMediaDeviceChanged(KEY, OLD_KEY, mDeviceData);
         reset(mListener);
         // WHEN a second key migration event is received for media
-        mDataListener.onMediaDataLoaded(KEY, OLD_KEY, mMediaData);
+        mManager.onMediaDataLoaded(KEY, OLD_KEY, mMediaData);
         // THEN the key has already been migrated
         ArgumentCaptor<MediaData> captor = ArgumentCaptor.forClass(MediaData.class);
         verify(mListener).onMediaDataLoaded(eq(KEY), eq(KEY), captor.capture());
@@ -174,12 +164,12 @@
     @Test
     public void migrateKeyDeviceAfter() {
         // GIVEN that media and device info has already been received
-        mDataListener.onMediaDataLoaded(OLD_KEY, null, mMediaData);
-        mDeviceListener.onMediaDeviceChanged(OLD_KEY, null, mDeviceData);
-        mDataListener.onMediaDataLoaded(KEY, OLD_KEY, mMediaData);
+        mManager.onMediaDataLoaded(OLD_KEY, null, mMediaData);
+        mManager.onMediaDeviceChanged(OLD_KEY, null, mDeviceData);
+        mManager.onMediaDataLoaded(KEY, OLD_KEY, mMediaData);
         reset(mListener);
         // WHEN a second key migration event is received for the device
-        mDeviceListener.onMediaDeviceChanged(KEY, OLD_KEY, mDeviceData);
+        mManager.onMediaDeviceChanged(KEY, OLD_KEY, mDeviceData);
         // THEN the key has already be migrated
         ArgumentCaptor<MediaData> captor = ArgumentCaptor.forClass(MediaData.class);
         verify(mListener).onMediaDataLoaded(eq(KEY), eq(KEY), captor.capture());
@@ -189,60 +179,34 @@
     @Test
     public void mediaDataRemoved() {
         // WHEN media data is removed without first receiving device or data
-        mDataListener.onMediaDataRemoved(KEY);
+        mManager.onMediaDataRemoved(KEY);
         // THEN a removed event isn't emitted
         verify(mListener, never()).onMediaDataRemoved(eq(KEY));
     }
 
     @Test
     public void mediaDataRemovedAfterMediaEvent() {
-        mDataListener.onMediaDataLoaded(KEY, null, mMediaData);
-        mDataListener.onMediaDataRemoved(KEY);
+        mManager.onMediaDataLoaded(KEY, null, mMediaData);
+        mManager.onMediaDataRemoved(KEY);
         verify(mListener).onMediaDataRemoved(eq(KEY));
     }
 
     @Test
     public void mediaDataRemovedAfterDeviceEvent() {
-        mDeviceListener.onMediaDeviceChanged(KEY, null, mDeviceData);
-        mDataListener.onMediaDataRemoved(KEY);
+        mManager.onMediaDeviceChanged(KEY, null, mDeviceData);
+        mManager.onMediaDataRemoved(KEY);
         verify(mListener).onMediaDataRemoved(eq(KEY));
     }
 
     @Test
     public void mediaDataKeyUpdated() {
         // GIVEN that device and media events have already been received
-        mDataListener.onMediaDataLoaded(KEY, null, mMediaData);
-        mDeviceListener.onMediaDeviceChanged(KEY, null, mDeviceData);
+        mManager.onMediaDataLoaded(KEY, null, mMediaData);
+        mManager.onMediaDeviceChanged(KEY, null, mDeviceData);
         // WHEN the key is changed
-        mDataListener.onMediaDataLoaded("NEW_KEY", KEY, mMediaData);
+        mManager.onMediaDataLoaded("NEW_KEY", KEY, mMediaData);
         // THEN the listener gets a load event with the correct keys
         ArgumentCaptor<MediaData> captor = ArgumentCaptor.forClass(MediaData.class);
         verify(mListener).onMediaDataLoaded(eq("NEW_KEY"), any(), captor.capture());
     }
-
-    @Test
-    public void getDataIncludesDevice() {
-        // GIVEN that device and media events have been received
-        mDeviceListener.onMediaDeviceChanged(KEY, null, mDeviceData);
-        mDataListener.onMediaDataLoaded(KEY, null, mMediaData);
-
-        // THEN the result of getData includes device info
-        Map<String, MediaData> results = mManager.getData();
-        assertThat(results.get(KEY)).isNotNull();
-        assertThat(results.get(KEY).getDevice()).isEqualTo(mDeviceData);
-    }
-
-    private MediaDataManager.Listener captureDataListener() {
-        ArgumentCaptor<MediaDataManager.Listener> captor = ArgumentCaptor.forClass(
-                MediaDataManager.Listener.class);
-        verify(mDataSource).addListener(captor.capture());
-        return captor.getValue();
-    }
-
-    private MediaDeviceManager.Listener captureDeviceListener() {
-        ArgumentCaptor<MediaDeviceManager.Listener> captor = ArgumentCaptor.forClass(
-                MediaDeviceManager.Listener.class);
-        verify(mDeviceSource).addListener(captor.capture());
-        return captor.getValue();
-    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataFilterTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataFilterTest.kt
index afb64a7..36b6527 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataFilterTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataFilterTest.kt
@@ -32,6 +32,7 @@
 import org.mockito.Mockito
 import org.mockito.Mockito.`when`
 import org.mockito.Mockito.never
+import org.mockito.Mockito.reset
 import org.mockito.Mockito.verify
 import org.mockito.MockitoAnnotations
 import java.util.concurrent.Executor
@@ -56,8 +57,6 @@
 class MediaDataFilterTest : SysuiTestCase() {
 
     @Mock
-    private lateinit var combineLatest: MediaDataCombineLatest
-    @Mock
     private lateinit var listener: MediaDataManager.Listener
     @Mock
     private lateinit var broadcastDispatcher: BroadcastDispatcher
@@ -78,8 +77,9 @@
     @Before
     fun setup() {
         MockitoAnnotations.initMocks(this)
-        mediaDataFilter = MediaDataFilter(combineLatest, broadcastDispatcher, mediaResumeListener,
-            mediaDataManager, lockscreenUserManager, executor)
+        mediaDataFilter = MediaDataFilter(broadcastDispatcher, mediaResumeListener,
+                lockscreenUserManager, executor)
+        mediaDataFilter.mediaDataManager = mediaDataManager
         mediaDataFilter.addListener(listener)
 
         // Start all tests as main user
@@ -152,8 +152,9 @@
     @Test
     fun testOnUserSwitched_addsNewUserControls() {
         // GIVEN that we had some media for both users
-        val dataMap = mapOf(KEY to dataMain, KEY_ALT to dataGuest)
-        `when`(combineLatest.getData()).thenReturn(dataMap)
+        mediaDataFilter.onMediaDataLoaded(KEY, null, dataMain)
+        mediaDataFilter.onMediaDataLoaded(KEY_ALT, null, dataGuest)
+        reset(listener)
 
         // and we switch to guest user
         setUser(USER_GUEST)
@@ -213,4 +214,4 @@
 
         verify(mediaDataManager).setTimedOut(eq(KEY), eq(true))
     }
-}
\ No newline at end of file
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataManagerTest.kt
index 59c2d0e..84c1bf9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataManagerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataManagerTest.kt
@@ -13,8 +13,10 @@
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.broadcast.BroadcastDispatcher
 import com.android.systemui.dump.DumpManager
+import com.android.systemui.plugins.ActivityStarter
 import com.android.systemui.statusbar.SbnBuilder
 import com.android.systemui.util.concurrency.FakeExecutor
+import com.android.systemui.util.mockito.capture
 import com.android.systemui.util.mockito.eq
 import com.android.systemui.util.time.FakeSystemClock
 import com.google.common.truth.Truth.assertThat
@@ -23,9 +25,13 @@
 import org.junit.Rule
 import org.junit.Test
 import org.junit.runner.RunWith
+import org.mockito.ArgumentCaptor
+import org.mockito.Captor
 import org.mockito.Mock
 import org.mockito.Mockito
 import org.mockito.Mockito.mock
+import org.mockito.Mockito.never
+import org.mockito.Mockito.reset
 import org.mockito.Mockito.verify
 import org.mockito.junit.MockitoJUnit
 import org.mockito.Mockito.`when` as whenever
@@ -47,6 +53,7 @@
 @RunWith(AndroidTestingRunner::class)
 class MediaDataManagerTest : SysuiTestCase() {
 
+    @JvmField @Rule val mockito = MockitoJUnit.rule()
     @Mock lateinit var mediaControllerFactory: MediaControllerFactory
     @Mock lateinit var controller: MediaController
     lateinit var session: MediaSession
@@ -57,19 +64,38 @@
     @Mock lateinit var broadcastDispatcher: BroadcastDispatcher
     @Mock lateinit var mediaTimeoutListener: MediaTimeoutListener
     @Mock lateinit var mediaResumeListener: MediaResumeListener
+    @Mock lateinit var mediaSessionBasedFilter: MediaSessionBasedFilter
+    @Mock lateinit var mediaDeviceManager: MediaDeviceManager
+    @Mock lateinit var mediaDataCombineLatest: MediaDataCombineLatest
+    @Mock lateinit var mediaDataFilter: MediaDataFilter
+    @Mock lateinit var listener: MediaDataManager.Listener
     @Mock lateinit var pendingIntent: PendingIntent
-    @JvmField @Rule val mockito = MockitoJUnit.rule()
+    @Mock lateinit var activityStarter: ActivityStarter
     lateinit var mediaDataManager: MediaDataManager
     lateinit var mediaNotification: StatusBarNotification
+    @Captor lateinit var mediaDataCaptor: ArgumentCaptor<MediaData>
 
     @Before
     fun setup() {
         foregroundExecutor = FakeExecutor(FakeSystemClock())
         backgroundExecutor = FakeExecutor(FakeSystemClock())
-        mediaDataManager = MediaDataManager(context, backgroundExecutor, foregroundExecutor,
-                mediaControllerFactory, broadcastDispatcher, dumpManager,
-                mediaTimeoutListener, mediaResumeListener, useMediaResumption = true,
-                useQsMediaPlayer = true)
+        mediaDataManager = MediaDataManager(
+            context = context,
+            backgroundExecutor = backgroundExecutor,
+            foregroundExecutor = foregroundExecutor,
+            mediaControllerFactory = mediaControllerFactory,
+            broadcastDispatcher = broadcastDispatcher,
+            dumpManager = dumpManager,
+            mediaTimeoutListener = mediaTimeoutListener,
+            mediaResumeListener = mediaResumeListener,
+            mediaSessionBasedFilter = mediaSessionBasedFilter,
+            mediaDeviceManager = mediaDeviceManager,
+            mediaDataCombineLatest = mediaDataCombineLatest,
+            mediaDataFilter = mediaDataFilter,
+            activityStarter = activityStarter,
+            useMediaResumption = true,
+            useQsMediaPlayer = true
+        )
         session = MediaSession(context, "MediaDataManagerTestSession")
         mediaNotification = SbnBuilder().run {
             setPkg(PACKAGE_NAME)
@@ -84,6 +110,12 @@
             putString(MediaMetadata.METADATA_KEY_TITLE, SESSION_TITLE)
         }
         whenever(mediaControllerFactory.create(eq(session.sessionToken))).thenReturn(controller)
+
+        // This is an ugly hack for now. The mediaSessionBasedFilter is one of the internal
+        // listeners in the internal processing pipeline. It receives events, but ince it is a
+        // mock, it doesn't pass those events along the chain to the external listeners. So, just
+        // treat mediaSessionBasedFilter as a listener for testing.
+        listener = mediaSessionBasedFilter
     }
 
     @After
@@ -113,8 +145,6 @@
 
     @Test
     fun testOnMetaDataLoaded_callsListener() {
-        val listener = mock(MediaDataManager.Listener::class.java)
-        mediaDataManager.addListener(listener)
         mediaDataManager.onNotificationAdded(KEY, mediaNotification)
         mediaDataManager.onMediaDataLoaded(KEY, oldKey = null, data = mock(MediaData::class.java))
         verify(listener).onMediaDataLoaded(eq(KEY), eq(null), anyObject())
@@ -122,84 +152,123 @@
 
     @Test
     fun testOnMetaDataLoaded_conservesActiveFlag() {
-        val listener = TestListener()
         whenever(mediaControllerFactory.create(anyObject())).thenReturn(controller)
         whenever(controller.metadata).thenReturn(metadataBuilder.build())
         mediaDataManager.addListener(listener)
         mediaDataManager.onNotificationAdded(KEY, mediaNotification)
         assertThat(backgroundExecutor.runAllReady()).isEqualTo(1)
         assertThat(foregroundExecutor.runAllReady()).isEqualTo(1)
-        assertThat(listener.data!!.active).isTrue()
+        verify(listener).onMediaDataLoaded(eq(KEY), eq(null), capture(mediaDataCaptor))
+        assertThat(mediaDataCaptor.value!!.active).isTrue()
     }
 
     @Test
     fun testOnNotificationRemoved_callsListener() {
-        val listener = mock(MediaDataManager.Listener::class.java)
-        mediaDataManager.addListener(listener)
         mediaDataManager.onNotificationAdded(KEY, mediaNotification)
         mediaDataManager.onMediaDataLoaded(KEY, oldKey = null, data = mock(MediaData::class.java))
         mediaDataManager.onNotificationRemoved(KEY)
-
         verify(listener).onMediaDataRemoved(eq(KEY))
     }
 
     @Test
     fun testOnNotificationRemoved_withResumption() {
         // GIVEN that the manager has a notification with a resume action
-        val listener = TestListener()
-        mediaDataManager.addListener(listener)
         whenever(controller.metadata).thenReturn(metadataBuilder.build())
         mediaDataManager.onNotificationAdded(KEY, mediaNotification)
         assertThat(backgroundExecutor.runAllReady()).isEqualTo(1)
         assertThat(foregroundExecutor.runAllReady()).isEqualTo(1)
-        val data = listener.data!!
+        verify(listener).onMediaDataLoaded(eq(KEY), eq(null), capture(mediaDataCaptor))
+        val data = mediaDataCaptor.value
         assertThat(data.resumption).isFalse()
         mediaDataManager.onMediaDataLoaded(KEY, null, data.copy(resumeAction = Runnable {}))
         // WHEN the notification is removed
         mediaDataManager.onNotificationRemoved(KEY)
         // THEN the media data indicates that it is for resumption
-        assertThat(listener.data!!.resumption).isTrue()
-        // AND the new key is the package name
-        assertThat(listener.key!!).isEqualTo(PACKAGE_NAME)
-        assertThat(listener.oldKey!!).isEqualTo(KEY)
-        assertThat(listener.removedKey).isNull()
+        verify(listener).onMediaDataLoaded(eq(PACKAGE_NAME), eq(KEY), capture(mediaDataCaptor))
+        assertThat(mediaDataCaptor.value.resumption).isTrue()
     }
 
     @Test
     fun testOnNotificationRemoved_twoWithResumption() {
         // GIVEN that the manager has two notifications with resume actions
-        val listener = TestListener()
-        mediaDataManager.addListener(listener)
         whenever(controller.metadata).thenReturn(metadataBuilder.build())
         mediaDataManager.onNotificationAdded(KEY, mediaNotification)
         mediaDataManager.onNotificationAdded(KEY_2, mediaNotification)
         assertThat(backgroundExecutor.runAllReady()).isEqualTo(2)
         assertThat(foregroundExecutor.runAllReady()).isEqualTo(2)
-        val data = listener.data!!
+        verify(listener).onMediaDataLoaded(eq(KEY), eq(null), capture(mediaDataCaptor))
+        val data = mediaDataCaptor.value
         assertThat(data.resumption).isFalse()
         val resumableData = data.copy(resumeAction = Runnable {})
         mediaDataManager.onMediaDataLoaded(KEY, null, resumableData)
         mediaDataManager.onMediaDataLoaded(KEY_2, null, resumableData)
+        reset(listener)
         // WHEN the first is removed
         mediaDataManager.onNotificationRemoved(KEY)
         // THEN the data is for resumption and the key is migrated to the package name
-        assertThat(listener.data!!.resumption).isTrue()
-        assertThat(listener.key!!).isEqualTo(PACKAGE_NAME)
-        assertThat(listener.oldKey!!).isEqualTo(KEY)
-        assertThat(listener.removedKey).isNull()
+        verify(listener).onMediaDataLoaded(eq(PACKAGE_NAME), eq(KEY), capture(mediaDataCaptor))
+        assertThat(mediaDataCaptor.value.resumption).isTrue()
+        verify(listener, never()).onMediaDataRemoved(eq(KEY))
         // WHEN the second is removed
         mediaDataManager.onNotificationRemoved(KEY_2)
         // THEN the data is for resumption and the second key is removed
-        assertThat(listener.data!!.resumption).isTrue()
-        assertThat(listener.key!!).isEqualTo(PACKAGE_NAME)
-        assertThat(listener.oldKey!!).isEqualTo(PACKAGE_NAME)
-        assertThat(listener.removedKey!!).isEqualTo(KEY_2)
+        verify(listener).onMediaDataLoaded(eq(PACKAGE_NAME), eq(PACKAGE_NAME),
+                capture(mediaDataCaptor))
+        assertThat(mediaDataCaptor.value.resumption).isTrue()
+        verify(listener).onMediaDataRemoved(eq(KEY_2))
+    }
+
+    @Test
+    fun testAppBlockedFromResumption() {
+        // GIVEN that the manager has a notification with a resume action
+        whenever(controller.metadata).thenReturn(metadataBuilder.build())
+        mediaDataManager.onNotificationAdded(KEY, mediaNotification)
+        assertThat(backgroundExecutor.runAllReady()).isEqualTo(1)
+        assertThat(foregroundExecutor.runAllReady()).isEqualTo(1)
+        verify(listener).onMediaDataLoaded(eq(KEY), eq(null), capture(mediaDataCaptor))
+        val data = mediaDataCaptor.value
+        assertThat(data.resumption).isFalse()
+        mediaDataManager.onMediaDataLoaded(KEY, null, data.copy(resumeAction = Runnable {}))
+
+        // and the manager should block the package from creating resume controls
+        val blocked = mutableSetOf(PACKAGE_NAME, "com.example.app")
+        mediaDataManager.appsBlockedFromResume = blocked
+
+        // WHEN the notification is removed
+        mediaDataManager.onNotificationRemoved(KEY)
+
+        // THEN the media data is removed
+        verify(listener).onMediaDataRemoved(eq(KEY))
+    }
+
+    @Test
+    fun testAppUnblockedFromResumption() {
+        // GIVEN that an app was blocked from resuming
+        val blocked = mutableSetOf(PACKAGE_NAME, "com.example.app")
+        mediaDataManager.appsBlockedFromResume = blocked
+
+        // and GIVEN that the manager has a notification from that app with a resume action
+        whenever(controller.metadata).thenReturn(metadataBuilder.build())
+        mediaDataManager.onNotificationAdded(KEY, mediaNotification)
+        assertThat(backgroundExecutor.runAllReady()).isEqualTo(1)
+        assertThat(foregroundExecutor.runAllReady()).isEqualTo(1)
+        verify(listener).onMediaDataLoaded(eq(KEY), eq(null), capture(mediaDataCaptor))
+        val data = mediaDataCaptor.value
+        assertThat(data.resumption).isFalse()
+        mediaDataManager.onMediaDataLoaded(KEY, null, data.copy(resumeAction = Runnable {}))
+
+        // WHEN the app is unblocked
+        mediaDataManager.appsBlockedFromResume = mutableSetOf("com.example.app")
+
+        // and the notification is removed
+        mediaDataManager.onNotificationRemoved(KEY)
+
+        // THEN the entry will stay as a resume control
+        verify(listener).onMediaDataLoaded(eq(PACKAGE_NAME), eq(KEY), capture(mediaDataCaptor))
     }
 
     @Test
     fun testAddResumptionControls() {
-        val listener = TestListener()
-        mediaDataManager.addListener(listener)
         // WHEN resumption controls are added`
         val desc = MediaDescription.Builder().run {
             setTitle(SESSION_TITLE)
@@ -210,32 +279,23 @@
         assertThat(backgroundExecutor.runAllReady()).isEqualTo(1)
         assertThat(foregroundExecutor.runAllReady()).isEqualTo(1)
         // THEN the media data indicates that it is for resumption
-        val data = listener.data!!
+        verify(listener).onMediaDataLoaded(eq(PACKAGE_NAME), eq(null), capture(mediaDataCaptor))
+        val data = mediaDataCaptor.value
         assertThat(data.resumption).isTrue()
         assertThat(data.song).isEqualTo(SESSION_TITLE)
         assertThat(data.app).isEqualTo(APP_NAME)
         assertThat(data.actions).hasSize(1)
     }
 
-    /**
-     * Simple implementation of [MediaDataManager.Listener] for the test.
-     *
-     * Giving up on trying to get a mock Listener and ArgumentCaptor to work.
-     */
-    private class TestListener : MediaDataManager.Listener {
-        var data: MediaData? = null
-        var key: String? = null
-        var oldKey: String? = null
-        var removedKey: String? = null
+    @Test
+    fun testDismissMedia_listenerCalled() {
+        mediaDataManager.onNotificationAdded(KEY, mediaNotification)
+        mediaDataManager.onMediaDataLoaded(KEY, oldKey = null, data = mock(MediaData::class.java))
+        mediaDataManager.dismissMediaData(KEY, 0L)
 
-        override fun onMediaDataLoaded(key: String, oldKey: String?, data: MediaData) {
-            this.key = key
-            this.oldKey = oldKey
-            this.data = data
-        }
+        foregroundExecutor.advanceClockToLast()
+        foregroundExecutor.runAllReady()
 
-        override fun onMediaDataRemoved(key: String) {
-            removedKey = key
-        }
+        verify(listener).onMediaDataRemoved(eq(KEY))
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/MediaDeviceManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/MediaDeviceManagerTest.kt
index 7bc15dd..fdb432c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/MediaDeviceManagerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/MediaDeviceManagerTest.kt
@@ -68,7 +68,6 @@
 public class MediaDeviceManagerTest : SysuiTestCase() {
 
     private lateinit var manager: MediaDeviceManager
-    @Mock private lateinit var mediaDataManager: MediaDataManager
     @Mock private lateinit var lmmFactory: LocalMediaManagerFactory
     @Mock private lateinit var lmm: LocalMediaManager
     @Mock private lateinit var mr2: MediaRouter2Manager
@@ -91,7 +90,7 @@
         fakeFgExecutor = FakeExecutor(FakeSystemClock())
         fakeBgExecutor = FakeExecutor(FakeSystemClock())
         manager = MediaDeviceManager(context, lmmFactory, mr2, fakeFgExecutor, fakeBgExecutor,
-                mediaDataManager, dumpster)
+                dumpster)
         manager.addListener(listener)
 
         // Configure mocks.
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/MediaHierarchyManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/MediaHierarchyManagerTest.kt
index 91c5ff8..d86dfa5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/MediaHierarchyManagerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/MediaHierarchyManagerTest.kt
@@ -142,4 +142,11 @@
         verify(mediaCarouselController).onDesiredLocationChanged(ArgumentMatchers.anyInt(),
                 any(MediaHostState::class.java), anyBoolean(), anyLong(), anyLong())
     }
+
+    @Test
+    fun testCloseGutsRelayToCarousel() {
+        mediaHiearchyManager.closeGuts()
+
+        verify(mediaCarouselController).closeGuts()
+    }
 }
\ No newline at end of file
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/MediaPlayerDataTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/MediaPlayerDataTest.kt
new file mode 100644
index 0000000..da1ec98
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/MediaPlayerDataTest.kt
@@ -0,0 +1,124 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.media
+
+import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Ignore
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mockito.mock
+import org.mockito.Mockito.`when` as whenever
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+public class MediaPlayerDataTest : SysuiTestCase() {
+
+    companion object {
+        val LOCAL = true
+        val RESUMPTION = true
+    }
+
+    @Before
+    fun setup() {
+        MediaPlayerData.clear()
+    }
+
+    @Test
+    fun addPlayingThenRemote() {
+        val playerIsPlaying = mock(MediaControlPanel::class.java)
+        whenever(playerIsPlaying.isPlaying).thenReturn(true)
+        val dataIsPlaying = createMediaData("app1", LOCAL, !RESUMPTION)
+
+        val playerIsRemote = mock(MediaControlPanel::class.java)
+        whenever(playerIsRemote.isPlaying).thenReturn(false)
+        val dataIsRemote = createMediaData("app2", !LOCAL, !RESUMPTION)
+
+        MediaPlayerData.addMediaPlayer("1", dataIsPlaying, playerIsPlaying)
+        MediaPlayerData.addMediaPlayer("2", dataIsRemote, playerIsRemote)
+
+        val players = MediaPlayerData.players()
+        assertThat(players).hasSize(2)
+        assertThat(players).containsExactly(playerIsPlaying, playerIsRemote).inOrder()
+    }
+
+    @Test
+    @Ignore("Flaky")
+    fun switchPlayersPlaying() {
+        val playerIsPlaying1 = mock(MediaControlPanel::class.java)
+        whenever(playerIsPlaying1.isPlaying).thenReturn(true)
+        val dataIsPlaying1 = createMediaData("app1", LOCAL, !RESUMPTION)
+
+        val playerIsPlaying2 = mock(MediaControlPanel::class.java)
+        whenever(playerIsPlaying2.isPlaying).thenReturn(false)
+        val dataIsPlaying2 = createMediaData("app2", LOCAL, !RESUMPTION)
+
+        MediaPlayerData.addMediaPlayer("1", dataIsPlaying1, playerIsPlaying1)
+        MediaPlayerData.addMediaPlayer("2", dataIsPlaying2, playerIsPlaying2)
+
+        whenever(playerIsPlaying1.isPlaying).thenReturn(false)
+        whenever(playerIsPlaying2.isPlaying).thenReturn(true)
+
+        MediaPlayerData.addMediaPlayer("1", dataIsPlaying1, playerIsPlaying1)
+        MediaPlayerData.addMediaPlayer("2", dataIsPlaying2, playerIsPlaying2)
+
+        val players = MediaPlayerData.players()
+        assertThat(players).hasSize(2)
+        assertThat(players).containsExactly(playerIsPlaying2, playerIsPlaying1).inOrder()
+    }
+
+    @Test
+    fun fullOrderTest() {
+        val playerIsPlaying = mock(MediaControlPanel::class.java)
+        whenever(playerIsPlaying.isPlaying).thenReturn(true)
+        val dataIsPlaying = createMediaData("app1", LOCAL, !RESUMPTION)
+
+        val playerIsPlayingAndRemote = mock(MediaControlPanel::class.java)
+        whenever(playerIsPlayingAndRemote.isPlaying).thenReturn(true)
+        val dataIsPlayingAndRemote = createMediaData("app2", !LOCAL, !RESUMPTION)
+
+        val playerIsStoppedAndLocal = mock(MediaControlPanel::class.java)
+        whenever(playerIsStoppedAndLocal.isPlaying).thenReturn(false)
+        val dataIsStoppedAndLocal = createMediaData("app3", LOCAL, !RESUMPTION)
+
+        val playerIsStoppedAndRemote = mock(MediaControlPanel::class.java)
+        whenever(playerIsStoppedAndLocal.isPlaying).thenReturn(false)
+        val dataIsStoppedAndRemote = createMediaData("app4", !LOCAL, !RESUMPTION)
+
+        val playerCanResume = mock(MediaControlPanel::class.java)
+        whenever(playerCanResume.isPlaying).thenReturn(false)
+        val dataCanResume = createMediaData("app5", LOCAL, RESUMPTION)
+
+        MediaPlayerData.addMediaPlayer("3", dataIsStoppedAndLocal, playerIsStoppedAndLocal)
+        MediaPlayerData.addMediaPlayer("5", dataIsStoppedAndRemote, playerIsStoppedAndRemote)
+        MediaPlayerData.addMediaPlayer("4", dataCanResume, playerCanResume)
+        MediaPlayerData.addMediaPlayer("1", dataIsPlaying, playerIsPlaying)
+        MediaPlayerData.addMediaPlayer("2", dataIsPlayingAndRemote, playerIsPlayingAndRemote)
+
+        val players = MediaPlayerData.players()
+        assertThat(players).hasSize(5)
+        assertThat(players).containsExactly(playerIsPlaying, playerIsPlayingAndRemote,
+            playerIsStoppedAndLocal, playerCanResume, playerIsStoppedAndRemote).inOrder()
+    }
+
+    private fun createMediaData(app: String, isLocalSession: Boolean, resumption: Boolean) =
+        MediaData(0, false, 0, app, null, null, null, null, emptyList(), emptyList<Int>(), "",
+            null, null, null, true, null, isLocalSession, resumption, null, false)
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/MediaSessionBasedFilterTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/MediaSessionBasedFilterTest.kt
new file mode 100644
index 0000000..887cc77
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/MediaSessionBasedFilterTest.kt
@@ -0,0 +1,383 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.media
+
+import android.graphics.Color
+import android.media.session.MediaController
+import android.media.session.MediaController.PlaybackInfo
+import android.media.session.MediaSession
+import android.media.session.MediaSessionManager
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper
+import androidx.test.filters.SmallTest
+
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.util.concurrency.FakeExecutor
+import com.android.systemui.util.mockito.eq
+import com.android.systemui.util.time.FakeSystemClock
+
+import org.junit.After
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentCaptor
+import org.mockito.Mock
+import org.mockito.Mockito
+import org.mockito.Mockito.any
+import org.mockito.Mockito.never
+import org.mockito.Mockito.reset
+import org.mockito.Mockito.verify
+import org.mockito.junit.MockitoJUnit
+import org.mockito.Mockito.`when` as whenever
+
+private const val PACKAGE = "PKG"
+private const val KEY = "TEST_KEY"
+private const val NOTIF_KEY = "TEST_KEY"
+private const val SESSION_ARTIST = "SESSION_ARTIST"
+private const val SESSION_TITLE = "SESSION_TITLE"
+private const val APP_NAME = "APP_NAME"
+private const val USER_ID = 0
+
+private val info = MediaData(
+    userId = USER_ID,
+    initialized = true,
+    backgroundColor = Color.DKGRAY,
+    app = APP_NAME,
+    appIcon = null,
+    artist = SESSION_ARTIST,
+    song = SESSION_TITLE,
+    artwork = null,
+    actions = emptyList(),
+    actionsToShowInCompact = emptyList(),
+    packageName = PACKAGE,
+    token = null,
+    clickIntent = null,
+    device = null,
+    active = true,
+    resumeAction = null,
+    resumption = false,
+    notificationKey = NOTIF_KEY,
+    hasCheckedForResume = false
+)
+
+private fun <T> eq(value: T): T = Mockito.eq(value) ?: value
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+@TestableLooper.RunWithLooper
+public class MediaSessionBasedFilterTest : SysuiTestCase() {
+
+    @JvmField @Rule val mockito = MockitoJUnit.rule()
+
+    // Unit to be tested
+    private lateinit var filter: MediaSessionBasedFilter
+
+    private lateinit var sessionListener: MediaSessionManager.OnActiveSessionsChangedListener
+    @Mock private lateinit var mediaListener: MediaDataManager.Listener
+
+    // MediaSessionBasedFilter dependencies
+    @Mock private lateinit var mediaSessionManager: MediaSessionManager
+    private lateinit var fgExecutor: FakeExecutor
+    private lateinit var bgExecutor: FakeExecutor
+
+    @Mock private lateinit var controller1: MediaController
+    @Mock private lateinit var controller2: MediaController
+    @Mock private lateinit var controller3: MediaController
+    @Mock private lateinit var controller4: MediaController
+
+    private lateinit var token1: MediaSession.Token
+    private lateinit var token2: MediaSession.Token
+    private lateinit var token3: MediaSession.Token
+    private lateinit var token4: MediaSession.Token
+
+    @Mock private lateinit var remotePlaybackInfo: PlaybackInfo
+    @Mock private lateinit var localPlaybackInfo: PlaybackInfo
+
+    private lateinit var session1: MediaSession
+    private lateinit var session2: MediaSession
+    private lateinit var session3: MediaSession
+    private lateinit var session4: MediaSession
+
+    private lateinit var mediaData1: MediaData
+    private lateinit var mediaData2: MediaData
+    private lateinit var mediaData3: MediaData
+    private lateinit var mediaData4: MediaData
+
+    @Before
+    fun setUp() {
+        fgExecutor = FakeExecutor(FakeSystemClock())
+        bgExecutor = FakeExecutor(FakeSystemClock())
+        filter = MediaSessionBasedFilter(context, mediaSessionManager, fgExecutor, bgExecutor)
+
+        // Configure mocks.
+        whenever(mediaSessionManager.getActiveSessions(any())).thenReturn(emptyList())
+
+        session1 = MediaSession(context, "MediaSessionBasedFilter1")
+        session2 = MediaSession(context, "MediaSessionBasedFilter2")
+        session3 = MediaSession(context, "MediaSessionBasedFilter3")
+        session4 = MediaSession(context, "MediaSessionBasedFilter4")
+
+        token1 = session1.sessionToken
+        token2 = session2.sessionToken
+        token3 = session3.sessionToken
+        token4 = session4.sessionToken
+
+        whenever(controller1.getSessionToken()).thenReturn(token1)
+        whenever(controller2.getSessionToken()).thenReturn(token2)
+        whenever(controller3.getSessionToken()).thenReturn(token3)
+        whenever(controller4.getSessionToken()).thenReturn(token4)
+
+        whenever(controller1.getPackageName()).thenReturn(PACKAGE)
+        whenever(controller2.getPackageName()).thenReturn(PACKAGE)
+        whenever(controller3.getPackageName()).thenReturn(PACKAGE)
+        whenever(controller4.getPackageName()).thenReturn(PACKAGE)
+
+        mediaData1 = info.copy(token = token1)
+        mediaData2 = info.copy(token = token2)
+        mediaData3 = info.copy(token = token3)
+        mediaData4 = info.copy(token = token4)
+
+        whenever(remotePlaybackInfo.getPlaybackType()).thenReturn(PlaybackInfo.PLAYBACK_TYPE_REMOTE)
+        whenever(localPlaybackInfo.getPlaybackType()).thenReturn(PlaybackInfo.PLAYBACK_TYPE_LOCAL)
+
+        whenever(controller1.getPlaybackInfo()).thenReturn(localPlaybackInfo)
+        whenever(controller2.getPlaybackInfo()).thenReturn(localPlaybackInfo)
+        whenever(controller3.getPlaybackInfo()).thenReturn(localPlaybackInfo)
+        whenever(controller4.getPlaybackInfo()).thenReturn(localPlaybackInfo)
+
+        // Capture listener
+        bgExecutor.runAllReady()
+        val listenerCaptor = ArgumentCaptor.forClass(
+                MediaSessionManager.OnActiveSessionsChangedListener::class.java)
+        verify(mediaSessionManager).addOnActiveSessionsChangedListener(
+                listenerCaptor.capture(), any())
+        sessionListener = listenerCaptor.value
+
+        filter.addListener(mediaListener)
+    }
+
+    @After
+    fun tearDown() {
+        session1.release()
+        session2.release()
+        session3.release()
+        session4.release()
+    }
+
+    @Test
+    fun noMediaSession_loadedEventNotFiltered() {
+        filter.onMediaDataLoaded(KEY, null, mediaData1)
+        bgExecutor.runAllReady()
+        fgExecutor.runAllReady()
+        verify(mediaListener).onMediaDataLoaded(eq(KEY), eq(null), eq(mediaData1))
+    }
+
+    @Test
+    fun noMediaSession_removedEventNotFiltered() {
+        filter.onMediaDataRemoved(KEY)
+        bgExecutor.runAllReady()
+        fgExecutor.runAllReady()
+        verify(mediaListener).onMediaDataRemoved(eq(KEY))
+    }
+
+    @Test
+    fun matchingMediaSession_loadedEventNotFiltered() {
+        // GIVEN an active session
+        val controllers = listOf(controller1)
+        whenever(mediaSessionManager.getActiveSessions(any())).thenReturn(controllers)
+        sessionListener.onActiveSessionsChanged(controllers)
+        // WHEN a loaded event is received that matches the session
+        filter.onMediaDataLoaded(KEY, null, mediaData1)
+        bgExecutor.runAllReady()
+        fgExecutor.runAllReady()
+        // THEN the event is not filtered
+        verify(mediaListener).onMediaDataLoaded(eq(KEY), eq(null), eq(mediaData1))
+    }
+
+    @Test
+    fun matchingMediaSession_removedEventNotFiltered() {
+        // GIVEN an active session
+        val controllers = listOf(controller1)
+        whenever(mediaSessionManager.getActiveSessions(any())).thenReturn(controllers)
+        sessionListener.onActiveSessionsChanged(controllers)
+        // WHEN a removed event is received
+        filter.onMediaDataRemoved(KEY)
+        bgExecutor.runAllReady()
+        fgExecutor.runAllReady()
+        // THEN the event is not filtered
+        verify(mediaListener).onMediaDataRemoved(eq(KEY))
+    }
+
+    @Test
+    fun remoteSession_loadedEventNotFiltered() {
+        // GIVEN a remove session
+        whenever(controller1.getPlaybackInfo()).thenReturn(remotePlaybackInfo)
+        val controllers = listOf(controller1)
+        whenever(mediaSessionManager.getActiveSessions(any())).thenReturn(controllers)
+        sessionListener.onActiveSessionsChanged(controllers)
+        // WHEN a loaded event is received that matche the session
+        filter.onMediaDataLoaded(KEY, null, mediaData1)
+        bgExecutor.runAllReady()
+        fgExecutor.runAllReady()
+        // THEN the event is not filtered
+        verify(mediaListener).onMediaDataLoaded(eq(KEY), eq(null), eq(mediaData1))
+    }
+
+    @Test
+    fun remoteAndLocalSessions_localLoadedEventFiltered() {
+        // GIVEN remote and local sessions
+        whenever(controller1.getPlaybackInfo()).thenReturn(remotePlaybackInfo)
+        val controllers = listOf(controller1, controller2)
+        whenever(mediaSessionManager.getActiveSessions(any())).thenReturn(controllers)
+        sessionListener.onActiveSessionsChanged(controllers)
+        // WHEN a loaded event is received that matches the remote session
+        filter.onMediaDataLoaded(KEY, null, mediaData1)
+        bgExecutor.runAllReady()
+        fgExecutor.runAllReady()
+        // THEN the event is not filtered
+        verify(mediaListener).onMediaDataLoaded(eq(KEY), eq(null), eq(mediaData1))
+        // WHEN a loaded event is received that matches the local session
+        filter.onMediaDataLoaded(KEY, null, mediaData2)
+        bgExecutor.runAllReady()
+        fgExecutor.runAllReady()
+        // THEN the event is filtered
+        verify(mediaListener, never()).onMediaDataLoaded(eq(KEY), eq(null), eq(mediaData2))
+    }
+
+    @Test
+    fun remoteAndLocalHaveDifferentKeys_localLoadedEventFiltered() {
+        // GIVEN remote and local sessions
+        val key1 = "KEY_1"
+        val key2 = "KEY_2"
+        whenever(controller1.getPlaybackInfo()).thenReturn(remotePlaybackInfo)
+        val controllers = listOf(controller1, controller2)
+        whenever(mediaSessionManager.getActiveSessions(any())).thenReturn(controllers)
+        sessionListener.onActiveSessionsChanged(controllers)
+        // WHEN a loaded event is received that matches the remote session
+        filter.onMediaDataLoaded(key1, null, mediaData1)
+        bgExecutor.runAllReady()
+        fgExecutor.runAllReady()
+        // THEN the event is not filtered
+        verify(mediaListener).onMediaDataLoaded(eq(key1), eq(null), eq(mediaData1))
+        // WHEN a loaded event is received that matches the local session
+        filter.onMediaDataLoaded(key2, null, mediaData2)
+        bgExecutor.runAllReady()
+        fgExecutor.runAllReady()
+        // THEN the event is filtered
+        verify(mediaListener, never()).onMediaDataLoaded(eq(key2), eq(null), eq(mediaData2))
+        // AND there should be a removed event for key2
+        verify(mediaListener).onMediaDataRemoved(eq(key2))
+    }
+
+    @Test
+    fun multipleRemoteSessions_loadedEventNotFiltered() {
+        // GIVEN two remote sessions
+        whenever(controller1.getPlaybackInfo()).thenReturn(remotePlaybackInfo)
+        whenever(controller2.getPlaybackInfo()).thenReturn(remotePlaybackInfo)
+        val controllers = listOf(controller1, controller2)
+        whenever(mediaSessionManager.getActiveSessions(any())).thenReturn(controllers)
+        sessionListener.onActiveSessionsChanged(controllers)
+        // WHEN a loaded event is received that matches the remote session
+        filter.onMediaDataLoaded(KEY, null, mediaData1)
+        bgExecutor.runAllReady()
+        fgExecutor.runAllReady()
+        // THEN the event is not filtered
+        verify(mediaListener).onMediaDataLoaded(eq(KEY), eq(null), eq(mediaData1))
+        // WHEN a loaded event is received that matches the local session
+        filter.onMediaDataLoaded(KEY, null, mediaData2)
+        bgExecutor.runAllReady()
+        fgExecutor.runAllReady()
+        // THEN the event is not filtered
+        verify(mediaListener).onMediaDataLoaded(eq(KEY), eq(null), eq(mediaData2))
+    }
+
+    @Test
+    fun multipleOtherSessions_loadedEventNotFiltered() {
+        // GIVEN multiple active sessions from other packages
+        val controllers = listOf(controller1, controller2, controller3, controller4)
+        whenever(controller1.getPackageName()).thenReturn("PKG_1")
+        whenever(controller2.getPackageName()).thenReturn("PKG_2")
+        whenever(controller3.getPackageName()).thenReturn("PKG_3")
+        whenever(controller4.getPackageName()).thenReturn("PKG_4")
+        whenever(mediaSessionManager.getActiveSessions(any())).thenReturn(controllers)
+        sessionListener.onActiveSessionsChanged(controllers)
+        // WHEN a loaded event is received
+        filter.onMediaDataLoaded(KEY, null, mediaData1)
+        bgExecutor.runAllReady()
+        fgExecutor.runAllReady()
+        // THEN the event is not filtered
+        verify(mediaListener).onMediaDataLoaded(eq(KEY), eq(null), eq(mediaData1))
+    }
+
+    @Test
+    fun doNotFilterDuringKeyMigration() {
+        val key1 = "KEY_1"
+        val key2 = "KEY_2"
+        // GIVEN a loaded event
+        filter.onMediaDataLoaded(key1, null, mediaData2)
+        bgExecutor.runAllReady()
+        fgExecutor.runAllReady()
+        reset(mediaListener)
+        // GIVEN remote and local sessions
+        whenever(controller1.getPlaybackInfo()).thenReturn(remotePlaybackInfo)
+        val controllers = listOf(controller1, controller2)
+        whenever(mediaSessionManager.getActiveSessions(any())).thenReturn(controllers)
+        sessionListener.onActiveSessionsChanged(controllers)
+        // WHEN a loaded event is received that matches the local session but it is a key migration
+        filter.onMediaDataLoaded(key2, key1, mediaData2)
+        bgExecutor.runAllReady()
+        fgExecutor.runAllReady()
+        // THEN the key migration event is fired
+        verify(mediaListener).onMediaDataLoaded(eq(key2), eq(key1), eq(mediaData2))
+    }
+
+    @Test
+    fun filterAfterKeyMigration() {
+        val key1 = "KEY_1"
+        val key2 = "KEY_2"
+        // GIVEN a loaded event
+        filter.onMediaDataLoaded(key1, null, mediaData1)
+        filter.onMediaDataLoaded(key1, null, mediaData2)
+        bgExecutor.runAllReady()
+        fgExecutor.runAllReady()
+        reset(mediaListener)
+        // GIVEN remote and local sessions
+        whenever(controller1.getPlaybackInfo()).thenReturn(remotePlaybackInfo)
+        val controllers = listOf(controller1, controller2)
+        whenever(mediaSessionManager.getActiveSessions(any())).thenReturn(controllers)
+        sessionListener.onActiveSessionsChanged(controllers)
+        // GIVEN that the keys have been migrated
+        filter.onMediaDataLoaded(key2, key1, mediaData1)
+        filter.onMediaDataLoaded(key2, key1, mediaData2)
+        bgExecutor.runAllReady()
+        fgExecutor.runAllReady()
+        reset(mediaListener)
+        // WHEN a loaded event is received that matches the local session
+        filter.onMediaDataLoaded(key2, null, mediaData2)
+        bgExecutor.runAllReady()
+        fgExecutor.runAllReady()
+        // THEN the key migration event is filtered
+        verify(mediaListener, never()).onMediaDataLoaded(eq(key2), eq(null), eq(mediaData2))
+        // WHEN a loaded event is received that matches the remote session
+        filter.onMediaDataLoaded(key2, null, mediaData1)
+        bgExecutor.runAllReady()
+        fgExecutor.runAllReady()
+        // THEN the key migration event is fired
+        verify(mediaListener).onMediaDataLoaded(eq(key2), eq(null), eq(mediaData1))
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/MediaTimeoutListenerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/MediaTimeoutListenerTest.kt
index 7a8e4f7..f385243 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/MediaTimeoutListenerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/MediaTimeoutListenerTest.kt
@@ -155,6 +155,22 @@
     }
 
     @Test
+    fun testOnMediaDataLoaded_migratesKeys_noTimeoutExtension() {
+        // From not playing
+        mediaTimeoutListener.onMediaDataLoaded(KEY, null, mediaData)
+        clearInvocations(mediaController)
+
+        // Migrate, still not playing
+        val playingState = mock(android.media.session.PlaybackState::class.java)
+        `when`(playingState.state).thenReturn(PlaybackState.STATE_PAUSED)
+        `when`(mediaController.playbackState).thenReturn(playingState)
+        mediaTimeoutListener.onMediaDataLoaded("NEWKEY", KEY, mediaData)
+
+        // Never cancels callback, or schedule another one
+        verify(cancellationRunnable, never()).run()
+    }
+
+    @Test
     fun testOnPlaybackStateChanged_schedulesTimeout_whenPaused() {
         // Assuming we're registered
         testOnMediaDataLoaded_registersPlaybackListener()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NavigationBarButtonTest.java b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarButtonTest.java
similarity index 94%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NavigationBarButtonTest.java
rename to packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarButtonTest.java
index aae0757..37b7cbe 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NavigationBarButtonTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarButtonTest.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2018 The Android Open Source Project
+ * 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.
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.systemui.statusbar.phone;
+package com.android.systemui.navigationbar;
 
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertTrue;
@@ -35,6 +35,7 @@
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.SysuiTestableContext;
 import com.android.systemui.assist.AssistManager;
+import com.android.systemui.navigationbar.NavigationBarView;
 import com.android.systemui.recents.OverviewProxyService;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
 
@@ -65,6 +66,7 @@
         mDependency.injectMockDependency(AssistManager.class);
         mDependency.injectMockDependency(OverviewProxyService.class);
         mDependency.injectMockDependency(KeyguardStateController.class);
+        mDependency.injectMockDependency(NavigationBarController.class);
         mNavBar = new NavigationBarView(context, null);
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NavigationBarControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarControllerTest.java
similarity index 72%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/NavigationBarControllerTest.java
rename to packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarControllerTest.java
index 3c66ac6..4bc9b46 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NavigationBarControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarControllerTest.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2019 The Android Open Source Project
+ * 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.
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.systemui.statusbar;
+package com.android.systemui.navigationbar;
 
 import static android.view.Display.DEFAULT_DISPLAY;
 import static android.view.Display.INVALID_DISPLAY;
@@ -36,18 +36,38 @@
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper.RunWithLooper;
 import android.util.SparseArray;
+import android.view.WindowManager;
+import android.view.accessibility.AccessibilityManager;
 
 import androidx.test.filters.SmallTest;
 
+import com.android.internal.logging.MetricsLogger;
+import com.android.internal.logging.UiEventLogger;
 import com.android.systemui.Dependency;
 import com.android.systemui.SysuiTestCase;
-import com.android.systemui.statusbar.phone.NavigationBarFragment;
+import com.android.systemui.accessibility.SystemActions;
+import com.android.systemui.assist.AssistManager;
+import com.android.systemui.broadcast.BroadcastDispatcher;
+import com.android.systemui.model.SysUiState;
+import com.android.systemui.plugins.statusbar.StatusBarStateController;
+import com.android.systemui.recents.OverviewProxyService;
+import com.android.systemui.recents.Recents;
+import com.android.systemui.stackdivider.SplitScreenController;
+import com.android.systemui.statusbar.CommandQueue;
+import com.android.systemui.statusbar.NotificationRemoteInputManager;
+import com.android.systemui.statusbar.phone.ShadeController;
+import com.android.systemui.statusbar.phone.StatusBar;
+import com.android.systemui.statusbar.policy.AccessibilityManagerWrapper;
+import com.android.systemui.statusbar.policy.ConfigurationController;
+import com.android.systemui.statusbar.policy.DeviceProvisionedController;
 
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
+import java.util.Optional;
+
 /** atest NavigationBarControllerTest */
 @RunWith(AndroidTestingRunner.class)
 @RunWithLooper
@@ -55,27 +75,47 @@
 public class NavigationBarControllerTest extends SysuiTestCase {
 
     private NavigationBarController mNavigationBarController;
-    private NavigationBarFragment mDefaultNavBar;
-    private NavigationBarFragment mSecondaryNavBar;
+    private NavigationBar mDefaultNavBar;
+    private NavigationBar mSecondaryNavBar;
 
     private static final int SECONDARY_DISPLAY = 1;
 
     @Before
     public void setUp() {
         mNavigationBarController = spy(
-                new NavigationBarController(mContext, Dependency.get(Dependency.MAIN_HANDLER),
-                        mock(CommandQueue.class)));
+                new NavigationBarController(mContext,
+                        mock(WindowManager.class),
+                        () -> mock(AssistManager.class),
+                        mock(AccessibilityManager.class),
+                        mock(AccessibilityManagerWrapper.class),
+                        mock(DeviceProvisionedController.class),
+                        mock(MetricsLogger.class),
+                        mock(OverviewProxyService.class),
+                        mock(NavigationModeController.class),
+                        mock(StatusBarStateController.class),
+                        mock(SysUiState.class),
+                        mock(BroadcastDispatcher.class),
+                        mock(CommandQueue.class),
+                        Optional.of(mock(SplitScreenController.class)),
+                        Optional.of(mock(Recents.class)),
+                        () -> mock(StatusBar.class),
+                        mock(ShadeController.class),
+                        mock(NotificationRemoteInputManager.class),
+                        mock(SystemActions.class),
+                        Dependency.get(Dependency.MAIN_HANDLER),
+                        mock(UiEventLogger.class),
+                        mock(ConfigurationController.class)));
         initializeNavigationBars();
     }
 
     private void initializeNavigationBars() {
         mNavigationBarController.mNavigationBars = mock(SparseArray.class);
-        mDefaultNavBar = mock(NavigationBarFragment.class);
+        mDefaultNavBar = mock(NavigationBar.class);
         mDefaultNavBar.mDisplayId = DEFAULT_DISPLAY;
         doReturn(mDefaultNavBar)
                 .when(mNavigationBarController.mNavigationBars).get(DEFAULT_DISPLAY);
 
-        mSecondaryNavBar = mock(NavigationBarFragment.class);
+        mSecondaryNavBar = mock(NavigationBar.class);
         mSecondaryNavBar.mDisplayId = SECONDARY_DISPLAY;
         doReturn(mSecondaryNavBar)
                 .when(mNavigationBarController.mNavigationBars).get(SECONDARY_DISPLAY);
@@ -90,22 +130,22 @@
 
     @Test
     public void testCreateNavigationBarsIncludeDefaultTrue() {
-        doNothing().when(mNavigationBarController).createNavigationBar(any(), any());
+        doNothing().when(mNavigationBarController).createNavigationBar(any(), any(), any());
 
         mNavigationBarController.createNavigationBars(true, null);
 
         verify(mNavigationBarController).createNavigationBar(
-                argThat(display -> display.getDisplayId() == DEFAULT_DISPLAY), any());
+                argThat(display -> display.getDisplayId() == DEFAULT_DISPLAY), any(), any());
     }
 
     @Test
     public void testCreateNavigationBarsIncludeDefaultFalse() {
-        doNothing().when(mNavigationBarController).createNavigationBar(any(), any());
+        doNothing().when(mNavigationBarController).createNavigationBar(any(), any(), any());
 
         mNavigationBarController.createNavigationBars(false, null);
 
         verify(mNavigationBarController, never()).createNavigationBar(
-                argThat(display -> display.getDisplayId() == DEFAULT_DISPLAY), any());
+                argThat(display -> display.getDisplayId() == DEFAULT_DISPLAY), any(), any());
     }
 
     // Tests if NPE occurs when call checkNavBarModes() with invalid display.
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NavigationBarInflaterViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarInflaterViewTest.java
similarity index 90%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NavigationBarInflaterViewTest.java
rename to packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarInflaterViewTest.java
index 80e33fb..7369c82 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NavigationBarInflaterViewTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarInflaterViewTest.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2019 The Android Open Source Project
+ * 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.
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.systemui.statusbar.phone;
+package com.android.systemui.navigationbar;
 
 import static org.junit.Assert.assertEquals;
 import static org.mockito.Mockito.doNothing;
@@ -32,6 +32,9 @@
 
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.assist.AssistManager;
+import com.android.systemui.navigationbar.buttons.ButtonDispatcher;
+import com.android.systemui.navigationbar.NavigationBarInflaterView;
+import com.android.systemui.navigationbar.NavigationModeController;
 import com.android.systemui.recents.OverviewProxyService;
 
 import org.junit.After;
@@ -54,6 +57,7 @@
         mDependency.injectMockDependency(AssistManager.class);
         mDependency.injectMockDependency(OverviewProxyService.class);
         mDependency.injectMockDependency(NavigationModeController.class);
+        mDependency.injectMockDependency(NavigationBarController.class);
 
         mNavBarInflaterView = spy(new NavigationBarInflaterView(mContext, null));
         doNothing().when(mNavBarInflaterView).createInflaters();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NavigationBarRotationContextTest.java b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarRotationContextTest.java
similarity index 93%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NavigationBarRotationContextTest.java
rename to packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarRotationContextTest.java
index f21235c..51cf501 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NavigationBarRotationContextTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarRotationContextTest.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2018 The Android Open Source Project
+ * 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.
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.systemui.statusbar.phone;
+package com.android.systemui.navigationbar;
 
 import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.mock;
@@ -30,7 +30,9 @@
 
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.SysuiTestableContext;
-import com.android.systemui.statusbar.policy.KeyButtonDrawable;
+import com.android.systemui.navigationbar.buttons.KeyButtonDrawable;
+import com.android.systemui.navigationbar.RotationButton;
+import com.android.systemui.navigationbar.RotationButtonController;
 import com.android.systemui.statusbar.policy.RotationLockController;
 
 import org.junit.Before;
@@ -60,7 +62,7 @@
         final View view = new View(mContext);
         mRotationButton = mock(RotationButton.class);
         mRotationButtonController = spy(new RotationButtonController(mContext, 0, 0,
-                mRotationButton));
+                mRotationButton, (visibility) -> {}));
         final KeyButtonDrawable kbd = mock(KeyButtonDrawable.class);
         doReturn(view).when(mRotationButton).getCurrentView();
         doReturn(true).when(mRotationButton).acceptRotationProposal();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarTest.java b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarTest.java
new file mode 100644
index 0000000..63821c4
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarTest.java
@@ -0,0 +1,239 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.navigationbar;
+
+import static android.app.StatusBarManager.NAVIGATION_HINT_BACK_ALT;
+import static android.app.StatusBarManager.NAVIGATION_HINT_IME_SHOWN;
+import static android.inputmethodservice.InputMethodService.BACK_DISPOSITION_DEFAULT;
+import static android.inputmethodservice.InputMethodService.IME_VISIBLE;
+import static android.view.Display.DEFAULT_DISPLAY;
+import static android.view.DisplayAdjustments.DEFAULT_DISPLAY_ADJUSTMENTS;
+
+import static com.android.systemui.navigationbar.NavigationBar.NavBarActionEvent.NAVBAR_ASSIST_LONGPRESS;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+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.Mockito.doNothing;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.IntentFilter;
+import android.hardware.display.DisplayManagerGlobal;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.UserHandle;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
+import android.testing.TestableLooper.RunWithLooper;
+import android.view.Display;
+import android.view.DisplayInfo;
+import android.view.WindowManager;
+import android.view.WindowMetrics;
+import android.view.accessibility.AccessibilityManager;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.internal.logging.MetricsLogger;
+import com.android.internal.logging.UiEventLogger;
+import com.android.systemui.Dependency;
+import com.android.systemui.SysuiTestCase;
+import com.android.systemui.SysuiTestableContext;
+import com.android.systemui.accessibility.SystemActions;
+import com.android.systemui.assist.AssistManager;
+import com.android.systemui.broadcast.BroadcastDispatcher;
+import com.android.systemui.model.SysUiState;
+import com.android.systemui.plugins.statusbar.StatusBarStateController;
+import com.android.systemui.recents.OverviewProxyService;
+import com.android.systemui.recents.Recents;
+import com.android.systemui.stackdivider.SplitScreenController;
+import com.android.systemui.statusbar.CommandQueue;
+import com.android.systemui.statusbar.NotificationRemoteInputManager;
+import com.android.systemui.statusbar.phone.ShadeController;
+import com.android.systemui.statusbar.phone.StatusBar;
+import com.android.systemui.statusbar.policy.AccessibilityManagerWrapper;
+import com.android.systemui.statusbar.policy.DeviceProvisionedController;
+import com.android.systemui.statusbar.policy.KeyguardStateController;
+import com.android.systemui.utils.leaks.LeakCheckedTest;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.Optional;
+
+@RunWith(AndroidTestingRunner.class)
+@RunWithLooper(setAsMainLooper = true)
+@SmallTest
+public class NavigationBarTest extends SysuiTestCase {
+    private static final int EXTERNAL_DISPLAY_ID = 2;
+
+    private NavigationBar mNavigationBar;
+    private NavigationBar mExternalDisplayNavigationBar;
+
+    private SysuiTestableContext mSysuiTestableContextExternal;
+    private OverviewProxyService mOverviewProxyService;
+    private CommandQueue mCommandQueue;
+    private SysUiState mMockSysUiState;
+    private Handler mHandler;
+    @Mock
+    private BroadcastDispatcher mBroadcastDispatcher;
+    @Mock
+    private UiEventLogger mUiEventLogger;
+
+    @Rule
+    public final LeakCheckedTest.SysuiLeakCheck mLeakCheck = new LeakCheckedTest.SysuiLeakCheck();
+    private AccessibilityManagerWrapper mAccessibilityWrapper =
+            new AccessibilityManagerWrapper(mContext);
+
+    @Before
+    public void setup() throws Exception {
+        MockitoAnnotations.initMocks(this);
+
+        mCommandQueue = new CommandQueue(mContext);
+        setupSysuiDependency();
+        mDependency.injectMockDependency(AssistManager.class);
+        mDependency.injectMockDependency(KeyguardStateController.class);
+        mDependency.injectMockDependency(StatusBarStateController.class);
+        mDependency.injectMockDependency(NavigationBarController.class);
+        mOverviewProxyService = mDependency.injectMockDependency(OverviewProxyService.class);
+        TestableLooper.get(this).runWithLooper(() -> {
+            mHandler = new Handler();
+            mNavigationBar = createNavBar(mContext);
+            mExternalDisplayNavigationBar = createNavBar(mSysuiTestableContextExternal);
+        });
+    }
+
+    private void setupSysuiDependency() {
+        Display display = new Display(DisplayManagerGlobal.getInstance(), EXTERNAL_DISPLAY_ID,
+                new DisplayInfo(), DEFAULT_DISPLAY_ADJUSTMENTS);
+        mSysuiTestableContextExternal = (SysuiTestableContext) getContext().createDisplayContext(
+                display);
+
+        WindowManager windowManager = mock(WindowManager.class);
+        Display defaultDisplay = mContext.getSystemService(WindowManager.class).getDefaultDisplay();
+        when(windowManager.getDefaultDisplay()).thenReturn(
+                defaultDisplay);
+        WindowMetrics metrics = mContext.getSystemService(WindowManager.class)
+                .getMaximumWindowMetrics();
+        when(windowManager.getMaximumWindowMetrics()).thenReturn(metrics);
+        doNothing().when(windowManager).addView(any(), any());
+        mContext.addMockSystemService(Context.WINDOW_SERVICE, windowManager);
+        mSysuiTestableContextExternal.addMockSystemService(Context.WINDOW_SERVICE, windowManager);
+
+        mDependency.injectTestDependency(Dependency.BG_LOOPER, Looper.getMainLooper());
+        mDependency.injectTestDependency(AccessibilityManagerWrapper.class, mAccessibilityWrapper);
+
+        mMockSysUiState = mock(SysUiState.class);
+        when(mMockSysUiState.setFlag(anyInt(), anyBoolean())).thenReturn(mMockSysUiState);
+    }
+
+    @Test
+    public void testHomeLongPress() {
+        mNavigationBar.onViewAttachedToWindow(mNavigationBar.createView(null));
+        mNavigationBar.onHomeLongClick(mNavigationBar.getView());
+
+        verify(mUiEventLogger, times(1)).log(NAVBAR_ASSIST_LONGPRESS);
+    }
+
+    @Test
+    public void testRegisteredWithDispatcher() {
+        mNavigationBar.onViewAttachedToWindow(mNavigationBar.createView(null));
+        verify(mBroadcastDispatcher).registerReceiverWithHandler(
+                any(BroadcastReceiver.class),
+                any(IntentFilter.class),
+                any(Handler.class),
+                any(UserHandle.class));
+    }
+
+    @Test
+    public void testSetImeWindowStatusWhenImeSwitchOnDisplay() {
+        // Create default & external NavBar fragment.
+        NavigationBar defaultNavBar = mNavigationBar;
+        NavigationBar externalNavBar = mExternalDisplayNavigationBar;
+        doNothing().when(defaultNavBar).checkNavBarModes();
+        doNothing().when(externalNavBar).checkNavBarModes();
+        defaultNavBar.createView(null);
+        externalNavBar.createView(null);
+
+        // Set IME window status for default NavBar.
+        mCommandQueue.setImeWindowStatus(DEFAULT_DISPLAY, null, IME_VISIBLE,
+                BACK_DISPOSITION_DEFAULT, true, false);
+        processAllMessages();
+
+        // Verify IME window state will be updated in default NavBar & external NavBar state reset.
+        assertEquals(NAVIGATION_HINT_BACK_ALT | NAVIGATION_HINT_IME_SHOWN,
+                defaultNavBar.getNavigationIconHints());
+        assertFalse((externalNavBar.getNavigationIconHints() & NAVIGATION_HINT_BACK_ALT) != 0);
+        assertFalse((externalNavBar.getNavigationIconHints() & NAVIGATION_HINT_IME_SHOWN) != 0);
+
+        // Set IME window status for external NavBar.
+        mCommandQueue.setImeWindowStatus(EXTERNAL_DISPLAY_ID, null,
+                IME_VISIBLE, BACK_DISPOSITION_DEFAULT, true, false);
+        processAllMessages();
+
+        // Verify IME window state will be updated in external NavBar & default NavBar state reset.
+        assertEquals(NAVIGATION_HINT_BACK_ALT | NAVIGATION_HINT_IME_SHOWN,
+                externalNavBar.getNavigationIconHints());
+        assertFalse((defaultNavBar.getNavigationIconHints() & NAVIGATION_HINT_BACK_ALT) != 0);
+        assertFalse((defaultNavBar.getNavigationIconHints() & NAVIGATION_HINT_IME_SHOWN) != 0);
+    }
+
+    private NavigationBar createNavBar(Context context) {
+        DeviceProvisionedController deviceProvisionedController =
+                mock(DeviceProvisionedController.class);
+        when(deviceProvisionedController.isDeviceProvisioned()).thenReturn(true);
+        assertNotNull(mAccessibilityWrapper);
+        return spy(new NavigationBar(context,
+                mock(WindowManager.class),
+                () -> mock(AssistManager.class),
+                mock(AccessibilityManager.class),
+                context.getDisplayId() == DEFAULT_DISPLAY ? mAccessibilityWrapper
+                        : mock(AccessibilityManagerWrapper.class),
+                deviceProvisionedController,
+                new MetricsLogger(),
+                mOverviewProxyService,
+                mock(NavigationModeController.class),
+                mock(StatusBarStateController.class),
+                mMockSysUiState,
+                mBroadcastDispatcher,
+                mCommandQueue,
+                Optional.of(mock(SplitScreenController.class)),
+                Optional.of(mock(Recents.class)),
+                () -> mock(StatusBar.class),
+                mock(ShadeController.class),
+                mock(NotificationRemoteInputManager.class),
+                mock(SystemActions.class),
+                mHandler,
+                mUiEventLogger));
+    }
+
+    private void processAllMessages() {
+        TestableLooper.get(this).processAllMessages();
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NavigationBarTransitionsTest.java b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarTransitionsTest.java
similarity index 76%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NavigationBarTransitionsTest.java
rename to packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarTransitionsTest.java
index 14c6e9f9..7e0920c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NavigationBarTransitionsTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarTransitionsTest.java
@@ -1,18 +1,20 @@
 /*
- * Copyright (C) 2017 The Android Open Source Project
+ * 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
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
  *
  *      http://www.apache.org/licenses/LICENSE-2.0
  *
- * Unless required by applicable law or agreed to in writing, software distributed under the
- * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the specific language governing
- * permissions and limitations under the License.
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
  */
 
-package com.android.systemui.statusbar.phone;
+package com.android.systemui.navigationbar;
 
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
@@ -30,9 +32,13 @@
 
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.assist.AssistManager;
+import com.android.systemui.navigationbar.NavigationBarTransitions;
+import com.android.systemui.navigationbar.NavigationBarView;
+import com.android.systemui.navigationbar.NavigationModeController;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.recents.OverviewProxyService;
 import com.android.systemui.statusbar.CommandQueue;
+import com.android.systemui.statusbar.phone.BarTransitions;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
 
 import org.junit.Before;
@@ -53,6 +59,7 @@
         mDependency.injectMockDependency(OverviewProxyService.class);
         mDependency.injectMockDependency(StatusBarStateController.class);
         mDependency.injectMockDependency(KeyguardStateController.class);
+        mDependency.injectMockDependency(NavigationBarController.class);
         doReturn(mContext)
                 .when(mDependency.injectMockDependency(NavigationModeController.class))
                 .getCurrentUserContext();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/KeyButtonViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/buttons/KeyButtonViewTest.java
similarity index 79%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/KeyButtonViewTest.java
rename to packages/SystemUI/tests/src/com/android/systemui/navigationbar/buttons/KeyButtonViewTest.java
index d52d686..3f10c8d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/KeyButtonViewTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/buttons/KeyButtonViewTest.java
@@ -1,18 +1,20 @@
 /*
- * Copyright (C) 2017 The Android Open Source Project
+ * 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
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
  *
  *      http://www.apache.org/licenses/LICENSE-2.0
  *
- * Unless required by applicable law or agreed to in writing, software distributed under the
- * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the specific language governing
- * permissions and limitations under the License.
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
  */
 
-package com.android.systemui.statusbar.policy;
+package com.android.systemui.navigationbar.buttons;
 
 import static android.view.KeyEvent.ACTION_DOWN;
 import static android.view.KeyEvent.ACTION_UP;
@@ -23,12 +25,12 @@
 import static android.view.KeyEvent.KEYCODE_BACK;
 import static android.view.KeyEvent.KEYCODE_HOME;
 
-import static com.android.systemui.statusbar.policy.KeyButtonView.NavBarButtonEvent.NAVBAR_BACK_BUTTON_LONGPRESS;
-import static com.android.systemui.statusbar.policy.KeyButtonView.NavBarButtonEvent.NAVBAR_BACK_BUTTON_TAP;
-import static com.android.systemui.statusbar.policy.KeyButtonView.NavBarButtonEvent.NAVBAR_HOME_BUTTON_LONGPRESS;
-import static com.android.systemui.statusbar.policy.KeyButtonView.NavBarButtonEvent.NAVBAR_HOME_BUTTON_TAP;
-import static com.android.systemui.statusbar.policy.KeyButtonView.NavBarButtonEvent.NAVBAR_OVERVIEW_BUTTON_LONGPRESS;
-import static com.android.systemui.statusbar.policy.KeyButtonView.NavBarButtonEvent.NAVBAR_OVERVIEW_BUTTON_TAP;
+import static com.android.systemui.navigationbar.buttons.KeyButtonView.NavBarButtonEvent.NAVBAR_BACK_BUTTON_LONGPRESS;
+import static com.android.systemui.navigationbar.buttons.KeyButtonView.NavBarButtonEvent.NAVBAR_BACK_BUTTON_TAP;
+import static com.android.systemui.navigationbar.buttons.KeyButtonView.NavBarButtonEvent.NAVBAR_HOME_BUTTON_LONGPRESS;
+import static com.android.systemui.navigationbar.buttons.KeyButtonView.NavBarButtonEvent.NAVBAR_HOME_BUTTON_TAP;
+import static com.android.systemui.navigationbar.buttons.KeyButtonView.NavBarButtonEvent.NAVBAR_OVERVIEW_BUTTON_LONGPRESS;
+import static com.android.systemui.navigationbar.buttons.KeyButtonView.NavBarButtonEvent.NAVBAR_OVERVIEW_BUTTON_TAP;
 
 import static junit.framework.Assert.assertEquals;
 
@@ -52,6 +54,7 @@
 import com.android.internal.logging.UiEventLogger;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.bubbles.BubbleController;
+import com.android.systemui.navigationbar.buttons.KeyButtonView;
 import com.android.systemui.recents.OverviewProxyService;
 
 import org.junit.Before;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NavigationBarContextTest.java b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/buttons/NavigationBarContextTest.java
similarity index 93%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NavigationBarContextTest.java
rename to packages/SystemUI/tests/src/com/android/systemui/navigationbar/buttons/NavigationBarContextTest.java
index b5060ee..d56aa77 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NavigationBarContextTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/buttons/NavigationBarContextTest.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2018 The Android Open Source Project
+ * 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.
@@ -11,10 +11,10 @@
  * distributed under the License is distributed on an "AS IS" BASIS,
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  * See the License for the specific language governing permissions and
- * limitations under the License
+ * limitations under the License.
  */
 
-package com.android.systemui.statusbar.phone;
+package com.android.systemui.navigationbar.buttons;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
@@ -35,7 +35,9 @@
 
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.assist.AssistManager;
-import com.android.systemui.statusbar.policy.KeyButtonDrawable;
+import com.android.systemui.navigationbar.buttons.ContextualButton;
+import com.android.systemui.navigationbar.buttons.ContextualButtonGroup;
+import com.android.systemui.navigationbar.buttons.KeyButtonDrawable;
 
 import org.junit.Before;
 import org.junit.Ignore;
@@ -65,9 +67,9 @@
         mDependency.injectMockDependency(AssistManager.class);
 
         mGroup = new ContextualButtonGroup(GROUP_ID);
-        mBtn0 = new ContextualButton(BUTTON_0_ID, ICON_RES_ID);
-        mBtn1 = new ContextualButton(BUTTON_1_ID, ICON_RES_ID);
-        mBtn2 = new ContextualButton(BUTTON_2_ID, ICON_RES_ID);
+        mBtn0 = new ContextualButton(BUTTON_0_ID, mContext, ICON_RES_ID);
+        mBtn1 = new ContextualButton(BUTTON_1_ID, mContext, ICON_RES_ID);
+        mBtn2 = new ContextualButton(BUTTON_2_ID, mContext, ICON_RES_ID);
 
         // Order of adding buttons to group determines the priority, ascending priority order
         mGroup.addButton(mBtn0);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NearestTouchFrameTest.java b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/buttons/NearestTouchFrameTest.java
similarity index 91%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NearestTouchFrameTest.java
rename to packages/SystemUI/tests/src/com/android/systemui/navigationbar/buttons/NearestTouchFrameTest.java
index a04bcc0..0320103 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NearestTouchFrameTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/buttons/NearestTouchFrameTest.java
@@ -1,18 +1,20 @@
 /*
- * Copyright (C) 2017 The Android Open Source Project
+ * 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
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
  *
  *      http://www.apache.org/licenses/LICENSE-2.0
  *
- * Unless required by applicable law or agreed to in writing, software distributed under the
- * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the specific language governing
- * permissions and limitations under the License.
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
  */
 
-package com.android.systemui.statusbar.phone;
+package com.android.systemui.navigationbar.buttons;
 
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.eq;
@@ -31,6 +33,7 @@
 import androidx.test.filters.SmallTest;
 
 import com.android.systemui.SysuiTestCase;
+import com.android.systemui.navigationbar.buttons.NearestTouchFrame;
 
 import org.junit.Before;
 import org.junit.Test;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/onehanded/OneHandedAnimationControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/onehanded/OneHandedAnimationControllerTest.java
index 73164b5..7fabf82 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/onehanded/OneHandedAnimationControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/onehanded/OneHandedAnimationControllerTest.java
@@ -54,8 +54,7 @@
         MockitoAnnotations.initMocks(this);
 
         mTutorialHandler = new OneHandedTutorialHandler(mContext);
-        mOneHandedAnimationController = new OneHandedAnimationController(
-                new OneHandedSurfaceTransactionHelper(mContext));
+        mOneHandedAnimationController = new OneHandedAnimationController(mContext);
     }
 
     @Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/onehanded/OneHandedManagerImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/onehanded/OneHandedControllerTest.java
similarity index 77%
rename from packages/SystemUI/tests/src/com/android/systemui/onehanded/OneHandedManagerImplTest.java
rename to packages/SystemUI/tests/src/com/android/systemui/onehanded/OneHandedControllerTest.java
index 3418ebf..02d587d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/onehanded/OneHandedManagerImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/onehanded/OneHandedControllerTest.java
@@ -33,6 +33,7 @@
 import androidx.test.filters.SmallTest;
 
 import com.android.systemui.model.SysUiState;
+import com.android.systemui.statusbar.CommandQueue;
 import com.android.wm.shell.common.DisplayController;
 
 import org.junit.Before;
@@ -45,12 +46,14 @@
 @SmallTest
 @RunWith(AndroidTestingRunner.class)
 @TestableLooper.RunWithLooper
-public class OneHandedManagerImplTest extends OneHandedTestCase {
+public class OneHandedControllerTest extends OneHandedTestCase {
     Display mDisplay;
-    OneHandedManagerImpl mOneHandedManagerImpl;
+    OneHandedController mOneHandedController;
     OneHandedTimeoutHandler mTimeoutHandler;
 
     @Mock
+    CommandQueue mCommandQueue;
+    @Mock
     DisplayController mMockDisplayController;
     @Mock
     OneHandedDisplayAreaOrganizer mMockDisplayAreaOrganizer;
@@ -67,7 +70,9 @@
     public void setUp() throws Exception {
         MockitoAnnotations.initMocks(this);
         mDisplay = mContext.getDisplay();
-        mOneHandedManagerImpl = new OneHandedManagerImpl(getContext(),
+        mOneHandedController = new OneHandedController(
+                getContext(),
+                mCommandQueue,
                 mMockDisplayController,
                 mMockDisplayAreaOrganizer,
                 mMockTouchHandler,
@@ -82,10 +87,8 @@
 
     @Test
     public void testDefaultShouldNotInOneHanded() {
-        final OneHandedSurfaceTransactionHelper transactionHelper =
-                new OneHandedSurfaceTransactionHelper(mContext);
         final OneHandedAnimationController animationController = new OneHandedAnimationController(
-                transactionHelper);
+                mContext);
         OneHandedDisplayAreaOrganizer displayAreaOrganizer = new OneHandedDisplayAreaOrganizer(
                 mContext, mMockDisplayController, animationController, mMockTutorialHandler);
 
@@ -94,20 +97,20 @@
 
     @Test
     public void testRegisterOrganizer() {
-        verify(mMockDisplayAreaOrganizer, times(1)).registerOrganizer(anyInt());
+        verify(mMockDisplayAreaOrganizer).registerOrganizer(anyInt());
     }
 
     @Test
     public void testStartOneHanded() {
-        mOneHandedManagerImpl.startOneHanded();
+        mOneHandedController.startOneHanded();
 
-        verify(mMockDisplayAreaOrganizer, times(1)).scheduleOffset(anyInt(), anyInt());
+        verify(mMockDisplayAreaOrganizer).scheduleOffset(anyInt(), anyInt());
     }
 
     @Test
     public void testStopOneHanded() {
         when(mMockDisplayAreaOrganizer.isInOneHanded()).thenReturn(false);
-        mOneHandedManagerImpl.stopOneHanded();
+        mOneHandedController.stopOneHanded();
 
         verify(mMockDisplayAreaOrganizer, never()).scheduleOffset(anyInt(), anyInt());
     }
@@ -119,17 +122,24 @@
 
     @Test
     public void testStopOneHanded_shouldRemoveTimer() {
-        mOneHandedManagerImpl.stopOneHanded();
+        mOneHandedController.stopOneHanded();
 
-        verify(mTimeoutHandler, times(1)).removeTimer();
+        verify(mTimeoutHandler).removeTimer();
     }
 
     @Test
     public void testUpdateIsEnabled() {
         final boolean enabled = true;
-        mOneHandedManagerImpl.setOneHandedEnabled(enabled);
+        mOneHandedController.setOneHandedEnabled(enabled);
 
-        verify(mMockTouchHandler, atLeastOnce()).onOneHandedEnabled(enabled);
+        verify(mMockTouchHandler, times(2)).onOneHandedEnabled(enabled);
     }
 
+    @Test
+    public void testUpdateSwipeToNotificationIsEnabled() {
+        final boolean enabled = true;
+        mOneHandedController.setSwipeToNotificationEnabled(enabled);
+
+        verify(mMockTouchHandler, times(2)).onOneHandedEnabled(enabled);
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/onehanded/OneHandedGestureHandlerTest.java b/packages/SystemUI/tests/src/com/android/systemui/onehanded/OneHandedGestureHandlerTest.java
index 694f51b..756382a6 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/onehanded/OneHandedGestureHandlerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/onehanded/OneHandedGestureHandlerTest.java
@@ -24,15 +24,14 @@
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 
-import android.app.Instrumentation;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
 
 import androidx.test.filters.SmallTest;
-import androidx.test.platform.app.InstrumentationRegistry;
 
 import com.android.systemui.model.SysUiState;
-import com.android.systemui.statusbar.phone.NavigationModeController;
+import com.android.systemui.navigationbar.NavigationModeController;
+import com.android.systemui.statusbar.CommandQueue;
 import com.android.wm.shell.common.DisplayController;
 
 import org.junit.Before;
@@ -46,11 +45,12 @@
 @RunWith(AndroidTestingRunner.class)
 @TestableLooper.RunWithLooper
 public class OneHandedGestureHandlerTest extends OneHandedTestCase {
-    Instrumentation mInstrumentation;
     OneHandedTouchHandler mTouchHandler;
     OneHandedTutorialHandler mTutorialHandler;
     OneHandedGestureHandler mGestureHandler;
-    OneHandedManagerImpl mOneHandedManagerImpl;
+    OneHandedController mOneHandedController;
+    @Mock
+    CommandQueue mCommandQueue;
     @Mock
     DisplayController mMockDisplayController;
     @Mock
@@ -62,12 +62,13 @@
     @Before
     public void setUp() throws Exception {
         MockitoAnnotations.initMocks(this);
-        mInstrumentation = InstrumentationRegistry.getInstrumentation();
         mTouchHandler = new OneHandedTouchHandler();
         mTutorialHandler = new OneHandedTutorialHandler(mContext);
         mGestureHandler = Mockito.spy(new OneHandedGestureHandler(
                 mContext, mMockDisplayController, mMockNavigationModeController));
-        mOneHandedManagerImpl = new OneHandedManagerImpl(mInstrumentation.getContext(),
+        mOneHandedController = new OneHandedController(
+                getContext(),
+                mCommandQueue,
                 mMockDisplayController,
                 mMockDisplayAreaOrganizer,
                 mTouchHandler,
@@ -78,7 +79,7 @@
 
     @Test
     public void testOneHandedManager_registerForDisplayAreaOrganizer() {
-        verify(mMockDisplayAreaOrganizer, times(1)).registerTransitionCallback(mGestureHandler);
+        verify(mMockDisplayAreaOrganizer).registerTransitionCallback(mGestureHandler);
     }
 
     @Test
@@ -92,14 +93,15 @@
     public void testReceiveNewConfig_whenSetOneHandedEnabled() {
         // 1st called at init
         verify(mGestureHandler).onOneHandedEnabled(true);
-        mOneHandedManagerImpl.setOneHandedEnabled(true);
+        mOneHandedController.setOneHandedEnabled(true);
         // 2nd called by setOneHandedEnabled()
         verify(mGestureHandler, times(2)).onOneHandedEnabled(true);
     }
 
     @Test
     public void testOneHandedDisabled_shouldDisposeInputChannel() {
-        mOneHandedManagerImpl.setOneHandedEnabled(false);
+        mOneHandedController.setOneHandedEnabled(false);
+        mOneHandedController.setSwipeToNotificationEnabled(false);
 
         assertThat(mGestureHandler.mInputMonitor).isNull();
         assertThat(mGestureHandler.mInputEventReceiver).isNull();
@@ -109,7 +111,7 @@
     public void testChangeNavBarTo2Button_shouldDisposeInputChannel() {
         // 1st called at init
         verify(mGestureHandler).onOneHandedEnabled(true);
-        mOneHandedManagerImpl.setOneHandedEnabled(true);
+        mOneHandedController.setOneHandedEnabled(true);
         // 2nd called by setOneHandedEnabled()
         verify(mGestureHandler, times(2)).onOneHandedEnabled(true);
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/onehanded/OneHandedSettingsUtilTest.java b/packages/SystemUI/tests/src/com/android/systemui/onehanded/OneHandedSettingsUtilTest.java
index c157ae6..990eb63 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/onehanded/OneHandedSettingsUtilTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/onehanded/OneHandedSettingsUtilTest.java
@@ -40,14 +40,12 @@
 @RunWith(AndroidTestingRunner.class)
 @TestableLooper.RunWithLooper
 public class OneHandedSettingsUtilTest extends OneHandedTestCase {
-    OneHandedSettingsUtil mOneHandedSettingsUtil;
     ContentResolver mContentResolver;
     ContentObserver mContentObserver;
     boolean mOnChanged;
 
     @Before
     public void setUp() {
-        mOneHandedSettingsUtil = new OneHandedSettingsUtil();
         mContentResolver = mContext.getContentResolver();
         mContentObserver = new ContentObserver(mContext.getMainThreadHandler()) {
             @Override
@@ -60,20 +58,20 @@
 
     @Test
     public void testRegisterSecureKeyObserver() {
-        final Uri result = mOneHandedSettingsUtil.registerSettingsKeyObserver(
+        final Uri result = OneHandedSettingsUtil.registerSettingsKeyObserver(
                 Settings.Secure.ONE_HANDED_MODE_ENABLED, mContentResolver, mContentObserver);
 
         assertThat(result).isNotNull();
 
-        mOneHandedSettingsUtil.registerSettingsKeyObserver(
+        OneHandedSettingsUtil.registerSettingsKeyObserver(
                 Settings.Secure.ONE_HANDED_MODE_ENABLED, mContentResolver, mContentObserver);
     }
 
     @Test
     public void testUnregisterSecureKeyObserver() {
-        mOneHandedSettingsUtil.registerSettingsKeyObserver(
+        OneHandedSettingsUtil.registerSettingsKeyObserver(
                 Settings.Secure.ONE_HANDED_MODE_ENABLED, mContentResolver, mContentObserver);
-        mOneHandedSettingsUtil.unregisterSettingsKeyObserver(mContentResolver, mContentObserver);
+        OneHandedSettingsUtil.unregisterSettingsKeyObserver(mContentResolver, mContentObserver);
 
         assertThat(mOnChanged).isFalse();
 
@@ -85,23 +83,29 @@
 
     @Test
     public void testGetSettingsIsOneHandedModeEnabled() {
-        assertThat(mOneHandedSettingsUtil.getSettingsOneHandedModeEnabled(
+        assertThat(OneHandedSettingsUtil.getSettingsOneHandedModeEnabled(
                 mContentResolver)).isAnyOf(true, false);
     }
 
     @Test
     public void testGetSettingsTapsAppToExit() {
-        assertThat(mOneHandedSettingsUtil.getSettingsTapsAppToExit(
+        assertThat(OneHandedSettingsUtil.getSettingsTapsAppToExit(
                 mContentResolver)).isAnyOf(true, false);
     }
 
     @Test
     public void testGetSettingsOneHandedModeTimeout() {
-        assertThat(mOneHandedSettingsUtil.getSettingsOneHandedModeTimeout(
+        assertThat(OneHandedSettingsUtil.getSettingsOneHandedModeTimeout(
                 mContentResolver)).isAnyOf(
                 ONE_HANDED_TIMEOUT_NEVER,
                 ONE_HANDED_TIMEOUT_SHORT_IN_SECONDS,
                 ONE_HANDED_TIMEOUT_MEDIUM_IN_SECONDS,
                 ONE_HANDED_TIMEOUT_LONG_IN_SECONDS);
     }
+
+    @Test
+    public void testGetSettingsSwipeToNotificationEnabled() {
+        assertThat(OneHandedSettingsUtil.getSettingsSwipeToNotificationEnabled(
+                mContentResolver)).isAnyOf(true, false);
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/onehanded/OneHandedTestCase.java b/packages/SystemUI/tests/src/com/android/systemui/onehanded/OneHandedTestCase.java
index befa42a..04ebf25 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/onehanded/OneHandedTestCase.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/onehanded/OneHandedTestCase.java
@@ -32,6 +32,7 @@
     static boolean sOrigEnabled;
     static boolean sOrigTapsAppToExitEnabled;
     static int sOrigTimeout;
+    static boolean sOrigSwipeToNotification;
 
     @Before
     public void setupSettings() {
@@ -41,12 +42,16 @@
                 getContext().getContentResolver());
         sOrigTapsAppToExitEnabled = OneHandedSettingsUtil.getSettingsTapsAppToExit(
                 getContext().getContentResolver());
+        sOrigSwipeToNotification = OneHandedSettingsUtil.getSettingsSwipeToNotificationEnabled(
+                getContext().getContentResolver());
         Settings.Secure.putInt(getContext().getContentResolver(),
                 Settings.Secure.ONE_HANDED_MODE_ENABLED, 1);
         Settings.Secure.putInt(getContext().getContentResolver(),
                 Settings.Secure.ONE_HANDED_MODE_TIMEOUT, ONE_HANDED_TIMEOUT_MEDIUM_IN_SECONDS);
         Settings.Secure.putInt(getContext().getContentResolver(),
                 Settings.Secure.TAPS_APP_TO_EXIT, 1);
+        Settings.Secure.putInt(getContext().getContentResolver(),
+                Settings.Secure.SWIPE_BOTTOM_TO_NOTIFICATION_ENABLED, 1);
     }
 
     @After
@@ -57,6 +62,9 @@
                 Settings.Secure.ONE_HANDED_MODE_TIMEOUT, sOrigTimeout);
         Settings.Secure.putInt(mContext.getContentResolver(),
                 Settings.Secure.TAPS_APP_TO_EXIT, sOrigTapsAppToExitEnabled ? 1 : 0);
+        Settings.Secure.putInt(mContext.getContentResolver(),
+                Settings.Secure.SWIPE_BOTTOM_TO_NOTIFICATION_ENABLED,
+                sOrigSwipeToNotification ? 1 : 0);
     }
 }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/onehanded/OneHandedTouchHandlerTest.java b/packages/SystemUI/tests/src/com/android/systemui/onehanded/OneHandedTouchHandlerTest.java
index fdb28d3..3c3ace0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/onehanded/OneHandedTouchHandlerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/onehanded/OneHandedTouchHandlerTest.java
@@ -22,15 +22,14 @@
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 
-import android.app.Instrumentation;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
 
 import androidx.test.filters.SmallTest;
-import androidx.test.platform.app.InstrumentationRegistry;
 
 import com.android.systemui.model.SysUiState;
-import com.android.systemui.statusbar.phone.NavigationModeController;
+import com.android.systemui.navigationbar.NavigationModeController;
+import com.android.systemui.statusbar.CommandQueue;
 import com.android.wm.shell.common.DisplayController;
 
 import org.junit.Before;
@@ -44,11 +43,12 @@
 @RunWith(AndroidTestingRunner.class)
 @TestableLooper.RunWithLooper
 public class OneHandedTouchHandlerTest extends OneHandedTestCase {
-    Instrumentation mInstrumentation;
     OneHandedTouchHandler mTouchHandler;
     OneHandedTutorialHandler mTutorialHandler;
     OneHandedGestureHandler mGestureHandler;
-    OneHandedManagerImpl mOneHandedManagerImpl;
+    OneHandedController mOneHandedController;
+    @Mock
+    CommandQueue mCommandQueue;
     @Mock
     DisplayController mMockDisplayController;
     @Mock
@@ -61,11 +61,12 @@
     @Before
     public void setUp() {
         MockitoAnnotations.initMocks(this);
-        mInstrumentation = InstrumentationRegistry.getInstrumentation();
         mTouchHandler = Mockito.spy(new OneHandedTouchHandler());
         mGestureHandler = new OneHandedGestureHandler(mContext, mMockDisplayController,
                 mMockNavigationModeController);
-        mOneHandedManagerImpl = new OneHandedManagerImpl(mInstrumentation.getContext(),
+        mOneHandedController = new OneHandedController(
+                getContext(),
+                mCommandQueue,
                 mMockDisplayController,
                 mMockDisplayAreaOrganizer,
                 mTouchHandler,
@@ -76,8 +77,7 @@
 
     @Test
     public void testOneHandedManager_registerForDisplayAreaOrganizer() {
-        verify(mMockDisplayAreaOrganizer, times(1))
-                .registerTransitionCallback(mTouchHandler);
+        verify(mMockDisplayAreaOrganizer).registerTransitionCallback(mTouchHandler);
     }
 
     @Test
@@ -88,14 +88,14 @@
 
     @Test
     public void testOneHandedDisabled_shouldDisposeInputChannel() {
-        mOneHandedManagerImpl.setOneHandedEnabled(false);
+        mOneHandedController.setOneHandedEnabled(false);
         assertThat(mTouchHandler.mInputMonitor).isNull();
         assertThat(mTouchHandler.mInputEventReceiver).isNull();
     }
 
     @Test
     public void testOneHandedEnabled_monitorInputChannel() {
-        mOneHandedManagerImpl.setOneHandedEnabled(true);
+        mOneHandedController.setOneHandedEnabled(true);
         assertThat(mTouchHandler.mInputMonitor).isNotNull();
         assertThat(mTouchHandler.mInputEventReceiver).isNotNull();
     }
@@ -104,7 +104,7 @@
     public void testReceiveNewConfig_whenSetOneHandedEnabled() {
         // 1st called at init
         verify(mTouchHandler).onOneHandedEnabled(true);
-        mOneHandedManagerImpl.setOneHandedEnabled(true);
+        mOneHandedController.setOneHandedEnabled(true);
         // 2nd called by setOneHandedEnabled()
         verify(mTouchHandler, times(2)).onOneHandedEnabled(true);
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/onehanded/OneHandedTutorialHandlerTest.java b/packages/SystemUI/tests/src/com/android/systemui/onehanded/OneHandedTutorialHandlerTest.java
index f4aa00e..1bffbf7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/onehanded/OneHandedTutorialHandlerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/onehanded/OneHandedTutorialHandlerTest.java
@@ -16,18 +16,16 @@
 
 package com.android.systemui.onehanded;
 
-import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 
-import android.app.Instrumentation;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
 
 import androidx.test.filters.SmallTest;
-import androidx.test.platform.app.InstrumentationRegistry;
 
 import com.android.systemui.model.SysUiState;
-import com.android.systemui.statusbar.phone.NavigationModeController;
+import com.android.systemui.navigationbar.NavigationModeController;
+import com.android.systemui.statusbar.CommandQueue;
 import com.android.wm.shell.common.DisplayController;
 
 import org.junit.Before;
@@ -41,11 +39,12 @@
 @RunWith(AndroidTestingRunner.class)
 @TestableLooper.RunWithLooper
 public class OneHandedTutorialHandlerTest extends OneHandedTestCase {
-    Instrumentation mInstrumentation;
     OneHandedTouchHandler mTouchHandler;
     OneHandedTutorialHandler mTutorialHandler;
     OneHandedGestureHandler mGestureHandler;
-    OneHandedManagerImpl mOneHandedManagerImpl;
+    OneHandedController mOneHandedController;
+    @Mock
+    CommandQueue mCommandQueue;
     @Mock
     DisplayController mMockDisplayController;
     @Mock
@@ -58,12 +57,13 @@
     @Before
     public void setUp() {
         MockitoAnnotations.initMocks(this);
-        mInstrumentation = InstrumentationRegistry.getInstrumentation();
         mTouchHandler = new OneHandedTouchHandler();
         mTutorialHandler = Mockito.spy(new OneHandedTutorialHandler(mContext));
         mGestureHandler = new OneHandedGestureHandler(mContext, mMockDisplayController,
                 mMockNavigationModeController);
-        mOneHandedManagerImpl = new OneHandedManagerImpl(mInstrumentation.getContext(),
+        mOneHandedController = new OneHandedController(
+                getContext(),
+                mCommandQueue,
                 mMockDisplayController,
                 mMockDisplayAreaOrganizer,
                 mTouchHandler,
@@ -74,7 +74,6 @@
 
     @Test
     public void testOneHandedManager_registerForDisplayAreaOrganizer() {
-        verify(mMockDisplayAreaOrganizer, times(1))
-                .registerTransitionCallback(mTutorialHandler);
+        verify(mMockDisplayAreaOrganizer).registerTransitionCallback(mTutorialHandler);
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/onehanded/OneHandedUITest.java b/packages/SystemUI/tests/src/com/android/systemui/onehanded/OneHandedUITest.java
index ffedb07..ae3df5d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/onehanded/OneHandedUITest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/onehanded/OneHandedUITest.java
@@ -16,8 +16,7 @@
 
 package com.android.systemui.onehanded;
 
-import static org.mockito.Mockito.any;
-import static org.mockito.Mockito.times;
+import static org.junit.Assume.assumeTrue;
 import static org.mockito.Mockito.verify;
 
 import android.os.SystemProperties;
@@ -28,7 +27,6 @@
 import androidx.test.filters.SmallTest;
 
 import com.android.keyguard.KeyguardUpdateMonitor;
-import com.android.systemui.dump.DumpManager;
 import com.android.systemui.keyguard.ScreenLifecycle;
 import com.android.systemui.statusbar.CommandQueue;
 
@@ -45,121 +43,65 @@
 public class OneHandedUITest extends OneHandedTestCase {
     private static final String SUPPORT_ONE_HANDED_MODE = "ro.support_one_handed_mode";
 
-    boolean mIsSupportOneHandedMode;
     CommandQueue mCommandQueue;
     KeyguardUpdateMonitor mKeyguardUpdateMonitor;
     OneHandedUI mOneHandedUI;
     ScreenLifecycle mScreenLifecycle;
     @Mock
-    OneHandedManagerImpl mMockOneHandedManagerImpl;
-    @Mock
-    DumpManager mMockDumpManager;
-    @Mock
-    OneHandedSettingsUtil mMockSettingsUtil;
+    OneHandedController mOneHandedController;
     @Mock
     OneHandedTimeoutHandler mMockTimeoutHandler;
 
     @Before
     public void setUp() throws Exception {
         MockitoAnnotations.initMocks(this);
-        mIsSupportOneHandedMode = SystemProperties.getBoolean(SUPPORT_ONE_HANDED_MODE, false);
         mCommandQueue = new CommandQueue(mContext);
         mScreenLifecycle = new ScreenLifecycle();
         mOneHandedUI = new OneHandedUI(mContext,
                 mCommandQueue,
-                mMockOneHandedManagerImpl,
-                mMockDumpManager,
-                mMockSettingsUtil,
+                mOneHandedController,
                 mScreenLifecycle);
         mOneHandedUI.start();
         mKeyguardUpdateMonitor = mDependency.injectMockDependency(KeyguardUpdateMonitor.class);
     }
 
+    @Before
+    public void assumeOneHandedModeSupported() {
+        assumeTrue(SystemProperties.getBoolean(SUPPORT_ONE_HANDED_MODE, false));
+    }
+
     @Test
     public void testStartOneHanded() {
-        // Bypass test if device not support one-handed mode
-        if (!mIsSupportOneHandedMode) {
-            return;
-        }
         mOneHandedUI.startOneHanded();
 
-        verify(mMockOneHandedManagerImpl, times(1)).startOneHanded();
+        verify(mOneHandedController).startOneHanded();
     }
 
     @Test
     public void testStopOneHanded() {
-        // Bypass test if device not support one-handed mode
-        if (!mIsSupportOneHandedMode) {
-            return;
-        }
         mOneHandedUI.stopOneHanded();
 
-        verify(mMockOneHandedManagerImpl, times(1)).stopOneHanded();
-    }
-
-    @Test
-    public void testRegisterSettingsObserver_forEnabled() {
-        // Bypass test if device not support one-handed mode
-        if (!mIsSupportOneHandedMode) {
-            return;
-        }
-        final String key = Settings.Secure.ONE_HANDED_MODE_ENABLED;
-
-        verify(mMockSettingsUtil, times(1)).registerSettingsKeyObserver(key, any(), any());
-    }
-
-    @Test
-    public void testRegisterSettingsObserver_forTimeout() {
-        // Bypass test if device not support one-handed mode
-        if (!mIsSupportOneHandedMode) {
-            return;
-        }
-        final String key = Settings.Secure.ONE_HANDED_MODE_TIMEOUT;
-
-        verify(mMockSettingsUtil, times(1)).registerSettingsKeyObserver(key, any(), any());
-    }
-
-    @Test
-    public void testRegisterSettingsObserver_forTapAppExit() {
-        // Bypass test if device not support one-handed mode
-        if (!mIsSupportOneHandedMode) {
-            return;
-        }
-        final String key = Settings.Secure.TAPS_APP_TO_EXIT;
-
-        verify(mMockSettingsUtil, times(1)).registerSettingsKeyObserver(key, any(), any());
+        verify(mOneHandedController).stopOneHanded();
     }
 
     @Test
     public void tesSettingsObserver_updateTapAppToExit() {
-        // Bypass test if device not support one-handed mode
-        if (!mIsSupportOneHandedMode) {
-            return;
-        }
         Settings.Secure.putInt(mContext.getContentResolver(),
                 Settings.Secure.TAPS_APP_TO_EXIT, 1);
 
-        verify(mMockOneHandedManagerImpl, times(1)).setTaskChangeToExit(true);
+        verify(mOneHandedController).setTaskChangeToExit(true);
     }
 
     @Test
     public void tesSettingsObserver_updateEnabled() {
-        // Bypass test if device not support one-handed mode
-        if (!mIsSupportOneHandedMode) {
-            return;
-        }
         Settings.Secure.putInt(mContext.getContentResolver(),
                 Settings.Secure.ONE_HANDED_MODE_ENABLED, 1);
 
-        verify(mMockOneHandedManagerImpl, times(1)).setOneHandedEnabled(true);
+        verify(mOneHandedController).setOneHandedEnabled(true);
     }
 
     @Test
     public void tesSettingsObserver_updateTimeout() {
-        // Bypass test if device not support one-handed mode
-        if (!mIsSupportOneHandedMode) {
-            return;
-        }
         Settings.Secure.putInt(mContext.getContentResolver(),
                 Settings.Secure.ONE_HANDED_MODE_TIMEOUT,
                 OneHandedSettingsUtil.ONE_HANDED_TIMEOUT_MEDIUM_IN_SECONDS);
@@ -168,27 +110,27 @@
                 OneHandedSettingsUtil.ONE_HANDED_TIMEOUT_MEDIUM_IN_SECONDS);
     }
 
+    @Test
+    public void tesSettingsObserver_updateSwipeToNotification() {
+        Settings.Secure.putInt(mContext.getContentResolver(),
+                Settings.Secure.SWIPE_BOTTOM_TO_NOTIFICATION_ENABLED, 1);
+
+        verify(mOneHandedController).setSwipeToNotificationEnabled(true);
+    }
+
     @Ignore("Clarifying do not receive callback")
     @Test
     public void testKeyguardBouncerShowing_shouldStopOneHanded() {
-        // Bypass test if device not support one-handed mode
-        if (!mIsSupportOneHandedMode) {
-            return;
-        }
         mKeyguardUpdateMonitor.sendKeyguardBouncerChanged(true);
 
-        verify(mMockOneHandedManagerImpl, times(1)).stopOneHanded();
+        verify(mOneHandedController).stopOneHanded();
     }
 
     @Test
     public void testScreenTurningOff_shouldStopOneHanded() {
-        // Bypass test if device not support one-handed mode
-        if (!mIsSupportOneHandedMode) {
-            return;
-        }
         mScreenLifecycle.dispatchScreenTurningOff();
 
-        verify(mMockOneHandedManagerImpl, times(1)).stopOneHanded();
+        verify(mOneHandedController).stopOneHanded();
     }
 
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/pip/PipAnimationControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/pip/PipAnimationControllerTest.java
index c9c1111..f0066ba 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/pip/PipAnimationControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/pip/PipAnimationControllerTest.java
@@ -16,7 +16,7 @@
 
 package com.android.systemui.pip;
 
-import static com.android.systemui.pip.PipAnimationController.TRANSITION_DIRECTION_TO_FULLSCREEN;
+import static com.android.systemui.pip.PipAnimationController.TRANSITION_DIRECTION_LEAVE_PIP;
 import static com.android.systemui.pip.PipAnimationController.TRANSITION_DIRECTION_TO_PIP;
 
 import static org.junit.Assert.assertEquals;
@@ -116,9 +116,9 @@
 
         animator = mPipAnimationController
                 .getAnimator(mLeash, new Rect(), 0f, 1f)
-                .setTransitionDirection(TRANSITION_DIRECTION_TO_FULLSCREEN);
+                .setTransitionDirection(TRANSITION_DIRECTION_LEAVE_PIP);
         assertEquals("Transition to fullscreen mode",
-                animator.getTransitionDirection(), TRANSITION_DIRECTION_TO_FULLSCREEN);
+                animator.getTransitionDirection(), TRANSITION_DIRECTION_LEAVE_PIP);
     }
 
     @Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/pip/PipBoundsHandlerTest.java b/packages/SystemUI/tests/src/com/android/systemui/pip/PipBoundsHandlerTest.java
index e9d2b73..cdb1770 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/pip/PipBoundsHandlerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/pip/PipBoundsHandlerTest.java
@@ -19,7 +19,6 @@
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
-import static org.mockito.Mockito.mock;
 
 import android.content.ComponentName;
 import android.graphics.Rect;
@@ -33,7 +32,6 @@
 import androidx.test.filters.SmallTest;
 
 import com.android.systemui.SysuiTestCase;
-import com.android.wm.shell.common.DisplayController;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -65,8 +63,7 @@
     @Before
     public void setUp() throws Exception {
         initializeMockResources();
-        mPipBoundsHandler = new PipBoundsHandler(mContext, new PipSnapAlgorithm(mContext),
-                mock(DisplayController.class));
+        mPipBoundsHandler = new PipBoundsHandler(mContext);
         mTestComponentName1 = new ComponentName(mContext, "component1");
         mTestComponentName2 = new ComponentName(mContext, "component2");
 
@@ -113,7 +110,7 @@
         res.addOverride(com.android.internal.R.dimen.config_pictureInPictureDefaultAspectRatio,
                 newDefaultAspectRatio);
 
-        mPipBoundsHandler.onConfigurationChanged();
+        mPipBoundsHandler.onConfigurationChanged(mContext);
 
         assertEquals("Default aspect ratio should be reloaded",
                 mPipBoundsHandler.getDefaultAspectRatio(), newDefaultAspectRatio,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/pip/phone/PipTouchHandlerTest.java b/packages/SystemUI/tests/src/com/android/systemui/pip/phone/PipTouchHandlerTest.java
index 96bb521..c8d4aca 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/pip/phone/PipTouchHandlerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/pip/phone/PipTouchHandlerTest.java
@@ -37,6 +37,7 @@
 import com.android.systemui.pip.PipBoundsHandler;
 import com.android.systemui.pip.PipSnapAlgorithm;
 import com.android.systemui.pip.PipTaskOrganizer;
+import com.android.systemui.pip.PipUiEventLogger;
 import com.android.systemui.shared.system.InputConsumerController;
 import com.android.systemui.util.DeviceConfigProxy;
 import com.android.systemui.util.FloatingContentCoordinator;
@@ -71,9 +72,6 @@
     private InputConsumerController mInputConsumerController;
 
     @Mock
-    private PipBoundsHandler mPipBoundsHandler;
-
-    @Mock
     private PipTaskOrganizer mPipTaskOrganizer;
 
     @Mock
@@ -85,6 +83,10 @@
     @Mock
     private SysUiState mSysUiState;
 
+    @Mock
+    private PipUiEventLogger mPipUiEventLogger;
+
+    private PipBoundsHandler mPipBoundsHandler;
     private PipSnapAlgorithm mPipSnapAlgorithm;
     private PipMotionHelper mMotionHelper;
     private PipResizeGestureHandler mPipResizeGestureHandler;
@@ -100,11 +102,13 @@
     @Before
     public void setUp() throws Exception {
         MockitoAnnotations.initMocks(this);
+        mPipBoundsHandler = new PipBoundsHandler(mContext);
+        mPipSnapAlgorithm = mPipBoundsHandler.getSnapAlgorithm();
         mPipSnapAlgorithm = new PipSnapAlgorithm(mContext);
         mPipTouchHandler = new PipTouchHandler(mContext, mActivityManager,
                 mPipMenuActivityController, mInputConsumerController, mPipBoundsHandler,
-                mPipTaskOrganizer, mFloatingContentCoordinator, mDeviceConfigProxy,
-                mPipSnapAlgorithm, mSysUiState);
+                mPipTaskOrganizer, mFloatingContentCoordinator, mDeviceConfigProxy, mSysUiState,
+                mPipUiEventLogger);
         mMotionHelper = Mockito.spy(mPipTouchHandler.getMotionHelper());
         mPipResizeGestureHandler = Mockito.spy(mPipTouchHandler.getPipResizeGestureHandler());
         mPipTouchHandler.setPipMotionHelper(mMotionHelper);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/power/PowerUITest.java b/packages/SystemUI/tests/src/com/android/systemui/power/PowerUITest.java
index 548da8e..3562032 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/power/PowerUITest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/power/PowerUITest.java
@@ -384,7 +384,7 @@
         mPowerUI.mSevereWarningShownThisChargeCycle = false;
         BatteryStateSnapshotWrapper state = new BatteryStateSnapshotWrapper();
 
-        // sanity check to make sure we can show for a valid config
+        // readiness check to make sure we can show for a valid config
         state.mBatteryLevel = 10;
         state.mTimeRemainingMillis = Duration.ofHours(2).toMillis();
         boolean shouldShow = mPowerUI.shouldShowHybridWarning(state.get());
@@ -449,7 +449,7 @@
         mPowerUI.mSevereWarningShownThisChargeCycle = false;
         BatteryStateSnapshotWrapper state = new BatteryStateSnapshotWrapper();
 
-        // sanity check to make sure we can show for a valid config
+        // readiness check to make sure we can show for a valid config
         state.mBatteryLevel = 1;
         state.mTimeRemainingMillis = Duration.ofMinutes(1).toMillis();
         boolean shouldShow = mPowerUI.shouldShowHybridWarning(state.get());
@@ -572,7 +572,7 @@
         state.mIsHybrid = false;
         BatteryStateSnapshot lastState = state.get();
 
-        // sanity check to make sure we can show for a valid config
+        // readiness check to make sure we can show for a valid config
         state.mBatteryLevel = 10;
         state.mBucket = -1;
         boolean shouldShow = mPowerUI.shouldShowLowBatteryWarning(state.get(), lastState);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/privacy/PrivacyItemControllerFlagsTest.kt b/packages/SystemUI/tests/src/com/android/systemui/privacy/PrivacyItemControllerFlagsTest.kt
new file mode 100644
index 0000000..4ba29e6
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/privacy/PrivacyItemControllerFlagsTest.kt
@@ -0,0 +1,219 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.privacy
+
+import android.os.UserManager
+import android.provider.DeviceConfig
+import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
+import com.android.internal.config.sysui.SystemUiDeviceConfigFlags
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.appops.AppOpsController
+import com.android.systemui.broadcast.BroadcastDispatcher
+import com.android.systemui.dump.DumpManager
+import com.android.systemui.util.DeviceConfigProxy
+import com.android.systemui.util.DeviceConfigProxyFake
+import com.android.systemui.util.concurrency.FakeExecutor
+import com.android.systemui.util.time.FakeSystemClock
+import org.junit.Assert.assertFalse
+import org.junit.Assert.assertTrue
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentCaptor
+import org.mockito.Mock
+import org.mockito.Mockito
+import org.mockito.Mockito.anyBoolean
+import org.mockito.Mockito.atLeastOnce
+import org.mockito.Mockito.never
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+
+@RunWith(AndroidTestingRunner::class)
+@SmallTest
+class PrivacyItemControllerFlagsTest : SysuiTestCase() {
+    companion object {
+        fun <T> capture(argumentCaptor: ArgumentCaptor<T>): T = argumentCaptor.capture()
+        fun <T> eq(value: T): T = Mockito.eq(value) ?: value
+        fun <T> any(): T = Mockito.any<T>()
+
+        private const val ALL_INDICATORS =
+                SystemUiDeviceConfigFlags.PROPERTY_PERMISSIONS_HUB_ENABLED
+        private const val MIC_CAMERA = SystemUiDeviceConfigFlags.PROPERTY_MIC_CAMERA_ENABLED
+    }
+
+    @Mock
+    private lateinit var appOpsController: AppOpsController
+    @Mock
+    private lateinit var callback: PrivacyItemController.Callback
+    @Mock
+    private lateinit var userManager: UserManager
+    @Mock
+    private lateinit var broadcastDispatcher: BroadcastDispatcher
+    @Mock
+    private lateinit var dumpManager: DumpManager
+
+    private lateinit var privacyItemController: PrivacyItemController
+    private lateinit var executor: FakeExecutor
+    private lateinit var deviceConfigProxy: DeviceConfigProxy
+
+    fun PrivacyItemController(): PrivacyItemController {
+        return PrivacyItemController(
+                appOpsController,
+                executor,
+                executor,
+                broadcastDispatcher,
+                deviceConfigProxy,
+                userManager,
+                dumpManager
+        )
+    }
+
+    @Before
+    fun setup() {
+        MockitoAnnotations.initMocks(this)
+        executor = FakeExecutor(FakeSystemClock())
+        deviceConfigProxy = DeviceConfigProxyFake()
+
+        privacyItemController = PrivacyItemController()
+        privacyItemController.addCallback(callback)
+
+        executor.runAllReady()
+    }
+
+    @Test
+    fun testNotListeningByDefault() {
+        assertFalse(privacyItemController.allIndicatorsAvailable)
+        assertFalse(privacyItemController.micCameraAvailable)
+
+        verify(appOpsController, never()).addCallback(any(), any())
+    }
+
+    @Test
+    fun testMicCameraChanged() {
+        changeMicCamera(true)
+        executor.runAllReady()
+
+        verify(callback).onFlagMicCameraChanged(true)
+        verify(callback, never()).onFlagAllChanged(anyBoolean())
+
+        assertTrue(privacyItemController.micCameraAvailable)
+        assertFalse(privacyItemController.allIndicatorsAvailable)
+    }
+
+    @Test
+    fun testAllChanged() {
+        changeAll(true)
+        executor.runAllReady()
+
+        verify(callback).onFlagAllChanged(true)
+        verify(callback, never()).onFlagMicCameraChanged(anyBoolean())
+
+        assertTrue(privacyItemController.allIndicatorsAvailable)
+        assertFalse(privacyItemController.micCameraAvailable)
+    }
+
+    @Test
+    fun testBothChanged() {
+        changeAll(true)
+        changeMicCamera(true)
+        executor.runAllReady()
+
+        verify(callback, atLeastOnce()).onFlagAllChanged(true)
+        verify(callback, atLeastOnce()).onFlagMicCameraChanged(true)
+
+        assertTrue(privacyItemController.allIndicatorsAvailable)
+        assertTrue(privacyItemController.micCameraAvailable)
+    }
+
+    @Test
+    fun testAll_listeningToAll() {
+        changeAll(true)
+        executor.runAllReady()
+
+        verify(appOpsController).addCallback(eq(PrivacyItemController.OPS), any())
+    }
+
+    @Test
+    fun testMicCamera_listening() {
+        changeMicCamera(true)
+        executor.runAllReady()
+
+        verify(appOpsController).addCallback(eq(PrivacyItemController.OPS), any())
+    }
+
+    @Test
+    fun testAll_listening() {
+        changeAll(true)
+        executor.runAllReady()
+
+        verify(appOpsController).addCallback(eq(PrivacyItemController.OPS), any())
+    }
+
+    @Test
+    fun testAllFalse_notListening() {
+        changeAll(true)
+        executor.runAllReady()
+        changeAll(false)
+        executor.runAllReady()
+
+        verify(appOpsController).removeCallback(any(), any())
+    }
+
+    @Test
+    fun testSomeListening_stillListening() {
+        changeAll(true)
+        changeMicCamera(true)
+        executor.runAllReady()
+        changeAll(false)
+        executor.runAllReady()
+
+        verify(appOpsController, never()).removeCallback(any(), any())
+    }
+
+    @Test
+    fun testAllDeleted_stopListening() {
+        changeAll(true)
+        executor.runAllReady()
+        changeAll(null)
+        executor.runAllReady()
+
+        verify(appOpsController).removeCallback(any(), any())
+    }
+
+    @Test
+    fun testMicDeleted_stopListening() {
+        changeMicCamera(true)
+        executor.runAllReady()
+        changeMicCamera(null)
+        executor.runAllReady()
+
+        verify(appOpsController).removeCallback(any(), any())
+    }
+
+    private fun changeMicCamera(value: Boolean?) = changeProperty(MIC_CAMERA, value)
+    private fun changeAll(value: Boolean?) = changeProperty(ALL_INDICATORS, value)
+
+    private fun changeProperty(name: String, value: Boolean?) {
+        deviceConfigProxy.setProperty(
+                DeviceConfig.NAMESPACE_PRIVACY,
+                name,
+                value?.toString(),
+                false
+        )
+    }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/tests/src/com/android/systemui/privacy/PrivacyItemControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/privacy/PrivacyItemControllerTest.kt
index dddc350..fb42baa 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/privacy/PrivacyItemControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/privacy/PrivacyItemControllerTest.kt
@@ -18,7 +18,6 @@
 
 import android.app.ActivityManager
 import android.app.AppOpsManager
-import android.content.Context
 import android.content.Intent
 import android.content.pm.UserInfo
 import android.os.UserHandle
@@ -69,10 +68,11 @@
     companion object {
         val CURRENT_USER_ID = ActivityManager.getCurrentUser()
         val TEST_UID = CURRENT_USER_ID * UserHandle.PER_USER_RANGE
-        const val SYSTEM_UID = 1000
         const val TEST_PACKAGE_NAME = "test"
-        const val DEVICE_SERVICES_STRING = "Device services"
-        const val TAG = "PrivacyItemControllerTest"
+
+        private const val ALL_INDICATORS =
+                SystemUiDeviceConfigFlags.PROPERTY_PERMISSIONS_HUB_ENABLED
+        private const val MIC_CAMERA = SystemUiDeviceConfigFlags.PROPERTY_MIC_CAMERA_ENABLED
         fun <T> capture(argumentCaptor: ArgumentCaptor<T>): T = argumentCaptor.capture()
         fun <T> eq(value: T): T = Mockito.eq(value) ?: value
         fun <T> any(): T = Mockito.any<T>()
@@ -97,9 +97,8 @@
     private lateinit var executor: FakeExecutor
     private lateinit var deviceConfigProxy: DeviceConfigProxy
 
-    fun PrivacyItemController(context: Context): PrivacyItemController {
+    fun PrivacyItemController(): PrivacyItemController {
         return PrivacyItemController(
-                context,
                 appOpsController,
                 executor,
                 executor,
@@ -116,11 +115,8 @@
         executor = FakeExecutor(FakeSystemClock())
         deviceConfigProxy = DeviceConfigProxyFake()
 
-        appOpsController = mDependency.injectMockDependency(AppOpsController::class.java)
-
-        deviceConfigProxy.setProperty(DeviceConfig.NAMESPACE_PRIVACY,
-                SystemUiDeviceConfigFlags.PROPERTY_PERMISSIONS_HUB_ENABLED,
-                "true", false)
+        // Listen to everything by default
+        changeAll(true)
 
         doReturn(listOf(object : UserInfo() {
             init {
@@ -128,7 +124,7 @@
             }
         })).`when`(userManager).getProfiles(anyInt())
 
-        privacyItemController = PrivacyItemController(mContext)
+        privacyItemController = PrivacyItemController()
     }
 
     @Test
@@ -276,15 +272,59 @@
 
     @Test
     fun testNotListeningWhenIndicatorsDisabled() {
-        deviceConfigProxy.setProperty(
-                DeviceConfig.NAMESPACE_PRIVACY,
-                SystemUiDeviceConfigFlags.PROPERTY_PERMISSIONS_HUB_ENABLED,
-                "false",
-                false
-        )
+        changeAll(false)
         privacyItemController.addCallback(callback)
         executor.runAllReady()
         verify(appOpsController, never()).addCallback(eq(PrivacyItemController.OPS),
                 any())
     }
+
+    @Test
+    fun testNotSendingLocationWhenOnlyMicCamera() {
+        changeAll(false)
+        changeMicCamera(true)
+        executor.runAllReady()
+
+        doReturn(listOf(AppOpItem(AppOpsManager.OP_CAMERA, TEST_UID, "", 0),
+                AppOpItem(AppOpsManager.OP_COARSE_LOCATION, TEST_UID, "", 0)))
+                .`when`(appOpsController).getActiveAppOpsForUser(anyInt())
+
+        privacyItemController.addCallback(callback)
+        executor.runAllReady()
+
+        verify(callback).onPrivacyItemsChanged(capture(argCaptor))
+
+        assertEquals(1, argCaptor.value.size)
+        assertEquals(PrivacyType.TYPE_CAMERA, argCaptor.value[0].privacyType)
+    }
+
+    @Test
+    fun testNotUpdated_LocationChangeWhenOnlyMicCamera() {
+        doReturn(listOf(AppOpItem(AppOpsManager.OP_COARSE_LOCATION, TEST_UID, "", 0)))
+                .`when`(appOpsController).getActiveAppOpsForUser(anyInt())
+
+        privacyItemController.addCallback(callback)
+        changeAll(false)
+        changeMicCamera(true)
+        executor.runAllReady()
+        reset(callback) // Clean callback
+
+        verify(appOpsController).addCallback(any(), capture(argCaptorCallback))
+        argCaptorCallback.value.onActiveStateChanged(
+                AppOpsManager.OP_FINE_LOCATION, TEST_UID, TEST_PACKAGE_NAME, true)
+
+        verify(callback, never()).onPrivacyItemsChanged(any())
+    }
+
+    private fun changeMicCamera(value: Boolean?) = changeProperty(MIC_CAMERA, value)
+    private fun changeAll(value: Boolean?) = changeProperty(ALL_INDICATORS, value)
+
+    private fun changeProperty(name: String, value: Boolean?) {
+        deviceConfigProxy.setProperty(
+                DeviceConfig.NAMESPACE_PRIVACY,
+                name,
+                value?.toString(),
+                false
+        )
+    }
 }
\ No newline at end of file
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSFragmentTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/QSFragmentTest.java
index d338cbf..b46c6ef 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSFragmentTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSFragmentTest.java
@@ -150,7 +150,10 @@
         return new QSFragment(
                 new RemoteInputQuickSettingsDisabler(context, mock(ConfigurationController.class),
                         commandQueue),
-                new InjectionInflationController(SystemUIFactory.getInstance().getRootComponent()),
+                new InjectionInflationController(
+                        SystemUIFactory.getInstance()
+                                .getSysUIComponent()
+                                .createViewInstanceCreatorFactory()),
                 mock(QSTileHost.class),
                 mock(StatusBarStateController.class),
                 commandQueue,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSTileHostTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/QSTileHostTest.java
index bdb7166..c8e1a74 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSTileHostTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSTileHostTest.java
@@ -78,7 +78,7 @@
 
 @RunWith(AndroidTestingRunner.class)
 @SmallTest
-@RunWithLooper
+@RunWithLooper(setAsMainLooper = true)
 public class QSTileHostTest extends SysuiTestCase {
 
     private static String MOCK_STATE_STRING = "MockState";
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/external/CustomTileTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/external/CustomTileTest.kt
index c2579dd..3aa40de 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/external/CustomTileTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/external/CustomTileTest.kt
@@ -53,7 +53,7 @@
 
 @SmallTest
 @RunWith(AndroidTestingRunner::class)
-@TestableLooper.RunWithLooper
+@TestableLooper.RunWithLooper(setAsMainLooper = true)
 class CustomTileTest : SysuiTestCase() {
 
     companion object {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSTileImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSTileImplTest.java
index 103e558..61a0d6c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSTileImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSTileImplTest.java
@@ -74,7 +74,7 @@
 import org.mockito.MockitoAnnotations;
 
 @RunWith(AndroidTestingRunner.class)
-@RunWithLooper
+@RunWithLooper(setAsMainLooper = true)
 @SmallTest
 public class QSTileImplTest extends SysuiTestCase {
 
@@ -244,6 +244,8 @@
         assertNotEquals(DESTROYED, mTile.getLifecycle().getCurrentState());
         mTile.handleDestroy();
 
+        mTestableLooper.processAllMessages();
+
         assertEquals(DESTROYED, mTile.getLifecycle().getCurrentState());
     }
 
@@ -298,6 +300,25 @@
         assertNotEquals(DESTROYED, mTile.getLifecycle().getCurrentState());
     }
 
+    @Test
+    public void testRefreshStateAfterDestroyedDoesNotCrash() {
+        mTile.destroy();
+        mTile.refreshState();
+
+        mTestableLooper.processAllMessages();
+    }
+
+    @Test
+    public void testSetListeningAfterDestroyedDoesNotCrash() {
+        Object o = new Object();
+        mTile.destroy();
+
+        mTile.setListening(o, true);
+        mTile.setListening(o, false);
+
+        mTestableLooper.processAllMessages();
+    }
+
     private void assertEvent(UiEventLogger.UiEventEnum eventType,
             UiEventLoggerFake.FakeUiEvent fakeEvent) {
         assertEquals(eventType.getId(), fakeEvent.eventId);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/BatterySaverTileTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/BatterySaverTileTest.kt
index f70106a..2006a75 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/BatterySaverTileTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/BatterySaverTileTest.kt
@@ -38,7 +38,7 @@
 import org.mockito.MockitoAnnotations
 
 @RunWith(AndroidTestingRunner::class)
-@RunWithLooper
+@RunWithLooper(setAsMainLooper = true)
 @SmallTest
 class BatterySaverTileTest : SysuiTestCase() {
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/CastTileTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/CastTileTest.java
index 8ece622..5d14898 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/CastTileTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/CastTileTest.java
@@ -58,7 +58,7 @@
 
 
 @RunWith(AndroidTestingRunner.class)
-@TestableLooper.RunWithLooper
+@TestableLooper.RunWithLooper(setAsMainLooper = true)
 @SmallTest
 public class CastTileTest extends SysuiTestCase {
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/ScreenRecordTileTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/ScreenRecordTileTest.java
index 2d276bb..6b54791 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/ScreenRecordTileTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/ScreenRecordTileTest.java
@@ -47,7 +47,7 @@
 import org.mockito.MockitoAnnotations;
 
 @RunWith(AndroidTestingRunner.class)
-@TestableLooper.RunWithLooper()
+@TestableLooper.RunWithLooper(setAsMainLooper = true)
 @SmallTest
 public class ScreenRecordTileTest extends SysuiTestCase {
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScreenshotNotificationSmartActionsTest.java b/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScreenshotNotificationSmartActionsTest.java
index 184329ec..e23f926 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScreenshotNotificationSmartActionsTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScreenshotNotificationSmartActionsTest.java
@@ -86,7 +86,7 @@
         CompletableFuture<List<Notification.Action>> smartActionsFuture =
                 mScreenshotSmartActions.getSmartActionsFuture(
                         "", Uri.parse("content://authority/data"), bitmap, smartActionsProvider,
-                        true, UserHandle.getUserHandleForUid(UserHandle.myUserId()));
+                        true, UserHandle.of(UserHandle.myUserId()));
         assertNotNull(smartActionsFuture);
         List<Notification.Action> smartActions = smartActionsFuture.get(5, TimeUnit.MILLISECONDS);
         assertEquals(Collections.emptyList(), smartActions);
@@ -126,7 +126,7 @@
         CompletableFuture<List<Notification.Action>> smartActionsFuture =
                 mScreenshotSmartActions.getSmartActionsFuture(
                         "", Uri.parse("content://autority/data"), bitmap, mSmartActionsProvider,
-                        true, UserHandle.getUserHandleForUid(UserHandle.myUserId()));
+                        true, UserHandle.of(UserHandle.myUserId()));
         verify(mSmartActionsProvider, never()).getActions(any(), any(), any(), any(), any());
         assertNotNull(smartActionsFuture);
         List<Notification.Action> smartActions = smartActionsFuture.get(5, TimeUnit.MILLISECONDS);
@@ -140,7 +140,7 @@
         when(bitmap.getConfig()).thenReturn(Bitmap.Config.HARDWARE);
         mScreenshotSmartActions.getSmartActionsFuture(
                 "", Uri.parse("content://autority/data"), bitmap, mSmartActionsProvider, true,
-                UserHandle.getUserHandleForUid(UserHandle.myUserId()));
+                UserHandle.of(UserHandle.myUserId()));
         verify(mSmartActionsProvider, times(1)).getActions(any(), any(), any(), any(), any());
     }
 
@@ -156,7 +156,7 @@
         CompletableFuture<List<Notification.Action>> smartActionsFuture =
                 mScreenshotSmartActions.getSmartActionsFuture("", null, bitmap,
                         actionsProvider,
-                        true, UserHandle.getUserHandleForUid(UserHandle.myUserId()));
+                        true, UserHandle.of(UserHandle.myUserId()));
         assertNotNull(smartActionsFuture);
         List<Notification.Action> smartActions = smartActionsFuture.get(5, TimeUnit.MILLISECONDS);
         assertEquals(smartActions.size(), 0);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScrollCaptureTest.java b/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScrollCaptureTest.java
new file mode 100644
index 0000000..e7ef64e
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScrollCaptureTest.java
@@ -0,0 +1,101 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.screenshot;
+
+import static org.junit.Assert.fail;
+
+import android.content.Intent;
+import android.graphics.Point;
+import android.graphics.Rect;
+import android.os.RemoteException;
+import android.testing.AndroidTestingRunner;
+import android.util.Log;
+import android.view.Display;
+import android.view.IScrollCaptureClient;
+import android.view.IScrollCaptureController;
+import android.view.IWindowManager;
+import android.view.WindowManagerGlobal;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import com.android.systemui.SysuiTestCase;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Tests the of internal framework Scroll Capture API from SystemUI.
+ */
+@RunWith(AndroidTestingRunner.class)
+@SmallTest
+public class ScrollCaptureTest extends SysuiTestCase {
+    private static final String TAG = "ScrollCaptureTest";
+
+    /**
+     * Verifies that a request traverses from SystemUI, to WindowManager and to the app process and
+     * is returned without error. Device must be unlocked.
+     */
+    @Test
+    public void testBasicOperation() throws InterruptedException {
+        IWindowManager wms = WindowManagerGlobal.getWindowManagerService();
+
+        // Start an activity to be on top that will be targeted
+        InstrumentationRegistry.getInstrumentation().startActivitySync(
+                new Intent(mContext, ScrollViewActivity.class).addFlags(
+                        Intent.FLAG_ACTIVITY_NEW_TASK));
+
+        final CountDownLatch latch = new CountDownLatch(1);
+        try {
+            wms.requestScrollCapture(Display.DEFAULT_DISPLAY, null, -1,
+                    new IScrollCaptureController.Stub() {
+                        @Override
+                        public void onClientConnected(
+                                IScrollCaptureClient client, Rect scrollBounds,
+                                Point positionInWindow) {
+                            Log.d(TAG,
+                                    "client connected: " + client + "[scrollBounds= " + scrollBounds
+                                            + ", positionInWindow=" + positionInWindow + "]");
+                            latch.countDown();
+                        }
+
+                        @Override
+                        public void onClientUnavailable() {
+                        }
+
+                        @Override
+                        public void onCaptureStarted() {
+                        }
+
+                        @Override
+                        public void onCaptureBufferSent(long frameNumber, Rect capturedArea) {
+                        }
+
+                        @Override
+                        public void onConnectionClosed() {
+                        }
+                    });
+        } catch (RemoteException e) {
+            Log.e(TAG, "request failed", e);
+            fail("caught remote exception " + e);
+        }
+        latch.await(1000, TimeUnit.MILLISECONDS);
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScrollViewActivity.java b/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScrollViewActivity.java
new file mode 100644
index 0000000..bd37259
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScrollViewActivity.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.screenshot;
+
+import android.app.Activity;
+import android.os.Bundle;
+import android.util.TypedValue;
+import android.widget.LinearLayout;
+import android.widget.ScrollView;
+import android.widget.TextView;
+
+import androidx.annotation.Nullable;
+
+public class ScrollViewActivity extends Activity {
+    @Override
+    protected void onCreate(@Nullable Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        ScrollView scrollView = new ScrollView(this);
+        LinearLayout linearLayout = new LinearLayout(this);
+        linearLayout.setOrientation(LinearLayout.VERTICAL);
+        TextView text = new TextView(this);
+        text.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 40);
+        text.setText(com.android.systemui.R.string.test_content);
+        linearLayout.addView(text);
+        scrollView.addView(linearLayout);
+        setContentView(scrollView);
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NonPhoneDependencyTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NonPhoneDependencyTest.java
index 644ed3d..8089561 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NonPhoneDependencyTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NonPhoneDependencyTest.java
@@ -34,7 +34,6 @@
 import com.android.systemui.statusbar.notification.row.NotificationGutsManager.OnSettingsClickListener;
 import com.android.systemui.statusbar.notification.row.NotificationInfo.CheckSaveListener;
 import com.android.systemui.statusbar.notification.stack.NotificationListContainer;
-import com.android.systemui.statusbar.phone.NotificationShadeWindowController;
 import com.android.systemui.statusbar.phone.ShadeController;
 import com.android.systemui.statusbar.policy.HeadsUpManager;
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationShadeDepthControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationShadeDepthControllerTest.kt
index 56df193..a36a4c4 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationShadeDepthControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationShadeDepthControllerTest.kt
@@ -29,7 +29,6 @@
 import com.android.systemui.statusbar.notification.ActivityLaunchAnimator
 import com.android.systemui.statusbar.phone.BiometricUnlockController
 import com.android.systemui.statusbar.phone.DozeParameters
-import com.android.systemui.statusbar.phone.NotificationShadeWindowController
 import com.android.systemui.statusbar.policy.KeyguardStateController
 import com.android.systemui.util.mockito.eq
 import org.junit.Before
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationViewHierarchyManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationViewHierarchyManagerTest.java
index 6f46923..1259d28 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationViewHierarchyManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationViewHierarchyManagerTest.java
@@ -19,12 +19,10 @@
 import static junit.framework.Assert.assertTrue;
 
 import static org.junit.Assert.assertEquals;
-import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.Mockito.clearInvocations;
 import static org.mockito.Mockito.doAnswer;
 import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
@@ -46,9 +44,9 @@
 import com.android.systemui.statusbar.notification.DynamicPrivacyController;
 import com.android.systemui.statusbar.notification.NotificationActivityStarter;
 import com.android.systemui.statusbar.notification.NotificationEntryManager;
-import com.android.systemui.statusbar.notification.VisualStabilityManager;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 import com.android.systemui.statusbar.notification.collection.inflation.LowPriorityInflationHelper;
+import com.android.systemui.statusbar.notification.collection.legacy.VisualStabilityManager;
 import com.android.systemui.statusbar.notification.logging.NotificationLogger;
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
 import com.android.systemui.statusbar.notification.row.ExpandableView;
@@ -212,19 +210,6 @@
     }
 
     @Test
-    public void testUpdateNotificationViews_appOps() throws Exception {
-        NotificationEntry entry0 = createEntry();
-        entry0.setRow(spy(entry0.getRow()));
-        when(mEntryManager.getVisibleNotifications()).thenReturn(
-                Lists.newArrayList(entry0));
-        mListContainer.addContainerView(entry0.getRow());
-
-        mViewHierarchyManager.updateNotificationViews();
-
-        verify(entry0.getRow(), times(1)).showAppOpsIcons(any());
-    }
-
-    @Test
     public void testReentrantCallsToOnDynamicPrivacyChangedPostForLater() {
         // GIVEN a ListContainer that will make a re-entrant call to updateNotificationViews()
         mMadeReentrantCall = false;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/AssistantFeedbackControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/AssistantFeedbackControllerTest.java
index 619d244..fb8c3d9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/AssistantFeedbackControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/AssistantFeedbackControllerTest.java
@@ -57,8 +57,8 @@
 
     @Before
     public void setUp() {
-        switchSetting(ON);
         mAssistantFeedbackController = new AssistantFeedbackController(mContext);
+        switchSetting(ON);
         mSbn = new StatusBarNotification(TEST_PACKAGE_NAME, TEST_PACKAGE_NAME,
                 0, null, TEST_UID, 0, new Notification(),
                 UserHandle.CURRENT, null, 0);
@@ -72,7 +72,6 @@
 
     @Test
     public void testUserControls_settingEnabled() {
-        switchSetting(ON);
         assertTrue(mAssistantFeedbackController.isFeedbackEnabled());
     }
 
@@ -113,7 +112,8 @@
     }
 
     private void switchSetting(int setting) {
-        Settings.Secure.putIntForUser(mContext.getContentResolver(),
-                Settings.Secure.NOTIFICATION_FEEDBACK_ENABLED, setting, UserHandle.USER_CURRENT);
+        Settings.Global.putInt(mContext.getContentResolver(),
+                Settings.Global.NOTIFICATION_FEEDBACK_ENABLED, setting);
+        mAssistantFeedbackController.update(null);
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/VisualStabilityManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/VisualStabilityManagerTest.java
index ea1b498..baeedcf 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/VisualStabilityManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/VisualStabilityManagerTest.java
@@ -32,14 +32,17 @@
 import androidx.test.filters.SmallTest;
 
 import com.android.systemui.SysuiTestCase;
-import com.android.systemui.statusbar.NotificationPresenter;
+import com.android.systemui.keyguard.WakefulnessLifecycle;
+import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder;
+import com.android.systemui.statusbar.notification.collection.legacy.VisualStabilityManager;
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
 
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
 
 @SmallTest
 @RunWith(AndroidTestingRunner.class)
@@ -54,109 +57,126 @@
     private ExpandableNotificationRow mRow = mock(ExpandableNotificationRow.class);
     private NotificationEntry mEntry;
 
+    private StatusBarStateController.StateListener mStatusBarStateListener;
+    private WakefulnessLifecycle.Observer mWakefulnessObserver;
+
     @Before
     public void setUp() {
+        StatusBarStateController statusBarStateController = mock(StatusBarStateController.class);
+        WakefulnessLifecycle wakefulnessLifecycle = mock(WakefulnessLifecycle.class);
+
         mTestableLooper = TestableLooper.get(this);
         mVisualStabilityManager = new VisualStabilityManager(
                 mock(NotificationEntryManager.class),
-                new Handler(mTestableLooper.getLooper()));
+                new Handler(mTestableLooper.getLooper()),
+                statusBarStateController,
+                wakefulnessLifecycle);
 
-        mVisualStabilityManager.setUpWithPresenter(mock(NotificationPresenter.class));
         mVisualStabilityManager.setVisibilityLocationProvider(mLocationProvider);
         mEntry = new NotificationEntryBuilder().build();
         mEntry.setRow(mRow);
 
         when(mRow.getEntry()).thenReturn(mEntry);
+
+        ArgumentCaptor<StatusBarStateController.StateListener> stateListenerCaptor =
+                ArgumentCaptor.forClass(StatusBarStateController.StateListener.class);
+        verify(statusBarStateController).addCallback(stateListenerCaptor.capture());
+        mStatusBarStateListener = stateListenerCaptor.getValue();
+
+        ArgumentCaptor<WakefulnessLifecycle.Observer> wakefulnessObserverCaptor =
+                ArgumentCaptor.forClass(WakefulnessLifecycle.Observer.class);
+        verify(wakefulnessLifecycle).addObserver(wakefulnessObserverCaptor.capture());
+        mWakefulnessObserver = wakefulnessObserverCaptor.getValue();
     }
 
     @Test
     public void testPanelExpansion() {
-        mVisualStabilityManager.setPanelExpanded(true);
-        mVisualStabilityManager.setScreenOn(true);
+        setPanelExpanded(true);
+        setScreenOn(true);
         assertFalse(mVisualStabilityManager.canReorderNotification(mRow));
-        mVisualStabilityManager.setPanelExpanded(false);
+        setPanelExpanded(false);
         assertTrue(mVisualStabilityManager.canReorderNotification(mRow));
     }
 
     @Test
     public void testScreenOn() {
-        mVisualStabilityManager.setPanelExpanded(true);
-        mVisualStabilityManager.setScreenOn(true);
+        setPanelExpanded(true);
+        setScreenOn(true);
         assertFalse(mVisualStabilityManager.canReorderNotification(mRow));
-        mVisualStabilityManager.setScreenOn(false);
+        setScreenOn(false);
         assertTrue(mVisualStabilityManager.canReorderNotification(mRow));
     }
 
     @Test
     public void testReorderingAllowedChangesScreenOn() {
-        mVisualStabilityManager.setPanelExpanded(true);
-        mVisualStabilityManager.setScreenOn(true);
+        setPanelExpanded(true);
+        setScreenOn(true);
         assertFalse(mVisualStabilityManager.isReorderingAllowed());
-        mVisualStabilityManager.setScreenOn(false);
+        setScreenOn(false);
         assertTrue(mVisualStabilityManager.isReorderingAllowed());
     }
 
     @Test
     public void testReorderingAllowedChangesPanel() {
-        mVisualStabilityManager.setPanelExpanded(true);
-        mVisualStabilityManager.setScreenOn(true);
+        setPanelExpanded(true);
+        setScreenOn(true);
         assertFalse(mVisualStabilityManager.isReorderingAllowed());
-        mVisualStabilityManager.setPanelExpanded(false);
+        setPanelExpanded(false);
         assertTrue(mVisualStabilityManager.isReorderingAllowed());
     }
 
     @Test
     public void testCallBackCalledScreenOn() {
-        mVisualStabilityManager.setPanelExpanded(true);
-        mVisualStabilityManager.setScreenOn(true);
+        setPanelExpanded(true);
+        setScreenOn(true);
         mVisualStabilityManager.addReorderingAllowedCallback(mCallback, false  /* persistent */);
-        mVisualStabilityManager.setScreenOn(false);
+        setScreenOn(false);
         verify(mCallback).onChangeAllowed();
     }
 
     @Test
     public void testCallBackCalledPanelExpanded() {
-        mVisualStabilityManager.setPanelExpanded(true);
-        mVisualStabilityManager.setScreenOn(true);
+        setPanelExpanded(true);
+        setScreenOn(true);
         mVisualStabilityManager.addReorderingAllowedCallback(mCallback, false  /* persistent */);
-        mVisualStabilityManager.setPanelExpanded(false);
+        setPanelExpanded(false);
         verify(mCallback).onChangeAllowed();
     }
 
     @Test
     public void testCallBackExactlyOnce() {
-        mVisualStabilityManager.setPanelExpanded(true);
-        mVisualStabilityManager.setScreenOn(true);
+        setPanelExpanded(true);
+        setScreenOn(true);
         mVisualStabilityManager.addReorderingAllowedCallback(mCallback, false  /* persistent */);
-        mVisualStabilityManager.setScreenOn(false);
-        mVisualStabilityManager.setScreenOn(true);
-        mVisualStabilityManager.setScreenOn(false);
+        setScreenOn(false);
+        setScreenOn(true);
+        setScreenOn(false);
         verify(mCallback).onChangeAllowed();
     }
 
     @Test
     public void testCallBackCalledContinuouslyWhenRequested() {
-        mVisualStabilityManager.setPanelExpanded(true);
-        mVisualStabilityManager.setScreenOn(true);
+        setPanelExpanded(true);
+        setScreenOn(true);
         mVisualStabilityManager.addReorderingAllowedCallback(mCallback, true  /* persistent */);
-        mVisualStabilityManager.setScreenOn(false);
-        mVisualStabilityManager.setScreenOn(true);
-        mVisualStabilityManager.setScreenOn(false);
+        setScreenOn(false);
+        setScreenOn(true);
+        setScreenOn(false);
         verify(mCallback, times(2)).onChangeAllowed();
     }
 
     @Test
     public void testAddedCanReorder() {
-        mVisualStabilityManager.setPanelExpanded(true);
-        mVisualStabilityManager.setScreenOn(true);
+        setPanelExpanded(true);
+        setScreenOn(true);
         mVisualStabilityManager.notifyViewAddition(mRow);
         assertTrue(mVisualStabilityManager.canReorderNotification(mRow));
     }
 
     @Test
     public void testReorderingVisibleHeadsUpNotAllowed() {
-        mVisualStabilityManager.setPanelExpanded(true);
-        mVisualStabilityManager.setScreenOn(true);
+        setPanelExpanded(true);
+        setScreenOn(true);
         when(mLocationProvider.isInVisibleLocation(any(NotificationEntry.class))).thenReturn(true);
         mVisualStabilityManager.onHeadsUpStateChanged(mEntry, true);
         assertFalse(mVisualStabilityManager.canReorderNotification(mRow));
@@ -164,8 +184,8 @@
 
     @Test
     public void testReorderingVisibleHeadsUpAllowed() {
-        mVisualStabilityManager.setPanelExpanded(true);
-        mVisualStabilityManager.setScreenOn(true);
+        setPanelExpanded(true);
+        setScreenOn(true);
         when(mLocationProvider.isInVisibleLocation(any(NotificationEntry.class))).thenReturn(false);
         mVisualStabilityManager.onHeadsUpStateChanged(mEntry, true);
         assertTrue(mVisualStabilityManager.canReorderNotification(mRow));
@@ -173,8 +193,8 @@
 
     @Test
     public void testReorderingVisibleHeadsUpAllowedOnce() {
-        mVisualStabilityManager.setPanelExpanded(true);
-        mVisualStabilityManager.setScreenOn(true);
+        setPanelExpanded(true);
+        setScreenOn(true);
         when(mLocationProvider.isInVisibleLocation(any(NotificationEntry.class))).thenReturn(false);
         mVisualStabilityManager.onHeadsUpStateChanged(mEntry, true);
         mVisualStabilityManager.onReorderingFinished();
@@ -183,33 +203,33 @@
 
     @Test
     public void testPulsing() {
-        mVisualStabilityManager.setPulsing(true);
+        setPulsing(true);
         assertFalse(mVisualStabilityManager.canReorderNotification(mRow));
-        mVisualStabilityManager.setPulsing(false);
+        setPulsing(false);
         assertTrue(mVisualStabilityManager.canReorderNotification(mRow));
     }
 
     @Test
     public void testReorderingAllowedChanges_Pulsing() {
-        mVisualStabilityManager.setPulsing(true);
+        setPulsing(true);
         assertFalse(mVisualStabilityManager.isReorderingAllowed());
-        mVisualStabilityManager.setPulsing(false);
+        setPulsing(false);
         assertTrue(mVisualStabilityManager.isReorderingAllowed());
     }
 
     @Test
     public void testCallBackCalled_Pulsing() {
-        mVisualStabilityManager.setPulsing(true);
+        setPulsing(true);
         mVisualStabilityManager.addReorderingAllowedCallback(mCallback, false  /* persistent */);
-        mVisualStabilityManager.setPulsing(false);
+        setPulsing(false);
         verify(mCallback).onChangeAllowed();
     }
 
     @Test
     public void testTemporarilyAllowReorderingNotifiesCallbacks() {
         // GIVEN having the panel open (which would block reordering)
-        mVisualStabilityManager.setScreenOn(true);
-        mVisualStabilityManager.setPanelExpanded(true);
+        setScreenOn(true);
+        setPanelExpanded(true);
         mVisualStabilityManager.addReorderingAllowedCallback(mCallback, false  /* persistent */);
 
         // WHEN we temprarily allow reordering
@@ -223,7 +243,7 @@
     @Test
     public void testTemporarilyAllowReorderingDoesntOverridePulsing() {
         // GIVEN we are in a pulsing state
-        mVisualStabilityManager.setPulsing(true);
+        setPulsing(true);
         mVisualStabilityManager.addReorderingAllowedCallback(mCallback, false  /* persistent */);
 
         // WHEN we temprarily allow reordering
@@ -237,8 +257,8 @@
     @Test
     public void testTemporarilyAllowReorderingExpires() {
         // GIVEN having the panel open (which would block reordering)
-        mVisualStabilityManager.setScreenOn(true);
-        mVisualStabilityManager.setPanelExpanded(true);
+        setScreenOn(true);
+        setPanelExpanded(true);
         mVisualStabilityManager.addReorderingAllowedCallback(mCallback, false  /* persistent */);
 
         // WHEN we temprarily allow reordering and then wait until the window expires
@@ -249,4 +269,20 @@
         // THEN reordering is no longer allowed
         assertFalse(mVisualStabilityManager.isReorderingAllowed());
     }
+
+    private void setPanelExpanded(boolean expanded) {
+        mStatusBarStateListener.onExpandedChanged(expanded);
+    }
+
+    private void setPulsing(boolean pulsing) {
+        mStatusBarStateListener.onPulsingChanged(pulsing);
+    }
+
+    private void setScreenOn(boolean screenOn) {
+        if (screenOn) {
+            mWakefulnessObserver.onStartedWakingUp();
+        } else {
+            mWakefulnessObserver.onFinishedGoingToSleep();
+        }
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotificationEntryBuilder.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotificationEntryBuilder.java
index 1523653..3dc29a1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotificationEntryBuilder.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotificationEntryBuilder.java
@@ -53,7 +53,6 @@
 
     /* ListEntry properties */
     private GroupEntry mParent;
-    private int mSection = -1;
 
     /* If set, use this creation time instead of mClock.uptimeMillis */
     private long mCreationTime = -1;
@@ -68,7 +67,6 @@
         mRankingBuilder = new RankingBuilder(source.getRanking());
 
         mParent = source.getParent();
-        mSection = source.getSection();
         mCreationTime = source.getCreationTime();
     }
 
@@ -104,7 +102,6 @@
 
         /* ListEntry properties */
         entry.setParent(mParent);
-        entry.getAttachState().setSectionIndex(mSection);
         return entry;
     }
 
@@ -117,14 +114,6 @@
     }
 
     /**
-     * Sets the section.
-     */
-    public NotificationEntryBuilder setSection(int section) {
-        mSection = section;
-        return this;
-    }
-
-    /**
      * Sets the SBN directly. If set, causes all calls to delegated SbnBuilder methods to be
      * ignored.
      */
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilderTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilderTest.java
index 6fa5055..8acb705 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilderTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilderTest.java
@@ -35,6 +35,8 @@
 import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.verify;
 
+import static java.util.Collections.singletonList;
+
 import android.os.SystemClock;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
@@ -46,6 +48,7 @@
 import com.android.systemui.dump.DumpManager;
 import com.android.systemui.statusbar.NotificationInteractionTracker;
 import com.android.systemui.statusbar.notification.collection.ShadeListBuilder.OnRenderListListener;
+import com.android.systemui.statusbar.notification.collection.listbuilder.NotifSection;
 import com.android.systemui.statusbar.notification.collection.listbuilder.OnBeforeFinalizeFilterListener;
 import com.android.systemui.statusbar.notification.collection.listbuilder.OnBeforeRenderListListener;
 import com.android.systemui.statusbar.notification.collection.listbuilder.OnBeforeSortListener;
@@ -54,7 +57,7 @@
 import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifComparator;
 import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifFilter;
 import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifPromoter;
-import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifSection;
+import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifSectioner;
 import com.android.systemui.statusbar.notification.collection.notifcollection.CollectionReadyForBuildListener;
 import com.android.systemui.util.time.FakeSystemClock;
 
@@ -71,7 +74,6 @@
 
 import java.util.ArrayList;
 import java.util.Arrays;
-import java.util.Collections;
 import java.util.List;
 import java.util.Map;
 import java.util.Objects;
@@ -496,7 +498,7 @@
         assertTrue(entry.hasFinishedInitialization());
 
         // WHEN the pipeline is kicked off
-        mReadyForBuildListener.onBuildList(Arrays.asList(entry));
+        mReadyForBuildListener.onBuildList(singletonList(entry));
 
         // THEN the entry's initialization time is reset
         assertFalse(entry.hasFinishedInitialization());
@@ -609,13 +611,18 @@
         // GIVEN a filter that removes all PACKAGE_4 notifs and sections that divide
         // notifs based on package name
         mListBuilder.addPreGroupFilter(new PackageFilter(PACKAGE_4));
-        final NotifSection pkg1Section = spy(new PackageSection(PACKAGE_1));
-        final NotifSection pkg2Section = spy(new PackageSection(PACKAGE_2));
+        final NotifSectioner pkg1Sectioner = spy(new PackageSectioner(PACKAGE_1));
+        final NotifSectioner pkg2Sectioner = spy(new PackageSectioner(PACKAGE_2));
         // NOTE: no package 3 section explicitly added, so notifs with package 3 will get set by
         // ShadeListBuilder's sDefaultSection which will demote it to the last section
-        final NotifSection pkg4Section = spy(new PackageSection(PACKAGE_4));
-        final NotifSection pkg5Section = spy(new PackageSection(PACKAGE_5));
-        mListBuilder.setSections(Arrays.asList(pkg1Section, pkg2Section, pkg4Section, pkg5Section));
+        final NotifSectioner pkg4Sectioner = spy(new PackageSectioner(PACKAGE_4));
+        final NotifSectioner pkg5Sectioner = spy(new PackageSectioner(PACKAGE_5));
+        mListBuilder.setSectioners(
+                Arrays.asList(pkg1Sectioner, pkg2Sectioner, pkg4Sectioner, pkg5Sectioner));
+
+        final NotifSection pkg1Section = new NotifSection(pkg1Sectioner, 0);
+        final NotifSection pkg2Section = new NotifSection(pkg2Sectioner, 1);
+        final NotifSection pkg5Section = new NotifSection(pkg5Sectioner, 3);
 
         // WHEN we build a list with different packages
         addNotif(0, PACKAGE_4);
@@ -648,72 +655,61 @@
 
         // THEN the first section (pkg1Section) is called on all top level elements (but
         // no children and no entries that were filtered out)
-        verify(pkg1Section).isInSection(mEntrySet.get(1));
-        verify(pkg1Section).isInSection(mEntrySet.get(2));
-        verify(pkg1Section).isInSection(mEntrySet.get(3));
-        verify(pkg1Section).isInSection(mEntrySet.get(7));
-        verify(pkg1Section).isInSection(mEntrySet.get(8));
-        verify(pkg1Section).isInSection(mEntrySet.get(9));
-        verify(pkg1Section).isInSection(mBuiltList.get(3));
+        verify(pkg1Sectioner).isInSection(mEntrySet.get(1));
+        verify(pkg1Sectioner).isInSection(mEntrySet.get(2));
+        verify(pkg1Sectioner).isInSection(mEntrySet.get(3));
+        verify(pkg1Sectioner).isInSection(mEntrySet.get(7));
+        verify(pkg1Sectioner).isInSection(mEntrySet.get(8));
+        verify(pkg1Sectioner).isInSection(mEntrySet.get(9));
+        verify(pkg1Sectioner).isInSection(mBuiltList.get(3));
 
-        verify(pkg1Section, never()).isInSection(mEntrySet.get(0));
-        verify(pkg1Section, never()).isInSection(mEntrySet.get(4));
-        verify(pkg1Section, never()).isInSection(mEntrySet.get(5));
-        verify(pkg1Section, never()).isInSection(mEntrySet.get(6));
-        verify(pkg1Section, never()).isInSection(mEntrySet.get(10));
+        verify(pkg1Sectioner, never()).isInSection(mEntrySet.get(0));
+        verify(pkg1Sectioner, never()).isInSection(mEntrySet.get(4));
+        verify(pkg1Sectioner, never()).isInSection(mEntrySet.get(5));
+        verify(pkg1Sectioner, never()).isInSection(mEntrySet.get(6));
+        verify(pkg1Sectioner, never()).isInSection(mEntrySet.get(10));
 
         // THEN the last section (pkg5Section) is not called on any of the entries that were
         // filtered or already in a section
-        verify(pkg5Section, never()).isInSection(mEntrySet.get(0));
-        verify(pkg5Section, never()).isInSection(mEntrySet.get(1));
-        verify(pkg5Section, never()).isInSection(mEntrySet.get(2));
-        verify(pkg5Section, never()).isInSection(mEntrySet.get(4));
-        verify(pkg5Section, never()).isInSection(mEntrySet.get(5));
-        verify(pkg5Section, never()).isInSection(mEntrySet.get(6));
-        verify(pkg5Section, never()).isInSection(mEntrySet.get(7));
-        verify(pkg5Section, never()).isInSection(mEntrySet.get(8));
-        verify(pkg5Section, never()).isInSection(mEntrySet.get(10));
+        verify(pkg5Sectioner, never()).isInSection(mEntrySet.get(0));
+        verify(pkg5Sectioner, never()).isInSection(mEntrySet.get(1));
+        verify(pkg5Sectioner, never()).isInSection(mEntrySet.get(2));
+        verify(pkg5Sectioner, never()).isInSection(mEntrySet.get(4));
+        verify(pkg5Sectioner, never()).isInSection(mEntrySet.get(5));
+        verify(pkg5Sectioner, never()).isInSection(mEntrySet.get(6));
+        verify(pkg5Sectioner, never()).isInSection(mEntrySet.get(7));
+        verify(pkg5Sectioner, never()).isInSection(mEntrySet.get(8));
+        verify(pkg5Sectioner, never()).isInSection(mEntrySet.get(10));
 
-        verify(pkg5Section).isInSection(mEntrySet.get(3));
-        verify(pkg5Section).isInSection(mEntrySet.get(9));
+        verify(pkg5Sectioner).isInSection(mEntrySet.get(3));
+        verify(pkg5Sectioner).isInSection(mEntrySet.get(9));
 
         // THEN the correct section is assigned for entries in pkg1Section
-        assertEquals(pkg1Section, mEntrySet.get(2).getNotifSection());
-        assertEquals(0, mEntrySet.get(2).getSection());
-        assertEquals(pkg1Section, mEntrySet.get(7).getNotifSection());
-        assertEquals(0, mEntrySet.get(7).getSection());
+        assertEquals(pkg1Section, mEntrySet.get(2).getSection());
+        assertEquals(pkg1Section, mEntrySet.get(7).getSection());
 
         // THEN the correct section is assigned for entries in pkg2Section
-        assertEquals(pkg2Section, mEntrySet.get(1).getNotifSection());
-        assertEquals(1, mEntrySet.get(1).getSection());
-        assertEquals(pkg2Section, mEntrySet.get(8).getNotifSection());
-        assertEquals(1, mEntrySet.get(8).getSection());
-        assertEquals(pkg2Section, mBuiltList.get(3).getNotifSection());
-        assertEquals(1, mBuiltList.get(3).getSection());
+        assertEquals(pkg2Section, mEntrySet.get(1).getSection());
+        assertEquals(pkg2Section, mEntrySet.get(8).getSection());
+        assertEquals(pkg2Section, mBuiltList.get(3).getSection());
 
         // THEN no section was assigned to entries in pkg4Section (since they were filtered)
-        assertEquals(null, mEntrySet.get(0).getNotifSection());
-        assertEquals(-1, mEntrySet.get(0).getSection());
-        assertEquals(null, mEntrySet.get(10).getNotifSection());
-        assertEquals(-1, mEntrySet.get(10).getSection());
-
+        assertNull(mEntrySet.get(0).getSection());
+        assertNull(mEntrySet.get(10).getSection());
 
         // THEN the correct section is assigned for entries in pkg5Section
-        assertEquals(pkg5Section, mEntrySet.get(9).getNotifSection());
-        assertEquals(3, mEntrySet.get(9).getSection());
+        assertEquals(pkg5Section, mEntrySet.get(9).getSection());
 
         // THEN the children entries are assigned the same section as its parent
-        assertEquals(mBuiltList.get(3).getNotifSection(), child(5).entry.getNotifSection());
         assertEquals(mBuiltList.get(3).getSection(), child(5).entry.getSection());
-        assertEquals(mBuiltList.get(3).getNotifSection(), child(6).entry.getNotifSection());
         assertEquals(mBuiltList.get(3).getSection(), child(6).entry.getSection());
     }
 
     @Test
     public void testNotifUsesDefaultSection() {
         // GIVEN a Section for Package2
-        final NotifSection pkg2Section = spy(new PackageSection(PACKAGE_2));
-        mListBuilder.setSections(Arrays.asList(pkg2Section));
+        final NotifSectioner pkg2Section = spy(new PackageSectioner(PACKAGE_2));
+        mListBuilder.setSectioners(singletonList(pkg2Section));
 
         // WHEN we build a list with pkg1 and pkg2 packages
         addNotif(0, PACKAGE_1);
@@ -727,8 +723,8 @@
         );
 
         // THEN the entry that didn't have an explicit section gets assigned the DefaultSection
-        assertEquals(1, notif(0).entry.getSection());
-        assertNotNull(notif(0).entry.getNotifSection());
+        assertNotNull(notif(0).entry.getSection());
+        assertEquals(1, notif(0).entry.getSectionIndex());
     }
 
     @Test
@@ -763,15 +759,15 @@
         // GIVEN a bunch of registered listeners and pluggables
         NotifFilter preGroupFilter = spy(new PackageFilter(PACKAGE_1));
         NotifPromoter promoter = spy(new IdPromoter(3));
-        NotifSection section = spy(new PackageSection(PACKAGE_1));
+        NotifSectioner section = spy(new PackageSectioner(PACKAGE_1));
         NotifComparator comparator = spy(new HypeComparator(PACKAGE_4));
         NotifFilter preRenderFilter = spy(new PackageFilter(PACKAGE_5));
         mListBuilder.addPreGroupFilter(preGroupFilter);
         mListBuilder.addOnBeforeTransformGroupsListener(mOnBeforeTransformGroupsListener);
         mListBuilder.addPromoter(promoter);
         mListBuilder.addOnBeforeSortListener(mOnBeforeSortListener);
-        mListBuilder.setComparators(Collections.singletonList(comparator));
-        mListBuilder.setSections(Arrays.asList(section));
+        mListBuilder.setComparators(singletonList(comparator));
+        mListBuilder.setSectioners(singletonList(section));
         mListBuilder.addOnBeforeFinalizeFilterListener(mOnBeforeFinalizeFilterListener);
         mListBuilder.addFinalizeFilter(preRenderFilter);
         mListBuilder.addOnBeforeRenderListListener(mOnBeforeRenderListListener);
@@ -821,13 +817,13 @@
         // GIVEN a variety of pluggables
         NotifFilter packageFilter = new PackageFilter(PACKAGE_1);
         NotifPromoter idPromoter = new IdPromoter(4);
-        NotifSection section = new PackageSection(PACKAGE_1);
+        NotifSectioner section = new PackageSectioner(PACKAGE_1);
         NotifComparator hypeComparator = new HypeComparator(PACKAGE_2);
 
         mListBuilder.addPreGroupFilter(packageFilter);
         mListBuilder.addPromoter(idPromoter);
-        mListBuilder.setSections(Arrays.asList(section));
-        mListBuilder.setComparators(Collections.singletonList(hypeComparator));
+        mListBuilder.setSectioners(singletonList(section));
+        mListBuilder.setComparators(singletonList(hypeComparator));
 
         // GIVEN a set of random notifs
         addNotif(0, PACKAGE_1);
@@ -973,7 +969,7 @@
         RecordingOnBeforeSortListener listener =
                 new RecordingOnBeforeSortListener();
         mListBuilder.addOnBeforeSortListener(listener);
-        mListBuilder.setComparators(Arrays.asList(new HypeComparator(PACKAGE_3)));
+        mListBuilder.setComparators(singletonList(new HypeComparator(PACKAGE_3)));
 
         // GIVEN some new notifs out of order
         addNotif(0, PACKAGE_1);
@@ -1093,7 +1089,7 @@
         NotifComparator comparator = new HypeComparator(PACKAGE_5);
         OnBeforeRenderListListener listener =
                 (list) -> comparator.invalidateList();
-        mListBuilder.setComparators(Collections.singletonList(comparator));
+        mListBuilder.setComparators(singletonList(comparator));
         mListBuilder.addOnBeforeRenderListListener(listener);
 
         // WHEN we try to run the pipeline and the comparator is invalidated
@@ -1420,10 +1416,10 @@
     }
 
     /** Represents a section for the passed pkg */
-    private static class PackageSection extends NotifSection {
+    private static class PackageSectioner extends NotifSectioner {
         private final String mPackage;
 
-        PackageSection(String pkg) {
+        PackageSectioner(String pkg) {
             super("PackageSection_" + pkg);
             mPackage = pkg;
         }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/AppOpsCoordinatorTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/AppOpsCoordinatorTest.java
index 960ea79..639e791 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/AppOpsCoordinatorTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/AppOpsCoordinatorTest.java
@@ -21,11 +21,9 @@
 import static android.app.NotificationManager.IMPORTANCE_HIGH;
 import static android.app.NotificationManager.IMPORTANCE_MIN;
 
-import static junit.framework.Assert.assertEquals;
 import static junit.framework.Assert.assertFalse;
 import static junit.framework.Assert.assertTrue;
 
-import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
@@ -37,7 +35,6 @@
 import android.service.notification.StatusBarNotification;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
-import android.util.ArraySet;
 
 import androidx.test.filters.SmallTest;
 
@@ -48,8 +45,7 @@
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder;
 import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifFilter;
-import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifSection;
-import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener;
+import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifSectioner;
 import com.android.systemui.statusbar.notification.collection.notifcollection.NotifLifetimeExtender;
 import com.android.systemui.util.concurrency.FakeExecutor;
 import com.android.systemui.util.time.FakeSystemClock;
@@ -61,8 +57,6 @@
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 
-import java.util.List;
-
 @SmallTest
 @RunWith(AndroidTestingRunner.class)
 @TestableLooper.RunWithLooper
@@ -77,10 +71,8 @@
     private NotificationEntryBuilder mEntryBuilder;
     private AppOpsCoordinator mAppOpsCoordinator;
     private NotifFilter mForegroundFilter;
-    private NotifCollectionListener mNotifCollectionListener;
-    private AppOpsController.Callback mAppOpsCallback;
     private NotifLifetimeExtender mForegroundNotifLifetimeExtender;
-    private NotifSection mFgsSection;
+    private NotifSectioner mFgsSection;
 
     private FakeSystemClock mClock = new FakeSystemClock();
     private FakeExecutor mExecutor = new FakeExecutor(mClock);
@@ -113,20 +105,7 @@
                 lifetimeExtenderCaptor.capture());
         mForegroundNotifLifetimeExtender = lifetimeExtenderCaptor.getValue();
 
-        // capture notifCollectionListener
-        ArgumentCaptor<NotifCollectionListener> notifCollectionCaptor =
-                ArgumentCaptor.forClass(NotifCollectionListener.class);
-        verify(mNotifPipeline, times(1)).addCollectionListener(
-                notifCollectionCaptor.capture());
-        mNotifCollectionListener = notifCollectionCaptor.getValue();
-
-        // capture app ops callback
-        ArgumentCaptor<AppOpsController.Callback> appOpsCaptor =
-                ArgumentCaptor.forClass(AppOpsController.Callback.class);
-        verify(mAppOpsController).addCallback(any(int[].class), appOpsCaptor.capture());
-        mAppOpsCallback = appOpsCaptor.getValue();
-
-        mFgsSection = mAppOpsCoordinator.getSection();
+        mFgsSection = mAppOpsCoordinator.getSectioner();
     }
 
     @Test
@@ -230,136 +209,6 @@
     }
 
     @Test
-    public void testAppOpsUpdateOnlyAppliedToRelevantNotificationWithStandardLayout() {
-        // GIVEN three current notifications, two with the same key but from different users
-        NotificationEntry entry1 = new NotificationEntryBuilder()
-                .setUser(new UserHandle(NOTIF_USER_ID))
-                .setPkg(TEST_PKG)
-                .setId(1)
-                .build();
-        NotificationEntry entry2 = new NotificationEntryBuilder()
-                .setUser(new UserHandle(NOTIF_USER_ID))
-                .setPkg(TEST_PKG)
-                .setId(2)
-                .build();
-        NotificationEntry entry3_diffUser = new NotificationEntryBuilder()
-                .setUser(new UserHandle(NOTIF_USER_ID + 1))
-                .setPkg(TEST_PKG)
-                .setId(2)
-                .build();
-        when(mNotifPipeline.getAllNotifs()).thenReturn(List.of(entry1, entry2, entry3_diffUser));
-
-        // GIVEN that only entry2 has a standard layout
-        when(mForegroundServiceController.getStandardLayoutKeys(NOTIF_USER_ID, TEST_PKG))
-                .thenReturn(new ArraySet<>(List.of(entry2.getKey())));
-
-        // WHEN a new app ops code comes in
-        mAppOpsCallback.onActiveStateChanged(47, NOTIF_USER_ID, TEST_PKG, true);
-        mExecutor.runAllReady();
-
-        // THEN entry2's app ops are updated, but no one else's are
-        assertEquals(
-                new ArraySet<>(),
-                entry1.mActiveAppOps);
-        assertEquals(
-                new ArraySet<>(List.of(47)),
-                entry2.mActiveAppOps);
-        assertEquals(
-                new ArraySet<>(),
-                entry3_diffUser.mActiveAppOps);
-    }
-
-    @Test
-    public void testAppOpsUpdateAppliedToAllNotificationsWithStandardLayouts() {
-        // GIVEN three notifications with standard layouts
-        NotificationEntry entry1 = new NotificationEntryBuilder()
-                .setUser(new UserHandle(NOTIF_USER_ID))
-                .setPkg(TEST_PKG)
-                .setId(1)
-                .build();
-        NotificationEntry entry2 = new NotificationEntryBuilder()
-                .setUser(new UserHandle(NOTIF_USER_ID))
-                .setPkg(TEST_PKG)
-                .setId(2)
-                .build();
-        NotificationEntry entry3 = new NotificationEntryBuilder()
-                .setUser(new UserHandle(NOTIF_USER_ID))
-                .setPkg(TEST_PKG)
-                .setId(3)
-                .build();
-        when(mNotifPipeline.getAllNotifs()).thenReturn(List.of(entry1, entry2, entry3));
-        when(mForegroundServiceController.getStandardLayoutKeys(NOTIF_USER_ID, TEST_PKG))
-                .thenReturn(new ArraySet<>(List.of(entry1.getKey(), entry2.getKey(),
-                        entry3.getKey())));
-
-        // WHEN a new app ops code comes in
-        mAppOpsCallback.onActiveStateChanged(47, NOTIF_USER_ID, TEST_PKG, true);
-        mExecutor.runAllReady();
-
-        // THEN all entries get updated
-        assertEquals(
-                new ArraySet<>(List.of(47)),
-                entry1.mActiveAppOps);
-        assertEquals(
-                new ArraySet<>(List.of(47)),
-                entry2.mActiveAppOps);
-        assertEquals(
-                new ArraySet<>(List.of(47)),
-                entry3.mActiveAppOps);
-    }
-
-    @Test
-    public void testAppOpsAreRemoved() {
-        // GIVEN One notification which is associated with app ops
-        NotificationEntry entry = new NotificationEntryBuilder()
-                .setUser(new UserHandle(NOTIF_USER_ID))
-                .setPkg(TEST_PKG)
-                .setId(2)
-                .build();
-        when(mNotifPipeline.getAllNotifs()).thenReturn(List.of(entry));
-        when(mForegroundServiceController.getStandardLayoutKeys(0, TEST_PKG))
-                .thenReturn(new ArraySet<>(List.of(entry.getKey())));
-
-        // GIVEN that the notification's app ops are already [47, 33]
-        mAppOpsCallback.onActiveStateChanged(47, NOTIF_USER_ID, TEST_PKG, true);
-        mAppOpsCallback.onActiveStateChanged(33, NOTIF_USER_ID, TEST_PKG, true);
-        mExecutor.runAllReady();
-        assertEquals(
-                new ArraySet<>(List.of(47, 33)),
-                entry.mActiveAppOps);
-
-        // WHEN one of the app ops is removed
-        mAppOpsCallback.onActiveStateChanged(47, NOTIF_USER_ID, TEST_PKG, false);
-        mExecutor.runAllReady();
-
-        // THEN the entry's active app ops are updated as well
-        assertEquals(
-                new ArraySet<>(List.of(33)),
-                entry.mActiveAppOps);
-    }
-
-    @Test
-    public void testNullAppOps() {
-        // GIVEN one notification with app ops
-        NotificationEntry entry = new NotificationEntryBuilder()
-                .setUser(new UserHandle(NOTIF_USER_ID))
-                .setPkg(TEST_PKG)
-                .setId(2)
-                .build();
-        entry.mActiveAppOps.clear();
-        entry.mActiveAppOps.addAll(List.of(47, 33));
-
-        // WHEN the notification is updated and the foreground service controller returns null for
-        // this notification
-        when(mForegroundServiceController.getAppOps(entry.getSbn().getUser().getIdentifier(),
-                entry.getSbn().getPackageName())).thenReturn(null);
-        mNotifCollectionListener.onEntryUpdated(entry);
-
-        // THEN the entry's active app ops is updated to empty
-        assertTrue(entry.mActiveAppOps.isEmpty());
-    }
-
-    @Test
     public void testIncludeFGSInSection_importanceDefault() {
         // GIVEN the notification represents a colorized foreground service with > min importance
         mEntryBuilder
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/ConversationCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/ConversationCoordinatorTest.kt
index be5c8a8..c49393d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/ConversationCoordinatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/ConversationCoordinatorTest.kt
@@ -25,7 +25,7 @@
 import com.android.systemui.statusbar.notification.collection.NotificationEntry
 import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder
 import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifPromoter
-import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifSection
+import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifSectioner
 import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier
 import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier.Companion.TYPE_PERSON
 import org.junit.Assert.assertFalse
@@ -45,7 +45,7 @@
 class ConversationCoordinatorTest : SysuiTestCase() {
     // captured listeners and pluggables:
     private lateinit var promoter: NotifPromoter
-    private lateinit var peopleSection: NotifSection
+    private lateinit var peopleSectioner: NotifSectioner
 
     @Mock
     private lateinit var pipeline: NotifPipeline
@@ -70,7 +70,7 @@
         verify(pipeline).addPromoter(notifPromoterCaptor.capture())
         promoter = notifPromoterCaptor.value
 
-        peopleSection = coordinator.getSection()
+        peopleSectioner = coordinator.sectioner
 
         entry = NotificationEntryBuilder().setChannel(channel).build()
     }
@@ -88,7 +88,7 @@
             entry.sbn, entry.ranking)).thenReturn(TYPE_PERSON)
 
         // only put people notifications in this section
-        assertTrue(peopleSection.isInSection(entry))
-        assertFalse(peopleSection.isInSection(NotificationEntryBuilder().build()))
+        assertTrue(peopleSectioner.isInSection(entry))
+        assertFalse(peopleSectioner.isInSection(NotificationEntryBuilder().build()))
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinatorTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinatorTest.java
index 730481a..fa992a5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinatorTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinatorTest.java
@@ -36,7 +36,7 @@
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder;
 import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifPromoter;
-import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifSection;
+import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifSectioner;
 import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener;
 import com.android.systemui.statusbar.notification.collection.notifcollection.NotifLifetimeExtender;
 import com.android.systemui.statusbar.notification.interruption.HeadsUpViewBinder;
@@ -64,7 +64,7 @@
     private NotifPromoter mNotifPromoter;
     private NotifLifetimeExtender mNotifLifetimeExtender;
     private OnHeadsUpChangedListener mOnHeadsUpChangedListener;
-    private NotifSection mNotifSection;
+    private NotifSectioner mNotifSectioner;
 
     @Mock private NotifPipeline mNotifPipeline;
     @Mock private HeadsUpManager mHeadsUpManager;
@@ -111,7 +111,7 @@
         mNotifLifetimeExtender = notifLifetimeExtenderCaptor.getValue();
         mOnHeadsUpChangedListener = headsUpChangedListenerCaptor.getValue();
 
-        mNotifSection = mCoordinator.getSection();
+        mNotifSectioner = mCoordinator.getSectioner();
         mNotifLifetimeExtender.setCallback(mEndLifetimeExtension);
         mEntry = new NotificationEntryBuilder().build();
     }
@@ -132,8 +132,8 @@
         setCurrentHUN(mEntry);
 
         // THEN only section the current HUN, mEntry
-        assertTrue(mNotifSection.isInSection(mEntry));
-        assertFalse(mNotifSection.isInSection(new NotificationEntryBuilder().build()));
+        assertTrue(mNotifSectioner.isInSection(mEntry));
+        assertFalse(mNotifSectioner.isInSection(new NotificationEntryBuilder().build()));
     }
 
     @Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/RankingCoordinatorTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/RankingCoordinatorTest.java
index 5f10f38..3a7d28a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/RankingCoordinatorTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/RankingCoordinatorTest.java
@@ -36,7 +36,7 @@
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder;
 import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifFilter;
-import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifSection;
+import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifSectioner;
 import com.android.systemui.statusbar.notification.collection.provider.HighPriorityProvider;
 
 import org.junit.Before;
@@ -61,8 +61,8 @@
     private NotifFilter mCapturedSuspendedFilter;
     private NotifFilter mCapturedDozingFilter;
 
-    private NotifSection mAlertingSection;
-    private NotifSection mSilentSection;
+    private NotifSectioner mAlertingSectioner;
+    private NotifSectioner mSilentSectioner;
 
     @Before
     public void setup() {
@@ -76,8 +76,8 @@
         mCapturedSuspendedFilter = mNotifFilterCaptor.getAllValues().get(0);
         mCapturedDozingFilter = mNotifFilterCaptor.getAllValues().get(1);
 
-        mAlertingSection = rankingCoordinator.getAlertingSection();
-        mSilentSection = rankingCoordinator.getSilentSection();
+        mAlertingSectioner = rankingCoordinator.getAlertingSectioner();
+        mSilentSectioner = rankingCoordinator.getSilentSectioner();
     }
 
     @Test
@@ -146,8 +146,8 @@
         when(mHighPriorityProvider.isHighPriority(mEntry)).thenReturn(true);
 
         // THEN entry is in the alerting section
-        assertTrue(mAlertingSection.isInSection(mEntry));
-        assertFalse(mSilentSection.isInSection(mEntry));
+        assertTrue(mAlertingSectioner.isInSection(mEntry));
+        assertFalse(mSilentSectioner.isInSection(mEntry));
     }
 
     @Test
@@ -156,8 +156,8 @@
         when(mHighPriorityProvider.isHighPriority(mEntry)).thenReturn(false);
 
         // THEN entry is in the silent section
-        assertFalse(mAlertingSection.isInSection(mEntry));
-        assertTrue(mSilentSection.isInSection(mEntry));
+        assertFalse(mAlertingSectioner.isInSection(mEntry));
+        assertTrue(mSilentSectioner.isInSection(mEntry));
     }
 
     private RankingBuilder getRankingForUnfilteredNotif() {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinatorTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinatorTest.java
new file mode 100644
index 0000000..605b4d1
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinatorTest.java
@@ -0,0 +1,341 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification.collection.coordinator;
+
+import static junit.framework.Assert.assertFalse;
+
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.reset;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.systemui.SysuiTestCase;
+import com.android.systemui.keyguard.WakefulnessLifecycle;
+import com.android.systemui.plugins.statusbar.StatusBarStateController;
+import com.android.systemui.statusbar.notification.collection.NotifPipeline;
+import com.android.systemui.statusbar.notification.collection.NotificationEntry;
+import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder;
+import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifStabilityManager;
+import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.Pluggable;
+import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener;
+import com.android.systemui.statusbar.policy.HeadsUpManager;
+import com.android.systemui.util.concurrency.FakeExecutor;
+import com.android.systemui.util.time.FakeSystemClock;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
+@TestableLooper.RunWithLooper
+public class VisualStabilityCoordinatorTest extends SysuiTestCase {
+
+    private VisualStabilityCoordinator mCoordinator;
+
+    // captured listeners and pluggables:
+    private NotifCollectionListener mCollectionListener;
+
+    @Mock private NotifPipeline mNotifPipeline;
+    @Mock private WakefulnessLifecycle mWakefulnessLifecycle;
+    @Mock private StatusBarStateController mStatusBarStateController;
+    @Mock private Pluggable.PluggableListener<NotifStabilityManager> mInvalidateListener;
+    @Mock private HeadsUpManager mHeadsUpManager;
+
+    @Captor private ArgumentCaptor<WakefulnessLifecycle.Observer> mWakefulnessObserverCaptor;
+    @Captor private ArgumentCaptor<StatusBarStateController.StateListener> mSBStateListenerCaptor;
+    @Captor private ArgumentCaptor<NotifStabilityManager> mNotifStabilityManagerCaptor;
+    @Captor private ArgumentCaptor<NotifCollectionListener> mNotifCollectionListenerCaptor;
+
+    private FakeSystemClock mFakeSystemClock = new FakeSystemClock();
+    private FakeExecutor mFakeExecutor = new FakeExecutor(mFakeSystemClock);
+
+    private WakefulnessLifecycle.Observer mWakefulnessObserver;
+    private StatusBarStateController.StateListener mStatusBarStateListener;
+    private NotifStabilityManager mNotifStabilityManager;
+    private NotificationEntry mEntry;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+
+        mCoordinator = new VisualStabilityCoordinator(
+                mHeadsUpManager,
+                mWakefulnessLifecycle,
+                mStatusBarStateController,
+                mFakeExecutor);
+
+        mCoordinator.attach(mNotifPipeline);
+
+        // capture arguments:
+        verify(mWakefulnessLifecycle).addObserver(mWakefulnessObserverCaptor.capture());
+        mWakefulnessObserver = mWakefulnessObserverCaptor.getValue();
+
+        verify(mStatusBarStateController).addCallback(mSBStateListenerCaptor.capture());
+        mStatusBarStateListener = mSBStateListenerCaptor.getValue();
+
+        verify(mNotifPipeline).setVisualStabilityManager(mNotifStabilityManagerCaptor.capture());
+        mNotifStabilityManager = mNotifStabilityManagerCaptor.getValue();
+        mNotifStabilityManager.setInvalidationListener(mInvalidateListener);
+
+        mEntry = new NotificationEntryBuilder()
+                .setPkg("testPkg1")
+                .build();
+
+        when(mHeadsUpManager.isAlerting(mEntry.getKey())).thenReturn(false);
+    }
+
+    @Test
+    public void testScreenOff_groupAndSectionChangesAllowed() {
+        // GIVEN screen is off, panel isn't expanded and device isn't pulsing
+        setScreenOn(false);
+        setPanelExpanded(false);
+        setPulsing(false);
+
+        // THEN group changes are allowed
+        assertTrue(mNotifStabilityManager.isGroupChangeAllowed(mEntry));
+
+        // THEN section changes are allowed
+        assertTrue(mNotifStabilityManager.isSectionChangeAllowed(mEntry));
+    }
+
+    @Test
+    public void testPanelNotExpanded_groupAndSectionChangesAllowed() {
+        // GIVEN screen is on but the panel isn't expanded and device isn't pulsing
+        setScreenOn(true);
+        setPanelExpanded(false);
+        setPulsing(false);
+
+        // THEN group changes are allowed
+        assertTrue(mNotifStabilityManager.isGroupChangeAllowed(mEntry));
+
+        // THEN section changes are allowed
+        assertTrue(mNotifStabilityManager.isSectionChangeAllowed(mEntry));
+    }
+
+    @Test
+    public void testPanelExpanded_groupAndSectionChangesNotAllowed() {
+        // GIVEN the panel true expanded and device isn't pulsing
+        setScreenOn(true);
+        setPanelExpanded(true);
+        setPulsing(false);
+
+        // THEN group changes are NOT allowed
+        assertFalse(mNotifStabilityManager.isGroupChangeAllowed(mEntry));
+
+        // THEN section changes are NOT allowed
+        assertFalse(mNotifStabilityManager.isSectionChangeAllowed(mEntry));
+    }
+
+    @Test
+    public void testPulsing_screenOff_groupAndSectionChangesNotAllowed() {
+        // GIVEN the device is pulsing and screen is off
+        setScreenOn(false);
+        setPulsing(true);
+
+        // THEN group changes are NOT allowed
+        assertFalse(mNotifStabilityManager.isGroupChangeAllowed(mEntry));
+
+        // THEN section changes are NOT allowed
+        assertFalse(mNotifStabilityManager.isSectionChangeAllowed(mEntry));
+    }
+
+    @Test
+    public void testPulsing_panelNotExpanded_groupAndSectionChangesNotAllowed() {
+        // GIVEN the device is pulsing and screen is off with the panel not expanded
+        setScreenOn(false);
+        setPanelExpanded(false);
+        setPulsing(true);
+
+        // THEN group changes are NOT allowed
+        assertFalse(mNotifStabilityManager.isGroupChangeAllowed(mEntry));
+
+        // THEN section changes are NOT allowed
+        assertFalse(mNotifStabilityManager.isSectionChangeAllowed(mEntry));
+    }
+
+    @Test
+    public void testOverrideReorderingSuppression_onlySectionChangesAllowed() {
+        // GIVEN section changes typically wouldn't be allowed because the panel is expanded and
+        // we're not pulsing
+        setScreenOn(true);
+        setPanelExpanded(true);
+        setPulsing(true);
+
+        // WHEN we temporarily allow section changes for this notification entry
+        mCoordinator.temporarilyAllowSectionChanges(mEntry, mFakeSystemClock.currentTimeMillis());
+
+        // THEN group changes aren't allowed
+        assertFalse(mNotifStabilityManager.isGroupChangeAllowed(mEntry));
+
+        // THEN section changes are allowed for this notification but not other notifications
+        assertTrue(mNotifStabilityManager.isSectionChangeAllowed(mEntry));
+        assertFalse(mNotifStabilityManager.isSectionChangeAllowed(
+                new NotificationEntryBuilder()
+                        .setPkg("testPkg2")
+                        .build()));
+    }
+
+    @Test
+    public void testTemporarilyAllowSectionChanges_callsInvalidate() {
+        // GIVEN section changes typically wouldn't be allowed because the panel is expanded
+        setScreenOn(true);
+        setPanelExpanded(true);
+        setPulsing(false);
+
+        // WHEN we temporarily allow section changes for this notification entry
+        mCoordinator.temporarilyAllowSectionChanges(mEntry, mFakeSystemClock.uptimeMillis());
+
+        // THEN the notification list is invalidated
+        verifyInvalidateCalled(true);
+    }
+
+    @Test
+    public void testTemporarilyAllowSectionChanges_noInvalidationCalled() {
+        // GIVEN section changes typically WOULD be allowed
+        setScreenOn(false);
+        setPanelExpanded(false);
+        setPulsing(false);
+
+        // WHEN we temporarily allow section changes for this notification entry
+        mCoordinator.temporarilyAllowSectionChanges(mEntry, mFakeSystemClock.currentTimeMillis());
+
+        // THEN invalidate is not called because this entry was never suppressed from reordering
+        verifyInvalidateCalled(false);
+    }
+
+    @Test
+    public void testTemporarilyAllowSectionChangesTimeout() {
+        // GIVEN section changes typically WOULD be allowed
+        setScreenOn(false);
+        setPanelExpanded(false);
+        setPulsing(false);
+        assertTrue(mNotifStabilityManager.isSectionChangeAllowed(mEntry));
+
+        // WHEN we temporarily allow section changes for this notification entry
+        mCoordinator.temporarilyAllowSectionChanges(mEntry, mFakeSystemClock.currentTimeMillis());
+
+        // THEN invalidate is not called because this entry was never suppressed from reordering;
+        // THEN section changes are allowed for this notification
+        verifyInvalidateCalled(false);
+        assertTrue(mNotifStabilityManager.isSectionChangeAllowed(mEntry));
+
+        // WHEN we're pulsing (now disallowing reordering)
+        setPulsing(true);
+
+        // THEN we're still allowed to reorder this section because it's still in the list of
+        // notifications to allow section changes
+        assertTrue(mNotifStabilityManager.isSectionChangeAllowed(mEntry));
+
+        // WHEN the timeout for the temporarily allow section reordering runnable is finsihed
+        mFakeExecutor.advanceClockToNext();
+        mFakeExecutor.runNextReady();
+
+        // THEN section changes aren't allowed anymore
+        assertFalse(mNotifStabilityManager.isSectionChangeAllowed(mEntry));
+    }
+
+    @Test
+    public void testTemporarilyAllowSectionChanges_isPulsingChangeBeforeTimeout() {
+        // GIVEN section changes typically wouldn't be allowed because the device is pulsing
+        setScreenOn(false);
+        setPanelExpanded(false);
+        setPulsing(true);
+
+        // WHEN we temporarily allow section changes for this notification entry
+        mCoordinator.temporarilyAllowSectionChanges(mEntry, mFakeSystemClock.currentTimeMillis());
+        verifyInvalidateCalled(true); // can now reorder, so invalidates
+
+        // WHEN reordering is now allowed because device isn't pulsing anymore
+        setPulsing(false);
+
+        // THEN invalidate isn't called since reordering was already allowed
+        verifyInvalidateCalled(false);
+    }
+
+    @Test
+    public void testNeverSuppressedChanges_noInvalidationCalled() {
+        // GIVEN no notifications are currently being suppressed from grouping nor being sorted
+
+        // WHEN device isn't pulsing anymore
+        setPulsing(false);
+
+        // WHEN screen isn't on
+        setScreenOn(false);
+
+        // WHEN panel isn't expanded
+        setPanelExpanded(false);
+
+        // THEN we never see any calls to invalidate since there weren't any notifications that
+        // were being suppressed from grouping or section changes
+        verifyInvalidateCalled(false);
+    }
+
+    @Test
+    public void testHeadsUp_allowedToChangeGroupAndSection() {
+        // GIVEN group + section changes disallowed
+        setScreenOn(true);
+        setPanelExpanded(true);
+        setPulsing(true);
+        assertFalse(mNotifStabilityManager.isGroupChangeAllowed(mEntry));
+        assertFalse(mNotifStabilityManager.isSectionChangeAllowed(mEntry));
+
+        // GIVEN mEntry is a HUN
+        when(mHeadsUpManager.isAlerting(mEntry.getKey())).thenReturn(true);
+
+        // THEN group + section changes are allowed
+        assertTrue(mNotifStabilityManager.isGroupChangeAllowed(mEntry));
+        assertTrue(mNotifStabilityManager.isSectionChangeAllowed(mEntry));
+
+    }
+
+    private void setPulsing(boolean pulsing) {
+        mStatusBarStateListener.onPulsingChanged(pulsing);
+    }
+
+    private void setScreenOn(boolean screenOn) {
+        if (screenOn) {
+            mWakefulnessObserver.onStartedWakingUp();
+        } else {
+            mWakefulnessObserver.onFinishedGoingToSleep();
+        }
+    }
+
+    private void setPanelExpanded(boolean expanded) {
+        mStatusBarStateListener.onExpandedChanged(expanded);
+    }
+
+    private void verifyInvalidateCalled(boolean invalidateCalled) {
+        if (invalidateCalled) {
+            verify(mInvalidateListener).onPluggableInvalidated(mNotifStabilityManager);
+        } else {
+            verify(mInvalidateListener, never()).onPluggableInvalidated(mNotifStabilityManager);
+        }
+
+        reset(mInvalidateListener);
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewDifferTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewDifferTest.java
new file mode 100644
index 0000000..bbe92f6
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewDifferTest.java
@@ -0,0 +1,304 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification.collection.render;
+
+import static org.junit.Assert.assertEquals;
+
+import android.content.Context;
+import android.testing.AndroidTestingRunner;
+import android.view.View;
+import android.widget.FrameLayout;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.test.filters.SmallTest;
+
+import com.android.systemui.SysuiTestCase;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.List;
+
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
+public class ShadeViewDifferTest extends SysuiTestCase {
+    private ShadeViewDiffer mDiffer;
+
+    private FakeController mRootController = new FakeController(mContext, "RootController");
+    private FakeController mController1 = new FakeController(mContext, "Controller1");
+    private FakeController mController2 = new FakeController(mContext, "Controller2");
+    private FakeController mController3 = new FakeController(mContext, "Controller3");
+    private FakeController mController4 = new FakeController(mContext, "Controller4");
+    private FakeController mController5 = new FakeController(mContext, "Controller5");
+    private FakeController mController6 = new FakeController(mContext, "Controller6");
+    private FakeController mController7 = new FakeController(mContext, "Controller7");
+
+    @Mock
+    ShadeViewDifferLogger mLogger;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+
+        mDiffer = new ShadeViewDiffer(mRootController, mLogger);
+    }
+
+    @Test
+    public void testAddInitialViews() {
+        // WHEN a spec is applied to an empty root
+        // THEN the final tree matches the spec
+        applySpecAndCheck(
+                node(mController1),
+                node(mController2,
+                        node(mController3),
+                        node(mController4)
+                ),
+                node(mController5)
+        );
+    }
+
+    @Test
+    public void testDetachViews() {
+        // GIVEN a preexisting tree of controllers
+        applySpecAndCheck(
+                node(mController1),
+                node(mController2,
+                        node(mController3),
+                        node(mController4)
+                ),
+                node(mController5)
+        );
+
+        // WHEN the new spec removes nodes
+        // THEN the final tree matches the spec
+        applySpecAndCheck(
+                node(mController5)
+        );
+    }
+
+    @Test
+    public void testReparentChildren() {
+        // GIVEN a preexisting tree of controllers
+        applySpecAndCheck(
+                node(mController1),
+                node(mController2,
+                        node(mController3),
+                        node(mController4)
+                ),
+                node(mController5)
+        );
+
+        // WHEN the parents of the controllers are all shuffled around
+        // THEN the final tree matches the spec
+        applySpecAndCheck(
+                node(mController1),
+                node(mController4),
+                node(mController3,
+                        node(mController2)
+                )
+        );
+    }
+
+    @Test
+    public void testReorderChildren() {
+        // GIVEN a preexisting tree of controllers
+        applySpecAndCheck(
+                node(mController1),
+                node(mController2),
+                node(mController3),
+                node(mController4)
+        );
+
+        // WHEN the children change order
+        // THEN the final tree matches the spec
+        applySpecAndCheck(
+                node(mController3),
+                node(mController2),
+                node(mController4),
+                node(mController1)
+        );
+    }
+
+    @Test
+    public void testRemovedGroupsAreKeptTogether() {
+        // GIVEN a preexisting tree with a group
+        applySpecAndCheck(
+                node(mController1),
+                node(mController2,
+                        node(mController3),
+                        node(mController4),
+                        node(mController5)
+                )
+        );
+
+        // WHEN the new spec removes the entire group
+        applySpecAndCheck(
+                node(mController1)
+        );
+
+        // THEN the group children are still attached to their parent
+        assertEquals(mController2.getView(), mController3.getView().getParent());
+        assertEquals(mController2.getView(), mController4.getView().getParent());
+        assertEquals(mController2.getView(), mController5.getView().getParent());
+    }
+
+    @Test
+    public void testUnmanagedViews() {
+        // GIVEN a preexisting tree of controllers
+        applySpecAndCheck(
+                node(mController1),
+                node(mController2,
+                        node(mController3),
+                        node(mController4)
+                ),
+                node(mController5)
+        );
+
+        // GIVEN some additional unmanaged views attached to the tree
+        View unmanagedView1 = new View(mContext);
+        View unmanagedView2 = new View(mContext);
+
+        mRootController.getView().addView(unmanagedView1, 1);
+        mController2.getView().addView(unmanagedView2, 0);
+
+        // WHEN a new spec is applied with additional nodes
+        // THEN the final tree matches the spec
+        applySpecAndCheck(
+                node(mController1),
+                node(mController2,
+                        node(mController3),
+                        node(mController4),
+                        node(mController6)
+                ),
+                node(mController5),
+                node(mController7)
+        );
+
+        // THEN the unmanaged views have been pushed to the end of their parents
+        assertEquals(unmanagedView1, mRootController.view.getChildAt(4));
+        assertEquals(unmanagedView2, mController2.view.getChildAt(3));
+    }
+
+    private void applySpecAndCheck(NodeSpec spec) {
+        mDiffer.applySpec(spec);
+        checkMatchesSpec(spec);
+    }
+
+    private void applySpecAndCheck(SpecBuilder... children) {
+        applySpecAndCheck(node(mRootController, children).build());
+    }
+
+    private void checkMatchesSpec(NodeSpec spec) {
+        final NodeController parent = spec.getController();
+        final List<NodeSpec> children = spec.getChildren();
+
+        for (int i = 0; i < children.size(); i++) {
+            NodeSpec childSpec = children.get(i);
+            View view = parent.getChildAt(i);
+
+            assertEquals(
+                    "Child " + i + " of parent " + parent.getNodeLabel() + " should be "
+                            + childSpec.getController().getNodeLabel() + " but is instead "
+                            + (view != null ? mDiffer.getViewLabel(view) : "null"),
+                    view,
+                    childSpec.getController().getView());
+
+            if (!childSpec.getChildren().isEmpty()) {
+                checkMatchesSpec(childSpec);
+            }
+        }
+    }
+
+    private static class FakeController implements NodeController {
+
+        public final FrameLayout view;
+        private final String mLabel;
+
+        FakeController(Context context, String label) {
+            view = new FrameLayout(context);
+            mLabel = label;
+        }
+
+        @NonNull
+        @Override
+        public String getNodeLabel() {
+            return mLabel;
+        }
+
+        @NonNull
+        @Override
+        public FrameLayout getView() {
+            return view;
+        }
+
+        @Override
+        public int getChildCount() {
+            return view.getChildCount();
+        }
+
+        @Override
+        public View getChildAt(int index) {
+            return view.getChildAt(index);
+        }
+
+        @Override
+        public void addChildAt(@NonNull NodeController child, int index) {
+            view.addView(child.getView(), index);
+        }
+
+        @Override
+        public void moveChildTo(@NonNull NodeController child, int index) {
+            view.removeView(child.getView());
+            view.addView(child.getView(), index);
+        }
+
+        @Override
+        public void removeChild(@NonNull NodeController child, boolean isTransfer) {
+            view.removeView(child.getView());
+        }
+    }
+
+    private static class SpecBuilder {
+        private final NodeController mController;
+        private final SpecBuilder[] mChildren;
+
+        SpecBuilder(NodeController controller, SpecBuilder... children) {
+            mController = controller;
+            mChildren = children;
+        }
+
+        public NodeSpec build() {
+            return build(null);
+        }
+
+        public NodeSpec build(@Nullable NodeSpec parent) {
+            final NodeSpecImpl spec = new NodeSpecImpl(parent, mController);
+            for (SpecBuilder childBuilder : mChildren) {
+                spec.getChildren().add(childBuilder.build(spec));
+            }
+            return spec;
+        }
+    }
+
+    private static SpecBuilder node(NodeController controller, SpecBuilder... children) {
+        return new SpecBuilder(controller, children);
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/AppOpsInfoTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/AppOpsInfoTest.java
deleted file mode 100644
index 43d8b50..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/AppOpsInfoTest.java
+++ /dev/null
@@ -1,230 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package com.android.systemui.statusbar.notification.row;
-
-import static android.app.AppOpsManager.OP_CAMERA;
-import static android.app.AppOpsManager.OP_RECORD_AUDIO;
-import static android.app.AppOpsManager.OP_SYSTEM_ALERT_WINDOW;
-
-import static junit.framework.Assert.assertEquals;
-import static junit.framework.Assert.assertTrue;
-
-import static org.mockito.Mockito.any;
-import static org.mockito.Mockito.anyBoolean;
-import static org.mockito.Mockito.anyInt;
-import static org.mockito.Mockito.anyString;
-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.app.Notification;
-import android.content.pm.ApplicationInfo;
-import android.content.pm.PackageInfo;
-import android.content.pm.PackageManager;
-import android.graphics.drawable.Drawable;
-import android.os.UserHandle;
-import android.service.notification.StatusBarNotification;
-import android.test.suitebuilder.annotation.SmallTest;
-import android.testing.AndroidTestingRunner;
-import android.testing.UiThreadTest;
-import android.util.ArraySet;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.widget.ImageView;
-import android.widget.TextView;
-
-import com.android.internal.logging.testing.UiEventLoggerFake;
-import com.android.systemui.R;
-import com.android.systemui.SysuiTestCase;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import java.util.concurrent.CountDownLatch;
-
-@SmallTest
-@RunWith(AndroidTestingRunner.class)
-@UiThreadTest
-public class AppOpsInfoTest extends SysuiTestCase {
-    private static final String TEST_PACKAGE_NAME = "test_package";
-    private static final int TEST_UID = 1;
-
-    private AppOpsInfo mAppOpsInfo;
-    private final PackageManager mMockPackageManager = mock(PackageManager.class);
-    private final NotificationGuts mGutsParent = mock(NotificationGuts.class);
-    private StatusBarNotification mSbn;
-    private UiEventLoggerFake mUiEventLogger = new UiEventLoggerFake();
-
-    @Before
-    public void setUp() throws Exception {
-        // Inflate the layout
-        final LayoutInflater layoutInflater = LayoutInflater.from(mContext);
-        mAppOpsInfo = (AppOpsInfo) layoutInflater.inflate(R.layout.app_ops_info, null);
-        mAppOpsInfo.setGutsParent(mGutsParent);
-
-        // PackageManager must return a packageInfo and applicationInfo.
-        final PackageInfo packageInfo = new PackageInfo();
-        packageInfo.packageName = TEST_PACKAGE_NAME;
-        when(mMockPackageManager.getPackageInfo(eq(TEST_PACKAGE_NAME), anyInt()))
-                .thenReturn(packageInfo);
-        final ApplicationInfo applicationInfo = new ApplicationInfo();
-        applicationInfo.uid = TEST_UID;  // non-zero
-        when(mMockPackageManager.getApplicationInfo(anyString(), anyInt())).thenReturn(
-                applicationInfo);
-
-        mSbn = new StatusBarNotification(TEST_PACKAGE_NAME, TEST_PACKAGE_NAME, 0, null, TEST_UID, 0,
-                new Notification(), UserHandle.CURRENT, null, 0);
-    }
-
-    @Test
-    public void testBindNotification_SetsTextApplicationName() {
-        when(mMockPackageManager.getApplicationLabel(any())).thenReturn("App Name");
-        mAppOpsInfo.bindGuts(mMockPackageManager, null, mSbn, mUiEventLogger, new ArraySet<>());
-        final TextView textView = mAppOpsInfo.findViewById(R.id.pkgname);
-        assertTrue(textView.getText().toString().contains("App Name"));
-    }
-
-    @Test
-    public void testBindNotification_SetsPackageIcon() {
-        final Drawable iconDrawable = mock(Drawable.class);
-        when(mMockPackageManager.getApplicationIcon(any(ApplicationInfo.class)))
-                .thenReturn(iconDrawable);
-        mAppOpsInfo.bindGuts(mMockPackageManager, null, mSbn, mUiEventLogger, new ArraySet<>());
-        final ImageView iconView = mAppOpsInfo.findViewById(R.id.pkgicon);
-        assertEquals(iconDrawable, iconView.getDrawable());
-    }
-
-    @Test
-    public void testBindNotification_SetsOnClickListenerForSettings() throws Exception {
-        ArraySet<Integer> expectedOps = new ArraySet<>();
-        expectedOps.add(OP_CAMERA);
-        final CountDownLatch latch = new CountDownLatch(1);
-        mAppOpsInfo.bindGuts(mMockPackageManager, (View v, String pkg, int uid,
-                ArraySet<Integer> ops) -> {
-            assertEquals(TEST_PACKAGE_NAME, pkg);
-            assertEquals(expectedOps, ops);
-            assertEquals(TEST_UID, uid);
-            latch.countDown();
-        }, mSbn, mUiEventLogger, expectedOps);
-
-        final View settingsButton = mAppOpsInfo.findViewById(R.id.settings);
-        settingsButton.performClick();
-        // Verify that listener was triggered.
-        assertEquals(0, latch.getCount());
-    }
-
-    @Test
-    public void testBindNotification_LogsOpen() throws Exception {
-        mAppOpsInfo.bindGuts(mMockPackageManager, null, mSbn, mUiEventLogger, new ArraySet<>());
-        assertEquals(1, mUiEventLogger.numLogs());
-        assertEquals(NotificationAppOpsEvent.NOTIFICATION_APP_OPS_OPEN.getId(),
-                mUiEventLogger.eventId(0));
-    }
-
-    @Test
-    public void testOk() {
-        ArraySet<Integer> expectedOps = new ArraySet<>();
-        expectedOps.add(OP_CAMERA);
-        final CountDownLatch latch = new CountDownLatch(1);
-        mAppOpsInfo.bindGuts(mMockPackageManager, (View v, String pkg, int uid,
-                ArraySet<Integer> ops) -> {
-            assertEquals(TEST_PACKAGE_NAME, pkg);
-            assertEquals(expectedOps, ops);
-            assertEquals(TEST_UID, uid);
-            latch.countDown();
-        }, mSbn, mUiEventLogger, expectedOps);
-
-        final View okButton = mAppOpsInfo.findViewById(R.id.ok);
-        okButton.performClick();
-        assertEquals(1, latch.getCount());
-        verify(mGutsParent, times(1)).closeControls(eq(okButton), anyBoolean());
-    }
-
-    @Test
-    public void testPrompt_camera() {
-        ArraySet<Integer> expectedOps = new ArraySet<>();
-        expectedOps.add(OP_CAMERA);
-        mAppOpsInfo.bindGuts(mMockPackageManager, null, mSbn, mUiEventLogger, expectedOps);
-        TextView prompt = mAppOpsInfo.findViewById(R.id.prompt);
-        assertEquals("This app is using the camera.", prompt.getText());
-    }
-
-    @Test
-    public void testPrompt_mic() {
-        ArraySet<Integer> expectedOps = new ArraySet<>();
-        expectedOps.add(OP_RECORD_AUDIO);
-        mAppOpsInfo.bindGuts(mMockPackageManager, null, mSbn, mUiEventLogger, expectedOps);
-        TextView prompt = mAppOpsInfo.findViewById(R.id.prompt);
-        assertEquals("This app is using the microphone.", prompt.getText());
-    }
-
-    @Test
-    public void testPrompt_overlay() {
-        ArraySet<Integer> expectedOps = new ArraySet<>();
-        expectedOps.add(OP_SYSTEM_ALERT_WINDOW);
-        mAppOpsInfo.bindGuts(mMockPackageManager, null, mSbn, mUiEventLogger, expectedOps);
-        TextView prompt = mAppOpsInfo.findViewById(R.id.prompt);
-        assertEquals("This app is displaying over other apps on your screen.", prompt.getText());
-    }
-
-    @Test
-    public void testPrompt_camera_mic() {
-        ArraySet<Integer> expectedOps = new ArraySet<>();
-        expectedOps.add(OP_CAMERA);
-        expectedOps.add(OP_RECORD_AUDIO);
-        mAppOpsInfo.bindGuts(mMockPackageManager, null, mSbn, mUiEventLogger, expectedOps);
-        TextView prompt = mAppOpsInfo.findViewById(R.id.prompt);
-        assertEquals("This app is using the microphone and camera.", prompt.getText());
-    }
-
-    @Test
-    public void testPrompt_camera_mic_overlay() {
-        ArraySet<Integer> expectedOps = new ArraySet<>();
-        expectedOps.add(OP_CAMERA);
-        expectedOps.add(OP_RECORD_AUDIO);
-        expectedOps.add(OP_SYSTEM_ALERT_WINDOW);
-        mAppOpsInfo.bindGuts(mMockPackageManager, null, mSbn, mUiEventLogger, expectedOps);
-        TextView prompt = mAppOpsInfo.findViewById(R.id.prompt);
-        assertEquals("This app is displaying over other apps on your screen and using"
-                + " the microphone and camera.", prompt.getText());
-    }
-
-    @Test
-    public void testPrompt_camera_overlay() {
-        ArraySet<Integer> expectedOps = new ArraySet<>();
-        expectedOps.add(OP_CAMERA);
-        expectedOps.add(OP_SYSTEM_ALERT_WINDOW);
-        mAppOpsInfo.bindGuts(mMockPackageManager, null, mSbn, mUiEventLogger, expectedOps);
-        TextView prompt = mAppOpsInfo.findViewById(R.id.prompt);
-        assertEquals("This app is displaying over other apps on your screen and using"
-                + " the camera.", prompt.getText());
-    }
-
-    @Test
-    public void testPrompt_mic_overlay() {
-        ArraySet<Integer> expectedOps = new ArraySet<>();
-        expectedOps.add(OP_RECORD_AUDIO);
-        expectedOps.add(OP_SYSTEM_ALERT_WINDOW);
-        mAppOpsInfo.bindGuts(mMockPackageManager, null, mSbn, mUiEventLogger, expectedOps);
-        TextView prompt = mAppOpsInfo.findViewById(R.id.prompt);
-        assertEquals("This app is displaying over other apps on your screen and using"
-                + " the microphone.", prompt.getText());
-    }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java
index dc4a6ca..f29b46c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java
@@ -35,12 +35,10 @@
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
-import android.app.AppOpsManager;
 import android.app.NotificationChannel;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
 import android.testing.TestableLooper.RunWithLooper;
-import android.util.ArraySet;
 import android.view.View;
 
 import androidx.test.filters.SmallTest;
@@ -213,46 +211,6 @@
     }
 
     @Test
-    public void testShowAppOps_noHeader() {
-        // public notification is custom layout - no header
-        mGroupRow.setSensitive(true, true);
-        mGroupRow.setAppOpsOnClickListener(null);
-        mGroupRow.showAppOpsIcons(null);
-    }
-
-    @Test
-    public void testShowAppOpsIcons_header() {
-        NotificationContentView publicLayout = mock(NotificationContentView.class);
-        mGroupRow.setPublicLayout(publicLayout);
-        NotificationContentView privateLayout = mock(NotificationContentView.class);
-        mGroupRow.setPrivateLayout(privateLayout);
-        NotificationChildrenContainer mockContainer = mock(NotificationChildrenContainer.class);
-        when(mockContainer.getNotificationChildCount()).thenReturn(1);
-        mGroupRow.setChildrenContainer(mockContainer);
-
-        ArraySet<Integer> ops = new ArraySet<>();
-        ops.add(AppOpsManager.OP_ANSWER_PHONE_CALLS);
-        mGroupRow.showAppOpsIcons(ops);
-
-        verify(mockContainer, times(1)).showAppOpsIcons(ops);
-        verify(privateLayout, times(1)).showAppOpsIcons(ops);
-        verify(publicLayout, times(1)).showAppOpsIcons(ops);
-
-    }
-
-    @Test
-    public void testAppOpsOnClick() {
-        ExpandableNotificationRow.CoordinateOnClickListener l = mock(
-                ExpandableNotificationRow.CoordinateOnClickListener.class);
-        View view = mock(View.class);
-
-        mGroupRow.setAppOpsOnClickListener(l);
-
-        mGroupRow.getAppOpsOnClickListener().onClick(view);
-        verify(l, times(1)).onClick(any(), anyInt(), anyInt(), any());
-    }
-
-    @Test
     public void testFeedback_noHeader() {
         // public notification is custom layout - no header
         mGroupRow.setSensitive(true, true);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentViewTest.java
index 6d4a711..c2091da 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentViewTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentViewTest.java
@@ -76,32 +76,6 @@
 
     @Test
     @UiThreadTest
-    public void testShowAppOpsIcons() {
-        View mockContracted = mock(NotificationHeaderView.class);
-        when(mockContracted.findViewById(com.android.internal.R.id.mic))
-                .thenReturn(mockContracted);
-        View mockExpanded = mock(NotificationHeaderView.class);
-        when(mockExpanded.findViewById(com.android.internal.R.id.mic))
-                .thenReturn(mockExpanded);
-        View mockHeadsUp = mock(NotificationHeaderView.class);
-        when(mockHeadsUp.findViewById(com.android.internal.R.id.mic))
-                .thenReturn(mockHeadsUp);
-
-        mView.setContractedChild(mockContracted);
-        mView.setExpandedChild(mockExpanded);
-        mView.setHeadsUpChild(mockHeadsUp);
-
-        ArraySet<Integer> ops = new ArraySet<>();
-        ops.add(AppOpsManager.OP_RECORD_AUDIO);
-        mView.showAppOpsIcons(ops);
-
-        verify(mockContracted, times(1)).setVisibility(View.VISIBLE);
-        verify(mockExpanded, times(1)).setVisibility(View.VISIBLE);
-        verify(mockHeadsUp, times(1)).setVisibility(View.VISIBLE);
-    }
-
-    @Test
-    @UiThreadTest
     public void testShowFeedbackIcon() {
         View mockContracted = mock(NotificationHeaderView.class);
         when(mockContracted.findViewById(com.android.internal.R.id.feedback))
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfoTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfoTest.java
index 83fc8263..3c5aa1a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfoTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfoTest.java
@@ -79,7 +79,6 @@
 import com.android.systemui.bubbles.BubbleController;
 import com.android.systemui.bubbles.BubblesTestActivity;
 import com.android.systemui.statusbar.SbnBuilder;
-import com.android.systemui.statusbar.notification.VisualStabilityManager;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder;
 import com.android.systemui.statusbar.phone.ShadeController;
@@ -135,7 +134,7 @@
     @Mock
     private PackageManager mMockPackageManager;
     @Mock
-    private VisualStabilityManager mVisualStabilityManager;
+    private OnUserInteractionCallback mOnUserInteractionCallback;
     @Mock
     private BubbleController mBubbleController;
     @Mock
@@ -244,7 +243,7 @@
                 mShortcutManager,
                 mMockPackageManager,
                 mMockINotificationManager,
-                mVisualStabilityManager,
+                mOnUserInteractionCallback,
                 TEST_PACKAGE_NAME,
                 mNotificationChannel,
                 mEntry,
@@ -268,7 +267,7 @@
                 mShortcutManager,
                 mMockPackageManager,
                 mMockINotificationManager,
-                mVisualStabilityManager,
+                mOnUserInteractionCallback,
                 TEST_PACKAGE_NAME,
                 mNotificationChannel,
                 mEntry,
@@ -293,7 +292,7 @@
                 mLauncherApps,
                 mMockPackageManager,
                 mMockINotificationManager,
-                mVisualStabilityManager,
+                mOnUserInteractionCallback,
                 TEST_PACKAGE_NAME,
                 mNotificationChannel,
                 mEntry,
@@ -319,7 +318,7 @@
                 mShortcutManager,
                 mMockPackageManager,
                 mMockINotificationManager,
-                mVisualStabilityManager,
+                mOnUserInteractionCallback,
                 TEST_PACKAGE_NAME,
                 mNotificationChannel,
                 mEntry,
@@ -344,7 +343,7 @@
                 mShortcutManager,
                 mMockPackageManager,
                 mMockINotificationManager,
-                mVisualStabilityManager,
+                mOnUserInteractionCallback,
                 TEST_PACKAGE_NAME,
                 mNotificationChannel,
                 mEntry,
@@ -368,7 +367,7 @@
                 mShortcutManager,
                 mMockPackageManager,
                 mMockINotificationManager,
-                mVisualStabilityManager,
+                mOnUserInteractionCallback,
                 TEST_PACKAGE_NAME,
                 mNotificationChannel,
                 mEntry,
@@ -403,7 +402,7 @@
                 mShortcutManager,
                 mMockPackageManager,
                 mMockINotificationManager,
-                mVisualStabilityManager,
+                mOnUserInteractionCallback,
                 TEST_PACKAGE_NAME,
                 mNotificationChannel,
                 entry,
@@ -428,7 +427,7 @@
                 mShortcutManager,
                 mMockPackageManager,
                 mMockINotificationManager,
-                mVisualStabilityManager,
+                mOnUserInteractionCallback,
                 TEST_PACKAGE_NAME,
                 mNotificationChannel,
                 mEntry,
@@ -457,7 +456,7 @@
                 mShortcutManager,
                 mMockPackageManager,
                 mMockINotificationManager,
-                mVisualStabilityManager,
+                mOnUserInteractionCallback,
                 TEST_PACKAGE_NAME,
                 mNotificationChannel,
                 mEntry,
@@ -481,7 +480,7 @@
                 mShortcutManager,
                 mMockPackageManager,
                 mMockINotificationManager,
-                mVisualStabilityManager,
+                mOnUserInteractionCallback,
                 TEST_PACKAGE_NAME,
                 mNotificationChannel,
                 mEntry,
@@ -509,7 +508,7 @@
                 mShortcutManager,
                 mMockPackageManager,
                 mMockINotificationManager,
-                mVisualStabilityManager,
+                mOnUserInteractionCallback,
                 TEST_PACKAGE_NAME,
                 mNotificationChannel,
                 mEntry,
@@ -537,7 +536,7 @@
                 mShortcutManager,
                 mMockPackageManager,
                 mMockINotificationManager,
-                mVisualStabilityManager,
+                mOnUserInteractionCallback,
                 TEST_PACKAGE_NAME,
                 mNotificationChannel,
                 mEntry,
@@ -568,7 +567,7 @@
                 mShortcutManager,
                 mMockPackageManager,
                 mMockINotificationManager,
-                mVisualStabilityManager,
+                mOnUserInteractionCallback,
                 TEST_PACKAGE_NAME,
                 mNotificationChannel,
                 mEntry,
@@ -598,7 +597,7 @@
                 mShortcutManager,
                 mMockPackageManager,
                 mMockINotificationManager,
-                mVisualStabilityManager,
+                mOnUserInteractionCallback,
                 TEST_PACKAGE_NAME,
                 mNotificationChannel,
                 mEntry,
@@ -642,7 +641,7 @@
                 mShortcutManager,
                 mMockPackageManager,
                 mMockINotificationManager,
-                mVisualStabilityManager,
+                mOnUserInteractionCallback,
                 TEST_PACKAGE_NAME,
                 mNotificationChannel,
                 mEntry,
@@ -685,7 +684,7 @@
                 mShortcutManager,
                 mMockPackageManager,
                 mMockINotificationManager,
-                mVisualStabilityManager,
+                mOnUserInteractionCallback,
                 TEST_PACKAGE_NAME,
                 mNotificationChannel,
                 mEntry,
@@ -729,7 +728,7 @@
                 mShortcutManager,
                 mMockPackageManager,
                 mMockINotificationManager,
-                mVisualStabilityManager,
+                mOnUserInteractionCallback,
                 TEST_PACKAGE_NAME,
                 mNotificationChannel,
                 mEntry,
@@ -766,7 +765,7 @@
                 mShortcutManager,
                 mMockPackageManager,
                 mMockINotificationManager,
-                mVisualStabilityManager,
+                mOnUserInteractionCallback,
                 TEST_PACKAGE_NAME,
                 mNotificationChannel,
                 mEntry,
@@ -802,7 +801,7 @@
                 mShortcutManager,
                 mMockPackageManager,
                 mMockINotificationManager,
-                mVisualStabilityManager,
+                mOnUserInteractionCallback,
                 TEST_PACKAGE_NAME,
                 mNotificationChannel,
                 mEntry,
@@ -840,7 +839,7 @@
                 mShortcutManager,
                 mMockPackageManager,
                 mMockINotificationManager,
-                mVisualStabilityManager,
+                mOnUserInteractionCallback,
                 TEST_PACKAGE_NAME,
                 mNotificationChannel,
                 mEntry,
@@ -876,7 +875,7 @@
                 mShortcutManager,
                 mMockPackageManager,
                 mMockINotificationManager,
-                mVisualStabilityManager,
+                mOnUserInteractionCallback,
                 TEST_PACKAGE_NAME,
                 mNotificationChannel,
                 mEntry,
@@ -912,7 +911,7 @@
                 mShortcutManager,
                 mMockPackageManager,
                 mMockINotificationManager,
-                mVisualStabilityManager,
+                mOnUserInteractionCallback,
                 TEST_PACKAGE_NAME,
                 mNotificationChannel,
                 mEntry,
@@ -947,7 +946,7 @@
                 mShortcutManager,
                 mMockPackageManager,
                 mMockINotificationManager,
-                mVisualStabilityManager,
+                mOnUserInteractionCallback,
                 TEST_PACKAGE_NAME,
                 mNotificationChannel,
                 mEntry,
@@ -981,7 +980,7 @@
                 mShortcutManager,
                 mMockPackageManager,
                 mMockINotificationManager,
-                mVisualStabilityManager,
+                mOnUserInteractionCallback,
                 TEST_PACKAGE_NAME,
                 mNotificationChannel,
                 mEntry,
@@ -1006,7 +1005,7 @@
                 mShortcutManager,
                 mMockPackageManager,
                 mMockINotificationManager,
-                mVisualStabilityManager,
+                mOnUserInteractionCallback,
                 TEST_PACKAGE_NAME,
                 mNotificationChannel,
                 mEntry,
@@ -1041,7 +1040,7 @@
                 mShortcutManager,
                 mMockPackageManager,
                 mMockINotificationManager,
-                mVisualStabilityManager,
+                mOnUserInteractionCallback,
                 TEST_PACKAGE_NAME,
                 mNotificationChannel,
                 mEntry,
@@ -1081,7 +1080,7 @@
                 mShortcutManager,
                 mMockPackageManager,
                 mMockINotificationManager,
-                mVisualStabilityManager,
+                mOnUserInteractionCallback,
                 TEST_PACKAGE_NAME,
                 mNotificationChannel,
                 mEntry,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationEntryManagerInflationTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationEntryManagerInflationTest.java
index a90af87..7a0a19b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationEntryManagerInflationTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationEntryManagerInflationTest.java
@@ -221,6 +221,7 @@
                 .thenAnswer((Answer<ExpandableNotificationRowController>) invocation ->
                         new ExpandableNotificationRowController(
                                 viewCaptor.getValue(),
+                                mListContainer,
                                 mock(ActivatableNotificationViewController.class),
                                 mNotificationMediaManager,
                                 mock(PluginManager.class),
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.java
index 14994d5..c2c40ca 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.java
@@ -74,12 +74,11 @@
 import com.android.systemui.statusbar.NotificationPresenter;
 import com.android.systemui.statusbar.notification.AssistantFeedbackController;
 import com.android.systemui.statusbar.notification.NotificationActivityStarter;
-import com.android.systemui.statusbar.notification.VisualStabilityManager;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 import com.android.systemui.statusbar.notification.collection.provider.HighPriorityProvider;
 import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier;
 import com.android.systemui.statusbar.notification.row.NotificationGutsManager.OnSettingsClickListener;
-import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout;
+import com.android.systemui.statusbar.notification.stack.NotificationListContainer;
 import com.android.systemui.statusbar.phone.StatusBar;
 import com.android.systemui.statusbar.policy.DeviceProvisionedController;
 
@@ -114,10 +113,10 @@
 
     @Rule public MockitoRule mockito = MockitoJUnit.rule();
     @Mock private MetricsLogger mMetricsLogger;
-    @Mock private VisualStabilityManager mVisualStabilityManager;
+    @Mock private OnUserInteractionCallback mOnUserInteractionCallback;
     @Mock private NotificationPresenter mPresenter;
     @Mock private NotificationActivityStarter mNotificationActivityStarter;
-    @Mock private NotificationStackScrollLayout mStackScroller;
+    @Mock private NotificationListContainer mNotificationListContainer;
     @Mock private NotificationInfo.CheckSaveListener mCheckSaveListener;
     @Mock private OnSettingsClickListener mOnSettingsClickListener;
     @Mock private DeviceProvisionedController mDeviceProvisionedController;
@@ -143,20 +142,22 @@
         mDependency.injectTestDependency(DeviceProvisionedController.class,
                 mDeviceProvisionedController);
         mDependency.injectTestDependency(MetricsLogger.class, mMetricsLogger);
-        mDependency.injectTestDependency(VisualStabilityManager.class, mVisualStabilityManager);
+        mDependency.injectTestDependency(
+                OnUserInteractionCallback.class,
+                mOnUserInteractionCallback);
         mDependency.injectTestDependency(BubbleController.class, mBubbleController);
         mDependency.injectMockDependency(NotificationLockscreenUserManager.class);
         mHandler = Handler.createAsync(mTestableLooper.getLooper());
         mHelper = new NotificationTestHelper(mContext, mDependency, TestableLooper.get(this));
         when(mAccessibilityManager.isTouchExplorationEnabled()).thenReturn(false);
 
-        mGutsManager = new NotificationGutsManager(mContext, mVisualStabilityManager,
+        mGutsManager = new NotificationGutsManager(mContext,
                 () -> mStatusBar, mHandler, mHandler, mAccessibilityManager, mHighPriorityProvider,
                 mINotificationManager, mLauncherApps, mShortcutManager,
                 mChannelEditorDialogController, mContextTracker, mProvider,
                 mAssistantFeedbackController, mBubbleController,
-                new UiEventLoggerFake());
-        mGutsManager.setUpWithPresenter(mPresenter, mStackScroller,
+                new UiEventLoggerFake(), mOnUserInteractionCallback);
+        mGutsManager.setUpWithPresenter(mPresenter, mNotificationListContainer,
                 mCheckSaveListener, mOnSettingsClickListener);
         mGutsManager.setNotificationActivityStarter(mNotificationActivityStarter);
     }
@@ -360,7 +361,7 @@
         verify(notificationInfoView).bindNotification(
                 any(PackageManager.class),
                 any(INotificationManager.class),
-                eq(mVisualStabilityManager),
+                eq(mOnUserInteractionCallback),
                 eq(mChannelEditorDialogController),
                 eq(statusBarNotification.getPackageName()),
                 any(NotificationChannel.class),
@@ -394,7 +395,7 @@
         verify(notificationInfoView).bindNotification(
                 any(PackageManager.class),
                 any(INotificationManager.class),
-                eq(mVisualStabilityManager),
+                eq(mOnUserInteractionCallback),
                 eq(mChannelEditorDialogController),
                 eq(statusBarNotification.getPackageName()),
                 any(NotificationChannel.class),
@@ -426,7 +427,7 @@
         verify(notificationInfoView).bindNotification(
                 any(PackageManager.class),
                 any(INotificationManager.class),
-                eq(mVisualStabilityManager),
+                eq(mOnUserInteractionCallback),
                 eq(mChannelEditorDialogController),
                 eq(statusBarNotification.getPackageName()),
                 any(NotificationChannel.class),
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationInfoTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationInfoTest.java
index fd8b72b..02a3e11 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationInfoTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationInfoTest.java
@@ -66,7 +66,6 @@
 import com.android.systemui.Dependency;
 import com.android.systemui.R;
 import com.android.systemui.SysuiTestCase;
-import com.android.systemui.statusbar.notification.VisualStabilityManager;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder;
 
@@ -114,7 +113,7 @@
     @Mock
     private PackageManager mMockPackageManager;
     @Mock
-    private VisualStabilityManager mVisualStabilityManager;
+    private OnUserInteractionCallback mOnUserInteractionCallback;
     @Mock
     private ChannelEditorDialogController mChannelEditorDialogController;
 
@@ -181,7 +180,7 @@
         mNotificationInfo.bindNotification(
                 mMockPackageManager,
                 mMockINotificationManager,
-                mVisualStabilityManager,
+                mOnUserInteractionCallback,
                 mChannelEditorDialogController,
                 TEST_PACKAGE_NAME,
                 mNotificationChannel,
@@ -207,7 +206,7 @@
         mNotificationInfo.bindNotification(
                 mMockPackageManager,
                 mMockINotificationManager,
-                mVisualStabilityManager,
+                mOnUserInteractionCallback,
                 mChannelEditorDialogController,
                 TEST_PACKAGE_NAME,
                 mNotificationChannel,
@@ -229,7 +228,7 @@
         mNotificationInfo.bindNotification(
                 mMockPackageManager,
                 mMockINotificationManager,
-                mVisualStabilityManager,
+                mOnUserInteractionCallback,
                 mChannelEditorDialogController,
                 TEST_PACKAGE_NAME,
                 mNotificationChannel,
@@ -260,7 +259,7 @@
         mNotificationInfo.bindNotification(
                 mMockPackageManager,
                 mMockINotificationManager,
-                mVisualStabilityManager,
+                mOnUserInteractionCallback,
                 mChannelEditorDialogController,
                 TEST_PACKAGE_NAME,
                 mNotificationChannel,
@@ -283,7 +282,7 @@
         mNotificationInfo.bindNotification(
                 mMockPackageManager,
                 mMockINotificationManager,
-                mVisualStabilityManager,
+                mOnUserInteractionCallback,
                 mChannelEditorDialogController,
                 TEST_PACKAGE_NAME,
                 mNotificationChannel,
@@ -311,7 +310,7 @@
         mNotificationInfo.bindNotification(
                 mMockPackageManager,
                 mMockINotificationManager,
-                mVisualStabilityManager,
+                mOnUserInteractionCallback,
                 mChannelEditorDialogController,
                 TEST_PACKAGE_NAME,
                 mNotificationChannel,
@@ -334,7 +333,7 @@
         mNotificationInfo.bindNotification(
                 mMockPackageManager,
                 mMockINotificationManager,
-                mVisualStabilityManager,
+                mOnUserInteractionCallback,
                 mChannelEditorDialogController,
                 TEST_PACKAGE_NAME,
                 mNotificationChannel,
@@ -356,7 +355,7 @@
         mNotificationInfo.bindNotification(
                 mMockPackageManager,
                 mMockINotificationManager,
-                mVisualStabilityManager,
+                mOnUserInteractionCallback,
                 mChannelEditorDialogController,
                 TEST_PACKAGE_NAME,
                 mDefaultNotificationChannel,
@@ -382,7 +381,7 @@
         mNotificationInfo.bindNotification(
                 mMockPackageManager,
                 mMockINotificationManager,
-                mVisualStabilityManager,
+                mOnUserInteractionCallback,
                 mChannelEditorDialogController,
                 TEST_PACKAGE_NAME,
                 mDefaultNotificationChannel,
@@ -404,7 +403,7 @@
         mNotificationInfo.bindNotification(
                 mMockPackageManager,
                 mMockINotificationManager,
-                mVisualStabilityManager,
+                mOnUserInteractionCallback,
                 mChannelEditorDialogController,
                 TEST_PACKAGE_NAME,
                 mNotificationChannel,
@@ -427,7 +426,7 @@
         mNotificationInfo.bindNotification(
                 mMockPackageManager,
                 mMockINotificationManager,
-                mVisualStabilityManager,
+                mOnUserInteractionCallback,
                 mChannelEditorDialogController,
                 TEST_PACKAGE_NAME,
                 mNotificationChannel,
@@ -455,7 +454,7 @@
         mNotificationInfo.bindNotification(
                 mMockPackageManager,
                 mMockINotificationManager,
-                mVisualStabilityManager,
+                mOnUserInteractionCallback,
                 mChannelEditorDialogController,
                 TEST_PACKAGE_NAME,
                 mNotificationChannel,
@@ -478,7 +477,7 @@
         mNotificationInfo.bindNotification(
                 mMockPackageManager,
                 mMockINotificationManager,
-                mVisualStabilityManager,
+                mOnUserInteractionCallback,
                 mChannelEditorDialogController,
                 TEST_PACKAGE_NAME,
                 mNotificationChannel,
@@ -502,7 +501,7 @@
         mNotificationInfo.bindNotification(
                 mMockPackageManager,
                 mMockINotificationManager,
-                mVisualStabilityManager,
+                mOnUserInteractionCallback,
                 mChannelEditorDialogController,
                 TEST_PACKAGE_NAME,
                 mNotificationChannel,
@@ -518,7 +517,7 @@
         mNotificationInfo.bindNotification(
                 mMockPackageManager,
                 mMockINotificationManager,
-                mVisualStabilityManager,
+                mOnUserInteractionCallback,
                 mChannelEditorDialogController,
                 TEST_PACKAGE_NAME,
                 mNotificationChannel,
@@ -541,7 +540,7 @@
         mNotificationInfo.bindNotification(
                 mMockPackageManager,
                 mMockINotificationManager,
-                mVisualStabilityManager,
+                mOnUserInteractionCallback,
                 mChannelEditorDialogController,
                 TEST_PACKAGE_NAME, mNotificationChannel,
                 createMultipleChannelSet(MULTIPLE_CHANNEL_COUNT),
@@ -569,7 +568,7 @@
         mNotificationInfo.bindNotification(
                 mMockPackageManager,
                 mMockINotificationManager,
-                mVisualStabilityManager,
+                mOnUserInteractionCallback,
                 mChannelEditorDialogController,
                 TEST_PACKAGE_NAME,
                 mNotificationChannel,
@@ -593,7 +592,7 @@
         mNotificationInfo.bindNotification(
                 mMockPackageManager,
                 mMockINotificationManager,
-                mVisualStabilityManager,
+                mOnUserInteractionCallback,
                 mChannelEditorDialogController,
                 TEST_PACKAGE_NAME,
                 mNotificationChannel,
@@ -617,7 +616,7 @@
         mNotificationInfo.bindNotification(
                 mMockPackageManager,
                 mMockINotificationManager,
-                mVisualStabilityManager,
+                mOnUserInteractionCallback,
                 mChannelEditorDialogController,
                 TEST_PACKAGE_NAME,
                 mNotificationChannel,
@@ -643,7 +642,7 @@
         mNotificationInfo.bindNotification(
                 mMockPackageManager,
                 mMockINotificationManager,
-                mVisualStabilityManager,
+                mOnUserInteractionCallback,
                 mChannelEditorDialogController,
                 TEST_PACKAGE_NAME,
                 mNotificationChannel,
@@ -665,7 +664,7 @@
         mNotificationInfo.bindNotification(
                 mMockPackageManager,
                 mMockINotificationManager,
-                mVisualStabilityManager,
+                mOnUserInteractionCallback,
                 mChannelEditorDialogController,
                 TEST_PACKAGE_NAME,
                 mNotificationChannel,
@@ -688,7 +687,7 @@
         mNotificationInfo.bindNotification(
                 mMockPackageManager,
                 mMockINotificationManager,
-                mVisualStabilityManager,
+                mOnUserInteractionCallback,
                 mChannelEditorDialogController,
                 TEST_PACKAGE_NAME,
                 mNotificationChannel,
@@ -709,7 +708,7 @@
         mNotificationInfo.bindNotification(
                 mMockPackageManager,
                 mMockINotificationManager,
-                mVisualStabilityManager,
+                mOnUserInteractionCallback,
                 mChannelEditorDialogController,
                 TEST_PACKAGE_NAME,
                 mNotificationChannel,
@@ -730,7 +729,7 @@
         mNotificationInfo.bindNotification(
                 mMockPackageManager,
                 mMockINotificationManager,
-                mVisualStabilityManager,
+                mOnUserInteractionCallback,
                 mChannelEditorDialogController,
                 TEST_PACKAGE_NAME,
                 mNotificationChannel,
@@ -751,7 +750,7 @@
         mNotificationInfo.bindNotification(
                 mMockPackageManager,
                 mMockINotificationManager,
-                mVisualStabilityManager,
+                mOnUserInteractionCallback,
                 mChannelEditorDialogController,
                 TEST_PACKAGE_NAME,
                 mNotificationChannel,
@@ -774,7 +773,7 @@
         mNotificationInfo.bindNotification(
                 mMockPackageManager,
                 mMockINotificationManager,
-                mVisualStabilityManager,
+                mOnUserInteractionCallback,
                 mChannelEditorDialogController,
                 TEST_PACKAGE_NAME,
                 mNotificationChannel,
@@ -798,7 +797,7 @@
         mNotificationInfo.bindNotification(
                 mMockPackageManager,
                 mMockINotificationManager,
-                mVisualStabilityManager,
+                mOnUserInteractionCallback,
                 mChannelEditorDialogController,
                 TEST_PACKAGE_NAME,
                 mNotificationChannel,
@@ -825,7 +824,7 @@
         mNotificationInfo.bindNotification(
                 mMockPackageManager,
                 mMockINotificationManager,
-                mVisualStabilityManager,
+                mOnUserInteractionCallback,
                 mChannelEditorDialogController,
                 TEST_PACKAGE_NAME,
                 mNotificationChannel,
@@ -852,7 +851,7 @@
         mNotificationInfo.bindNotification(
                 mMockPackageManager,
                 mMockINotificationManager,
-                mVisualStabilityManager,
+                mOnUserInteractionCallback,
                 mChannelEditorDialogController,
                 TEST_PACKAGE_NAME,
                 mNotificationChannel,
@@ -879,7 +878,7 @@
         mNotificationInfo.bindNotification(
                 mMockPackageManager,
                 mMockINotificationManager,
-                mVisualStabilityManager,
+                mOnUserInteractionCallback,
                 mChannelEditorDialogController,
                 TEST_PACKAGE_NAME,
                 mNotificationChannel,
@@ -914,7 +913,7 @@
         mNotificationInfo.bindNotification(
                 mMockPackageManager,
                 mMockINotificationManager,
-                mVisualStabilityManager,
+                mOnUserInteractionCallback,
                 mChannelEditorDialogController,
                 TEST_PACKAGE_NAME,
                 mNotificationChannel,
@@ -942,7 +941,7 @@
         mNotificationInfo.bindNotification(
                 mMockPackageManager,
                 mMockINotificationManager,
-                mVisualStabilityManager,
+                mOnUserInteractionCallback,
                 mChannelEditorDialogController,
                 TEST_PACKAGE_NAME,
                 mNotificationChannel,
@@ -982,7 +981,7 @@
         mNotificationInfo.bindNotification(
                 mMockPackageManager,
                 mMockINotificationManager,
-                mVisualStabilityManager,
+                mOnUserInteractionCallback,
                 mChannelEditorDialogController,
                 TEST_PACKAGE_NAME,
                 mNotificationChannel,
@@ -1017,7 +1016,7 @@
         mNotificationInfo.bindNotification(
                 mMockPackageManager,
                 mMockINotificationManager,
-                mVisualStabilityManager,
+                mOnUserInteractionCallback,
                 mChannelEditorDialogController,
                 TEST_PACKAGE_NAME,
                 mNotificationChannel,
@@ -1047,7 +1046,7 @@
         mNotificationInfo.bindNotification(
                 mMockPackageManager,
                 mMockINotificationManager,
-                mVisualStabilityManager,
+                mOnUserInteractionCallback,
                 mChannelEditorDialogController,
                 TEST_PACKAGE_NAME,
                 mNotificationChannel,
@@ -1082,7 +1081,7 @@
         mNotificationInfo.bindNotification(
                 mMockPackageManager,
                 mMockINotificationManager,
-                mVisualStabilityManager,
+                mOnUserInteractionCallback,
                 mChannelEditorDialogController,
                 TEST_PACKAGE_NAME,
                 mNotificationChannel,
@@ -1120,7 +1119,7 @@
         mNotificationInfo.bindNotification(
                 mMockPackageManager,
                 mMockINotificationManager,
-                mVisualStabilityManager,
+                mOnUserInteractionCallback,
                 mChannelEditorDialogController,
                 TEST_PACKAGE_NAME,
                 mNotificationChannel,
@@ -1157,7 +1156,7 @@
         mNotificationInfo.bindNotification(
                 mMockPackageManager,
                 mMockINotificationManager,
-                mVisualStabilityManager,
+                mOnUserInteractionCallback,
                 mChannelEditorDialogController,
                 TEST_PACKAGE_NAME,
                 mNotificationChannel,
@@ -1175,7 +1174,7 @@
         mNotificationInfo.findViewById(R.id.done).performClick();
         mNotificationInfo.handleCloseControls(true, false);
 
-        verify(mVisualStabilityManager).temporarilyAllowReordering();
+        verify(mOnUserInteractionCallback).onImportanceChanged(mEntry);
     }
 
     @Test
@@ -1185,7 +1184,7 @@
         mNotificationInfo.bindNotification(
                 mMockPackageManager,
                 mMockINotificationManager,
-                mVisualStabilityManager,
+                mOnUserInteractionCallback,
                 mChannelEditorDialogController,
                 TEST_PACKAGE_NAME,
                 mNotificationChannel,
@@ -1216,7 +1215,7 @@
         mNotificationInfo.bindNotification(
                 mMockPackageManager,
                 mMockINotificationManager,
-                mVisualStabilityManager,
+                mOnUserInteractionCallback,
                 mChannelEditorDialogController,
                 TEST_PACKAGE_NAME,
                 mNotificationChannel,
@@ -1250,7 +1249,7 @@
         mNotificationInfo.bindNotification(
                 mMockPackageManager,
                 mMockINotificationManager,
-                mVisualStabilityManager,
+                mOnUserInteractionCallback,
                 mChannelEditorDialogController,
                 TEST_PACKAGE_NAME,
                 mNotificationChannel,
@@ -1283,7 +1282,7 @@
         mNotificationInfo.bindNotification(
                 mMockPackageManager,
                 mMockINotificationManager,
-                mVisualStabilityManager,
+                mOnUserInteractionCallback,
                 mChannelEditorDialogController,
                 TEST_PACKAGE_NAME,
                 mNotificationChannel,
@@ -1316,7 +1315,7 @@
         mNotificationInfo.bindNotification(
                 mMockPackageManager,
                 mMockINotificationManager,
-                mVisualStabilityManager,
+                mOnUserInteractionCallback,
                 mChannelEditorDialogController,
                 TEST_PACKAGE_NAME,
                 mNotificationChannel,
@@ -1342,7 +1341,7 @@
         mNotificationInfo.bindNotification(
                 mMockPackageManager,
                 mMockINotificationManager,
-                mVisualStabilityManager,
+                mOnUserInteractionCallback,
                 mChannelEditorDialogController,
                 TEST_PACKAGE_NAME,
                 mNotificationChannel,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java
index 8ccbb2e..9a8678f0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java
@@ -52,6 +52,7 @@
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.statusbar.NotificationMediaManager;
 import com.android.systemui.statusbar.NotificationRemoteInputManager;
+import com.android.systemui.statusbar.NotificationShadeWindowController;
 import com.android.systemui.statusbar.SmartReplyController;
 import com.android.systemui.statusbar.notification.ConversationNotificationProcessor;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
@@ -68,7 +69,6 @@
 import com.android.systemui.statusbar.phone.HeadsUpManagerPhone;
 import com.android.systemui.statusbar.phone.KeyguardBypassController;
 import com.android.systemui.statusbar.phone.NotificationGroupManager;
-import com.android.systemui.statusbar.phone.NotificationShadeWindowController;
 import com.android.systemui.statusbar.policy.SmartReplyConstants;
 
 import org.mockito.ArgumentCaptor;
@@ -422,10 +422,10 @@
                 mock(OnExpandClickListener.class),
                 mock(NotificationMediaManager.class),
                 mock(ExpandableNotificationRow.CoordinateOnClickListener.class),
-                mock(ExpandableNotificationRow.CoordinateOnClickListener.class),
                 mock(FalsingManager.class),
                 mStatusBarStateController,
-                mPeopleNotificationIdentifier);
+                mPeopleNotificationIdentifier,
+                mock(OnUserInteractionCallback.class));
         row.setAboveShelfChangedListener(aboveShelf -> { });
         mBindStage.getStageParams(entry).requireContentViews(extraInflationFlags);
         inflateAndWait(entry);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java
index ddac2ec..bca7b31 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java
@@ -24,9 +24,7 @@
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyBoolean;
 import static org.mockito.ArgumentMatchers.anyInt;
-import static org.mockito.ArgumentMatchers.argThat;
 import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Mockito.RETURNS_DEEP_STUBS;
 import static org.mockito.Mockito.atLeastOnce;
 import static org.mockito.Mockito.clearInvocations;
 import static org.mockito.Mockito.doNothing;
@@ -47,23 +45,20 @@
 import androidx.test.filters.SmallTest;
 
 import com.android.internal.logging.MetricsLogger;
-import com.android.internal.logging.nano.MetricsProto;
 import com.android.internal.logging.testing.UiEventLoggerFake;
 import com.android.internal.statusbar.IStatusBarService;
 import com.android.systemui.ExpandHelper;
 import com.android.systemui.R;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.classifier.FalsingManagerFake;
-import com.android.systemui.media.KeyguardMediaController;
 import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin;
 import com.android.systemui.statusbar.EmptyShadeView;
 import com.android.systemui.statusbar.FeatureFlags;
-import com.android.systemui.statusbar.NotificationLockscreenUserManager;
-import com.android.systemui.statusbar.NotificationLockscreenUserManager.UserChangedListener;
 import com.android.systemui.statusbar.NotificationMediaManager;
 import com.android.systemui.statusbar.NotificationPresenter;
 import com.android.systemui.statusbar.NotificationRemoteInputManager;
 import com.android.systemui.statusbar.NotificationShelf;
+import com.android.systemui.statusbar.NotificationShelfController;
 import com.android.systemui.statusbar.RemoteInputController;
 import com.android.systemui.statusbar.StatusBarState;
 import com.android.systemui.statusbar.SysuiStatusBarStateController;
@@ -73,27 +68,26 @@
 import com.android.systemui.statusbar.notification.NotificationEntryManagerLogger;
 import com.android.systemui.statusbar.notification.NotificationFilter;
 import com.android.systemui.statusbar.notification.NotificationSectionsFeatureManager;
-import com.android.systemui.statusbar.notification.VisualStabilityManager;
 import com.android.systemui.statusbar.notification.collection.NotifCollection;
 import com.android.systemui.statusbar.notification.collection.NotifPipeline;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder;
 import com.android.systemui.statusbar.notification.collection.NotificationRankingManager;
 import com.android.systemui.statusbar.notification.collection.inflation.NotificationRowBinder;
+import com.android.systemui.statusbar.notification.collection.legacy.VisualStabilityManager;
 import com.android.systemui.statusbar.notification.collection.provider.HighPriorityProvider;
 import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier;
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
 import com.android.systemui.statusbar.notification.row.FooterView;
 import com.android.systemui.statusbar.notification.row.NotificationBlockingHelperManager;
 import com.android.systemui.statusbar.notification.row.NotificationGutsManager;
+import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout.KeyguardBypassEnabledProvider;
 import com.android.systemui.statusbar.phone.HeadsUpManagerPhone;
-import com.android.systemui.statusbar.phone.KeyguardBypassController;
 import com.android.systemui.statusbar.phone.NotificationGroupManager;
 import com.android.systemui.statusbar.phone.NotificationIconAreaController;
 import com.android.systemui.statusbar.phone.ScrimController;
 import com.android.systemui.statusbar.phone.ShadeController;
 import com.android.systemui.statusbar.phone.StatusBar;
-import com.android.systemui.statusbar.policy.ZenModeController;
 import com.android.systemui.util.leak.LeakDetector;
 
 import org.junit.After;
@@ -102,7 +96,6 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.ArgumentCaptor;
-import org.mockito.ArgumentMatcher;
 import org.mockito.Mock;
 import org.mockito.junit.MockitoJUnit;
 import org.mockito.junit.MockitoRule;
@@ -134,14 +127,11 @@
     @Mock private NotificationIconAreaController mNotificationIconAreaController;
     @Mock private MetricsLogger mMetricsLogger;
     @Mock private NotificationRoundnessManager mNotificationRoundnessManager;
-    @Mock private KeyguardBypassController mKeyguardBypassController;
-    @Mock private KeyguardMediaController mKeyguardMediaController;
-    @Mock private ZenModeController mZenModeController;
+    @Mock private KeyguardBypassEnabledProvider mKeyguardBypassEnabledProvider;
     @Mock private NotificationSectionsManager mNotificationSectionsManager;
     @Mock private NotificationSection mNotificationSection;
-    @Mock private NotificationLockscreenUserManager mLockscreenUserManager;
     @Mock private FeatureFlags mFeatureFlags;
-    private UserChangedListener mUserChangedListener;
+    @Mock private SysuiStatusBarStateController mStatusBarStateController;
     private NotificationEntryManager mEntryManager;
     private int mOriginalInterruptionModelSetting;
     private UiEventLoggerFake mUiEventLoggerFake = new UiEventLoggerFake();
@@ -171,8 +161,6 @@
         mDependency.injectMockDependency(ShadeController.class);
         when(mRemoteInputManager.getController()).thenReturn(mRemoteInputController);
 
-        ArgumentCaptor<UserChangedListener> userChangedCaptor = ArgumentCaptor
-                .forClass(UserChangedListener.class);
         mEntryManager = new NotificationEntryManager(
                 mock(NotificationEntryManagerLogger.class),
                 mock(NotificationGroupManager.class),
@@ -197,7 +185,10 @@
         mEntryManager.setUpWithPresenter(mock(NotificationPresenter.class));
         when(mFeatureFlags.isNewNotifPipelineRenderingEnabled()).thenReturn(false);
 
+        NotificationShelfController notificationShelfController =
+                mock(NotificationShelfController.class);
         NotificationShelf notificationShelf = mock(NotificationShelf.class);
+        when(notificationShelfController.getView()).thenReturn(notificationShelf);
         when(mNotificationSectionsManager.createSectionsForBuckets()).thenReturn(
                 new NotificationSection[]{
                         mNotificationSection
@@ -207,17 +198,15 @@
         // which refer to members of NotificationStackScrollLayout. The spy
         // holds a copy of the CUT's instances of these KeyguardBypassController, so they still
         // refer to the CUT's member variables, not the spy's member variables.
-        mStackScrollerInternal = new NotificationStackScrollLayout(getContext(), null,
-                true /* allowLongPress */, mNotificationRoundnessManager,
+        mStackScrollerInternal = new NotificationStackScrollLayout(
+                getContext(),
+                null,
+                mNotificationRoundnessManager,
                 mock(DynamicPrivacyController.class),
-                mock(SysuiStatusBarStateController.class),
+                mStatusBarStateController,
                 mHeadsUpManager,
-                mKeyguardBypassController,
-                mKeyguardMediaController,
                 new FalsingManagerFake(),
-                mLockscreenUserManager,
                 mock(NotificationGutsManager.class),
-                mZenModeController,
                 mNotificationSectionsManager,
                 mock(ForegroundServiceSectionController.class),
                 mock(ForegroundServiceDismissalFeatureController.class),
@@ -227,15 +216,13 @@
                 mock(NotifCollection.class),
                 mUiEventLoggerFake
         );
-        verify(mLockscreenUserManager).addUserChangedListener(userChangedCaptor.capture());
-        mUserChangedListener = userChangedCaptor.getValue();
+        mStackScrollerInternal.initView(getContext(), mKeyguardBypassEnabledProvider);
         mStackScroller = spy(mStackScrollerInternal);
-        mStackScroller.setShelf(notificationShelf);
+        mStackScroller.setShelfController(notificationShelfController);
         mStackScroller.setStatusBar(mBar);
         mStackScroller.setScrimController(mock(ScrimController.class));
         mStackScroller.setGroupManager(mGroupManager);
         mStackScroller.setEmptyShadeView(mEmptyShadeView);
-        mStackScroller.setIconAreaController(mNotificationIconAreaController);
 
         // Stub out functionality that isn't necessary to test.
         doNothing().when(mBar)
@@ -266,9 +253,8 @@
     @Test
     public void updateEmptyView_dndSuppressing() {
         when(mEmptyShadeView.willBeGone()).thenReturn(true);
-        when(mZenModeController.areNotificationsHiddenInShade()).thenReturn(true);
 
-        mStackScroller.updateEmptyShadeView(true);
+        mStackScroller.updateEmptyShadeView(true, true);
 
         verify(mEmptyShadeView).setText(R.string.dnd_suppressing_shade_text);
     }
@@ -277,9 +263,8 @@
     public void updateEmptyView_dndNotSuppressing() {
         mStackScroller.setEmptyShadeView(mEmptyShadeView);
         when(mEmptyShadeView.willBeGone()).thenReturn(true);
-        when(mZenModeController.areNotificationsHiddenInShade()).thenReturn(false);
 
-        mStackScroller.updateEmptyShadeView(true);
+        mStackScroller.updateEmptyShadeView(true, false);
 
         verify(mEmptyShadeView).setText(R.string.empty_shade_text);
     }
@@ -288,12 +273,10 @@
     public void updateEmptyView_noNotificationsToDndSuppressing() {
         mStackScroller.setEmptyShadeView(mEmptyShadeView);
         when(mEmptyShadeView.willBeGone()).thenReturn(true);
-        when(mZenModeController.areNotificationsHiddenInShade()).thenReturn(false);
-        mStackScroller.updateEmptyShadeView(true);
+        mStackScroller.updateEmptyShadeView(true, false);
         verify(mEmptyShadeView).setText(R.string.empty_shade_text);
 
-        when(mZenModeController.areNotificationsHiddenInShade()).thenReturn(true);
-        mStackScroller.updateEmptyShadeView(true);
+        mStackScroller.updateEmptyShadeView(true, true);
         verify(mEmptyShadeView).setText(R.string.dnd_suppressing_shade_text);
     }
 
@@ -309,12 +292,6 @@
     }
 
     @Test
-    public void testOnStatePostChange_verifyIfProfileIsPublic() {
-        mUserChangedListener.onUserChanged(0);
-        verify(mLockscreenUserManager).isAnyProfilePublicMode();
-    }
-
-    @Test
     public void manageNotifications_visible() {
         FooterView view = mock(FooterView.class);
         mStackScroller.setFooterView(view);
@@ -433,9 +410,9 @@
     }
 
     @Test
-    public void testOnDensityOrFontScaleChanged_reInflatesFooterViews() {
+    public void testReInflatesFooterViews() {
         clearInvocations(mStackScroller);
-        mStackScroller.onDensityOrFontScaleChanged();
+        mStackScroller.reinflateViews();
         verify(mStackScroller).setFooterView(any());
         verify(mStackScroller).setEmptyShadeView(any());
     }
@@ -470,61 +447,6 @@
         assertNull(swipeActionHelper.getExposedMenuView());
     }
 
-    class LogMatcher implements ArgumentMatcher<LogMaker> {
-        private int mCategory, mType;
-
-        LogMatcher(int category, int type) {
-            mCategory = category;
-            mType = type;
-        }
-        public boolean matches(LogMaker l) {
-            return (l.getCategory() == mCategory)
-                    && (l.getType() == mType);
-        }
-
-        public String toString() {
-            return String.format("LogMaker(%d, %d)", mCategory, mType);
-        }
-    }
-
-    private LogMaker logMatcher(int category, int type) {
-        return argThat(new LogMatcher(category, type));
-    }
-
-    @Test
-    @UiThreadTest
-    public void testOnMenuClickedLogging() {
-        // Set up the object under test to have a valid mLongPressListener.  We're testing an
-        // anonymous-class member, mMenuEventListener, so we need to modify the state of the
-        // class itself, not the Mockito spy copied from it.  See notes in setup.
-        mStackScrollerInternal.setLongPressListener(
-                mock(ExpandableNotificationRow.LongPressListener.class));
-
-        ExpandableNotificationRow row = mock(ExpandableNotificationRow.class, RETURNS_DEEP_STUBS);
-        when(row.getEntry().getSbn().getLogMaker()).thenReturn(new LogMaker(
-                MetricsProto.MetricsEvent.VIEW_UNKNOWN));
-
-        mStackScroller.mMenuEventListener.onMenuClicked(row, 0, 0, mock(
-                NotificationMenuRowPlugin.MenuItem.class));
-        verify(row.getEntry().getSbn()).getLogMaker();  // This writes most of the log data
-        verify(mMetricsLogger).write(logMatcher(MetricsProto.MetricsEvent.ACTION_TOUCH_GEAR,
-                MetricsProto.MetricsEvent.TYPE_ACTION));
-    }
-
-    @Test
-    @UiThreadTest
-    public void testOnMenuShownLogging() { ;
-
-        ExpandableNotificationRow row = mock(ExpandableNotificationRow.class, RETURNS_DEEP_STUBS);
-        when(row.getEntry().getSbn().getLogMaker()).thenReturn(new LogMaker(
-                MetricsProto.MetricsEvent.VIEW_UNKNOWN));
-
-        mStackScroller.mMenuEventListener.onMenuShown(row);
-        verify(row.getEntry().getSbn()).getLogMaker();  // This writes most of the log data
-        verify(mMetricsLogger).write(logMatcher(MetricsProto.MetricsEvent.ACTION_REVEAL_GEAR,
-                MetricsProto.MetricsEvent.TYPE_ACTION));
-    }
-
     @Test
     public void testClearNotifications_All() {
         mStackScroller.clearNotifications(NotificationStackScrollLayout.ROWS_ALL, true);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollerControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollerControllerTest.java
new file mode 100644
index 0000000..83d6ac9
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollerControllerTest.java
@@ -0,0 +1,290 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification.stack;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.argThat;
+import static org.mockito.Mockito.RETURNS_DEEP_STUBS;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.reset;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.metrics.LogMaker;
+import android.testing.AndroidTestingRunner;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.internal.logging.MetricsLogger;
+import com.android.internal.logging.nano.MetricsProto;
+import com.android.systemui.SysuiTestCase;
+import com.android.systemui.colorextraction.SysuiColorExtractor;
+import com.android.systemui.media.KeyguardMediaController;
+import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin;
+import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin.OnMenuEventListener;
+import com.android.systemui.plugins.statusbar.StatusBarStateController;
+import com.android.systemui.statusbar.NotificationLockscreenUserManager;
+import com.android.systemui.statusbar.NotificationLockscreenUserManager.UserChangedListener;
+import com.android.systemui.statusbar.SysuiStatusBarStateController;
+import com.android.systemui.statusbar.notification.DynamicPrivacyController;
+import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
+import com.android.systemui.statusbar.notification.row.NotificationGutsManager;
+import com.android.systemui.statusbar.phone.HeadsUpManagerPhone;
+import com.android.systemui.statusbar.phone.KeyguardBypassController;
+import com.android.systemui.statusbar.policy.ConfigurationController;
+import com.android.systemui.statusbar.policy.ZenModeController;
+import com.android.systemui.tuner.TunerService;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.ArgumentMatcher;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+/**
+ * Tests for {@link NotificationStackScrollLayoutController}.
+ */
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
+public class NotificationStackScrollerControllerTest extends SysuiTestCase {
+
+    @Mock
+    private NotificationGutsManager mNotificationGutsManager;
+    @Mock
+    private HeadsUpManagerPhone mHeadsUpManager;
+    @Mock
+    private NotificationRoundnessManager mNotificationRoundnessManager;
+    @Mock
+    private TunerService mTunerService;
+    @Mock
+    private DynamicPrivacyController mDynamicPrivacyController;
+    @Mock
+    private ConfigurationController mConfigurationController;
+    @Mock
+    private NotificationStackScrollLayout mNotificationStackScrollLayout;
+    @Mock
+    private ZenModeController mZenModeController;
+    @Mock
+    private KeyguardMediaController mKeyguardMediaController;
+    @Mock
+    private SysuiStatusBarStateController mSysuiStatusBarStateController;
+    @Mock
+    private KeyguardBypassController mKeyguardBypassController;
+    @Mock
+    private SysuiColorExtractor mColorExtractor;
+    @Mock
+    private NotificationLockscreenUserManager mNotificationLockscreenUserManager;
+    @Mock
+    private MetricsLogger mMetricsLogger;
+
+    private NotificationStackScrollLayoutController mController;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+
+        mController = new NotificationStackScrollLayoutController(
+                true,
+                mNotificationGutsManager,
+                mHeadsUpManager,
+                mNotificationRoundnessManager,
+                mTunerService,
+                mDynamicPrivacyController,
+                mConfigurationController,
+                mSysuiStatusBarStateController,
+                mKeyguardMediaController,
+                mKeyguardBypassController,
+                mZenModeController,
+                mColorExtractor,
+                mNotificationLockscreenUserManager,
+                mMetricsLogger
+        );
+
+        when(mNotificationStackScrollLayout.isAttachedToWindow()).thenReturn(true);
+    }
+
+
+    @Test
+    public void testAttach_viewAlreadyAttached() {
+        mController.attach(mNotificationStackScrollLayout);
+
+        verify(mConfigurationController).addCallback(
+                any(ConfigurationController.ConfigurationListener.class));
+    }
+    @Test
+    public void testAttach_viewAttachedAfterInit() {
+        when(mNotificationStackScrollLayout.isAttachedToWindow()).thenReturn(false);
+
+        mController.attach(mNotificationStackScrollLayout);
+
+        verify(mConfigurationController, never()).addCallback(
+                any(ConfigurationController.ConfigurationListener.class));
+
+        mController.mOnAttachStateChangeListener.onViewAttachedToWindow(
+                mNotificationStackScrollLayout);
+
+        verify(mConfigurationController).addCallback(
+                any(ConfigurationController.ConfigurationListener.class));
+    }
+
+    @Test
+    public void testOnDensityOrFontScaleChanged_reInflatesFooterViews() {
+        mController.attach(mNotificationStackScrollLayout);
+        mController.mConfigurationListener.onDensityOrFontScaleChanged();
+        verify(mNotificationStackScrollLayout).reinflateViews();
+    }
+
+    @Test
+    public void testUpdateEmptyShadeView_notificationsVisible() {
+        when(mZenModeController.areNotificationsHiddenInShade()).thenReturn(true);
+        mController.attach(mNotificationStackScrollLayout);
+
+        mController.updateEmptyShadeView(true /* visible */);
+        verify(mNotificationStackScrollLayout).updateEmptyShadeView(
+                true /* visible */,
+                true /* notifVisibleInShade */);
+        reset(mNotificationStackScrollLayout);
+        mController.updateEmptyShadeView(false /* visible */);
+        verify(mNotificationStackScrollLayout).updateEmptyShadeView(
+                false /* visible */,
+                true /* notifVisibleInShade */);
+    }
+
+    @Test
+    public void testUpdateEmptyShadeView_notificationsHidden() {
+        when(mZenModeController.areNotificationsHiddenInShade()).thenReturn(false);
+        mController.attach(mNotificationStackScrollLayout);
+
+        mController.updateEmptyShadeView(true /* visible */);
+        verify(mNotificationStackScrollLayout).updateEmptyShadeView(
+                true /* visible */,
+                false /* notifVisibleInShade */);
+        reset(mNotificationStackScrollLayout);
+        mController.updateEmptyShadeView(false /* visible */);
+        verify(mNotificationStackScrollLayout).updateEmptyShadeView(
+                false /* visible */,
+                false /* notifVisibleInShade */);
+    }
+
+    @Test
+    public void testOnUserChange_verifySensitiveProfile() {
+        when(mNotificationLockscreenUserManager.isAnyProfilePublicMode()).thenReturn(true);
+
+        ArgumentCaptor<UserChangedListener> userChangedCaptor = ArgumentCaptor
+                .forClass(UserChangedListener.class);
+
+        mController.attach(mNotificationStackScrollLayout);
+        verify(mNotificationLockscreenUserManager)
+                .addUserChangedListener(userChangedCaptor.capture());
+        reset(mNotificationStackScrollLayout);
+
+        UserChangedListener changedListener = userChangedCaptor.getValue();
+        changedListener.onUserChanged(0);
+        verify(mNotificationStackScrollLayout).setCurrentUserid(0);
+        verify(mNotificationStackScrollLayout).updateSensitiveness(false, true);
+    }
+
+    @Test
+    public void testOnStatePostChange_verifyIfProfileIsPublic() {
+        when(mNotificationLockscreenUserManager.isAnyProfilePublicMode()).thenReturn(true);
+
+        ArgumentCaptor<StatusBarStateController.StateListener> stateListenerArgumentCaptor =
+                ArgumentCaptor.forClass(StatusBarStateController.StateListener.class);
+
+        mController.attach(mNotificationStackScrollLayout);
+        verify(mSysuiStatusBarStateController).addCallback(
+                stateListenerArgumentCaptor.capture(), anyInt());
+
+        StatusBarStateController.StateListener stateListener =
+                stateListenerArgumentCaptor.getValue();
+
+        stateListener.onStatePostChange();
+        verify(mNotificationStackScrollLayout).updateSensitiveness(false, true);
+    }
+
+
+    @Test
+    public void testOnMenuShownLogging() { ;
+
+        ExpandableNotificationRow row = mock(ExpandableNotificationRow.class, RETURNS_DEEP_STUBS);
+        when(row.getEntry().getSbn().getLogMaker()).thenReturn(new LogMaker(
+                MetricsProto.MetricsEvent.VIEW_UNKNOWN));
+
+
+        ArgumentCaptor<OnMenuEventListener> onMenuEventListenerArgumentCaptor =
+                ArgumentCaptor.forClass(OnMenuEventListener.class);
+
+        mController.attach(mNotificationStackScrollLayout);
+        verify(mNotificationStackScrollLayout).setMenuEventListener(
+                onMenuEventListenerArgumentCaptor.capture());
+
+        OnMenuEventListener onMenuEventListener = onMenuEventListenerArgumentCaptor.getValue();
+
+        onMenuEventListener.onMenuShown(row);
+        verify(row.getEntry().getSbn()).getLogMaker();  // This writes most of the log data
+        verify(mMetricsLogger).write(logMatcher(MetricsProto.MetricsEvent.ACTION_REVEAL_GEAR,
+                MetricsProto.MetricsEvent.TYPE_ACTION));
+    }
+
+    @Test
+    public void testOnMenuClickedLogging() {
+        ExpandableNotificationRow row = mock(ExpandableNotificationRow.class, RETURNS_DEEP_STUBS);
+        when(row.getEntry().getSbn().getLogMaker()).thenReturn(new LogMaker(
+                MetricsProto.MetricsEvent.VIEW_UNKNOWN));
+
+
+        ArgumentCaptor<OnMenuEventListener> onMenuEventListenerArgumentCaptor =
+                ArgumentCaptor.forClass(OnMenuEventListener.class);
+
+        mController.attach(mNotificationStackScrollLayout);
+        verify(mNotificationStackScrollLayout).setMenuEventListener(
+                onMenuEventListenerArgumentCaptor.capture());
+
+        OnMenuEventListener onMenuEventListener = onMenuEventListenerArgumentCaptor.getValue();
+
+        onMenuEventListener.onMenuClicked(row, 0, 0, mock(
+                NotificationMenuRowPlugin.MenuItem.class));
+        verify(row.getEntry().getSbn()).getLogMaker();  // This writes most of the log data
+        verify(mMetricsLogger).write(logMatcher(MetricsProto.MetricsEvent.ACTION_TOUCH_GEAR,
+                MetricsProto.MetricsEvent.TYPE_ACTION));
+    }
+
+    private LogMaker logMatcher(int category, int type) {
+        return argThat(new LogMatcher(category, type));
+    }
+
+    static class LogMatcher implements ArgumentMatcher<LogMaker> {
+        private int mCategory, mType;
+
+        LogMatcher(int category, int type) {
+            mCategory = category;
+            mType = type;
+        }
+        public boolean matches(LogMaker l) {
+            return (l.getCategory() == mCategory)
+                    && (l.getType() == mType);
+        }
+
+        public String toString() {
+            return String.format("LogMaker(%d, %d)", mCategory, mType);
+        }
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/BiometricsUnlockControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/BiometricsUnlockControllerTest.java
index 64907ee..f1c8ece 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/BiometricsUnlockControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/BiometricsUnlockControllerTest.java
@@ -43,6 +43,7 @@
 import com.android.systemui.dump.DumpManager;
 import com.android.systemui.keyguard.KeyguardViewMediator;
 import com.android.systemui.statusbar.NotificationMediaManager;
+import com.android.systemui.statusbar.NotificationShadeWindowController;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
 
 import org.junit.Before;
@@ -76,7 +77,7 @@
     @Mock
     private ScrimController mScrimController;
     @Mock
-    private StatusBar mStatusBar;
+    private BiometricUnlockController.BiometricModeListener mBiometricModeListener;
     @Mock
     private ShadeController mShadeController;
     @Mock
@@ -105,11 +106,12 @@
         mDependency.injectTestDependency(NotificationMediaManager.class, mMediaManager);
         res.addOverride(com.android.internal.R.integer.config_wakeUpDelayDoze, 0);
         mBiometricUnlockController = new BiometricUnlockController(mContext, mDozeScrimController,
-                mKeyguardViewMediator, mScrimController, mStatusBar, mShadeController,
+                mKeyguardViewMediator, mScrimController, mShadeController,
                 mNotificationShadeWindowController, mKeyguardStateController, mHandler,
                 mUpdateMonitor, res.getResources(), mKeyguardBypassController, mDozeParameters,
                 mMetricsLogger, mDumpManager);
         mBiometricUnlockController.setKeyguardViewController(mStatusBarKeyguardViewManager);
+        mBiometricUnlockController.setBiometricModeListener(mBiometricModeListener);
     }
 
     @Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CollapsedStatusBarFragmentTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CollapsedStatusBarFragmentTest.java
index ae87eef..781f875 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CollapsedStatusBarFragmentTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CollapsedStatusBarFragmentTest.java
@@ -41,7 +41,7 @@
 import org.mockito.Mockito;
 
 @RunWith(AndroidTestingRunner.class)
-@RunWithLooper()
+@RunWithLooper(setAsMainLooper = true)
 @SmallTest
 public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest {
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/DozeServiceHostTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/DozeServiceHostTest.java
index a6e2918..37ccac0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/DozeServiceHostTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/DozeServiceHostTest.java
@@ -36,15 +36,16 @@
 import com.android.keyguard.KeyguardUpdateMonitor;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.assist.AssistManager;
+import com.android.systemui.biometrics.AuthController;
 import com.android.systemui.doze.DozeHost;
 import com.android.systemui.doze.DozeLog;
 import com.android.systemui.keyguard.KeyguardViewMediator;
 import com.android.systemui.keyguard.WakefulnessLifecycle;
+import com.android.systemui.statusbar.NotificationShadeWindowController;
 import com.android.systemui.statusbar.PulseExpansionHandler;
 import com.android.systemui.statusbar.StatusBarState;
 import com.android.systemui.statusbar.StatusBarStateControllerImpl;
 import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator;
-import com.android.systemui.statusbar.notification.VisualStabilityManager;
 import com.android.systemui.statusbar.policy.BatteryController;
 import com.android.systemui.statusbar.policy.DeviceProvisionedController;
 
@@ -68,7 +69,6 @@
     @Mock private HeadsUpManagerPhone mHeadsUpManager;
     @Mock private ScrimController mScrimController;
     @Mock private DozeScrimController mDozeScrimController;
-    @Mock private VisualStabilityManager mVisualStabilityManager;
     @Mock private KeyguardViewMediator mKeyguardViewMediator;
     @Mock private StatusBarStateControllerImpl mStatusBarStateController;
     @Mock private BatteryController mBatteryController;
@@ -89,6 +89,7 @@
     @Mock private View mAmbientIndicationContainer;
     @Mock private BiometricUnlockController mBiometricUnlockController;
     @Mock private LockscreenLockIconController mLockscreenLockIconController;
+    @Mock private AuthController mAuthController;
 
     @Before
     public void setup() {
@@ -97,13 +98,16 @@
                 mStatusBarStateController, mDeviceProvisionedController, mHeadsUpManager,
                 mBatteryController, mScrimController, () -> mBiometricUnlockController,
                 mKeyguardViewMediator, () -> mAssistManager, mDozeScrimController,
-                mKeyguardUpdateMonitor, mVisualStabilityManager, mPulseExpansionHandler,
+                mKeyguardUpdateMonitor, mPulseExpansionHandler,
                 mNotificationShadeWindowController, mNotificationWakeUpCoordinator,
-                mLockscreenLockIconController);
+                mLockscreenLockIconController, mAuthController, mNotificationIconAreaController);
 
-        mDozeServiceHost.initialize(mStatusBar, mNotificationIconAreaController,
-                mStatusBarKeyguardViewManager, mNotificationShadeWindowViewController,
-                mNotificationPanel, mAmbientIndicationContainer);
+        mDozeServiceHost.initialize(
+                mStatusBar,
+                mStatusBarKeyguardViewManager,
+                mNotificationShadeWindowViewController,
+                mNotificationPanel,
+                mAmbientIndicationContainer);
     }
 
     @Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceControllerTest.java
index e546dff..8dea84c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceControllerTest.java
@@ -38,7 +38,7 @@
 import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator;
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
 import com.android.systemui.statusbar.notification.row.NotificationTestHelper;
-import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout;
+import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
 
 import org.junit.Assert;
@@ -51,8 +51,8 @@
 @RunWithLooper
 public class HeadsUpAppearanceControllerTest extends SysuiTestCase {
 
-    private final NotificationStackScrollLayout mStackScroller =
-            mock(NotificationStackScrollLayout.class);
+    private final NotificationStackScrollLayoutController mStackScrollerController =
+            mock(NotificationStackScrollLayoutController.class);
     private final NotificationPanelViewController mPanelView =
             mock(NotificationPanelViewController.class);
     private final DarkIconDispatcher mDarkIconDispatcher = mock(DarkIconDispatcher.class);
@@ -93,9 +93,9 @@
                 mWakeUpCoordinator,
                 mKeyguardStateController,
                 mCommandQueue,
-                mHeadsUpStatusBarView,
-                mStackScroller,
+                mStackScrollerController,
                 mPanelView,
+                mHeadsUpStatusBarView,
                 new View(mContext),
                 mOperatorNameView,
                 new View(mContext));
@@ -172,9 +172,9 @@
                 mWakeUpCoordinator,
                 mKeyguardStateController,
                 mCommandQueue,
-                mHeadsUpStatusBarView,
-                mStackScroller,
+                mStackScrollerController,
                 mPanelView,
+                mHeadsUpStatusBarView,
                 new View(mContext),
                 new View(mContext),
                 new View(mContext));
@@ -193,14 +193,14 @@
         reset(mHeadsUpManager);
         reset(mDarkIconDispatcher);
         reset(mPanelView);
-        reset(mStackScroller);
+        reset(mStackScrollerController);
         mHeadsUpAppearanceController.destroy();
         verify(mHeadsUpManager).removeListener(any());
         verify(mDarkIconDispatcher).removeDarkReceiver((DarkIconDispatcher.DarkReceiver) any());
         verify(mPanelView).removeVerticalTranslationListener(any());
         verify(mPanelView).removeTrackingHeadsUpListener(any());
         verify(mPanelView).setHeadsUpAppearanceController(any());
-        verify(mStackScroller).removeOnExpandedHeightChangedListener(any());
-        verify(mStackScroller).removeOnLayoutChangeListener(any());
+        verify(mStackScrollerController).removeOnExpandedHeightChangedListener(any());
+        verify(mStackScrollerController).removeOnLayoutChangeListener(any());
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhoneTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhoneTest.java
index 6d642ec..2239b1b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhoneTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhoneTest.java
@@ -33,9 +33,10 @@
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.statusbar.AlertingNotificationManager;
 import com.android.systemui.statusbar.AlertingNotificationManagerTest;
-import com.android.systemui.statusbar.notification.VisualStabilityManager;
+import com.android.systemui.statusbar.NotificationShadeWindowController;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder;
+import com.android.systemui.statusbar.notification.collection.legacy.VisualStabilityManager;
 import com.android.systemui.statusbar.policy.AccessibilityManagerWrapper;
 import com.android.systemui.statusbar.policy.ConfigurationController;
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LightBarControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LightBarControllerTest.java
index 3e46907..ccdc69a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LightBarControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LightBarControllerTest.java
@@ -34,6 +34,7 @@
 
 import com.android.internal.view.AppearanceRegion;
 import com.android.systemui.SysuiTestCase;
+import com.android.systemui.navigationbar.NavigationModeController;
 import com.android.systemui.statusbar.policy.BatteryController;
 
 import org.junit.Before;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NavigationBarFragmentTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NavigationBarFragmentTest.java
deleted file mode 100644
index 00cbddc..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NavigationBarFragmentTest.java
+++ /dev/null
@@ -1,336 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
- * except in compliance with the License. You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software distributed under the
- * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the specific language governing
- * permissions and limitations under the License.
- */
-
-package com.android.systemui.statusbar.phone;
-
-import static android.app.StatusBarManager.NAVIGATION_HINT_BACK_ALT;
-import static android.app.StatusBarManager.NAVIGATION_HINT_IME_SHOWN;
-import static android.inputmethodservice.InputMethodService.BACK_DISPOSITION_DEFAULT;
-import static android.inputmethodservice.InputMethodService.IME_VISIBLE;
-import static android.view.Display.DEFAULT_DISPLAY;
-import static android.view.DisplayAdjustments.DEFAULT_DISPLAY_ADJUSTMENTS;
-
-import static com.android.systemui.statusbar.phone.NavigationBarFragment.NavBarActionEvent.NAVBAR_ASSIST_LONGPRESS;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-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.Mockito.mock;
-import static org.mockito.Mockito.times;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
-import android.annotation.LayoutRes;
-import android.annotation.Nullable;
-import android.app.Fragment;
-import android.app.FragmentController;
-import android.app.FragmentHostCallback;
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.IntentFilter;
-import android.hardware.display.DisplayManagerGlobal;
-import android.os.Bundle;
-import android.os.Handler;
-import android.os.Looper;
-import android.os.UserHandle;
-import android.testing.AndroidTestingRunner;
-import android.testing.LeakCheck.Tracker;
-import android.testing.TestableLooper;
-import android.testing.TestableLooper.RunWithLooper;
-import android.view.Display;
-import android.view.DisplayInfo;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-import android.view.WindowManager;
-import android.view.accessibility.AccessibilityManager.AccessibilityServicesStateChangeListener;
-
-import androidx.test.filters.SmallTest;
-
-import com.android.internal.logging.MetricsLogger;
-import com.android.internal.logging.UiEventLogger;
-import com.android.systemui.Dependency;
-import com.android.systemui.SysuiBaseFragmentTest;
-import com.android.systemui.SysuiTestableContext;
-import com.android.systemui.accessibility.SystemActions;
-import com.android.systemui.assist.AssistManager;
-import com.android.systemui.broadcast.BroadcastDispatcher;
-import com.android.systemui.model.SysUiState;
-import com.android.systemui.plugins.statusbar.StatusBarStateController;
-import com.android.systemui.recents.OverviewProxyService;
-import com.android.systemui.recents.Recents;
-import com.android.systemui.stackdivider.Divider;
-import com.android.systemui.statusbar.CommandQueue;
-import com.android.systemui.statusbar.NotificationRemoteInputManager;
-import com.android.systemui.statusbar.policy.AccessibilityManagerWrapper;
-import com.android.systemui.statusbar.policy.DeviceProvisionedController;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-
-import java.util.Optional;
-
-@RunWith(AndroidTestingRunner.class)
-@RunWithLooper(setAsMainLooper = true)
-@SmallTest
-public class NavigationBarFragmentTest extends SysuiBaseFragmentTest {
-    private static final int EXTERNAL_DISPLAY_ID = 2;
-    private static final int NAV_BAR_VIEW_ID = 43;
-
-    private Fragment mFragmentExternalDisplay;
-    private FragmentController mControllerExternalDisplay;
-
-    private SysuiTestableContext mSysuiTestableContextExternal;
-    private OverviewProxyService mOverviewProxyService;
-    private CommandQueue mCommandQueue;
-    private SysUiState mMockSysUiState;
-    @Mock
-    private BroadcastDispatcher mBroadcastDispatcher;
-    @Mock
-    private Divider mDivider;
-    @Mock
-    private Recents mRecents;
-    @Mock
-    private SystemActions mSystemActions;
-    @Mock
-    private UiEventLogger mUiEventLogger;
-
-    private AccessibilityManagerWrapper mAccessibilityWrapper =
-            new AccessibilityManagerWrapper(mContext) {
-                Tracker mTracker = mLeakCheck.getTracker("accessibility_manager");
-
-                @Override
-                public void addCallback(AccessibilityServicesStateChangeListener listener) {
-                    mTracker.getLeakInfo(listener).addAllocation(new Throwable());
-                }
-
-                @Override
-                public void removeCallback(AccessibilityServicesStateChangeListener listener) {
-                    mTracker.getLeakInfo(listener).clearAllocations();
-                }
-            };
-
-    public NavigationBarFragmentTest() {
-        super(NavigationBarFragment.class);
-    }
-
-    protected void createRootView() {
-        mView = new NavigationBarFrame(mSysuiContext);
-        mView.setId(NAV_BAR_VIEW_ID);
-    }
-
-    @Before
-    public void setupFragment() throws Exception {
-        MockitoAnnotations.initMocks(this);
-
-        mCommandQueue = new CommandQueue(mContext);
-        setupSysuiDependency();
-        createRootView();
-        mOverviewProxyService =
-                mDependency.injectMockDependency(OverviewProxyService.class);
-        TestableLooper.get(this).runWithLooper(() -> {
-            mHandler = new Handler();
-
-            mFragment = instantiate(mSysuiContext, NavigationBarFragment.class.getName(), null);
-            mFragments = FragmentController.createController(
-                    new HostCallbacksForExternalDisplay(mSysuiContext));
-            mFragments.attachHost(null);
-            mFragments.getFragmentManager().beginTransaction()
-                    .replace(NAV_BAR_VIEW_ID, mFragment)
-                    .commit();
-            mControllerExternalDisplay = FragmentController.createController(
-                    new HostCallbacksForExternalDisplay(mSysuiTestableContextExternal));
-            mControllerExternalDisplay.attachHost(null);
-            mFragmentExternalDisplay = instantiate(mSysuiTestableContextExternal,
-                    NavigationBarFragment.class.getName(), null);
-            mControllerExternalDisplay.getFragmentManager().beginTransaction()
-                    .replace(NAV_BAR_VIEW_ID, mFragmentExternalDisplay)
-                    .commit();
-        });
-    }
-
-    private void setupSysuiDependency() {
-        Display display = new Display(DisplayManagerGlobal.getInstance(), EXTERNAL_DISPLAY_ID,
-                new DisplayInfo(), DEFAULT_DISPLAY_ADJUSTMENTS);
-        mSysuiTestableContextExternal = (SysuiTestableContext) mSysuiContext.createDisplayContext(
-                display);
-
-        injectLeakCheckedDependencies(ALL_SUPPORTED_CLASSES);
-        WindowManager windowManager = mock(WindowManager.class);
-        Display defaultDisplay = mContext.getSystemService(WindowManager.class).getDefaultDisplay();
-        when(windowManager.getDefaultDisplay()).thenReturn(
-                defaultDisplay);
-        mContext.addMockSystemService(Context.WINDOW_SERVICE, windowManager);
-
-        mDependency.injectTestDependency(Dependency.BG_LOOPER, Looper.getMainLooper());
-        mDependency.injectTestDependency(AccessibilityManagerWrapper.class, mAccessibilityWrapper);
-
-        mMockSysUiState = mock(SysUiState.class);
-        when(mMockSysUiState.setFlag(anyInt(), anyBoolean())).thenReturn(mMockSysUiState);
-    }
-
-    @Test
-    public void testHomeLongPress() {
-        NavigationBarFragment navigationBarFragment = (NavigationBarFragment) mFragment;
-
-        mFragments.dispatchResume();
-        processAllMessages();
-        navigationBarFragment.onHomeLongClick(navigationBarFragment.getView());
-
-        verify(mUiEventLogger, times(1)).log(NAVBAR_ASSIST_LONGPRESS);
-    }
-
-    @Test
-    public void testRegisteredWithDispatcher() {
-        mFragments.dispatchResume();
-        processAllMessages();
-
-        verify(mBroadcastDispatcher).registerReceiverWithHandler(
-                any(BroadcastReceiver.class),
-                any(IntentFilter.class),
-                any(Handler.class),
-                any(UserHandle.class));
-    }
-
-    @Test
-    public void testSetImeWindowStatusWhenImeSwitchOnDisplay() {
-        // Create default & external NavBar fragment.
-        NavigationBarFragment defaultNavBar = (NavigationBarFragment) mFragment;
-        NavigationBarFragment externalNavBar = (NavigationBarFragment) mFragmentExternalDisplay;
-        mFragments.dispatchCreate();
-        processAllMessages();
-        mFragments.dispatchResume();
-        processAllMessages();
-        mControllerExternalDisplay.dispatchCreate();
-        processAllMessages();
-        mControllerExternalDisplay.dispatchResume();
-        processAllMessages();
-
-        // Set IME window status for default NavBar.
-        mCommandQueue.setImeWindowStatus(DEFAULT_DISPLAY, null, IME_VISIBLE,
-                BACK_DISPOSITION_DEFAULT, true, false);
-        processAllMessages();
-
-        // Verify IME window state will be updated in default NavBar & external NavBar state reset.
-        assertEquals(NAVIGATION_HINT_BACK_ALT | NAVIGATION_HINT_IME_SHOWN,
-                defaultNavBar.getNavigationIconHints());
-        assertFalse((externalNavBar.getNavigationIconHints() & NAVIGATION_HINT_BACK_ALT) != 0);
-        assertFalse((externalNavBar.getNavigationIconHints() & NAVIGATION_HINT_IME_SHOWN) != 0);
-
-        // Set IME window status for external NavBar.
-        mCommandQueue.setImeWindowStatus(EXTERNAL_DISPLAY_ID, null,
-                IME_VISIBLE, BACK_DISPOSITION_DEFAULT, true, false);
-        processAllMessages();
-
-        // Verify IME window state will be updated in external NavBar & default NavBar state reset.
-        assertEquals(NAVIGATION_HINT_BACK_ALT | NAVIGATION_HINT_IME_SHOWN,
-                externalNavBar.getNavigationIconHints());
-        assertFalse((defaultNavBar.getNavigationIconHints() & NAVIGATION_HINT_BACK_ALT) != 0);
-        assertFalse((defaultNavBar.getNavigationIconHints() & NAVIGATION_HINT_IME_SHOWN) != 0);
-    }
-
-    @Override
-    protected Fragment instantiate(Context context, String className, Bundle arguments) {
-        DeviceProvisionedController deviceProvisionedController =
-                mock(DeviceProvisionedController.class);
-        when(deviceProvisionedController.isDeviceProvisioned()).thenReturn(true);
-        assertNotNull(mAccessibilityWrapper);
-        return new NavigationBarFragment(
-                context.getDisplayId() == DEFAULT_DISPLAY ? mAccessibilityWrapper
-                        : mock(AccessibilityManagerWrapper.class),
-                deviceProvisionedController,
-                new MetricsLogger(),
-                mock(AssistManager.class),
-                mOverviewProxyService,
-                mock(NavigationModeController.class),
-                mock(StatusBarStateController.class),
-                mMockSysUiState,
-                mBroadcastDispatcher,
-                mCommandQueue,
-                mDivider,
-                Optional.of(mRecents),
-                () -> mock(StatusBar.class),
-                mock(ShadeController.class),
-                mock(NotificationRemoteInputManager.class),
-                mock(SystemActions.class),
-                mHandler,
-                mUiEventLogger);
-    }
-
-    private class HostCallbacksForExternalDisplay extends
-            FragmentHostCallback<NavigationBarFragmentTest> {
-        private Context mDisplayContext;
-
-        HostCallbacksForExternalDisplay(Context context) {
-            super(context, mHandler, 0);
-            mDisplayContext = context;
-        }
-
-        @Override
-        public NavigationBarFragmentTest onGetHost() {
-            return NavigationBarFragmentTest.this;
-        }
-
-        @Override
-        public Fragment instantiate(Context context, String className, Bundle arguments) {
-            return NavigationBarFragmentTest.this.instantiate(context, className, arguments);
-        }
-
-        @Override
-        public View onFindViewById(int id) {
-            return mView.findViewById(id);
-        }
-
-        @Override
-        public LayoutInflater onGetLayoutInflater() {
-            return new LayoutInflaterWrapper(mDisplayContext);
-        }
-    }
-
-    private static class LayoutInflaterWrapper extends LayoutInflater {
-        protected LayoutInflaterWrapper(Context context) {
-            super(context);
-        }
-
-        @Override
-        public LayoutInflater cloneInContext(Context newContext) {
-            return null;
-        }
-
-        @Override
-        public View inflate(@LayoutRes int resource, @Nullable ViewGroup root,
-                boolean attachToRoot) {
-            NavigationBarView view = mock(NavigationBarView.class);
-            when(view.getDisplay()).thenReturn(mContext.getDisplay());
-            when(view.getBackButton()).thenReturn(mock(ButtonDispatcher.class));
-            when(view.getHomeButton()).thenReturn(mock(ButtonDispatcher.class));
-            when(view.getRecentsButton()).thenReturn(mock(ButtonDispatcher.class));
-            when(view.getAccessibilityButton()).thenReturn(mock(ButtonDispatcher.class));
-            when(view.getRotateSuggestionButton()).thenReturn(mock(RotationContextButton.class));
-            when(view.getBarTransitions()).thenReturn(mock(NavigationBarTransitions.class));
-            when(view.getLightTransitionsController()).thenReturn(
-                    mock(LightBarTransitionsController.class));
-            when(view.getRotationButtonController()).thenReturn(
-                    mock(RotationButtonController.class));
-            when(view.isRecentsButtonVisible()).thenReturn(true);
-            return view;
-        }
-    }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationIconAreaControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationIconAreaControllerTest.java
index d1a439f..5222fff 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationIconAreaControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationIconAreaControllerTest.java
@@ -17,8 +17,6 @@
 
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
-import static org.mockito.ArgumentMatchers.anyInt;
-import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
@@ -29,10 +27,13 @@
 
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.bubbles.BubbleController;
+import com.android.systemui.demomode.DemoModeController;
+import com.android.systemui.plugins.DarkIconDispatcher;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.statusbar.NotificationListener;
 import com.android.systemui.statusbar.NotificationMediaManager;
 import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator;
+import com.android.systemui.statusbar.notification.collection.notifcollection.CommonNotifCollection;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -48,8 +49,6 @@
     @Mock
     private NotificationListener mListener;
     @Mock
-    StatusBar mStatusBar;
-    @Mock
     StatusBarStateController mStatusBarStateController;
     @Mock
     NotificationWakeUpCoordinator mWakeUpCoordinator;
@@ -58,26 +57,37 @@
     @Mock
     NotificationMediaManager mNotificationMediaManager;
     @Mock
-    NotificationIconContainer mNotificationIconContainer;
-    @Mock
     DozeParameters mDozeParameters;
     @Mock
-    NotificationShadeWindowView mNotificationShadeWindowView;
+    CommonNotifCollection mNotifCollection;
+    @Mock
+    DarkIconDispatcher mDarkIconDispatcher;
+    @Mock
+    StatusBarWindowController mStatusBarWindowController;
     private NotificationIconAreaController mController;
     @Mock
     private BubbleController mBubbleController;
+    @Mock private DemoModeController mDemoModeController;
+    @Mock
+    private NotificationIconContainer mAodIcons;
 
     @Before
     public void setup() {
         MockitoAnnotations.initMocks(this);
 
-        when(mStatusBar.getNotificationShadeWindowView()).thenReturn(mNotificationShadeWindowView);
-        when(mNotificationShadeWindowView.findViewById(anyInt())).thenReturn(
-                mNotificationIconContainer);
-
-        mController = new NotificationIconAreaController(mContext, mStatusBar,
-                mStatusBarStateController, mWakeUpCoordinator, mKeyguardBypassController,
-                mNotificationMediaManager, mListener, mDozeParameters, mBubbleController);
+        mController = new NotificationIconAreaController(
+                mContext,
+                mStatusBarStateController,
+                mWakeUpCoordinator,
+                mKeyguardBypassController,
+                mNotificationMediaManager,
+                mListener,
+                mDozeParameters,
+                mBubbleController,
+                mDemoModeController,
+                mDarkIconDispatcher,
+                mStatusBarWindowController);
+        mController.setupAodIcons(mAodIcons);
     }
 
     @Test
@@ -98,7 +108,7 @@
     public void testAppearResetsTranslation() {
         when(mDozeParameters.shouldControlScreenOff()).thenReturn(false);
         mController.appearAodIcons();
-        verify(mNotificationIconContainer).setTranslationY(0);
-        verify(mNotificationIconContainer).setAlpha(1.0f);
+        verify(mAodIcons).setTranslationY(0);
+        verify(mAodIcons).setAlpha(1.0f);
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewTest.java
index c7434f6..c9e9d94 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewTest.java
@@ -63,7 +63,7 @@
 import com.android.systemui.statusbar.FlingAnimationUtils;
 import com.android.systemui.statusbar.KeyguardAffordanceView;
 import com.android.systemui.statusbar.NotificationLockscreenUserManager;
-import com.android.systemui.statusbar.NotificationShelf;
+import com.android.systemui.statusbar.NotificationShelfController;
 import com.android.systemui.statusbar.PulseExpansionHandler;
 import com.android.systemui.statusbar.StatusBarStateControllerImpl;
 import com.android.systemui.statusbar.SysuiStatusBarStateController;
@@ -74,6 +74,7 @@
 import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator;
 import com.android.systemui.statusbar.notification.stack.NotificationRoundnessManager;
 import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout;
+import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController;
 import com.android.systemui.statusbar.policy.ConfigurationController;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
 import com.android.systemui.statusbar.policy.ZenModeController;
@@ -115,7 +116,7 @@
     @Mock
     private HeadsUpManagerPhone mHeadsUpManager;
     @Mock
-    private NotificationShelf mNotificationShelf;
+    private NotificationShelfController mNotificationShelfController;
     @Mock
     private NotificationGroupManager mGroupManager;
     @Mock
@@ -186,7 +187,8 @@
     private StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
     @Mock
     private KeyguardClockSwitchController mKeyguardClockSwitchController;
-    private FlingAnimationUtils.Builder mFlingAnimationUtilsBuilder;
+    @Mock
+    private NotificationStackScrollLayoutController mNotificationStackScrollLayoutController;
 
     private NotificationPanelViewController mNotificationPanelViewController;
     private View.AccessibilityDelegate mAccessibiltyDelegate;
@@ -205,14 +207,18 @@
         when(mView.findViewById(R.id.keyguard_clock_container)).thenReturn(mKeyguardClockSwitch);
         when(mView.findViewById(R.id.notification_stack_scroller))
                 .thenReturn(mNotificationStackScrollLayout);
-        when(mNotificationStackScrollLayout.getHeight()).thenReturn(1000);
-        when(mNotificationStackScrollLayout.getHeadsUpCallback()).thenReturn(mHeadsUpCallback);
+        when(mNotificationStackScrollLayout.getController())
+                .thenReturn(mNotificationStackScrollLayoutController);
+        when(mNotificationStackScrollLayoutController.getHeight()).thenReturn(1000);
+        when(mNotificationStackScrollLayoutController.getHeadsUpCallback())
+                .thenReturn(mHeadsUpCallback);
         when(mView.findViewById(R.id.keyguard_bottom_area)).thenReturn(mKeyguardBottomArea);
         when(mKeyguardBottomArea.getLeftView()).thenReturn(mock(KeyguardAffordanceView.class));
         when(mKeyguardBottomArea.getRightView()).thenReturn(mock(KeyguardAffordanceView.class));
         when(mView.findViewById(R.id.big_clock_container)).thenReturn(mBigClockContainer);
         when(mView.findViewById(R.id.qs_frame)).thenReturn(mQsFrame);
-        mFlingAnimationUtilsBuilder = new FlingAnimationUtils.Builder(mDisplayMetrics);
+        FlingAnimationUtils.Builder flingAnimationUtilsBuilder = new FlingAnimationUtils.Builder(
+                mDisplayMetrics);
 
         doAnswer((Answer<Void>) invocation -> {
             mTouchHandler = invocation.getArgument(0);
@@ -241,19 +247,23 @@
                 mDozeParameters, mCommandQueue, mVibratorHelper,
                 mLatencyTracker, mPowerManager, mAccessibilityManager, 0, mUpdateMonitor,
                 mMetricsLogger, mActivityManager, mZenModeController, mConfigurationController,
-                mFlingAnimationUtilsBuilder, mStatusBarTouchableRegionManager,
+                flingAnimationUtilsBuilder, mStatusBarTouchableRegionManager,
                 mConversationNotificationManager, mMediaHiearchyManager,
                 mBiometricUnlockController, mStatusBarKeyguardViewManager,
-                () -> mKeyguardClockSwitchController);
-        mNotificationPanelViewController.initDependencies(mStatusBar, mGroupManager,
-                mNotificationShelf, mNotificationAreaController, mScrimController);
+                () -> mKeyguardClockSwitchController,
+                mNotificationStackScrollLayoutController,
+                mNotificationAreaController);
+        mNotificationPanelViewController.initDependencies(
+                mStatusBar,
+                mGroupManager,
+                mNotificationShelfController,
+                mScrimController);
         mNotificationPanelViewController.setHeadsUpManager(mHeadsUpManager);
         mNotificationPanelViewController.setBar(mPanelBar);
 
         ArgumentCaptor<View.AccessibilityDelegate> accessibilityDelegateArgumentCaptor =
                 ArgumentCaptor.forClass(View.AccessibilityDelegate.class);
-        verify(mView)
-                .setAccessibilityDelegate(accessibilityDelegateArgumentCaptor.capture());
+        verify(mView).setAccessibilityDelegate(accessibilityDelegateArgumentCaptor.capture());
         mAccessibiltyDelegate = accessibilityDelegateArgumentCaptor.getValue();
     }
 
@@ -261,8 +271,10 @@
     public void testSetDozing_notifiesNsslAndStateController() {
         mNotificationPanelViewController.setDozing(true /* dozing */, true /* animate */,
                 null /* touch */);
-        InOrder inOrder = inOrder(mNotificationStackScrollLayout, mStatusBarStateController);
-        inOrder.verify(mNotificationStackScrollLayout).setDozing(eq(true), eq(true), eq(null));
+        InOrder inOrder = inOrder(
+                mNotificationStackScrollLayoutController, mStatusBarStateController);
+        inOrder.verify(mNotificationStackScrollLayoutController)
+                .setDozing(eq(true), eq(true), eq(null));
         inOrder.verify(mStatusBarStateController).setDozeAmount(eq(1f), eq(true));
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationShadeWindowControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationShadeWindowControllerImplTest.java
similarity index 97%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationShadeWindowControllerTest.java
rename to packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationShadeWindowControllerImplTest.java
index 8c37cf1..fcea17c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationShadeWindowControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationShadeWindowControllerImplTest.java
@@ -57,7 +57,7 @@
 @RunWith(AndroidTestingRunner.class)
 @RunWithLooper
 @SmallTest
-public class NotificationShadeWindowControllerTest extends SysuiTestCase {
+public class NotificationShadeWindowControllerImplTest extends SysuiTestCase {
 
     @Mock private WindowManager mWindowManager;
     @Mock private DozeParameters mDozeParameters;
@@ -72,7 +72,7 @@
     @Mock private DumpManager mDumpManager;
     @Captor private ArgumentCaptor<WindowManager.LayoutParams> mLayoutParameters;
 
-    private NotificationShadeWindowController mNotificationShadeWindowController;
+    private NotificationShadeWindowControllerImpl mNotificationShadeWindowController;
 
     @Before
     public void setUp() {
@@ -80,7 +80,7 @@
         when(mDozeParameters.getAlwaysOn()).thenReturn(true);
         when(mColorExtractor.getNeutralColors()).thenReturn(mGradientColors);
 
-        mNotificationShadeWindowController = new NotificationShadeWindowController(mContext,
+        mNotificationShadeWindowController = new NotificationShadeWindowControllerImpl(mContext,
                 mWindowManager, mActivityManager, mDozeParameters, mStatusBarStateController,
                 mConfigurationController, mKeyguardViewMediator, mKeyguardBypassController,
                 mColorExtractor, mDumpManager);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationShadeWindowViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationShadeWindowViewTest.java
index e04d25b..c1d51f3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationShadeWindowViewTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationShadeWindowViewTest.java
@@ -38,6 +38,7 @@
 import com.android.systemui.statusbar.DragDownHelper;
 import com.android.systemui.statusbar.NotificationLockscreenUserManager;
 import com.android.systemui.statusbar.NotificationShadeDepthController;
+import com.android.systemui.statusbar.NotificationShadeWindowController;
 import com.android.systemui.statusbar.PulseExpansionHandler;
 import com.android.systemui.statusbar.SuperStatusBarViewFactory;
 import com.android.systemui.statusbar.SysuiStatusBarStateController;
@@ -100,7 +101,9 @@
 
         mController = new NotificationShadeWindowViewController(
                 new InjectionInflationController(
-                        SystemUIFactory.getInstance().getRootComponent()),
+                        SystemUIFactory.getInstance()
+                                .getSysUIComponent()
+                                .createViewInstanceCreatorFactory()),
                 mCoordinator,
                 mPulseExpansionHandler,
                 mDynamicPrivacyController,
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 675a2df..2f45113 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
@@ -43,9 +43,12 @@
 import com.android.systemui.classifier.FalsingManagerFake;
 import com.android.systemui.dock.DockManager;
 import com.android.systemui.keyguard.DismissCallbackRegistry;
+import com.android.systemui.keyguard.FaceAuthScreenBrightnessController;
+import com.android.systemui.navigationbar.NavigationModeController;
 import com.android.systemui.plugins.ActivityStarter.OnDismissAction;
 import com.android.systemui.plugins.FalsingManager;
 import com.android.systemui.statusbar.NotificationMediaManager;
+import com.android.systemui.statusbar.NotificationShadeWindowController;
 import com.android.systemui.statusbar.SysuiStatusBarStateController;
 import com.android.systemui.statusbar.policy.ConfigurationController;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
@@ -56,6 +59,8 @@
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 
+import java.util.Optional;
+
 @SmallTest
 @RunWith(AndroidTestingRunner.class)
 @TestableLooper.RunWithLooper
@@ -87,6 +92,8 @@
     private View mNotificationContainer;
     @Mock
     private KeyguardBypassController mBypassController;
+    @Mock
+    private FaceAuthScreenBrightnessController mFaceAuthScreenBrightnessController;
     private StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
 
     @Before
@@ -106,6 +113,7 @@
                 mock(DockManager.class),
                 mock(NotificationShadeWindowController.class),
                 mKeyguardStateController,
+                mFaceAuthScreenBrightnessController,
                 mock(NotificationMediaManager.class));
         mStatusBarKeyguardViewManager.registerStatusBar(mStatusBar, mContainer,
                 mNotificationPanelView, mBiometrucUnlockController, mDismissCallbackRegistry,
@@ -272,11 +280,12 @@
                 DockManager dockManager,
                 NotificationShadeWindowController notificationShadeWindowController,
                 KeyguardStateController keyguardStateController,
+                FaceAuthScreenBrightnessController faceAuthScreenBrightnessController,
                 NotificationMediaManager notificationMediaManager) {
             super(context, callback, lockPatternUtils, sysuiStatusBarStateController,
                     configurationController, keyguardUpdateMonitor, navigationModeController,
                     dockManager, notificationShadeWindowController, keyguardStateController,
-                    notificationMediaManager);
+                    Optional.of(faceAuthScreenBrightnessController), notificationMediaManager);
         }
 
         @Override
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java
index 3306734..3f631b1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java
@@ -73,7 +73,7 @@
 import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProvider;
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
 import com.android.systemui.statusbar.notification.row.NotificationTestHelper;
-import com.android.systemui.statusbar.notification.row.OnDismissCallback;
+import com.android.systemui.statusbar.notification.row.OnUserInteractionCallback;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
 import com.android.systemui.util.concurrency.FakeExecutor;
 import com.android.systemui.util.time.FakeSystemClock;
@@ -132,7 +132,7 @@
     @Mock
     private Intent mContentIntentInner;
     @Mock
-    private OnDismissCallback mOnDismissCallback;
+    private OnUserInteractionCallback mOnUserInteractionCallback;
     @Mock
     private NotificationActivityStarter mNotificationActivityStarter;
     private FakeExecutor mUiBgExecutor = new FakeExecutor(new FakeSystemClock());
@@ -184,7 +184,6 @@
                         getContext(),
                         mock(CommandQueue.class),
                         mHandler,
-                        mHandler,
                         mUiBgExecutor,
                         mEntryManager,
                         mNotifPipeline,
@@ -211,7 +210,7 @@
                         mFeatureFlags,
                         mock(MetricsLogger.class),
                         mock(StatusBarNotificationActivityStarterLogger.class),
-                        mOnDismissCallback)
+                        mOnUserInteractionCallback)
                 .setStatusBar(mStatusBar)
                 .setNotificationPresenter(mock(NotificationPresenter.class))
                 .setNotificationPanelViewController(mock(NotificationPanelViewController.class))
@@ -234,9 +233,6 @@
         // set up Handler to synchronously invoke the Runnable arg
         doAnswer(answerVoid(Runnable::run))
                 .when(mHandler).post(any(Runnable.class));
-
-        doAnswer(answerVoid(Runnable::run))
-                .when(mHandler).postAtFrontOfQueue(any(Runnable.class));
     }
 
     @Test
@@ -271,7 +267,7 @@
                 eq(sbn.getKey()), any(NotificationVisibility.class));
 
         // Notification calls dismiss callback to remove notification due to FLAG_AUTO_CANCEL
-        verify(mOnDismissCallback).onDismiss(mNotificationRow.getEntry(), REASON_CLICK);
+        verify(mOnUserInteractionCallback).onDismiss(mNotificationRow.getEntry(), REASON_CLICK);
     }
 
     @Test
@@ -300,7 +296,8 @@
         verifyZeroInteractions(mContentIntent);
 
         // Notification should not be cancelled.
-        verify(mOnDismissCallback, never()).onDismiss(eq(mNotificationRow.getEntry()), anyInt());
+        verify(mOnUserInteractionCallback, never()).onDismiss(eq(mNotificationRow.getEntry()),
+                anyInt());
     }
 
     @Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenterTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenterTest.java
index 318e9b8..c0ebfad 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenterTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenterTest.java
@@ -23,13 +23,11 @@
 
 import android.app.Notification;
 import android.app.StatusBarManager;
-import android.content.Context;
 import android.metrics.LogMaker;
 import android.support.test.metricshelper.MetricsAsserts;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
 import android.testing.TestableLooper.RunWithLooper;
-import android.view.ViewGroup;
 
 import androidx.test.filters.SmallTest;
 
@@ -45,20 +43,23 @@
 import com.android.systemui.statusbar.NotificationLockscreenUserManager;
 import com.android.systemui.statusbar.NotificationMediaManager;
 import com.android.systemui.statusbar.NotificationRemoteInputManager;
+import com.android.systemui.statusbar.NotificationShadeWindowController;
 import com.android.systemui.statusbar.NotificationViewHierarchyManager;
 import com.android.systemui.statusbar.RemoteInputController;
 import com.android.systemui.statusbar.SysuiStatusBarStateController;
 import com.android.systemui.statusbar.notification.ActivityLaunchAnimator;
 import com.android.systemui.statusbar.notification.DynamicPrivacyController;
 import com.android.systemui.statusbar.notification.NotificationEntryManager;
-import com.android.systemui.statusbar.notification.VisualStabilityManager;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder;
+import com.android.systemui.statusbar.notification.collection.legacy.VisualStabilityManager;
 import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProvider;
 import com.android.systemui.statusbar.notification.interruption.NotificationInterruptSuppressor;
 import com.android.systemui.statusbar.notification.row.ActivatableNotificationView;
 import com.android.systemui.statusbar.notification.row.NotificationGutsManager;
 import com.android.systemui.statusbar.notification.stack.NotificationListContainer;
+import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout;
+import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
 
 import org.junit.Before;
@@ -112,11 +113,17 @@
 
         NotificationShadeWindowView notificationShadeWindowView =
                 mock(NotificationShadeWindowView.class);
+        NotificationStackScrollLayoutController stackScrollLayoutController =
+                mock(NotificationStackScrollLayoutController.class);
+        when(stackScrollLayoutController.getView()).thenReturn(
+                mock(NotificationStackScrollLayout.class));
+        when(stackScrollLayoutController.getNotificationListContainer()).thenReturn(
+                mock(NotificationListContainer.class));
         when(notificationShadeWindowView.getResources()).thenReturn(mContext.getResources());
 
         mStatusBarNotificationPresenter = new StatusBarNotificationPresenter(mContext,
                 mock(NotificationPanelViewController.class), mock(HeadsUpManagerPhone.class),
-                notificationShadeWindowView, mock(NotificationListContainerViewGroup.class),
+                notificationShadeWindowView, stackScrollLayoutController,
                 mock(DozeScrimController.class), mock(ScrimController.class),
                 mock(ActivityLaunchAnimator.class), mock(DynamicPrivacyController.class),
                 mock(KeyguardStateController.class),
@@ -205,14 +212,4 @@
                 new LogMaker(MetricsEvent.ACTION_LS_NOTE)
                         .setType(MetricsEvent.TYPE_ACTION));
     }
-
-    // We need this because mockito doesn't know how to construct a mock that extends ViewGroup
-    // and implements NotificationListContainer without it because of classloader issues.
-    private abstract static class NotificationListContainerViewGroup extends ViewGroup
-            implements NotificationListContainer {
-
-        public NotificationListContainerViewGroup(Context context) {
-            super(context);
-        }
-    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java
index 5a08c9c..869fbd8 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java
@@ -83,10 +83,12 @@
 import com.android.systemui.bubbles.BubbleController;
 import com.android.systemui.classifier.FalsingManagerFake;
 import com.android.systemui.colorextraction.SysuiColorExtractor;
+import com.android.systemui.demomode.DemoModeController;
 import com.android.systemui.keyguard.DismissCallbackRegistry;
 import com.android.systemui.keyguard.KeyguardViewMediator;
 import com.android.systemui.keyguard.ScreenLifecycle;
 import com.android.systemui.keyguard.WakefulnessLifecycle;
+import com.android.systemui.navigationbar.NavigationBarController;
 import com.android.systemui.plugins.ActivityStarter.OnDismissAction;
 import com.android.systemui.plugins.DarkIconDispatcher;
 import com.android.systemui.plugins.PluginDependencyProvider;
@@ -94,15 +96,15 @@
 import com.android.systemui.recents.Recents;
 import com.android.systemui.recents.ScreenPinningRequest;
 import com.android.systemui.shared.plugins.PluginManager;
-import com.android.systemui.stackdivider.Divider;
+import com.android.systemui.stackdivider.SplitScreenController;
 import com.android.systemui.statusbar.CommandQueue;
 import com.android.systemui.statusbar.KeyguardIndicationController;
-import com.android.systemui.statusbar.NavigationBarController;
 import com.android.systemui.statusbar.NotificationListener;
 import com.android.systemui.statusbar.NotificationLockscreenUserManager;
 import com.android.systemui.statusbar.NotificationMediaManager;
 import com.android.systemui.statusbar.NotificationRemoteInputManager;
 import com.android.systemui.statusbar.NotificationShadeDepthController;
+import com.android.systemui.statusbar.NotificationShadeWindowController;
 import com.android.systemui.statusbar.NotificationViewHierarchyManager;
 import com.android.systemui.statusbar.PulseExpansionHandler;
 import com.android.systemui.statusbar.RemoteInputController;
@@ -115,16 +117,18 @@
 import com.android.systemui.statusbar.notification.NotificationEntryManager;
 import com.android.systemui.statusbar.notification.NotificationFilter;
 import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator;
-import com.android.systemui.statusbar.notification.VisualStabilityManager;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder;
+import com.android.systemui.statusbar.notification.collection.legacy.VisualStabilityManager;
 import com.android.systemui.statusbar.notification.init.NotificationsController;
 import com.android.systemui.statusbar.notification.interruption.BypassHeadsUpNotifier;
 import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProviderImpl;
 import com.android.systemui.statusbar.notification.logging.NotificationLogger;
 import com.android.systemui.statusbar.notification.logging.NotificationPanelLoggerFake;
 import com.android.systemui.statusbar.notification.row.NotificationGutsManager;
+import com.android.systemui.statusbar.notification.stack.NotificationListContainer;
 import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout;
+import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController;
 import com.android.systemui.statusbar.phone.dagger.StatusBarComponent;
 import com.android.systemui.statusbar.policy.BatteryController;
 import com.android.systemui.statusbar.policy.ConfigurationController;
@@ -171,6 +175,8 @@
     @Mock private KeyguardStateController mKeyguardStateController;
     @Mock private KeyguardIndicationController mKeyguardIndicationController;
     @Mock private NotificationStackScrollLayout mStackScroller;
+    @Mock private NotificationStackScrollLayoutController mStackScrollerController;
+    @Mock private NotificationListContainer mNotificationListContainer;
     @Mock private HeadsUpManagerPhone mHeadsUpManager;
     @Mock private NotificationPanelViewController mNotificationPanelViewController;
     @Mock private NotificationPanelView mNotificationPanelView;
@@ -232,7 +238,7 @@
     @Mock private StatusBarComponent.Builder mStatusBarComponentBuilder;
     @Mock private StatusBarComponent mStatusBarComponent;
     @Mock private PluginManager mPluginManager;
-    @Mock private Divider mDivider;
+    @Mock private SplitScreenController mSplitScreenController;
     @Mock private SuperStatusBarViewFactory mSuperStatusBarViewFactory;
     @Mock private LightsOutNotifController mLightsOutNotifController;
     @Mock private ViewMediatorCallback mViewMediatorCallback;
@@ -248,6 +254,7 @@
     @Mock private ExtensionController mExtensionController;
     @Mock private UserInfoControllerImpl mUserInfoControllerImpl;
     @Mock private PhoneStatusBarPolicy mPhoneStatusBarPolicy;
+    @Mock private DemoModeController mDemoModeController;
     @Mock private Lazy<NotificationShadeDepthController> mNotificationShadeDepthControllerLazy;
     private ShadeController mShadeController;
     private FakeExecutor mUiBgExecutor = new FakeExecutor(new FakeSystemClock());
@@ -283,6 +290,10 @@
 
         mContext.setTheme(R.style.Theme_SystemUI_Light);
 
+        when(mStackScroller.getController()).thenReturn(mStackScrollerController);
+        when(mStackScrollerController.getView()).thenReturn(mStackScroller);
+        when(mStackScrollerController.getNotificationListContainer()).thenReturn(
+                mNotificationListContainer);
         when(mStackScroller.generateLayoutParams(any())).thenReturn(new LayoutParams(0, 0));
         when(mNotificationPanelViewController.getView()).thenReturn(mNotificationPanelView);
         when(mNotificationPanelView.getLayoutParams()).thenReturn(new LayoutParams(0, 0));
@@ -386,7 +397,7 @@
                 Optional.of(mRecents),
                 mStatusBarComponentBuilderProvider,
                 mPluginManager,
-                Optional.of(mDivider),
+                Optional.of(mSplitScreenController),
                 mLightsOutNotifController,
                 mStatusBarNotificationActivityStarterBuilder,
                 mShadeController,
@@ -394,7 +405,6 @@
                 mStatusBarKeyguardViewManager,
                 mViewMediatorCallback,
                 mInitController,
-                mDarkIconDispatcher,
                 new Handler(TestableLooper.get(this).getLooper()),
                 mPluginDependencyProvider,
                 mKeyguardDismissUtil,
@@ -403,8 +413,10 @@
                 mPhoneStatusBarPolicy,
                 mKeyguardIndicationController,
                 mDismissCallbackRegistry,
+                mDemoModeController,
                 mNotificationShadeDepthControllerLazy,
-                mStatusBarTouchableRegionManager);
+                mStatusBarTouchableRegionManager,
+                mNotificationIconAreaController);
 
         when(mNotificationShadeWindowView.findViewById(R.id.lock_icon_container)).thenReturn(
                 mLockIconContainer);
@@ -422,14 +434,13 @@
         mStatusBar.mNotificationShadeWindowView = mNotificationShadeWindowView;
         mStatusBar.mNotificationPanelViewController = mNotificationPanelViewController;
         mStatusBar.mDozeScrimController = mDozeScrimController;
-        mStatusBar.mNotificationIconAreaController = mNotificationIconAreaController;
         mStatusBar.mPresenter = mNotificationPresenter;
         mStatusBar.mKeyguardIndicationController = mKeyguardIndicationController;
         mStatusBar.mBarService = mBarService;
         mStatusBar.mStackScroller = mStackScroller;
         mStatusBar.startKeyguard();
         mInitController.executePostInitTasks();
-        notificationLogger.setUpWithContainer(mStackScroller);
+        notificationLogger.setUpWithContainer(mNotificationListContainer);
     }
 
     @Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BatteryControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BatteryControllerTest.java
index eca48c8..23fa6fd 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BatteryControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BatteryControllerTest.java
@@ -29,6 +29,7 @@
 
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.broadcast.BroadcastDispatcher;
+import com.android.systemui.demomode.DemoModeController;
 import com.android.systemui.power.EnhancedEstimates;
 
 import org.junit.Assert;
@@ -44,17 +45,21 @@
 @TestableLooper.RunWithLooper
 public class BatteryControllerTest extends SysuiTestCase {
 
-    @Mock
-    private PowerManager mPowerManager;
-    @Mock
-    private BroadcastDispatcher mBroadcastDispatcher;
+    @Mock private PowerManager mPowerManager;
+    @Mock private BroadcastDispatcher mBroadcastDispatcher;
+    @Mock private DemoModeController mDemoModeController;
     private BatteryControllerImpl mBatteryController;
 
     @Before
     public void setUp() {
         MockitoAnnotations.initMocks(this);
-        mBatteryController = new BatteryControllerImpl(getContext(), mock(EnhancedEstimates.class),
-                mPowerManager, mBroadcastDispatcher, new Handler(), new Handler());
+        mBatteryController = new BatteryControllerImpl(getContext(),
+                mock(EnhancedEstimates.class),
+                mPowerManager,
+                mBroadcastDispatcher,
+                mDemoModeController,
+                new Handler(),
+                new Handler());
         mBatteryController.init();
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/CallbackControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/CallbackControllerTest.java
index a16fb5e..86dacc1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/CallbackControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/CallbackControllerTest.java
@@ -37,7 +37,7 @@
 import org.junit.runner.RunWith;
 import org.mockito.ArgumentCaptor;
 
-@RunWithLooper
+@RunWithLooper(setAsMainLooper = true)
 @RunWith(AndroidTestingRunner.class)
 @SmallTest
 public class CallbackControllerTest extends SysuiTestCase {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerBaseTest.java
index aef454f..7db1b83 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerBaseTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerBaseTest.java
@@ -66,6 +66,7 @@
 import com.android.systemui.R;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.broadcast.BroadcastDispatcher;
+import com.android.systemui.demomode.DemoModeController;
 import com.android.systemui.statusbar.policy.DeviceProvisionedController.DeviceProvisionedListener;
 import com.android.systemui.statusbar.policy.NetworkController.IconState;
 import com.android.systemui.statusbar.policy.NetworkController.SignalCallback;
@@ -113,6 +114,7 @@
     protected DeviceProvisionedController mMockProvisionController;
     protected DeviceProvisionedListener mUserCallback;
     protected Instrumentation mInstrumentation;
+    protected DemoModeController mDemoModeController;
 
     protected int mSubId;
 
@@ -146,6 +148,7 @@
         res.addOverride(R.string.cell_data_off_content_description, NO_DATA_STRING);
         res.addOverride(R.string.not_default_data_content_description, NOT_DEFAULT_DATA_STRING);
 
+        mDemoModeController = mock(DemoModeController.class);
         mMockWm = mock(WifiManager.class);
         mMockTm = mock(TelephonyManager.class);
         mMockSm = mock(SubscriptionManager.class);
@@ -200,10 +203,21 @@
             return null;
         }).when(mMockProvisionController).addCallback(any());
 
-        mNetworkController = new NetworkControllerImpl(mContext, mMockCm, mMockTm, mMockWm,
-                mMockNsm, mMockSm, mConfig, TestableLooper.get(this).getLooper(), mCallbackHandler,
-                mock(AccessPointControllerImpl.class), mock(DataUsageController.class),
-                mMockSubDefaults, mMockProvisionController, mMockBd);
+        mNetworkController = new NetworkControllerImpl(mContext,
+                mMockCm,
+                mMockTm,
+                mMockWm,
+                mMockNsm,
+                mMockSm,
+                mConfig,
+                TestableLooper.get(this).getLooper(),
+                mCallbackHandler,
+                mock(AccessPointControllerImpl.class),
+                mock(DataUsageController.class),
+                mMockSubDefaults,
+                mMockProvisionController,
+                mMockBd,
+                mDemoModeController);
         setupNetworkController();
 
         // Trigger blank callbacks to always get the current state (some tests don't trigger
@@ -254,7 +268,7 @@
                         mConfig, TestableLooper.get(this).getLooper(), mCallbackHandler,
                         mock(AccessPointControllerImpl.class),
                         mock(DataUsageController.class), mMockSubDefaults,
-                        mock(DeviceProvisionedController.class), mMockBd);
+                        mock(DeviceProvisionedController.class), mMockBd, mDemoModeController);
 
         setupNetworkController();
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerDataTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerDataTest.java
index 6fffcff..d8aa29e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerDataTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerDataTest.java
@@ -106,7 +106,7 @@
                 mMockNsm, mMockSm, mConfig, Looper.getMainLooper(), mCallbackHandler,
                 mock(AccessPointControllerImpl.class),
                 mock(DataUsageController.class), mMockSubDefaults,
-                mock(DeviceProvisionedController.class), mMockBd);
+                mock(DeviceProvisionedController.class), mMockBd, mDemoModeController);
         setupNetworkController();
 
         setupDefaultSignal();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerSignalTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerSignalTest.java
index 3b27437..61f71b7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerSignalTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerSignalTest.java
@@ -63,7 +63,8 @@
         mNetworkController = new NetworkControllerImpl(mContext, mMockCm, mMockTm, mMockWm,
                 mMockNsm, mMockSm, mConfig, Looper.getMainLooper(), mCallbackHandler,
                 mock(AccessPointControllerImpl.class), mock(DataUsageController.class),
-                mMockSubDefaults, mock(DeviceProvisionedController.class), mMockBd);
+                mMockSubDefaults, mock(DeviceProvisionedController.class), mMockBd,
+                mDemoModeController);
         setupNetworkController();
 
         verifyLastMobileDataIndicators(false, -1, 0);
@@ -81,7 +82,8 @@
         mNetworkController = new NetworkControllerImpl(mContext, mMockCm, mMockTm, mMockWm,
                 mMockNsm, mMockSm, mConfig, Looper.getMainLooper(), mCallbackHandler,
                 mock(AccessPointControllerImpl.class), mock(DataUsageController.class),
-                mMockSubDefaults, mock(DeviceProvisionedController.class), mMockBd);
+                mMockSubDefaults, mock(DeviceProvisionedController.class), mMockBd,
+                mDemoModeController);
         mNetworkController.registerListeners();
 
         // Wait for the main looper to execute the previous command
@@ -147,7 +149,8 @@
         mNetworkController = new NetworkControllerImpl(mContext, mMockCm, mMockTm, mMockWm,
                 mMockNsm, mMockSm, mConfig, Looper.getMainLooper(), mCallbackHandler,
                 mock(AccessPointControllerImpl.class), mock(DataUsageController.class),
-                mMockSubDefaults, mock(DeviceProvisionedController.class), mMockBd);
+                mMockSubDefaults, mock(DeviceProvisionedController.class), mMockBd,
+                mDemoModeController);
         setupNetworkController();
 
         // No Subscriptions.
diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/DeviceConfigProxyFake.java b/packages/SystemUI/tests/src/com/android/systemui/util/DeviceConfigProxyFake.java
index 260ff2d..33ece00 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/util/DeviceConfigProxyFake.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/util/DeviceConfigProxyFake.java
@@ -70,9 +70,7 @@
 
         for (Pair<Executor, OnPropertiesChangedListener> listener : mListeners) {
             Properties.Builder propBuilder = new Properties.Builder(namespace);
-            for (String key : mProperties.get(namespace).keySet()) {
-                propBuilder.setString(key, mProperties.get(namespace).get(key));
-            }
+            propBuilder.setString(name, value);
             listener.first.execute(() -> listener.second.onPropertiesChanged(propBuilder.build()));
         }
         return true;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/DeviceConfigProxyFakeTest.java b/packages/SystemUI/tests/src/com/android/systemui/util/DeviceConfigProxyFakeTest.java
new file mode 100644
index 0000000..64e8957
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/util/DeviceConfigProxyFakeTest.java
@@ -0,0 +1,100 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.util;
+
+import static android.provider.DeviceConfig.Properties;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.provider.DeviceConfig.OnPropertiesChangedListener;
+
+import androidx.annotation.NonNull;
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.systemui.SysuiTestCase;
+import com.android.systemui.util.concurrency.FakeExecutor;
+import com.android.systemui.util.time.FakeSystemClock;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class DeviceConfigProxyFakeTest extends SysuiTestCase {
+    private static final String NAMESPACE = "foobar";
+
+    private FakeExecutor mFakeExecutor = new FakeExecutor(new FakeSystemClock());
+
+    private DeviceConfigProxyFake mDeviceConfigProxyFake;
+
+    @Before
+    public void setup() {
+        mDeviceConfigProxyFake = new DeviceConfigProxyFake();
+    }
+
+    @Test
+    public void testOnPropertiesChanged() {
+        TestableListener onPropertiesChangedListener = new TestableListener();
+        String key = "foo";
+        String value = "bar";
+
+        mDeviceConfigProxyFake.addOnPropertiesChangedListener(
+                NAMESPACE, mFakeExecutor, onPropertiesChangedListener);
+
+        mDeviceConfigProxyFake.setProperty(NAMESPACE, key, value, false);
+        mFakeExecutor.runAllReady();
+        assertThat(onPropertiesChangedListener.mProperties).isNotNull();
+        assertThat(onPropertiesChangedListener.mProperties.getKeyset().size()).isEqualTo(1);
+        assertThat(onPropertiesChangedListener.mProperties.getString(key, "")).isEqualTo(value);
+    }
+
+    @Test
+    public void testOnMultiplePropertiesChanged() {
+        TestableListener onPropertiesChangedListener = new TestableListener();
+        String keyA = "foo";
+        String valueA = "bar";
+        String keyB = "bada";
+        String valueB = "boom";
+
+        mDeviceConfigProxyFake.addOnPropertiesChangedListener(
+                NAMESPACE, mFakeExecutor, onPropertiesChangedListener);
+        mDeviceConfigProxyFake.setProperty(NAMESPACE, keyA, valueA, false);
+        mFakeExecutor.runAllReady();
+        assertThat(onPropertiesChangedListener.mProperties).isNotNull();
+        assertThat(onPropertiesChangedListener.mProperties.getKeyset().size()).isEqualTo(1);
+        assertThat(onPropertiesChangedListener.mProperties.getString(keyA, "")).isEqualTo(valueA);
+
+        mDeviceConfigProxyFake.setProperty(NAMESPACE, keyB, valueB, false);
+        mFakeExecutor.runAllReady();
+        assertThat(onPropertiesChangedListener.mProperties).isNotNull();
+        assertThat(onPropertiesChangedListener.mProperties.getKeyset().size()).isEqualTo(1);
+        assertThat(onPropertiesChangedListener.mProperties.getString(keyB, "")).isEqualTo(valueB);
+    }
+
+    private static class TestableListener implements OnPropertiesChangedListener {
+        Properties mProperties;
+
+        TestableListener() {
+        }
+        @Override
+        public void onPropertiesChanged(@NonNull Properties properties) {
+            mProperties = properties;
+        }
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/LifecycleFragmentTest.java b/packages/SystemUI/tests/src/com/android/systemui/util/LifecycleFragmentTest.java
index 0e1c560..b2f57d0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/util/LifecycleFragmentTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/util/LifecycleFragmentTest.java
@@ -39,7 +39,7 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
-@RunWithLooper
+@RunWithLooper(setAsMainLooper = true)
 @RunWith(AndroidTestingRunner.class)
 @SmallTest
 public class LifecycleFragmentTest extends SysuiBaseFragmentTest {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/SysuiLifecycleTest.java b/packages/SystemUI/tests/src/com/android/systemui/util/SysuiLifecycleTest.java
index 486939d..4f509ea 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/util/SysuiLifecycleTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/util/SysuiLifecycleTest.java
@@ -49,7 +49,7 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
-@RunWithLooper
+@RunWithLooper(setAsMainLooper = true)
 @RunWith(AndroidTestingRunner.class)
 @SmallTest
 public class SysuiLifecycleTest extends SysuiTestCase {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/sensors/ProximityCheckTest.java b/packages/SystemUI/tests/src/com/android/systemui/util/sensors/ProximityCheckTest.java
index 8ba11da..c5a197e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/util/sensors/ProximityCheckTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/util/sensors/ProximityCheckTest.java
@@ -16,6 +16,7 @@
 
 package com.android.systemui.util.sensors;
 
+import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
@@ -80,6 +81,8 @@
         mFakeExecutor.runAllReady();
 
         assertFalse(mFakeProximitySensor.isRegistered());
+        assertEquals(1, mTestableCallback.mNumCalls);
+        assertNull(mTestableCallback.mLastResult);
     }
 
     @Test
@@ -110,9 +113,12 @@
 
     private static class TestableCallback implements Consumer<Boolean> {
         Boolean mLastResult;
+        int mNumCalls = 0;
+
         @Override
         public void accept(Boolean result) {
             mLastResult = result;
+            mNumCalls++;
         }
     }
 }
diff --git a/packages/Tethering/AndroidManifestBase.xml b/packages/Tethering/AndroidManifestBase.xml
index fa85f66..97c3988 100644
--- a/packages/Tethering/AndroidManifestBase.xml
+++ b/packages/Tethering/AndroidManifestBase.xml
@@ -23,6 +23,7 @@
     <application
         android:label="Tethering"
         android:defaultToDeviceProtectedStorage="true"
-        android:directBootAware="true">
+        android:directBootAware="true"
+        android:usesCleartextTraffic="true">
     </application>
 </manifest>
diff --git a/packages/Tethering/res/values/config.xml b/packages/Tethering/res/values/config.xml
index 9b9dcde..5f8d299 100644
--- a/packages/Tethering/res/values/config.xml
+++ b/packages/Tethering/res/values/config.xml
@@ -73,6 +73,9 @@
     <!-- Use the old dnsmasq DHCP server for tethering instead of the framework implementation. -->
     <bool translatable="false" name="config_tether_enable_legacy_dhcp_server">false</bool>
 
+    <!-- Use legacy wifi p2p dedicated address instead of randomize address. -->
+    <bool translatable="false" name="config_tether_enable_legacy_wifi_p2p_dedicated_ip">false</bool>
+
     <!-- Dhcp range (min, max) to use for tethering purposes -->
     <string-array translatable="false" name="config_tether_dhcp_range">
     </string-array>
diff --git a/packages/Tethering/res/values/overlayable.xml b/packages/Tethering/res/values/overlayable.xml
index 6a33d55..0ee7a99 100644
--- a/packages/Tethering/res/values/overlayable.xml
+++ b/packages/Tethering/res/values/overlayable.xml
@@ -30,6 +30,7 @@
             -->
             <item type="bool" name="config_tether_enable_bpf_offload"/>
             <item type="bool" name="config_tether_enable_legacy_dhcp_server"/>
+            <item type="bool" name="config_tether_enable_legacy_wifi_p2p_dedicated_ip"/>
             <item type="integer" name="config_tether_offload_poll_interval"/>
             <item type="array" name="config_tether_upstream_types"/>
             <item type="bool" name="config_tether_upstream_automatic"/>
diff --git a/packages/Tethering/src/com/android/networkstack/tethering/PrivateAddressCoordinator.java b/packages/Tethering/src/com/android/networkstack/tethering/PrivateAddressCoordinator.java
index aa58a4b..fd9e360 100644
--- a/packages/Tethering/src/com/android/networkstack/tethering/PrivateAddressCoordinator.java
+++ b/packages/Tethering/src/com/android/networkstack/tethering/PrivateAddressCoordinator.java
@@ -15,6 +15,8 @@
  */
 package com.android.networkstack.tethering;
 
+import static android.net.TetheringManager.TETHERING_WIFI_P2P;
+
 import static java.util.Arrays.asList;
 
 import android.content.Context;
@@ -58,6 +60,7 @@
     private static final int BYTE_MASK = 0xff;
     // reserved for bluetooth tethering.
     private static final int BLUETOOTH_RESERVED = 44;
+    private static final int WIFI_P2P_RESERVED = 49;
     private static final byte DEFAULT_ID = (byte) 42;
 
     // Upstream monitor would be stopped when tethering is down. When tethering restart, downstream
@@ -71,15 +74,18 @@
     // 10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16
     // Tethering use 192.168.0.0/16 that has 256 contiguous class C network numbers.
     private static final String DEFAULT_TETHERING_PREFIX = "192.168.0.0/16";
+    private static final String LEGACY_WIFI_P2P_IFACE_ADDRESS = "192.168.49.1/24";
     private final IpPrefix mTetheringPrefix;
     private final ConnectivityManager mConnectivityMgr;
+    private final TetheringConfiguration mConfig;
 
-    public PrivateAddressCoordinator(Context context) {
+    public PrivateAddressCoordinator(Context context, TetheringConfiguration config) {
         mDownstreams = new ArraySet<>();
         mUpstreamPrefixMap = new ArrayMap<>();
         mTetheringPrefix = new IpPrefix(DEFAULT_TETHERING_PREFIX);
         mConnectivityMgr = (ConnectivityManager) context.getSystemService(
                 Context.CONNECTIVITY_SERVICE);
+        mConfig = config;
     }
 
     /**
@@ -141,12 +147,21 @@
         mUpstreamPrefixMap.removeAll(toBeRemoved);
     }
 
+    private boolean isReservedSubnet(final int subnet) {
+        return subnet == BLUETOOTH_RESERVED || subnet == WIFI_P2P_RESERVED;
+    }
+
     /**
      * Pick a random available address and mark its prefix as in use for the provided IpServer,
      * returns null if there is no available address.
      */
     @Nullable
     public LinkAddress requestDownstreamAddress(final IpServer ipServer) {
+        if (mConfig.shouldEnableWifiP2pDedicatedIp()
+                && ipServer.interfaceType() == TETHERING_WIFI_P2P) {
+            return new LinkAddress(LEGACY_WIFI_P2P_IFACE_ADDRESS);
+        }
+
         // Address would be 192.168.[subAddress]/24.
         final byte[] bytes = mTetheringPrefix.getRawAddress();
         final int subAddress = getRandomSubAddr();
@@ -154,7 +169,7 @@
         bytes[3] = getSanitizedAddressSuffix(subAddress, (byte) 0, (byte) 1, (byte) 0xff);
         for (int i = 0; i < MAX_UBYTE; i++) {
             final int newSubNet = (subNet + i) & BYTE_MASK;
-            if (newSubNet == BLUETOOTH_RESERVED) continue;
+            if (isReservedSubnet(newSubNet)) continue;
 
             bytes[2] = (byte) newSubNet;
             final InetAddress addr;
diff --git a/packages/Tethering/src/com/android/networkstack/tethering/Tethering.java b/packages/Tethering/src/com/android/networkstack/tethering/Tethering.java
index cfc6575..7dd5290 100644
--- a/packages/Tethering/src/com/android/networkstack/tethering/Tethering.java
+++ b/packages/Tethering/src/com/android/networkstack/tethering/Tethering.java
@@ -320,10 +320,13 @@
         mExecutor = new TetheringThreadExecutor(mHandler);
         mActiveDataSubIdListener = new ActiveDataSubIdListener(mExecutor);
         mNetdCallback = new NetdCallback();
-        mPrivateAddressCoordinator = new PrivateAddressCoordinator(mContext);
 
         // Load tethering configuration.
         updateConfiguration();
+        // It is OK for the configuration to be passed to the PrivateAddressCoordinator at
+        // construction time because the only part of the configuration it uses is
+        // shouldEnableWifiP2pDedicatedIp(), and currently do not support changing that.
+        mPrivateAddressCoordinator = new PrivateAddressCoordinator(mContext, mConfig);
 
         // Must be initialized after tethering configuration is loaded because BpfCoordinator
         // constructor needs to use the configuration.
diff --git a/packages/Tethering/src/com/android/networkstack/tethering/TetheringConfiguration.java b/packages/Tethering/src/com/android/networkstack/tethering/TetheringConfiguration.java
index e1771a5..5783805 100644
--- a/packages/Tethering/src/com/android/networkstack/tethering/TetheringConfiguration.java
+++ b/packages/Tethering/src/com/android/networkstack/tethering/TetheringConfiguration.java
@@ -84,6 +84,9 @@
     public static final String TETHER_ENABLE_LEGACY_DHCP_SERVER =
             "tether_enable_legacy_dhcp_server";
 
+    public static final String USE_LEGACY_WIFI_P2P_DEDICATED_IP =
+            "use_legacy_wifi_p2p_dedicated_ip";
+
     /**
      * Default value that used to periodic polls tether offload stats from tethering offload HAL
      * to make the data warnings work.
@@ -113,6 +116,7 @@
     private final int mOffloadPollInterval;
     // TODO: Add to TetheringConfigurationParcel if required.
     private final boolean mEnableBpfOffload;
+    private final boolean mEnableWifiP2pDedicatedIp;
 
     public TetheringConfiguration(Context ctx, SharedLog log, int id) {
         final SharedLog configLog = log.forSubComponent("config");
@@ -156,6 +160,10 @@
                 R.integer.config_tether_offload_poll_interval,
                 DEFAULT_TETHER_OFFLOAD_POLL_INTERVAL_MS);
 
+        mEnableWifiP2pDedicatedIp = getResourceBoolean(res,
+                R.bool.config_tether_enable_legacy_wifi_p2p_dedicated_ip,
+                false /* defaultValue */);
+
         configLog.log(toString());
     }
 
@@ -199,6 +207,11 @@
         return !TextUtils.isEmpty(provisioningAppNoUi);
     }
 
+    /** Check whether dedicated wifi p2p address is enabled. */
+    public boolean shouldEnableWifiP2pDedicatedIp() {
+        return mEnableWifiP2pDedicatedIp;
+    }
+
     /** Does the dumping.*/
     public void dump(PrintWriter pw) {
         pw.print("activeDataSubId: ");
@@ -233,6 +246,9 @@
 
         pw.print("enableLegacyDhcpServer: ");
         pw.println(enableLegacyDhcpServer);
+
+        pw.print("enableWifiP2pDedicatedIp: ");
+        pw.println(mEnableWifiP2pDedicatedIp);
     }
 
     /** Returns the string representation of this object.*/
diff --git a/packages/Tethering/tests/integration/src/android/net/EthernetTetheringTest.java b/packages/Tethering/tests/integration/src/android/net/EthernetTetheringTest.java
index 9bb01ae..64be2d9 100644
--- a/packages/Tethering/tests/integration/src/android/net/EthernetTetheringTest.java
+++ b/packages/Tethering/tests/integration/src/android/net/EthernetTetheringTest.java
@@ -50,7 +50,7 @@
 import androidx.test.filters.MediumTest;
 import androidx.test.runner.AndroidJUnit4;
 
-import com.android.testutils.HandlerUtilsKt;
+import com.android.testutils.HandlerUtils;
 import com.android.testutils.TapPacketReader;
 
 import org.junit.After;
@@ -366,7 +366,7 @@
     private TapPacketReader makePacketReader(FileDescriptor fd, int mtu) {
         final TapPacketReader reader = new TapPacketReader(mHandler, fd, mtu);
         mHandler.post(() -> reader.start());
-        HandlerUtilsKt.waitForIdle(mHandler, TIMEOUT_MS);
+        HandlerUtils.waitForIdle(mHandler, TIMEOUT_MS);
         return reader;
     }
 
diff --git a/packages/Tethering/tests/unit/src/android/net/util/TetheringUtilsTest.java b/packages/Tethering/tests/unit/src/android/net/util/TetheringUtilsTest.java
index 1499f3b..91c7771 100644
--- a/packages/Tethering/tests/unit/src/android/net/util/TetheringUtilsTest.java
+++ b/packages/Tethering/tests/unit/src/android/net/util/TetheringUtilsTest.java
@@ -27,7 +27,7 @@
 import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
 
-import com.android.testutils.MiscAssertsKt;
+import com.android.testutils.MiscAsserts;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -82,6 +82,6 @@
         request.showProvisioningUi = false;
         assertFalse(TetheringUtils.isTetheringRequestEquals(mTetheringRequest, request));
 
-        MiscAssertsKt.assertFieldCountEquals(5, TetheringRequestParcel.class);
+        MiscAsserts.assertFieldCountEquals(5, TetheringRequestParcel.class);
     }
 }
diff --git a/packages/Tethering/tests/unit/src/com/android/networkstack/tethering/OffloadControllerTest.java b/packages/Tethering/tests/unit/src/com/android/networkstack/tethering/OffloadControllerTest.java
index b291438..ce52ae2 100644
--- a/packages/Tethering/tests/unit/src/com/android/networkstack/tethering/OffloadControllerTest.java
+++ b/packages/Tethering/tests/unit/src/com/android/networkstack/tethering/OffloadControllerTest.java
@@ -30,8 +30,8 @@
 import static com.android.networkstack.tethering.OffloadController.StatsType.STATS_PER_UID;
 import static com.android.networkstack.tethering.OffloadHardwareInterface.ForwardedStats;
 import static com.android.networkstack.tethering.TetheringConfiguration.DEFAULT_TETHER_OFFLOAD_POLL_INTERVAL_MS;
-import static com.android.testutils.MiscAssertsKt.assertContainsAll;
-import static com.android.testutils.MiscAssertsKt.assertThrows;
+import static com.android.testutils.MiscAsserts.assertContainsAll;
+import static com.android.testutils.MiscAsserts.assertThrows;
 import static com.android.testutils.NetworkStatsUtilsKt.assertNetworkStatsEquals;
 
 import static junit.framework.Assert.assertNotNull;
diff --git a/packages/Tethering/tests/unit/src/com/android/networkstack/tethering/PrivateAddressCoordinatorTest.java b/packages/Tethering/tests/unit/src/com/android/networkstack/tethering/PrivateAddressCoordinatorTest.java
index 2c0df6f..8e93c2e 100644
--- a/packages/Tethering/tests/unit/src/com/android/networkstack/tethering/PrivateAddressCoordinatorTest.java
+++ b/packages/Tethering/tests/unit/src/com/android/networkstack/tethering/PrivateAddressCoordinatorTest.java
@@ -15,6 +15,11 @@
  */
 package com.android.networkstack.tethering;
 
+import static android.net.TetheringManager.TETHERING_ETHERNET;
+import static android.net.TetheringManager.TETHERING_USB;
+import static android.net.TetheringManager.TETHERING_WIFI;
+import static android.net.TetheringManager.TETHERING_WIFI_P2P;
+
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotEquals;
 import static org.mockito.Mockito.never;
@@ -54,22 +59,34 @@
     @Mock private IpServer mHotspotIpServer;
     @Mock private IpServer mUsbIpServer;
     @Mock private IpServer mEthernetIpServer;
+    @Mock private IpServer mWifiP2pIpServer;
     @Mock private Context mContext;
     @Mock private ConnectivityManager mConnectivityMgr;
+    @Mock private TetheringConfiguration mConfig;
 
     private PrivateAddressCoordinator mPrivateAddressCoordinator;
     private final IpPrefix mBluetoothPrefix = new IpPrefix("192.168.44.0/24");
+    private final LinkAddress mLegacyWifiP2pAddress = new LinkAddress("192.168.49.1/24");
     private final Network mWifiNetwork = new Network(1);
     private final Network mMobileNetwork = new Network(2);
     private final Network[] mAllNetworks = {mMobileNetwork, mWifiNetwork};
 
+    private void setUpIpServers() throws Exception {
+        when(mUsbIpServer.interfaceType()).thenReturn(TETHERING_USB);
+        when(mEthernetIpServer.interfaceType()).thenReturn(TETHERING_ETHERNET);
+        when(mHotspotIpServer.interfaceType()).thenReturn(TETHERING_WIFI);
+        when(mWifiP2pIpServer.interfaceType()).thenReturn(TETHERING_WIFI_P2P);
+    }
+
     @Before
     public void setUp() throws Exception {
         MockitoAnnotations.initMocks(this);
 
         when(mContext.getSystemService(Context.CONNECTIVITY_SERVICE)).thenReturn(mConnectivityMgr);
         when(mConnectivityMgr.getAllNetworks()).thenReturn(mAllNetworks);
-        mPrivateAddressCoordinator = spy(new PrivateAddressCoordinator(mContext));
+        when(mConfig.shouldEnableWifiP2pDedicatedIp()).thenReturn(false);
+        setUpIpServers();
+        mPrivateAddressCoordinator = spy(new PrivateAddressCoordinator(mContext, mConfig));
     }
 
     @Test
@@ -256,4 +273,38 @@
         final IpPrefix ethPrefix = PrefixUtils.asIpPrefix(ethAddr);
         assertEquals(predefinedPrefix, ethPrefix);
     }
+
+    private int getSubAddress(final byte... ipv4Address) {
+        assertEquals(4, ipv4Address.length);
+
+        int subnet = Byte.toUnsignedInt(ipv4Address[2]);
+        return (subnet << 8) + ipv4Address[3];
+    }
+
+    private void assertReseveredWifiP2pPrefix() throws Exception {
+        LinkAddress address = mPrivateAddressCoordinator.requestDownstreamAddress(
+                mHotspotIpServer);
+        final IpPrefix hotspotPrefix = PrefixUtils.asIpPrefix(address);
+        final IpPrefix legacyWifiP2pPrefix = PrefixUtils.asIpPrefix(mLegacyWifiP2pAddress);
+        assertNotEquals(legacyWifiP2pPrefix, hotspotPrefix);
+        mPrivateAddressCoordinator.releaseDownstream(mHotspotIpServer);
+    }
+
+    @Test
+    public void testEnableLegacyWifiP2PAddress() throws Exception {
+        when(mPrivateAddressCoordinator.getRandomSubAddr()).thenReturn(
+                getSubAddress(mLegacyWifiP2pAddress.getAddress().getAddress()));
+        // No matter #shouldEnableWifiP2pDedicatedIp() is enabled or not, legacy wifi p2p prefix
+        // is resevered.
+        assertReseveredWifiP2pPrefix();
+
+        when(mConfig.shouldEnableWifiP2pDedicatedIp()).thenReturn(true);
+        assertReseveredWifiP2pPrefix();
+
+        // If #shouldEnableWifiP2pDedicatedIp() is enabled, wifi P2P gets the configured address.
+        LinkAddress address = mPrivateAddressCoordinator.requestDownstreamAddress(
+                mWifiP2pIpServer);
+        assertEquals(mLegacyWifiP2pAddress, address);
+        mPrivateAddressCoordinator.releaseDownstream(mWifiP2pIpServer);
+    }
 }
diff --git a/packages/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringConfigurationTest.java b/packages/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringConfigurationTest.java
index a9ac4e2..dc0940c 100644
--- a/packages/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringConfigurationTest.java
+++ b/packages/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringConfigurationTest.java
@@ -128,6 +128,8 @@
                 .thenReturn(new String[0]);
         when(mResources.getBoolean(R.bool.config_tether_enable_legacy_dhcp_server)).thenReturn(
                 false);
+        when(mResources.getBoolean(R.bool.config_tether_enable_legacy_wifi_p2p_dedicated_ip))
+                .thenReturn(false);
         initializeBpfOffloadConfiguration(true, null /* unset */);
 
         mHasTelephonyManager = true;
@@ -413,4 +415,17 @@
                 R.string.config_mobile_hotspot_provision_response)).thenReturn(
                 PROVISIONING_APP_RESPONSE);
     }
+
+    @Test
+    public void testEnableLegacyWifiP2PAddress() throws Exception {
+        final TetheringConfiguration defaultCfg = new TetheringConfiguration(
+                mMockContext, mLog, INVALID_SUBSCRIPTION_ID);
+        assertFalse(defaultCfg.shouldEnableWifiP2pDedicatedIp());
+
+        when(mResources.getBoolean(R.bool.config_tether_enable_legacy_wifi_p2p_dedicated_ip))
+                .thenReturn(true);
+        final TetheringConfiguration testCfg = new TetheringConfiguration(
+                mMockContext, mLog, INVALID_SUBSCRIPTION_ID);
+        assertTrue(testCfg.shouldEnableWifiP2pDedicatedIp());
+    }
 }
diff --git a/packages/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringNotificationUpdaterTest.kt b/packages/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringNotificationUpdaterTest.kt
index 4b6bbac..75c819b 100644
--- a/packages/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringNotificationUpdaterTest.kt
+++ b/packages/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringNotificationUpdaterTest.kt
@@ -18,33 +18,40 @@
 
 import android.app.Notification
 import android.app.NotificationManager
+import android.app.PendingIntent
+import android.app.PendingIntent.FLAG_IMMUTABLE
 import android.content.Context
+import android.content.Intent
 import android.content.pm.ActivityInfo
 import android.content.pm.ApplicationInfo
 import android.content.pm.PackageManager
 import android.content.pm.ResolveInfo
 import android.content.res.Resources
 import android.net.ConnectivityManager.TETHERING_WIFI
+import android.net.NetworkCapabilities
+import android.net.NetworkCapabilities.NET_CAPABILITY_NOT_ROAMING
 import android.os.Handler
 import android.os.HandlerThread
 import android.os.Looper
-import android.net.NetworkCapabilities
-import android.net.NetworkCapabilities.NET_CAPABILITY_NOT_ROAMING
 import android.os.UserHandle
+import android.provider.Settings
 import android.telephony.TelephonyManager
 import androidx.test.filters.SmallTest
 import androidx.test.platform.app.InstrumentationRegistry
 import androidx.test.runner.AndroidJUnit4
 import com.android.internal.util.test.BroadcastInterceptingContext
+import com.android.networkstack.tethering.TetheringNotificationUpdater.ACTION_DISABLE_TETHERING
 import com.android.networkstack.tethering.TetheringNotificationUpdater.DOWNSTREAM_NONE
 import com.android.networkstack.tethering.TetheringNotificationUpdater.EVENT_SHOW_NO_UPSTREAM
 import com.android.networkstack.tethering.TetheringNotificationUpdater.NO_UPSTREAM_NOTIFICATION_ID
 import com.android.networkstack.tethering.TetheringNotificationUpdater.RESTRICTED_NOTIFICATION_ID
 import com.android.networkstack.tethering.TetheringNotificationUpdater.ROAMING_NOTIFICATION_ID
 import com.android.networkstack.tethering.TetheringNotificationUpdater.VERIZON_CARRIER_ID
+import com.android.networkstack.tethering.TetheringNotificationUpdater.getSettingsPackageName
 import com.android.testutils.waitForIdle
 import org.junit.After
 import org.junit.Assert.assertEquals
+import org.junit.Assert.assertNotNull
 import org.junit.Assert.fail
 import org.junit.Before
 import org.junit.Test
@@ -87,12 +94,17 @@
     // every test but should always be initialized before use (or the test should crash).
     private lateinit var context: TestContext
     private lateinit var notificationUpdater: TetheringNotificationUpdater
+
+    // Initializing the following members depends on initializing some of the mocks and
+    // is more logically done in setup().
     private lateinit var fakeTetheringThread: HandlerThread
 
     private val ROAMING_CAPABILITIES = NetworkCapabilities()
     private val HOME_CAPABILITIES = NetworkCapabilities().addCapability(NET_CAPABILITY_NOT_ROAMING)
     private val NOTIFICATION_ICON_ID = R.drawable.stat_sys_tether_general
     private val TIMEOUT_MS = 500L
+    private val ACTIVITY_PENDING_INTENT = 0
+    private val BROADCAST_PENDING_INTENT = 1
 
     private inner class TestContext(c: Context) : BroadcastInterceptingContext(c) {
         override fun createContextAsUser(user: UserHandle, flags: Int) =
@@ -146,10 +158,43 @@
         fakeTetheringThread.quitSafely()
     }
 
+    private fun verifyActivityPendingIntent(intent: Intent, flags: Int) {
+        // Use FLAG_NO_CREATE to verify whether PendingIntent has FLAG_IMMUTABLE flag(forcefully add
+        // the flag in creating arguments). If the described PendingIntent does not already exist,
+        // getActivity() will return null instead of PendingIntent object.
+        val pi = PendingIntent.getActivity(
+                context.createContextAsUser(UserHandle.CURRENT, 0 /* flags */),
+                0 /* requestCode */,
+                intent,
+                flags or FLAG_IMMUTABLE or PendingIntent.FLAG_NO_CREATE,
+                null /* options */)
+        assertNotNull("Activity PendingIntent with FLAG_IMMUTABLE does not exist.", pi)
+    }
+
+    private fun verifyBroadcastPendingIntent(intent: Intent, flags: Int) {
+        // Use FLAG_NO_CREATE to verify whether PendingIntent has FLAG_IMMUTABLE flag(forcefully add
+        // the flag in creating arguments). If the described PendingIntent does not already exist,
+        // getBroadcast() will return null instead of PendingIntent object.
+        val pi = PendingIntent.getBroadcast(
+                context.createContextAsUser(UserHandle.CURRENT, 0 /* flags */),
+                0 /* requestCode */,
+                intent,
+                flags or FLAG_IMMUTABLE or PendingIntent.FLAG_NO_CREATE)
+        assertNotNull("Broadcast PendingIntent with FLAG_IMMUTABLE does not exist.", pi)
+    }
+
     private fun Notification.title() = this.extras.getString(Notification.EXTRA_TITLE)
     private fun Notification.text() = this.extras.getString(Notification.EXTRA_TEXT)
 
-    private fun verifyNotification(iconId: Int, title: String, text: String, id: Int) {
+    private fun verifyNotification(
+        iconId: Int,
+        title: String,
+        text: String,
+        id: Int,
+        intentSenderType: Int,
+        intent: Intent,
+        flags: Int
+    ) {
         verify(notificationManager, never()).cancel(any(), eq(id))
 
         val notificationCaptor = ArgumentCaptor.forClass(Notification::class.java)
@@ -161,6 +206,11 @@
         assertEquals(title, notification.title())
         assertEquals(text, notification.text())
 
+        when (intentSenderType) {
+            ACTIVITY_PENDING_INTENT -> verifyActivityPendingIntent(intent, flags)
+            BROADCAST_PENDING_INTENT -> verifyBroadcastPendingIntent(intent, flags)
+        }
+
         reset(notificationManager)
     }
 
@@ -176,6 +226,10 @@
 
     @Test
     fun testRestrictedNotification() {
+        val settingsIntent = Intent(Settings.ACTION_TETHER_SETTINGS)
+                .setPackage(getSettingsPackageName(context.packageManager))
+                .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
+
         // Set test sub id.
         notificationUpdater.onActiveDataSubscriptionIdChanged(TEST_SUBID)
         verifyNotificationCancelled(listOf(NO_UPSTREAM_NOTIFICATION_ID, ROAMING_NOTIFICATION_ID))
@@ -183,7 +237,7 @@
         // User restrictions on. Show restricted notification.
         notificationUpdater.notifyTetheringDisabledByRestriction()
         verifyNotification(NOTIFICATION_ICON_ID, TEST_DISALLOW_TITLE, TEST_DISALLOW_MESSAGE,
-                RESTRICTED_NOTIFICATION_ID)
+                RESTRICTED_NOTIFICATION_ID, ACTIVITY_PENDING_INTENT, settingsIntent, FLAG_IMMUTABLE)
 
         // User restrictions off. Clear notification.
         notificationUpdater.tetheringRestrictionLifted()
@@ -196,7 +250,7 @@
         // User restrictions on again. Show restricted notification.
         notificationUpdater.notifyTetheringDisabledByRestriction()
         verifyNotification(NOTIFICATION_ICON_ID, TEST_DISALLOW_TITLE, TEST_DISALLOW_MESSAGE,
-                RESTRICTED_NOTIFICATION_ID)
+                RESTRICTED_NOTIFICATION_ID, ACTIVITY_PENDING_INTENT, settingsIntent, FLAG_IMMUTABLE)
     }
 
     val MAX_BACKOFF_MS = 200L
@@ -234,6 +288,8 @@
 
     @Test
     fun testNoUpstreamNotification() {
+        val disableIntent = Intent(ACTION_DISABLE_TETHERING).setPackage(context.packageName)
+
         // Set test sub id.
         notificationUpdater.onActiveDataSubscriptionIdChanged(TEST_SUBID)
         verifyNotificationCancelled(listOf(NO_UPSTREAM_NOTIFICATION_ID, ROAMING_NOTIFICATION_ID))
@@ -246,7 +302,8 @@
         notificationUpdater.onUpstreamCapabilitiesChanged(null)
         notificationUpdater.handler.waitForDelayedMessage(EVENT_SHOW_NO_UPSTREAM, TIMEOUT_MS)
         verifyNotification(NOTIFICATION_ICON_ID, TEST_NO_UPSTREAM_TITLE, TEST_NO_UPSTREAM_MESSAGE,
-                NO_UPSTREAM_NOTIFICATION_ID)
+                NO_UPSTREAM_NOTIFICATION_ID, BROADCAST_PENDING_INTENT, disableIntent,
+                FLAG_IMMUTABLE)
 
         // Same capabilities changed. Nothing happened.
         notificationUpdater.onUpstreamCapabilitiesChanged(null)
@@ -260,7 +317,8 @@
         notificationUpdater.onUpstreamCapabilitiesChanged(null)
         notificationUpdater.handler.waitForDelayedMessage(EVENT_SHOW_NO_UPSTREAM, TIMEOUT_MS)
         verifyNotification(NOTIFICATION_ICON_ID, TEST_NO_UPSTREAM_TITLE, TEST_NO_UPSTREAM_MESSAGE,
-                NO_UPSTREAM_NOTIFICATION_ID)
+                NO_UPSTREAM_NOTIFICATION_ID, BROADCAST_PENDING_INTENT, disableIntent,
+                FLAG_IMMUTABLE)
 
         // No downstream.
         notificationUpdater.onDownstreamChanged(DOWNSTREAM_NONE)
@@ -305,6 +363,11 @@
 
     @Test
     fun testRoamingNotification() {
+        val disableIntent = Intent(ACTION_DISABLE_TETHERING).setPackage(context.packageName)
+        val settingsIntent = Intent(Settings.ACTION_TETHER_SETTINGS)
+                .setPackage(getSettingsPackageName(context.packageManager))
+                .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
+
         // Set test sub id.
         notificationUpdater.onActiveDataSubscriptionIdChanged(TEST_SUBID)
         verifyNotificationCancelled(listOf(NO_UPSTREAM_NOTIFICATION_ID, ROAMING_NOTIFICATION_ID))
@@ -316,7 +379,7 @@
         // Upstream capabilities changed to roaming state. Show roaming notification.
         notificationUpdater.onUpstreamCapabilitiesChanged(ROAMING_CAPABILITIES)
         verifyNotification(NOTIFICATION_ICON_ID, TEST_ROAMING_TITLE, TEST_ROAMING_MESSAGE,
-                ROAMING_NOTIFICATION_ID)
+                ROAMING_NOTIFICATION_ID, ACTIVITY_PENDING_INTENT, settingsIntent, FLAG_IMMUTABLE)
 
         // Same capabilities change. Nothing happened.
         notificationUpdater.onUpstreamCapabilitiesChanged(ROAMING_CAPABILITIES)
@@ -329,14 +392,15 @@
         // Upstream capabilities changed to roaming state again. Show roaming notification.
         notificationUpdater.onUpstreamCapabilitiesChanged(ROAMING_CAPABILITIES)
         verifyNotification(NOTIFICATION_ICON_ID, TEST_ROAMING_TITLE, TEST_ROAMING_MESSAGE,
-                ROAMING_NOTIFICATION_ID)
+                ROAMING_NOTIFICATION_ID, ACTIVITY_PENDING_INTENT, settingsIntent, FLAG_IMMUTABLE)
 
         // No upstream. Clear roaming notification and show no upstream notification.
         notificationUpdater.onUpstreamCapabilitiesChanged(null)
         notificationUpdater.handler.waitForDelayedMessage(EVENT_SHOW_NO_UPSTREAM, TIMEOUT_MS)
         verifyNotificationCancelled(listOf(ROAMING_NOTIFICATION_ID), false)
         verifyNotification(NOTIFICATION_ICON_ID, TEST_NO_UPSTREAM_TITLE, TEST_NO_UPSTREAM_MESSAGE,
-                NO_UPSTREAM_NOTIFICATION_ID)
+                NO_UPSTREAM_NOTIFICATION_ID, BROADCAST_PENDING_INTENT, disableIntent,
+                FLAG_IMMUTABLE)
 
         // No downstream.
         notificationUpdater.onDownstreamChanged(DOWNSTREAM_NONE)
@@ -347,7 +411,8 @@
         notificationUpdater.handler.waitForDelayedMessage(EVENT_SHOW_NO_UPSTREAM, TIMEOUT_MS)
         verifyNotificationCancelled(listOf(ROAMING_NOTIFICATION_ID), false)
         verifyNotification(NOTIFICATION_ICON_ID, TEST_NO_UPSTREAM_TITLE, TEST_NO_UPSTREAM_MESSAGE,
-                NO_UPSTREAM_NOTIFICATION_ID)
+                NO_UPSTREAM_NOTIFICATION_ID, BROADCAST_PENDING_INTENT, disableIntent,
+                FLAG_IMMUTABLE)
 
         // Set R.bool.config_upstream_roaming_notification to false and change upstream
         // network to roaming state again. No roaming notification.
@@ -363,8 +428,7 @@
         val testSettingsPackageName = "com.android.test.settings"
         val pm = mock(PackageManager::class.java)
         doReturn(null).`when`(pm).resolveActivity(any(), anyInt())
-        assertEquals(defaultSettingsPackageName,
-                TetheringNotificationUpdater.getSettingsPackageName(pm))
+        assertEquals(defaultSettingsPackageName, getSettingsPackageName(pm))
 
         val resolveInfo = ResolveInfo().apply {
             activityInfo = ActivityInfo().apply {
@@ -375,7 +439,6 @@
             }
         }
         doReturn(resolveInfo).`when`(pm).resolveActivity(any(), anyInt())
-        assertEquals(testSettingsPackageName,
-                TetheringNotificationUpdater.getSettingsPackageName(pm))
+        assertEquals(testSettingsPackageName, getSettingsPackageName(pm))
     }
 }
diff --git a/packages/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringTest.java b/packages/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringTest.java
index 46fe5cf..1fe3840 100644
--- a/packages/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringTest.java
+++ b/packages/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringTest.java
@@ -143,7 +143,7 @@
 import com.android.internal.util.StateMachine;
 import com.android.internal.util.test.BroadcastInterceptingContext;
 import com.android.internal.util.test.FakeSettingsProvider;
-import com.android.testutils.MiscAssertsKt;
+import com.android.testutils.MiscAsserts;
 
 import org.junit.After;
 import org.junit.AfterClass;
@@ -1360,7 +1360,7 @@
         assertEquals(0, parcel.localOnlyList.length);
         assertEquals(0, parcel.erroredIfaceList.length);
         assertEquals(0, parcel.lastErrorList.length);
-        MiscAssertsKt.assertFieldCountEquals(5, TetherStatesParcel.class);
+        MiscAsserts.assertFieldCountEquals(5, TetherStatesParcel.class);
     }
 
     @Test
diff --git a/proto/src/metrics_constants/metrics_constants.proto b/proto/src/metrics_constants/metrics_constants.proto
index b0e401b..3f712dd 100644
--- a/proto/src/metrics_constants/metrics_constants.proto
+++ b/proto/src/metrics_constants/metrics_constants.proto
@@ -3706,33 +3706,40 @@
     // OS: O
     BACKUP_SETTINGS = 818;
 
+    // DEPRECATED: The metrics has been migrated to UiEvent per go/uievent.
     // ACTION: Picture-in-picture was explicitly entered for an activity
     // VALUE: true if it was entered while hiding as a result of moving to
     // another task, false otherwise
-    ACTION_PICTURE_IN_PICTURE_ENTERED = 819;
+    ACTION_PICTURE_IN_PICTURE_ENTERED = 819 [deprecated=true];
 
+    // DEPRECATED: The metrics has been migrated to UiEvent per go/uievent.
     // ACTION: The activity currently in picture-in-picture was expanded back to fullscreen
     // PACKAGE: The package name of the activity that was expanded back to fullscreen
-    ACTION_PICTURE_IN_PICTURE_EXPANDED_TO_FULLSCREEN = 820;
+    ACTION_PICTURE_IN_PICTURE_EXPANDED_TO_FULLSCREEN = 820 [deprecated=true];
 
+    // DEPRECATED: The metrics no longer used after migration to UiEvent per go/uievent.
     // ACTION: The activity currently in picture-in-picture was minimized
     // VALUE: True if the PiP was minimized, false otherwise
-    ACTION_PICTURE_IN_PICTURE_MINIMIZED = 821;
+    ACTION_PICTURE_IN_PICTURE_MINIMIZED = 821 [deprecated=true];
 
+    // DEPRECATED: The metrics has been migrated to UiEvent per go/uievent.
     // ACTION: Picture-in-picture was dismissed via the dismiss button
     // VALUE: 0 if dismissed by tap, 1 if dismissed by drag
-    ACTION_PICTURE_IN_PICTURE_DISMISSED = 822;
+    ACTION_PICTURE_IN_PICTURE_DISMISSED = 822 [deprecated=true];
 
-    // ACTION: The visibility of the picture-in-picture meny
+    // DEPRECATED: The metrics has been migrated to UiEvent per go/uievent.
+    // ACTION: The visibility of the picture-in-picture menu
     // VALUE: Whether or not the menu is visible
-    ACTION_PICTURE_IN_PICTURE_MENU = 823;
+    ACTION_PICTURE_IN_PICTURE_MENU = 823 [deprecated=true];
 
+    // DEPRECATED: The metrics has been migrated to UiEvent per go/uievent.
     // Enclosing category for group of PICTURE_IN_PICTURE_ASPECT_RATIO_FOO events,
     // logged when the aspect ratio changes
-    ACTION_PICTURE_IN_PICTURE_ASPECT_RATIO_CHANGED = 824;
+    ACTION_PICTURE_IN_PICTURE_ASPECT_RATIO_CHANGED = 824 [deprecated=true];
 
+    // DEPRECATED: The metrics no longer used after migration to UiEvent per go/uievent.
     // The current aspect ratio of the PiP, logged when it changes.
-    PICTURE_IN_PICTURE_ASPECT_RATIO = 825;
+    PICTURE_IN_PICTURE_ASPECT_RATIO = 825 [deprecated=true];
 
     // FIELD - length in dp of ACTION_LS_* gestures, or zero if not applicable
     // CATEGORY: GLOBAL_SYSTEM_UI
diff --git a/services/Android.bp b/services/Android.bp
index f0144ac..ef52c2a 100644
--- a/services/Android.bp
+++ b/services/Android.bp
@@ -30,6 +30,7 @@
         ":services.midi-sources",
         ":services.net-sources",
         ":services.print-sources",
+        ":services.profcollect-sources",
         ":services.restrictions-sources",
         ":services.startop.iorap-sources",
         ":services.systemcaptions-sources",
@@ -73,6 +74,7 @@
         "services.net",
         "services.people",
         "services.print",
+        "services.profcollect",
         "services.restrictions",
         "services.startop",
         "services.systemcaptions",
diff --git a/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java b/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java
index 9ad808a..a167ab1 100644
--- a/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java
+++ b/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java
@@ -148,6 +148,8 @@
 
     private boolean mRequestMultiFingerGestures;
 
+    private boolean mRequestTwoFingerPassthrough;
+
     boolean mRequestFilterKeyEvents;
 
     boolean mRetrieveInteractiveWindows;
@@ -325,8 +327,10 @@
                 & AccessibilityServiceInfo.FLAG_SERVICE_HANDLES_DOUBLE_TAP) != 0;
         mRequestMultiFingerGestures = (info.flags
                 & AccessibilityServiceInfo.FLAG_REQUEST_MULTI_FINGER_GESTURES) != 0;
-        mRequestFilterKeyEvents = (info.flags
-                & AccessibilityServiceInfo.FLAG_REQUEST_FILTER_KEY_EVENTS) != 0;
+        mRequestTwoFingerPassthrough =
+                (info.flags & AccessibilityServiceInfo.FLAG_REQUEST_2_FINGER_PASSTHROUGH) != 0;
+        mRequestFilterKeyEvents =
+                (info.flags & AccessibilityServiceInfo.FLAG_REQUEST_FILTER_KEY_EVENTS) != 0;
         mRetrieveInteractiveWindows = (info.flags
                 & AccessibilityServiceInfo.FLAG_RETRIEVE_INTERACTIVE_WINDOWS) != 0;
         mCaptureFingerprintGestures = (info.flags
@@ -1772,6 +1776,10 @@
         return mRequestMultiFingerGestures;
     }
 
+    public boolean isTwoFingerPassthroughEnabled() {
+        return mRequestTwoFingerPassthrough;
+    }
+
     @Override
     public void setGestureDetectionPassthroughRegion(int displayId, Region region) {
         mSystemSupport.setGestureDetectionPassthroughRegion(displayId, region);
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java
index 8b40f61..cd9ab8d 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java
@@ -118,6 +118,13 @@
      */
     static final int FLAG_REQUEST_MULTI_FINGER_GESTURES = 0x00000100;
 
+    /**
+     * Flag for enabling multi-finger gestures.
+     *
+     * @see #setUserAndEnabledFeatures(int, int)
+     */
+    static final int FLAG_REQUEST_2_FINGER_PASSTHROUGH = 0x00000200;
+
     static final int FEATURES_AFFECTING_MOTION_EVENTS =
             FLAG_FEATURE_INJECT_MOTION_EVENTS
                     | FLAG_FEATURE_AUTOCLICK
@@ -125,7 +132,8 @@
                     | FLAG_FEATURE_SCREEN_MAGNIFIER
                     | FLAG_FEATURE_TRIGGERED_SCREEN_MAGNIFIER
                     | FLAG_SERVICE_HANDLES_DOUBLE_TAP
-                    | FLAG_REQUEST_MULTI_FINGER_GESTURES;
+                    | FLAG_REQUEST_MULTI_FINGER_GESTURES
+                    | FLAG_REQUEST_2_FINGER_PASSTHROUGH;
 
     private final Context mContext;
 
@@ -421,6 +429,9 @@
                 if ((mEnabledFeatures & FLAG_REQUEST_MULTI_FINGER_GESTURES) != 0) {
                     explorer.setMultiFingerGesturesEnabled(true);
                 }
+                if ((mEnabledFeatures & FLAG_REQUEST_2_FINGER_PASSTHROUGH) != 0) {
+                    explorer.setTwoFingerPassthroughEnabled(true);
+                }
                 addFirstEventHandler(displayId, explorer);
                 mTouchExplorer.put(displayId, explorer);
             }
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
index 2cd4c69..c41f400 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
@@ -443,7 +443,11 @@
                     if (reboundAService || configurationChanged) {
                         onUserStateChangedLocked(userState);
                     }
-                    migrateAccessibilityButtonSettingsIfNecessaryLocked(userState, packageName);
+                    // Passing 0 for restoreFromSdkInt to have this migration check execute each
+                    // time. It can make sure a11y button settings are correctly if there's an a11y
+                    // service updated and modifies the a11y button configuration.
+                    migrateAccessibilityButtonSettingsIfNecessaryLocked(userState, packageName,
+                            /* restoreFromSdkInt = */0);
                 }
             }
 
@@ -554,7 +558,9 @@
                         synchronized (mLock) {
                             restoreEnabledAccessibilityServicesLocked(
                                     intent.getStringExtra(Intent.EXTRA_SETTING_PREVIOUS_VALUE),
-                                    intent.getStringExtra(Intent.EXTRA_SETTING_NEW_VALUE));
+                                    intent.getStringExtra(Intent.EXTRA_SETTING_NEW_VALUE),
+                                    intent.getIntExtra(Intent.EXTRA_SETTING_RESTORED_FROM_SDK_INT,
+                                            0));
                         }
                     } else if (ACCESSIBILITY_DISPLAY_MAGNIFICATION_NAVBAR_ENABLED.equals(which)) {
                         synchronized (mLock) {
@@ -563,6 +569,12 @@
                                     intent.getIntExtra(Intent.EXTRA_SETTING_RESTORED_FROM_SDK_INT,
                                             0));
                         }
+                    } else if (Settings.Secure.ACCESSIBILITY_BUTTON_TARGETS.equals(which)) {
+                        synchronized (mLock) {
+                            restoreAccessibilityButtonTargetsLocked(
+                                    intent.getStringExtra(Intent.EXTRA_SETTING_PREVIOUS_VALUE),
+                                    intent.getStringExtra(Intent.EXTRA_SETTING_NEW_VALUE));
+                        }
                     }
                 }
             }
@@ -588,7 +600,7 @@
         final AccessibilityUserState userState = getUserStateLocked(UserHandle.USER_SYSTEM);
         final Set<String> targetsFromSetting = new ArraySet<>();
         readColonDelimitedSettingToSet(Settings.Secure.ACCESSIBILITY_BUTTON_TARGETS,
-                userState.mUserId, targetsFromSetting, str -> str);
+                userState.mUserId, str -> str, targetsFromSetting);
         final boolean targetsContainMagnification = targetsFromSetting.contains(
                 MAGNIFICATION_CONTROLLER_NAME);
         if (targetsContainMagnification == displayMagnificationNavBarEnabled) {
@@ -1189,7 +1201,14 @@
             // the state since the context in which the current user
             // state was used has changed since it was inactive.
             onUserStateChangedLocked(userState);
-            migrateAccessibilityButtonSettingsIfNecessaryLocked(userState, null);
+            // It's better to have this migration in SettingsProvider. Unfortunately,
+            // SettingsProvider migrated database in a very early stage which A11yManagerService
+            // haven't finished or started the initialization. We cannot get enough information from
+            // A11yManagerService to execute these migrations in SettingsProvider. Passing 0 for
+            // restoreFromSdkInt to have this migration check execute every time, because we did not
+            // find out a way to detect the device finished the OTA and switch the user.
+            migrateAccessibilityButtonSettingsIfNecessaryLocked(userState, null,
+                    /* restoreFromSdkInt = */0);
 
             if (announceNewUser) {
                 // Schedule announcement of the current user if needed.
@@ -1234,7 +1253,8 @@
 
     // Called only during settings restore; currently supports only the owner user
     // TODO: http://b/22388012
-    void restoreEnabledAccessibilityServicesLocked(String oldSetting, String newSetting) {
+    void restoreEnabledAccessibilityServicesLocked(String oldSetting, String newSetting,
+            int restoreFromSdkInt) {
         readComponentNamesFromStringLocked(oldSetting, mTempComponentNameSet, false);
         readComponentNamesFromStringLocked(newSetting, mTempComponentNameSet, true);
 
@@ -1246,7 +1266,32 @@
                 userState.mEnabledServices,
                 UserHandle.USER_SYSTEM);
         onUserStateChangedLocked(userState);
-        migrateAccessibilityButtonSettingsIfNecessaryLocked(userState, null);
+        migrateAccessibilityButtonSettingsIfNecessaryLocked(userState, null, restoreFromSdkInt);
+    }
+
+    /**
+     * User could enable accessibility services and configure accessibility button during the SUW.
+     * Merges current value of accessibility button settings into the restored one to make sure
+     * user's preferences of accessibility button updated in SUW are not lost.
+     *
+     * Called only during settings restore; currently supports only the owner user
+     * TODO: http://b/22388012
+     */
+    void restoreAccessibilityButtonTargetsLocked(String oldSetting, String newSetting) {
+        final Set<String> targetsFromSetting = new ArraySet<>();
+        readColonDelimitedStringToSet(oldSetting, str -> str, targetsFromSetting,
+                /* doMerge = */false);
+        readColonDelimitedStringToSet(newSetting, str -> str, targetsFromSetting,
+                /* doMerge = */true);
+
+        final AccessibilityUserState userState = getUserStateLocked(UserHandle.USER_SYSTEM);
+        userState.mAccessibilityButtonTargets.clear();
+        userState.mAccessibilityButtonTargets.addAll(targetsFromSetting);
+        persistColonDelimitedSetToSettingLocked(Settings.Secure.ACCESSIBILITY_BUTTON_TARGETS,
+                UserHandle.USER_SYSTEM, userState.mAccessibilityButtonTargets, str -> str);
+
+        scheduleNotifyClientsOfServicesStateChangeLocked(userState);
+        onUserStateChangedLocked(userState);
     }
 
     private int getClientStateLocked(AccessibilityUserState userState) {
@@ -1485,12 +1530,12 @@
         int serviceCount = userState.mBoundServices.size();
         for (int i = 0; i < serviceCount; i++) {
             AccessibilityServiceConnection service = userState.mBoundServices.get(i);
-            relevantEventTypes |= isClientInPackageWhitelist(service.getServiceInfo(), client)
+            relevantEventTypes |= isClientInPackageAllowlist(service.getServiceInfo(), client)
                     ? service.getRelevantEventTypes()
                     : 0;
         }
 
-        relevantEventTypes |= isClientInPackageWhitelist(
+        relevantEventTypes |= isClientInPackageAllowlist(
                 mUiAutomationManager.getServiceInfo(), client)
                 ? mUiAutomationManager.getRelevantEventTypes()
                 : 0;
@@ -1526,7 +1571,7 @@
         }
     }
 
-    private static boolean isClientInPackageWhitelist(
+    private static boolean isClientInPackageAllowlist(
             @Nullable AccessibilityServiceInfo serviceInfo, Client client) {
         if (serviceInfo == null) return false;
 
@@ -1545,7 +1590,7 @@
                 Slog.d(LOG_TAG, "Dropping events: "
                         + Arrays.toString(clientPackages) + " -> "
                         + serviceInfo.getComponentName().flattenToShortString()
-                        + " due to not being in package whitelist "
+                        + " due to not being in package allowlist "
                         + Arrays.toString(serviceInfo.packageNames));
             }
         }
@@ -1569,8 +1614,8 @@
      */
     private void readComponentNamesFromSettingLocked(String settingName, int userId,
             Set<ComponentName> outComponentNames) {
-        readColonDelimitedSettingToSet(settingName, userId, outComponentNames,
-                str -> ComponentName.unflattenFromString(str));
+        readColonDelimitedSettingToSet(settingName, userId,
+                str -> ComponentName.unflattenFromString(str), outComponentNames);
     }
 
     /**
@@ -1585,8 +1630,8 @@
     private void readComponentNamesFromStringLocked(String names,
             Set<ComponentName> outComponentNames,
             boolean doMerge) {
-        readColonDelimitedStringToSet(names, outComponentNames, doMerge,
-                str -> ComponentName.unflattenFromString(str));
+        readColonDelimitedStringToSet(names, str -> ComponentName.unflattenFromString(str),
+                outComponentNames, doMerge);
     }
 
     @Override
@@ -1596,15 +1641,15 @@
                 componentName -> componentName.flattenToShortString());
     }
 
-    private <T> void readColonDelimitedSettingToSet(String settingName, int userId, Set<T> outSet,
-            Function<String, T> toItem) {
+    private <T> void readColonDelimitedSettingToSet(String settingName, int userId,
+            Function<String, T> toItem, Set<T> outSet) {
         final String settingValue = Settings.Secure.getStringForUser(mContext.getContentResolver(),
                 settingName, userId);
-        readColonDelimitedStringToSet(settingValue, outSet, false, toItem);
+        readColonDelimitedStringToSet(settingValue, toItem, outSet, false);
     }
 
-    private <T> void readColonDelimitedStringToSet(String names, Set<T> outSet, boolean doMerge,
-            Function<String, T> toItem) {
+    private <T> void readColonDelimitedStringToSet(String names, Function<String, T> toItem,
+            Set<T> outSet, boolean doMerge) {
         if (!doMerge) {
             outSet.clear();
         }
@@ -1792,6 +1837,9 @@
                 if (userState.isMultiFingerGesturesEnabledLocked()) {
                     flags |= AccessibilityInputFilter.FLAG_REQUEST_MULTI_FINGER_GESTURES;
                 }
+                if (userState.isTwoFingerPassthroughEnabledLocked()) {
+                    flags |= AccessibilityInputFilter.FLAG_REQUEST_2_FINGER_PASSTHROUGH;
+                }
             }
             if (userState.isFilterKeyEventsEnabledLocked()) {
                 flags |= AccessibilityInputFilter.FLAG_FEATURE_FILTER_KEY_EVENTS;
@@ -1943,9 +1991,9 @@
     }
 
     private void updateLegacyCapabilitiesLocked(AccessibilityUserState userState) {
-        // Up to JB-MR1 we had a white list with services that can enable touch
+        // Up to JB-MR1 we had a allowlist with services that can enable touch
         // exploration. When a service is first started we show a dialog to the
-        // use to get a permission to white list the service.
+        // use to get a permission to allowlist the service.
         final int installedServiceCount = userState.mInstalledServices.size();
         for (int i = 0; i < installedServiceCount; i++) {
             AccessibilityServiceInfo serviceInfo = userState.mInstalledServices.get(i);
@@ -2075,6 +2123,7 @@
         boolean touchExplorationEnabled = mUiAutomationManager.isTouchExplorationEnabledLocked();
         boolean serviceHandlesDoubleTapEnabled = false;
         boolean requestMultiFingerGestures = false;
+        boolean requestTwoFingerPassthrough = false;
         final int serviceCount = userState.mBoundServices.size();
         for (int i = 0; i < serviceCount; i++) {
             AccessibilityServiceConnection service = userState.mBoundServices.get(i);
@@ -2082,6 +2131,7 @@
                 touchExplorationEnabled = true;
                 serviceHandlesDoubleTapEnabled = service.isServiceHandlesDoubleTapEnabled();
                 requestMultiFingerGestures = service.isMultiFingerGesturesEnabled();
+                requestTwoFingerPassthrough = service.isTwoFingerPassthroughEnabled();
                 break;
             }
         }
@@ -2098,13 +2148,14 @@
         }
         userState.setServiceHandlesDoubleTapLocked(serviceHandlesDoubleTapEnabled);
         userState.setMultiFingerGesturesLocked(requestMultiFingerGestures);
+        userState.setTwoFingerPassthroughLocked(requestTwoFingerPassthrough);
     }
 
     private boolean readAccessibilityShortcutKeySettingLocked(AccessibilityUserState userState) {
         final String settingValue = Settings.Secure.getStringForUser(mContext.getContentResolver(),
                 Settings.Secure.ACCESSIBILITY_SHORTCUT_TARGET_SERVICE, userState.mUserId);
         final Set<String> targetsFromSetting = new ArraySet<>();
-        readColonDelimitedStringToSet(settingValue, targetsFromSetting, false, str -> str);
+        readColonDelimitedStringToSet(settingValue, str -> str, targetsFromSetting, false);
         // Fall back to device's default a11y service, only when setting is never updated.
         if (settingValue == null) {
             final String defaultService = mContext.getString(
@@ -2128,7 +2179,7 @@
     private boolean readAccessibilityButtonTargetsLocked(AccessibilityUserState userState) {
         final Set<String> targetsFromSetting = new ArraySet<>();
         readColonDelimitedSettingToSet(Settings.Secure.ACCESSIBILITY_BUTTON_TARGETS,
-                userState.mUserId, targetsFromSetting, str -> str);
+                userState.mUserId, str -> str, targetsFromSetting);
 
         final Set<String> currentTargets =
                 userState.getShortcutTargetsLocked(ACCESSIBILITY_BUTTON);
@@ -2210,9 +2261,9 @@
         }
         if (service.getServiceInfo().getResolveInfo().serviceInfo.applicationInfo.targetSdkVersion
                 <= Build.VERSION_CODES.JELLY_BEAN_MR1) {
-            // Up to JB-MR1 we had a white list with services that can enable touch
+            // Up to JB-MR1 we had a allowlist with services that can enable touch
             // exploration. When a service is first started we show a dialog to the
-            // use to get a permission to white list the service.
+            // use to get a permission to allowlist the service.
             if (userState.mTouchExplorationGrantedServices.contains(service.mComponentName)) {
                 return true;
             } else if (mEnableTouchExplorationDialog == null
@@ -2392,9 +2443,15 @@
      *    (It happens when an enabled accessibility service package is upgraded.)
      *
      * @param packageName The package name to check, or {@code null} to check all services.
+     * @param restoreFromSdkInt The target sdk version of the restored source device, or {@code 0}
+     *                          if the caller is not related to the restore.
      */
     private void migrateAccessibilityButtonSettingsIfNecessaryLocked(
-            AccessibilityUserState userState, @Nullable String packageName) {
+            AccessibilityUserState userState, @Nullable String packageName, int restoreFromSdkInt) {
+        // No need to migrate settings if they are restored from a version after Q.
+        if (restoreFromSdkInt > Build.VERSION_CODES.Q) {
+            return;
+        }
         final Set<String> buttonTargets =
                 userState.getShortcutTargetsLocked(ACCESSIBILITY_BUTTON);
         int lastSize = buttonTargets.size();
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityUserState.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityUserState.java
index f865aa7..4c9e444 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityUserState.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityUserState.java
@@ -110,6 +110,7 @@
     private boolean mIsTouchExplorationEnabled;
     private boolean mServiceHandlesDoubleTap;
     private boolean mRequestMultiFingerGestures;
+    private boolean mRequestTwoFingerPassthrough;
     private int mUserInteractiveUiTimeout;
     private int mUserNonInteractiveUiTimeout;
     private int mNonInteractiveUiTimeout = 0;
@@ -169,6 +170,7 @@
         mIsTouchExplorationEnabled = false;
         mServiceHandlesDoubleTap = false;
         mRequestMultiFingerGestures = false;
+        mRequestTwoFingerPassthrough = false;
         mIsDisplayMagnificationEnabled = false;
         mIsAutoclickEnabled = false;
         mUserNonInteractiveUiTimeout = 0;
@@ -456,6 +458,8 @@
                 .append(String.valueOf(mServiceHandlesDoubleTap));
         pw.append(", requestMultiFingerGestures=")
                 .append(String.valueOf(mRequestMultiFingerGestures));
+        pw.append(", requestTwoFingerPassthrough=")
+                .append(String.valueOf(mRequestTwoFingerPassthrough));
         pw.append(", displayMagnificationEnabled=").append(String.valueOf(
                 mIsDisplayMagnificationEnabled));
         pw.append(", autoclickEnabled=").append(String.valueOf(mIsAutoclickEnabled));
@@ -790,6 +794,14 @@
     public void setMultiFingerGesturesLocked(boolean enabled) {
         mRequestMultiFingerGestures = enabled;
     }
+    public boolean isTwoFingerPassthroughEnabledLocked() {
+        return mRequestTwoFingerPassthrough;
+    }
+
+    public void setTwoFingerPassthroughLocked(boolean enabled) {
+        mRequestTwoFingerPassthrough = enabled;
+    }
+
 
     public int getUserInteractiveUiTimeoutLocked() {
         return mUserInteractiveUiTimeout;
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityWindowManager.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityWindowManager.java
index 669bb24..a75b64c 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityWindowManager.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityWindowManager.java
@@ -284,11 +284,13 @@
          * Computes partial interactive region of given windowId.
          *
          * @param windowId The windowId
+         * @param forceComputeRegion set outRegion when the windowId matches one on the screen even
+         *                           though the region is not covered by other windows above it.
          * @param outRegion The output to which to write the bounds.
-         * @return true if outRegion is not empty.
+         * @return {@code true} if outRegion is not empty.
          */
         boolean computePartialInteractiveRegionForWindowLocked(int windowId,
-                @NonNull Region outRegion) {
+                boolean forceComputeRegion, @NonNull Region outRegion) {
             if (mWindows == null) {
                 return false;
             }
@@ -309,6 +311,9 @@
                         currentWindow.getRegionInScreen(currentWindowRegions);
                         outRegion.set(currentWindowRegions);
                         windowInteractiveRegion = outRegion;
+                        if (forceComputeRegion) {
+                            windowInteractiveRegionChanged = true;
+                        }
                         continue;
                     }
                 } else if (currentWindow.getType()
@@ -1240,10 +1245,13 @@
      */
     public boolean computePartialInteractiveRegionForWindowLocked(int windowId,
             @NonNull Region outRegion) {
-        windowId = resolveParentWindowIdLocked(windowId);
-        final DisplayWindowsObserver observer = getDisplayWindowObserverByWindowIdLocked(windowId);
+        final int parentWindowId = resolveParentWindowIdLocked(windowId);
+        final DisplayWindowsObserver observer = getDisplayWindowObserverByWindowIdLocked(
+                parentWindowId);
+
         if (observer != null) {
-            return observer.computePartialInteractiveRegionForWindowLocked(windowId, outRegion);
+            return observer.computePartialInteractiveRegionForWindowLocked(parentWindowId,
+                    parentWindowId != windowId, outRegion);
         }
 
         return false;
diff --git a/services/accessibility/java/com/android/server/accessibility/gestures/GestureManifold.java b/services/accessibility/java/com/android/server/accessibility/gestures/GestureManifold.java
index e9c70c6..8604fe7 100644
--- a/services/accessibility/java/com/android/server/accessibility/gestures/GestureManifold.java
+++ b/services/accessibility/java/com/android/server/accessibility/gestures/GestureManifold.java
@@ -94,8 +94,13 @@
     private boolean mServiceHandlesDoubleTap = false;
     // Whether multi-finger gestures are enabled.
     boolean mMultiFingerGesturesEnabled;
+    // Whether the two-finger passthrough is enabled when multi-finger gestures are enabled.
+    private boolean mTwoFingerPassthroughEnabled;
     // A list of all the multi-finger gestures, for easy adding and removal.
     private final List<GestureMatcher> mMultiFingerGestures = new ArrayList<>();
+    // A list of two-finger swipes, for easy adding and removal when turning on or off two-finger
+    // passthrough.
+    private final List<GestureMatcher> mTwoFingerSwipes = new ArrayList<>();
     // Shared state information.
     private TouchState mState;
 
@@ -105,6 +110,7 @@
         mListener = listener;
         mState = state;
         mMultiFingerGesturesEnabled = false;
+        mTwoFingerPassthroughEnabled = false;
         // Set up gestures.
         // Start with double tap.
         mGestures.add(new MultiTap(context, 2, GESTURE_DOUBLE_TAP, this));
@@ -161,14 +167,14 @@
         mMultiFingerGestures.add(
                 new MultiFingerMultiTap(mContext, 4, 3, GESTURE_4_FINGER_TRIPLE_TAP, this));
         // Two-finger swipes.
-        mMultiFingerGestures.add(
+        mTwoFingerSwipes.add(
                 new MultiFingerSwipe(context, 2, DOWN, GESTURE_2_FINGER_SWIPE_DOWN, this));
-        mMultiFingerGestures.add(
+        mTwoFingerSwipes.add(
                 new MultiFingerSwipe(context, 2, LEFT, GESTURE_2_FINGER_SWIPE_LEFT, this));
-        mMultiFingerGestures.add(
+        mTwoFingerSwipes.add(
                 new MultiFingerSwipe(context, 2, RIGHT, GESTURE_2_FINGER_SWIPE_RIGHT, this));
-        mMultiFingerGestures.add(
-                new MultiFingerSwipe(context, 2, UP, GESTURE_2_FINGER_SWIPE_UP, this));
+        mTwoFingerSwipes.add(new MultiFingerSwipe(context, 2, UP, GESTURE_2_FINGER_SWIPE_UP, this));
+        mMultiFingerGestures.addAll(mTwoFingerSwipes);
         // Three-finger swipes.
         mMultiFingerGestures.add(
                 new MultiFingerSwipe(context, 3, DOWN, GESTURE_3_FINGER_SWIPE_DOWN, this));
@@ -360,6 +366,25 @@
         }
     }
 
+    public boolean isTwoFingerPassthroughEnabled() {
+        return mTwoFingerPassthroughEnabled;
+    }
+
+    public void setTwoFingerPassthroughEnabled(boolean mode) {
+        if (mTwoFingerPassthroughEnabled != mode) {
+            mTwoFingerPassthroughEnabled = mode;
+            if (!mode) {
+                mMultiFingerGestures.addAll(mTwoFingerSwipes);
+                if (mMultiFingerGesturesEnabled) {
+                    mGestures.addAll(mTwoFingerSwipes);
+                }
+            } else {
+                mMultiFingerGestures.removeAll(mTwoFingerSwipes);
+                mGestures.removeAll(mTwoFingerSwipes);
+            }
+        }
+    }
+
     public void setServiceHandlesDoubleTap(boolean mode) {
         mServiceHandlesDoubleTap = mode;
     }
diff --git a/services/accessibility/java/com/android/server/accessibility/gestures/TouchExplorer.java b/services/accessibility/java/com/android/server/accessibility/gestures/TouchExplorer.java
index 696702f..b587dd3 100644
--- a/services/accessibility/java/com/android/server/accessibility/gestures/TouchExplorer.java
+++ b/services/accessibility/java/com/android/server/accessibility/gestures/TouchExplorer.java
@@ -41,6 +41,7 @@
 import android.content.Context;
 import android.graphics.Region;
 import android.os.Handler;
+import android.util.DisplayMetrics;
 import android.util.Slog;
 import android.view.InputDevice;
 import android.view.MotionEvent;
@@ -91,12 +92,21 @@
     // The timeout after which we are no longer trying to detect a gesture.
     private static final int EXIT_GESTURE_DETECTION_TIMEOUT = 2000;
 
+    // The height of the top and bottom edges for  edge-swipes.
+    // For now this is only used to allow three-finger edge-swipes from the bottom.
+    private static final float EDGE_SWIPE_HEIGHT_CM = 0.25f;
+
+    // The calculated edge height for the top and bottom edges.
+    private final float mEdgeSwipeHeightPixels;
     // Timeout before trying to decide what the user is trying to do.
     private final int mDetermineUserIntentTimeout;
 
     // Slop between the first and second tap to be a double tap.
     private final int mDoubleTapSlop;
 
+    // Slop to move before being considered a move rather than a tap.
+    private final int mTouchSlop;
+
     // The current state of the touch explorer.
     private TouchState mState;
 
@@ -174,6 +184,9 @@
         mDispatcher = new EventDispatcher(context, mAms, super.getNext(), mState);
         mDetermineUserIntentTimeout = ViewConfiguration.getDoubleTapTimeout();
         mDoubleTapSlop = ViewConfiguration.get(context).getScaledDoubleTapSlop();
+        mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop();
+        DisplayMetrics metrics = mContext.getResources().getDisplayMetrics();
+        mEdgeSwipeHeightPixels = metrics.ydpi / GestureUtils.CM_PER_INCH * EDGE_SWIPE_HEIGHT_CM;
         mHandler = mainHandler;
         mExitGestureDetectionModeDelayed = new ExitGestureDetectionModeDelayed();
         mSendHoverEnterAndMoveDelayed = new SendHoverEnterAndMoveDelayed();
@@ -219,16 +232,10 @@
         if (mState.isTouchExploring()) {
             // If a touch exploration gesture is in progress send events for its end.
             sendHoverExitAndTouchExplorationGestureEndIfNeeded(policyFlags);
-        }  else if (mState.isDragging()) {
-            mDraggingPointerId = INVALID_POINTER_ID;
-            // Send exit to all pointers that we have delivered.
-            mDispatcher.sendUpForInjectedDownPointers(event, policyFlags);
-        } else if (mState.isDelegating()) {
-            // Send exit to all pointers that we have delivered.
-            mDispatcher.sendUpForInjectedDownPointers(event, policyFlags);
-        } else if (mState.isGestureDetecting()) {
-            // No state specific cleanup required.
         }
+        mDraggingPointerId = INVALID_POINTER_ID;
+        // Send exit to any pointers that we have delivered as part of delegating or dragging.
+        mDispatcher.sendUpForInjectedDownPointers(event, policyFlags);
         // Remove all pending callbacks.
         mSendHoverEnterAndMoveDelayed.cancel();
         mSendHoverExitDelayed.cancel();
@@ -555,6 +562,7 @@
             sendHoverExitAndTouchExplorationGestureEndIfNeeded(policyFlags);
         }
     }
+
     /**
      * Handles ACTION_MOVE while in the touch interacting state. This is where transitions to
      * delegating and dragging states are handled.
@@ -563,7 +571,7 @@
             MotionEvent event, MotionEvent rawEvent, int policyFlags) {
         final int pointerId = mReceivedPointerTracker.getPrimaryPointerId();
         final int pointerIndex = event.findPointerIndex(pointerId);
-        final int pointerIdBits = (1 << pointerId);
+        int pointerIdBits = (1 << pointerId);
         switch (event.getPointerCount()) {
             case 1:
                 // We have not started sending events since we try to
@@ -574,12 +582,29 @@
                 }
                 break;
             case 2:
-                if (mGestureDetector.isMultiFingerGesturesEnabled()) {
+                if (mGestureDetector.isMultiFingerGesturesEnabled()
+                        && !mGestureDetector.isTwoFingerPassthroughEnabled()) {
                     return;
                 }
                 // Make sure we don't have any pending transitions to touch exploration
                 mSendHoverEnterAndMoveDelayed.cancel();
                 mSendHoverExitDelayed.cancel();
+                if (mGestureDetector.isMultiFingerGesturesEnabled()
+                        && mGestureDetector.isTwoFingerPassthroughEnabled()) {
+                    if (pointerIndex < 0) {
+                        return;
+                    }
+                    final float deltaX =
+                            mReceivedPointerTracker.getReceivedPointerDownX(pointerId)
+                                    - rawEvent.getX(pointerIndex);
+                    final float deltaY =
+                            mReceivedPointerTracker.getReceivedPointerDownY(pointerId)
+                                    - rawEvent.getY(pointerIndex);
+                    final double moveDelta = Math.hypot(deltaX, deltaY);
+                    if (moveDelta < mTouchSlop) {
+                        return;
+                    }
+                }
                 // More than one pointer so the user is not touch exploring
                 // and now we have to decide whether to delegate or drag.
                 // Remove move history before send injected non-move events
@@ -588,8 +613,8 @@
                     // Two pointers moving in the same direction within
                     // a given distance perform a drag.
                     mState.startDragging();
-                    mDraggingPointerId = pointerId;
-                    adjustEventLocationForDrag(event);
+                    computeDraggingPointerIdIfNeeded(event);
+                    pointerIdBits = 1 << mDraggingPointerId;
                     event.setEdgeFlags(mReceivedPointerTracker.getLastReceivedDownEdgeFlags());
                     mDispatcher.sendMotionEvent(
                             event, ACTION_DOWN, rawEvent, pointerIdBits, policyFlags);
@@ -601,12 +626,34 @@
                 break;
             default:
                 if (mGestureDetector.isMultiFingerGesturesEnabled()) {
-                    return;
+                    if (mGestureDetector.isTwoFingerPassthroughEnabled()) {
+                        if (event.getPointerCount() == 3) {
+                            boolean isOnBottomEdge = true;
+                            // If three fingers went down on the bottom edge of the screen, delegate
+                            // immediately.
+                            final long screenHeight =
+                                    mContext.getResources().getDisplayMetrics().heightPixels;
+                            for (int i = 0; i < TouchState.MAX_POINTER_COUNT; ++i) {
+                                if (mReceivedPointerTracker.getReceivedPointerDownY(i)
+                                        < (screenHeight - mEdgeSwipeHeightPixels)) {
+                                    isOnBottomEdge = false;
+                                }
+                            }
+                            if (isOnBottomEdge) {
+                                if (DEBUG) {
+                                    Slog.d(LOG_TAG, "Three-finger edge swipe detected.");
+                                }
+                                mState.startDelegating();
+                                mDispatcher.sendDownForAllNotInjectedPointers(event, policyFlags);
+                            }
+                        }
+                    }
+                } else {
+                    // More than two pointers are delegated to the view hierarchy.
+                    mState.startDelegating();
+                    event = MotionEvent.obtainNoHistory(event);
+                    mDispatcher.sendDownForAllNotInjectedPointers(event, policyFlags);
                 }
-                // More than two pointers are delegated to the view hierarchy.
-                mState.startDelegating();
-                event = MotionEvent.obtainNoHistory(event);
-                mDispatcher.sendDownForAllNotInjectedPointers(event, policyFlags);
                 break;
         }
     }
@@ -648,7 +695,8 @@
                         event, ACTION_HOVER_MOVE, rawEvent, pointerIdBits, policyFlags);
                 break;
             case 2:
-                if (mGestureDetector.isMultiFingerGesturesEnabled()) {
+                if (mGestureDetector.isMultiFingerGesturesEnabled()
+                        && !mGestureDetector.isTwoFingerPassthroughEnabled()) {
                     return;
                 }
                 if (mSendHoverEnterAndMoveDelayed.isPending()) {
@@ -703,7 +751,8 @@
      */
     private void handleMotionEventStateDragging(
             MotionEvent event, MotionEvent rawEvent, int policyFlags) {
-        if (mGestureDetector.isMultiFingerGesturesEnabled()) {
+        if (mGestureDetector.isMultiFingerGesturesEnabled()
+                && !mGestureDetector.isTwoFingerPassthroughEnabled()) {
             // Multi-finger gestures conflict with this functionality.
             return;
         }
@@ -749,13 +798,14 @@
                     case 2:
                         if (isDraggingGesture(event)) {
                             // If still dragging send a drag event.
-                            adjustEventLocationForDrag(event);
+                            computeDraggingPointerIdIfNeeded(event);
                             mDispatcher.sendMotionEvent(
                                     event, ACTION_MOVE, rawEvent, pointerIdBits, policyFlags);
                         } else {
                             // The two pointers are moving either in different directions or
                             // no close enough => delegate the gesture to the view hierarchy.
                             mState.startDelegating();
+                            mDraggingPointerId = INVALID_POINTER_ID;
                             // Remove move history before send injected non-move events
                             event = MotionEvent.obtainNoHistory(event);
                             // Send an event to the end of the drag gesture.
@@ -767,6 +817,7 @@
                         break;
                     default:
                         mState.startDelegating();
+                        mDraggingPointerId = INVALID_POINTER_ID;
                         event = MotionEvent.obtainNoHistory(event);
                         // Send an event to the end of the drag gesture.
                         mDispatcher.sendMotionEvent(
@@ -784,15 +835,16 @@
                 }
                 break;
             case ACTION_UP:
-                mAms.onTouchInteractionEnd();
-                // Announce the end of a new touch interaction.
-                mDispatcher.sendAccessibilityEvent(TYPE_TOUCH_INTERACTION_END);
                 if (event.getPointerId(GestureUtils.getActionIndex(event)) == mDraggingPointerId) {
                     mDraggingPointerId = INVALID_POINTER_ID;
                     // Send an event to the end of the drag gesture.
                     mDispatcher.sendMotionEvent(
                             event, ACTION_UP, rawEvent, pointerIdBits, policyFlags);
                 }
+                mAms.onTouchInteractionEnd();
+                // Announce the end of a new touch interaction.
+                mDispatcher.sendAccessibilityEvent(TYPE_TOUCH_INTERACTION_END);
+
                 break;
         }
     }
@@ -911,21 +963,45 @@
     }
 
     /**
-     * Adjust the location of an injected event when performing a drag The new location will be in
-     * between the two fingers touching the screen.
+     * Computes {@link #mDraggingPointerId} if it is invalid. The pointer will be the finger
+     * closet to an edge of the screen.
      */
-    private void adjustEventLocationForDrag(MotionEvent event) {
-
+    private void computeDraggingPointerIdIfNeeded(MotionEvent event) {
+        if (mDraggingPointerId != INVALID_POINTER_ID) {
+            // If we have a valid pointer ID, we should be good
+            final int pointerIndex = event.findPointerIndex(mDraggingPointerId);
+            if (event.findPointerIndex(pointerIndex) >= 0) {
+                return;
+            }
+        }
+        // Use the pointer that is closest to its closest edge.
         final float firstPtrX = event.getX(0);
         final float firstPtrY = event.getY(0);
+        final int firstPtrId = event.getPointerId(0);
         final float secondPtrX = event.getX(1);
         final float secondPtrY = event.getY(1);
-        final int pointerIndex = event.findPointerIndex(mDraggingPointerId);
-        final float deltaX =
-                (pointerIndex == 0) ? (secondPtrX - firstPtrX) : (firstPtrX - secondPtrX);
-        final float deltaY =
-                (pointerIndex == 0) ? (secondPtrY - firstPtrY) : (firstPtrY - secondPtrY);
-        event.offsetLocation(deltaX / 2, deltaY / 2);
+        final int secondPtrId = event.getPointerId(1);
+        mDraggingPointerId = (getDistanceToClosestEdge(firstPtrX, firstPtrY)
+                 < getDistanceToClosestEdge(secondPtrX, secondPtrY))
+                 ? firstPtrId : secondPtrId;
+    }
+
+    private float getDistanceToClosestEdge(float x, float y) {
+        final long width = mContext.getResources().getDisplayMetrics().widthPixels;
+        final long height = mContext.getResources().getDisplayMetrics().heightPixels;
+        float distance = Float.MAX_VALUE;
+        if (x < (width - x)) {
+            distance = x;
+        } else {
+            distance = width - x;
+        }
+        if (distance > y) {
+            distance = y;
+        }
+        if (distance > (height - y)) {
+            distance = (height - y);
+        }
+        return distance;
     }
 
     public TouchState getState() {
@@ -954,6 +1030,13 @@
         mGestureDetector.setMultiFingerGesturesEnabled(enabled);
     }
 
+    /**
+     * This function turns on and off two-finger passthrough gestures such as drag and pinch when
+     * multi-finger gestures are enabled.
+     */
+    public void setTwoFingerPassthroughEnabled(boolean enabled) {
+        mGestureDetector.setTwoFingerPassthroughEnabled(enabled);
+    }
     public void setGestureDetectionPassthroughRegion(Region region) {
         mGestureDetectionPassthroughRegion = region;
     }
diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationController.java b/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationController.java
index 44c4bf4..b6f2a47 100644
--- a/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationController.java
+++ b/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationController.java
@@ -16,8 +16,10 @@
 
 package com.android.server.accessibility.magnification;
 
+import android.animation.Animator;
 import android.animation.ValueAnimator;
 import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.Intent;
@@ -61,6 +63,8 @@
     private static final boolean DEBUG = false;
     private static final String LOG_TAG = "FullScreenMagnificationController";
 
+    private static final Runnable STUB_RUNNABLE = () -> {
+    };
     public static final float MIN_SCALE = 1.0f;
     public static final float MAX_SCALE = 8.0f;
 
@@ -292,7 +296,7 @@
                     // Adjust the current spec's offsets if necessary.
                     if (updateCurrentSpecWithOffsetsLocked(
                             mCurrentMagnificationSpec.offsetX, mCurrentMagnificationSpec.offsetY)) {
-                        sendSpecToAnimation(mCurrentMagnificationSpec, false);
+                        sendSpecToAnimation(mCurrentMagnificationSpec, null);
                     }
                     onMagnificationChangedLocked();
                 }
@@ -300,17 +304,18 @@
             }
         }
 
-        void sendSpecToAnimation(MagnificationSpec spec, boolean animate) {
+        void sendSpecToAnimation(MagnificationSpec spec, Runnable endCallback) {
             if (DEBUG) {
                 Slog.i(LOG_TAG,
-                        "sendSpecToAnimation(spec = " + spec + ", animate = " + animate + ")");
+                        "sendSpecToAnimation(spec = " + spec + ", endCallback = " + endCallback
+                                + ")");
             }
             if (Thread.currentThread().getId() == mMainThreadId) {
-                mSpecAnimationBridge.updateSentSpecMainThread(spec, animate);
+                mSpecAnimationBridge.updateSentSpecMainThread(spec, endCallback);
             } else {
                 final Message m = PooledLambda.obtainMessage(
                         SpecAnimationBridge::updateSentSpecMainThread,
-                        mSpecAnimationBridge, spec, animate);
+                        mSpecAnimationBridge, spec, endCallback);
                 mControllerCtx.getHandler().sendMessage(m);
             }
         }
@@ -410,6 +415,11 @@
 
         @GuardedBy("mLock")
         boolean reset(boolean animate) {
+            return reset(transformToStubRunnable(animate));
+        }
+
+        @GuardedBy("mLock")
+        boolean reset(Runnable endCallback) {
             if (!mRegistered) {
                 return false;
             }
@@ -420,7 +430,7 @@
                 onMagnificationChangedLocked();
             }
             mIdOfLastServiceToMagnify = INVALID_ID;
-            sendSpecToAnimation(spec, animate);
+            sendSpecToAnimation(spec, endCallback);
             return changed;
         }
 
@@ -448,24 +458,24 @@
             final float centerX = normPivotX + offsetX;
             final float centerY = normPivotY + offsetY;
             mIdOfLastServiceToMagnify = id;
-            return setScaleAndCenter(scale, centerX, centerY, animate, id);
+            return setScaleAndCenter(scale, centerX, centerY, transformToStubRunnable(animate), id);
         }
 
         @GuardedBy("mLock")
         boolean setScaleAndCenter(float scale, float centerX, float centerY,
-                boolean animate, int id) {
+                Runnable endCallback, int id) {
             if (!mRegistered) {
                 return false;
             }
             if (DEBUG) {
                 Slog.i(LOG_TAG,
                         "setScaleAndCenterLocked(scale = " + scale + ", centerX = " + centerX
-                                + ", centerY = " + centerY + ", animate = " + animate
+                                + ", centerY = " + centerY + ", endCallback = " + endCallback
                                 + ", id = " + id
                                 + ")");
             }
             final boolean changed = updateMagnificationSpecLocked(scale, centerX, centerY);
-            sendSpecToAnimation(mCurrentMagnificationSpec, animate);
+            sendSpecToAnimation(mCurrentMagnificationSpec, endCallback);
             if (isMagnifying() && (id != INVALID_ID)) {
                 mIdOfLastServiceToMagnify = id;
             }
@@ -531,7 +541,7 @@
             if (id != INVALID_ID) {
                 mIdOfLastServiceToMagnify = id;
             }
-            sendSpecToAnimation(mCurrentMagnificationSpec, false);
+            sendSpecToAnimation(mCurrentMagnificationSpec, null);
         }
 
         boolean updateCurrentSpecWithOffsetsLocked(float nonNormOffsetX, float nonNormOffsetY) {
@@ -865,12 +875,26 @@
      *         the spec did not change
      */
     public boolean reset(int displayId, boolean animate) {
+        return reset(displayId, animate ?  STUB_RUNNABLE : null);
+    }
+
+    /**
+     * Resets the magnification scale and center, optionally animating the
+     * transition.
+     *
+     * @param displayId The logical display id.
+     * @param endCallback Called when the animation is ended or the spec did not change.
+     *                    {@code null} to transition immediately
+     * @return {@code true} if the magnification spec changed, {@code false} if
+     *         the spec did not change
+     */
+    public boolean reset(int displayId, Runnable endCallback) {
         synchronized (mLock) {
             final DisplayMagnification display = mDisplays.get(displayId);
             if (display == null) {
                 return false;
             }
-            return display.reset(animate);
+            return display.reset(endCallback);
         }
     }
 
@@ -921,7 +945,8 @@
             if (display == null) {
                 return false;
             }
-            return display.setScaleAndCenter(Float.NaN, centerX, centerY, animate, id);
+            return display.setScaleAndCenter(Float.NaN, centerX, centerY,
+                    animate ?  STUB_RUNNABLE : null, id);
         }
     }
 
@@ -944,12 +969,35 @@
      */
     public boolean setScaleAndCenter(int displayId, float scale, float centerX, float centerY,
             boolean animate, int id) {
+        return setScaleAndCenter(displayId, scale, centerX, centerY,
+                transformToStubRunnable(animate), id);
+    }
+
+    /**
+     * Sets the scale and center of the magnified region, optionally
+     * animating the transition. If animation is disabled, the transition
+     * is immediate.
+     *
+     * @param displayId The logical display id.
+     * @param scale the target scale, or {@link Float#NaN} to leave unchanged
+     * @param centerX the screen-relative X coordinate around which to
+     *                center and scale, or {@link Float#NaN} to leave unchanged
+     * @param centerY the screen-relative Y coordinate around which to
+     *                center and scale, or {@link Float#NaN} to leave unchanged
+     * @param endCallback called when the transition is finished successfully or the spec did not
+     *                   change. {@code null} to transition immediately.
+     * @param id the ID of the service requesting the change
+     * @return {@code true} if the magnification spec changed, {@code false} if
+     *         the spec did not change
+     */
+    public boolean setScaleAndCenter(int displayId, float scale, float centerX, float centerY,
+            Runnable endCallback, int id) {
         synchronized (mLock) {
             final DisplayMagnification display = mDisplays.get(displayId);
             if (display == null) {
                 return false;
             }
-            return display.setScaleAndCenter(scale, centerX, centerY, animate, id);
+            return display.setScaleAndCenter(scale, centerX, centerY, endCallback, id);
         }
     }
 
@@ -1160,7 +1208,8 @@
      * Class responsible for animating spec on the main thread and sending spec
      * updates to the window manager.
      */
-    private static class SpecAnimationBridge implements ValueAnimator.AnimatorUpdateListener {
+    private static class SpecAnimationBridge implements ValueAnimator.AnimatorUpdateListener,
+            Animator.AnimatorListener {
         private final ControllerContext mControllerCtx;
 
         /**
@@ -1180,6 +1229,8 @@
          */
         private final ValueAnimator mValueAnimator;
 
+        // Called when the callee wants animating and the sent spec matches the target spec.
+        private Runnable mEndCallback;
         private final Object mLock;
 
         private final int mDisplayId;
@@ -1197,6 +1248,7 @@
             mValueAnimator.setInterpolator(new DecelerateInterpolator(2.5f));
             mValueAnimator.setFloatValues(0.0f, 1.0f);
             mValueAnimator.addUpdateListener(this);
+            mValueAnimator.addListener(this);
         }
 
         /**
@@ -1216,24 +1268,36 @@
             }
         }
 
-        public void updateSentSpecMainThread(MagnificationSpec spec, boolean animate) {
+        void updateSentSpecMainThread(MagnificationSpec spec, Runnable endCallback) {
             if (mValueAnimator.isRunning()) {
+                // Avoid AnimationEnd Callback.
+                mEndCallback = null;
                 mValueAnimator.cancel();
             }
 
+            mEndCallback = endCallback;
             // If the current and sent specs don't match, update the sent spec.
             synchronized (mLock) {
                 final boolean changed = !mSentMagnificationSpec.equals(spec);
                 if (changed) {
-                    if (animate) {
+                    if (mEndCallback != null) {
                         animateMagnificationSpecLocked(spec);
                     } else {
                         setMagnificationSpecLocked(spec);
                     }
+                } else {
+                    sendEndCallbackMainThread();
                 }
             }
         }
 
+        private void sendEndCallbackMainThread() {
+            if (mEndCallback != null) {
+                mEndCallback.run();
+                mEndCallback = null;
+            }
+        }
+
         @GuardedBy("mLock")
         private void setMagnificationSpecLocked(MagnificationSpec spec) {
             if (mEnabled) {
@@ -1270,6 +1334,26 @@
                 }
             }
         }
+
+        @Override
+        public void onAnimationStart(Animator animation) {
+
+        }
+
+        @Override
+        public void onAnimationEnd(Animator animation) {
+            sendEndCallbackMainThread();
+        }
+
+        @Override
+        public void onAnimationCancel(Animator animation) {
+
+        }
+
+        @Override
+        public void onAnimationRepeat(Animator animation) {
+
+        }
     }
 
     private static class ScreenStateObserver extends BroadcastReceiver {
@@ -1395,4 +1479,9 @@
             return mAnimationDuration;
         }
     }
+
+    @Nullable
+    private static Runnable transformToStubRunnable(boolean animate) {
+        return animate ? STUB_RUNNABLE : null;
+    }
 }
diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/WindowMagnificationGestureHandler.java b/services/accessibility/java/com/android/server/accessibility/magnification/WindowMagnificationGestureHandler.java
index bd25f2b..3ee5b28 100644
--- a/services/accessibility/java/com/android/server/accessibility/magnification/WindowMagnificationGestureHandler.java
+++ b/services/accessibility/java/com/android/server/accessibility/magnification/WindowMagnificationGestureHandler.java
@@ -25,10 +25,12 @@
 
 import android.annotation.Nullable;
 import android.content.Context;
+import android.graphics.Point;
 import android.provider.Settings;
 import android.util.Log;
 import android.util.MathUtils;
 import android.util.Slog;
+import android.view.Display;
 import android.view.MotionEvent;
 
 import com.android.internal.annotations.VisibleForTesting;
@@ -90,6 +92,8 @@
 
     private MotionEventDispatcherDelegate mMotionEventDispatcherDelegate;
     private final int mDisplayId;
+    private final Context mContext;
+    private final Point mTempPoint = new Point();
 
     private final Queue<MotionEvent> mDebugOutputEventHistory;
 
@@ -107,7 +111,7 @@
             Slog.i(LOG_TAG,
                     "WindowMagnificationGestureHandler() , displayId = " + displayId + ")");
         }
-
+        mContext = context;
         mWindowMagnificationMgr = windowMagnificationMgr;
         mDetectShortcutTrigger = detectShortcutTrigger;
         mDisplayId = displayId;
@@ -184,7 +188,14 @@
         if (!mDetectShortcutTrigger) {
             return;
         }
-        toggleMagnification(Float.NaN, Float.NaN);
+        final Point screenSize = mTempPoint;
+        getScreenSize(mTempPoint);
+        toggleMagnification(screenSize.x / 2.0f, screenSize.y / 2.0f);
+    }
+
+    private  void getScreenSize(Point outSize) {
+        final Display display = mContext.getDisplay();
+        display.getRealSize(outSize);
     }
 
     @Override
diff --git a/services/appwidget/java/com/android/server/appwidget/AppWidgetService.java b/services/appwidget/java/com/android/server/appwidget/AppWidgetService.java
index b229e9f..3562205 100644
--- a/services/appwidget/java/com/android/server/appwidget/AppWidgetService.java
+++ b/services/appwidget/java/com/android/server/appwidget/AppWidgetService.java
@@ -16,11 +16,13 @@
 
 package com.android.server.appwidget;
 
+import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.content.Context;
 
 import com.android.server.AppWidgetBackupBridge;
-import com.android.server.FgThread;
 import com.android.server.SystemService;
+import com.android.server.SystemService.TargetUser;
 
 /**
  * SystemService that publishes an IAppWidgetService.
@@ -49,12 +51,12 @@
     }
 
     @Override
-    public void onStopUser(int userHandle) {
-        mImpl.onUserStopped(userHandle);
+    public void onUserStopping(@NonNull TargetUser user) {
+        mImpl.onUserStopped(user.getUserIdentifier());
     }
 
     @Override
-    public void onSwitchUser(int userHandle) {
-        mImpl.reloadWidgetsMaskedStateForGroup(userHandle);
+    public void onUserSwitching(@Nullable TargetUser from, @NonNull TargetUser to) {
+        mImpl.reloadWidgetsMaskedStateForGroup(to.getUserIdentifier());
     }
 }
diff --git a/services/autofill/java/com/android/server/autofill/AutofillManagerService.java b/services/autofill/java/com/android/server/autofill/AutofillManagerService.java
index 089861b..ad85784 100644
--- a/services/autofill/java/com/android/server/autofill/AutofillManagerService.java
+++ b/services/autofill/java/com/android/server/autofill/AutofillManagerService.java
@@ -84,6 +84,7 @@
 import com.android.internal.util.SyncResultReceiver;
 import com.android.server.FgThread;
 import com.android.server.LocalServices;
+import com.android.server.SystemService.TargetUser;
 import com.android.server.autofill.ui.AutoFillUI;
 import com.android.server.infra.AbstractMasterSystemService;
 import com.android.server.infra.FrameworkResourcesServiceNameResolver;
@@ -359,11 +360,11 @@
 
     @Override // from SystemService
     public boolean isUserSupported(TargetUser user) {
-        return user.getUserInfo().isFull() || user.getUserInfo().isManagedProfile();
+        return user.isFull() || user.isManagedProfile();
     }
 
     @Override // from SystemService
-    public void onSwitchUser(int userHandle) {
+    public void onUserSwitching(@Nullable TargetUser from, @NonNull TargetUser to) {
         if (sDebug) Slog.d(TAG, "Hiding UI when user switched");
         mUi.hideAll(null);
     }
diff --git a/services/autofill/java/com/android/server/autofill/ui/SaveUi.java b/services/autofill/java/com/android/server/autofill/ui/SaveUi.java
index 1c31166..a92d334 100644
--- a/services/autofill/java/com/android/server/autofill/ui/SaveUi.java
+++ b/services/autofill/java/com/android/server/autofill/ui/SaveUi.java
@@ -27,6 +27,7 @@
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentSender;
+import android.content.pm.ActivityInfo;
 import android.content.pm.PackageManager;
 import android.content.res.Resources;
 import android.graphics.drawable.Drawable;
@@ -214,7 +215,13 @@
                     return componentName;
                 }
                 intent.addFlags(Intent.FLAG_ACTIVITY_MATCH_EXTERNAL);
-                return intent.resolveActivity(packageManager);
+                final ActivityInfo ai =
+                        intent.resolveActivityInfo(packageManager, PackageManager.MATCH_INSTANT);
+                if (ai != null) {
+                    return new ComponentName(ai.applicationInfo.packageName, ai.name);
+                }
+
+                return null;
             }
         };
         final LayoutInflater inflater = LayoutInflater.from(context);
diff --git a/services/backup/java/com/android/server/backup/BackupManagerService.java b/services/backup/java/com/android/server/backup/BackupManagerService.java
index c839ce94..186812b 100644
--- a/services/backup/java/com/android/server/backup/BackupManagerService.java
+++ b/services/backup/java/com/android/server/backup/BackupManagerService.java
@@ -19,6 +19,7 @@
 import static java.util.Collections.emptySet;
 
 import android.Manifest;
+import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.UserIdInt;
 import android.app.ActivityManager;
@@ -60,6 +61,7 @@
 import com.android.internal.util.DumpUtils;
 import com.android.server.SystemConfig;
 import com.android.server.SystemService;
+import com.android.server.SystemService.TargetUser;
 import com.android.server.backup.utils.RandomAccessFileUtils;
 
 import java.io.File;
@@ -1237,9 +1239,10 @@
 
     @Override
     public IRestoreSession beginRestoreSessionForUser(
-            int userId, String packageName, String transportID) throws RemoteException {
+            int userId, String packageName, String transportID,
+            @OperationType int operationType) throws RemoteException {
         return isUserReadyForBackup(userId)
-                ? beginRestoreSession(userId, packageName, transportID) : null;
+                ? beginRestoreSession(userId, packageName, transportID, operationType) : null;
     }
 
     /**
@@ -1248,13 +1251,15 @@
      */
     @Nullable
     public IRestoreSession beginRestoreSession(
-            @UserIdInt int userId, String packageName, String transportName) {
+            @UserIdInt int userId, String packageName, String transportName,
+            @OperationType int operationType) {
         UserBackupManagerService userBackupManagerService =
                 getServiceForUserIfCallerHasPermission(userId, "beginRestoreSession()");
 
         return userBackupManagerService == null
                 ? null
-                : userBackupManagerService.beginRestoreSession(packageName, transportName);
+                : userBackupManagerService.beginRestoreSession(packageName, transportName,
+                        operationType);
     }
 
     @Override
@@ -1605,13 +1610,13 @@
         }
 
         @Override
-        public void onUnlockUser(int userId) {
-            sInstance.onUnlockUser(userId);
+        public void onUserUnlocking(@NonNull TargetUser user) {
+            sInstance.onUnlockUser(user.getUserIdentifier());
         }
 
         @Override
-        public void onStopUser(int userId) {
-            sInstance.onStopUser(userId);
+        public void onUserStopping(@NonNull TargetUser user) {
+            sInstance.onStopUser(user.getUserIdentifier());
         }
 
         @VisibleForTesting
diff --git a/services/backup/java/com/android/server/backup/KeyValueAdbBackupEngine.java b/services/backup/java/com/android/server/backup/KeyValueAdbBackupEngine.java
index c9b09e3..0855b9d 100644
--- a/services/backup/java/com/android/server/backup/KeyValueAdbBackupEngine.java
+++ b/services/backup/java/com/android/server/backup/KeyValueAdbBackupEngine.java
@@ -10,6 +10,7 @@
 
 import android.app.ApplicationThreadConstants;
 import android.app.IBackupAgent;
+import android.app.backup.BackupManager;
 import android.app.backup.FullBackup;
 import android.app.backup.FullBackupDataOutput;
 import android.app.backup.IBackupCallback;
@@ -146,7 +147,8 @@
     private IBackupAgent bindToAgent(ApplicationInfo targetApp) {
         try {
             return mBackupManagerService.bindToAgentSynchronous(targetApp,
-                    ApplicationThreadConstants.BACKUP_MODE_INCREMENTAL);
+                    ApplicationThreadConstants.BACKUP_MODE_INCREMENTAL,
+                    BackupManager.OperationType.BACKUP);
         } catch (SecurityException e) {
             Slog.e(TAG, "error in binding to agent for package " + targetApp.packageName
                     + ". " + e);
diff --git a/services/backup/java/com/android/server/backup/UserBackupManagerService.java b/services/backup/java/com/android/server/backup/UserBackupManagerService.java
index 3ab81cb..e68c07ed 100644
--- a/services/backup/java/com/android/server/backup/UserBackupManagerService.java
+++ b/services/backup/java/com/android/server/backup/UserBackupManagerService.java
@@ -441,6 +441,7 @@
     private long mAncestralToken = 0;
     private long mCurrentToken = 0;
     @Nullable private File mAncestralSerialNumberFile;
+    @OperationType private volatile long mAncestralOperationType;
 
     private final ContentObserver mSetupObserver;
     private final BroadcastReceiver mRunInitReceiver;
@@ -881,6 +882,10 @@
         mAncestralToken = ancestralToken;
     }
 
+    public void setAncestralOperationType(@OperationType int operationType) {
+        mAncestralOperationType = operationType;
+    }
+
     public long getCurrentToken() {
         return mCurrentToken;
     }
@@ -1651,13 +1656,15 @@
 
     /** Fires off a backup agent, blocking until it attaches or times out. */
     @Nullable
-    public IBackupAgent bindToAgentSynchronous(ApplicationInfo app, int mode) {
+    public IBackupAgent bindToAgentSynchronous(ApplicationInfo app, int mode,
+            @OperationType int operationType) {
         IBackupAgent agent = null;
         synchronized (mAgentConnectLock) {
             mConnecting = true;
             mConnectedAgent = null;
             try {
-                if (mActivityManager.bindBackupAgent(app.packageName, mode, mUserId)) {
+                if (mActivityManager.bindBackupAgent(app.packageName, mode, mUserId,
+                        operationType)) {
                     Slog.d(TAG, addUserIdToLogMessage(mUserId, "awaiting agent for " + app));
 
                     // success; wait for the agent to arrive
@@ -1806,6 +1813,16 @@
         }
     }
 
+    private BackupEligibilityRules getEligibilityRulesForRestoreAtInstall(long restoreToken) {
+        if (mAncestralOperationType == OperationType.MIGRATION && restoreToken == mAncestralToken) {
+            return getEligibilityRulesForOperation(OperationType.MIGRATION);
+        } else {
+            // If we're not using the ancestral data set, it means we're restoring from a backup
+            // that happened on this device.
+            return mScheduledBackupEligibility;
+        }
+    }
+
     /**
      * Get the restore-set token for the best-available restore set for this {@code packageName}:
      * the active set if possible, else the ancestral one. Returns zero if none available.
@@ -3973,7 +3990,8 @@
                                 restoreSet,
                                 packageName,
                                 token,
-                                listener);
+                                listener,
+                                getEligibilityRulesForRestoreAtInstall(restoreSet));
                 mBackupHandler.sendMessage(msg);
             } catch (Exception e) {
                 // Calling into the transport broke; back off and proceed with the installation.
@@ -4002,13 +4020,15 @@
     }
 
     /** Hand off a restore session. */
-    public IRestoreSession beginRestoreSession(String packageName, String transport) {
+    public IRestoreSession beginRestoreSession(String packageName, String transport,
+            @OperationType int operationType) {
         if (DEBUG) {
             Slog.v(
                     TAG,
                     addUserIdToLogMessage(
                             mUserId,
-                            "beginRestoreSession: pkg=" + packageName + " transport=" + transport));
+                            "beginRestoreSession: pkg=" + packageName + " transport=" + transport
+                                + "operationType=" + operationType));
         }
 
         boolean needPermission = true;
@@ -4065,7 +4085,8 @@
                                 "Restore session requested but currently running backups"));
                 return null;
             }
-            mActiveRestoreSession = new ActiveRestoreSession(this, packageName, transport);
+            mActiveRestoreSession = new ActiveRestoreSession(this, packageName, transport,
+                    getEligibilityRulesForOperation(operationType));
             mBackupHandler.sendEmptyMessageDelayed(MSG_RESTORE_SESSION_TIMEOUT,
                     mAgentTimeoutParameters.getRestoreAgentTimeoutMillis());
         }
diff --git a/services/backup/java/com/android/server/backup/fullbackup/FullBackupEngine.java b/services/backup/java/com/android/server/backup/fullbackup/FullBackupEngine.java
index 846c6a2..fe5497f 100644
--- a/services/backup/java/com/android/server/backup/fullbackup/FullBackupEngine.java
+++ b/services/backup/java/com/android/server/backup/fullbackup/FullBackupEngine.java
@@ -41,6 +41,7 @@
 import com.android.server.backup.BackupRestoreTask;
 import com.android.server.backup.UserBackupManagerService;
 import com.android.server.backup.remote.RemoteCall;
+import com.android.server.backup.utils.BackupEligibilityRules;
 import com.android.server.backup.utils.FullBackupUtils;
 
 import java.io.File;
@@ -64,6 +65,7 @@
     private final int mOpToken;
     private final int mTransportFlags;
     private final BackupAgentTimeoutParameters mAgentTimeoutParameters;
+    private final BackupEligibilityRules mBackupEligibilityRules;
 
     class FullBackupRunner implements Runnable {
         private final @UserIdInt int mUserId;
@@ -190,7 +192,8 @@
             BackupRestoreTask timeoutMonitor,
             long quota,
             int opToken,
-            int transportFlags) {
+            int transportFlags,
+            BackupEligibilityRules backupEligibilityRules) {
         this.backupManagerService = backupManagerService;
         mOutput = output;
         mPreflightHook = preflightHook;
@@ -204,6 +207,7 @@
                 Objects.requireNonNull(
                         backupManagerService.getAgentTimeoutParameters(),
                         "Timeout parameters cannot be null");
+        mBackupEligibilityRules = backupEligibilityRules;
     }
 
     public int preflightCheck() throws RemoteException {
@@ -302,7 +306,8 @@
             }
             mAgent =
                     backupManagerService.bindToAgentSynchronous(
-                            mPkg.applicationInfo, ApplicationThreadConstants.BACKUP_MODE_FULL);
+                            mPkg.applicationInfo, ApplicationThreadConstants.BACKUP_MODE_FULL,
+                            mBackupEligibilityRules.getOperationType());
         }
         return mAgent != null;
     }
diff --git a/services/backup/java/com/android/server/backup/fullbackup/PerformAdbBackupTask.java b/services/backup/java/com/android/server/backup/fullbackup/PerformAdbBackupTask.java
index 0a11774..448e086 100644
--- a/services/backup/java/com/android/server/backup/fullbackup/PerformAdbBackupTask.java
+++ b/services/backup/java/com/android/server/backup/fullbackup/PerformAdbBackupTask.java
@@ -421,7 +421,8 @@
                                 this,
                                 Long.MAX_VALUE,
                                 mCurrentOpToken,
-                                /*transportFlags=*/ 0);
+                                /*transportFlags=*/ 0,
+                                mBackupEligibilityRules);
                 sendOnBackupPackage(isSharedStorage ? "Shared storage" : pkg.packageName);
 
                 // Don't need to check preflight result as there is no preflight hook.
diff --git a/services/backup/java/com/android/server/backup/fullbackup/PerformFullTransportBackupTask.java b/services/backup/java/com/android/server/backup/fullbackup/PerformFullTransportBackupTask.java
index 1fa8892..a4d47d4 100644
--- a/services/backup/java/com/android/server/backup/fullbackup/PerformFullTransportBackupTask.java
+++ b/services/backup/java/com/android/server/backup/fullbackup/PerformFullTransportBackupTask.java
@@ -860,7 +860,8 @@
                             this,
                             mQuota,
                             mCurrentOpToken,
-                            mTransportFlags);
+                            mTransportFlags,
+                            mBackupEligibilityRules);
             try {
                 try {
                     if (!mIsCancelled) {
diff --git a/services/backup/java/com/android/server/backup/internal/BackupHandler.java b/services/backup/java/com/android/server/backup/internal/BackupHandler.java
index 1bb4349..100dbae 100644
--- a/services/backup/java/com/android/server/backup/internal/BackupHandler.java
+++ b/services/backup/java/com/android/server/backup/internal/BackupHandler.java
@@ -305,8 +305,7 @@
                                 params.isSystemRestore,
                                 params.filterSet,
                                 params.listener,
-                                backupManagerService.getEligibilityRulesForOperation(
-                                        OperationType.BACKUP));
+                                params.backupEligibilityRules);
 
                 synchronized (backupManagerService.getPendingRestores()) {
                     if (backupManagerService.isRestoreInProgress()) {
diff --git a/services/backup/java/com/android/server/backup/keyvalue/KeyValueBackupTask.java b/services/backup/java/com/android/server/backup/keyvalue/KeyValueBackupTask.java
index 6124171..7267cdf 100644
--- a/services/backup/java/com/android/server/backup/keyvalue/KeyValueBackupTask.java
+++ b/services/backup/java/com/android/server/backup/keyvalue/KeyValueBackupTask.java
@@ -731,7 +731,8 @@
         try {
             agent =
                     mBackupManagerService.bindToAgentSynchronous(
-                            packageInfo.applicationInfo, BACKUP_MODE_INCREMENTAL);
+                            packageInfo.applicationInfo, BACKUP_MODE_INCREMENTAL,
+                            mBackupEligibilityRules.getOperationType());
             if (agent == null) {
                 mReporter.onAgentError(packageName);
                 throw AgentException.transitory();
diff --git a/services/backup/java/com/android/server/backup/params/RestoreParams.java b/services/backup/java/com/android/server/backup/params/RestoreParams.java
index a6fea6c..a08a1f8 100644
--- a/services/backup/java/com/android/server/backup/params/RestoreParams.java
+++ b/services/backup/java/com/android/server/backup/params/RestoreParams.java
@@ -23,6 +23,7 @@
 
 import com.android.server.backup.internal.OnTaskFinishedListener;
 import com.android.server.backup.transport.TransportClient;
+import com.android.server.backup.utils.BackupEligibilityRules;
 
 import java.util.Map;
 import java.util.Set;
@@ -37,6 +38,7 @@
     public final boolean isSystemRestore;
     @Nullable public final String[] filterSet;
     public final OnTaskFinishedListener listener;
+    public final BackupEligibilityRules backupEligibilityRules;
 
     /**
      * No kill after restore.
@@ -47,7 +49,8 @@
             IBackupManagerMonitor monitor,
             long token,
             PackageInfo packageInfo,
-            OnTaskFinishedListener listener) {
+            OnTaskFinishedListener listener,
+            BackupEligibilityRules eligibilityRules) {
         return new RestoreParams(
                 transportClient,
                 observer,
@@ -57,7 +60,8 @@
                 /* pmToken */ 0,
                 /* isSystemRestore */ false,
                 /* filterSet */ null,
-                listener);
+                listener,
+                eligibilityRules);
     }
 
     /**
@@ -70,7 +74,8 @@
             long token,
             String packageName,
             int pmToken,
-            OnTaskFinishedListener listener) {
+            OnTaskFinishedListener listener,
+            BackupEligibilityRules backupEligibilityRules) {
         String[] filterSet = {packageName};
         return new RestoreParams(
                 transportClient,
@@ -81,7 +86,8 @@
                 pmToken,
                 /* isSystemRestore */ false,
                 filterSet,
-                listener);
+                listener,
+                backupEligibilityRules);
     }
 
     /**
@@ -92,7 +98,8 @@
             IRestoreObserver observer,
             IBackupManagerMonitor monitor,
             long token,
-            OnTaskFinishedListener listener) {
+            OnTaskFinishedListener listener,
+            BackupEligibilityRules backupEligibilityRules) {
         return new RestoreParams(
                 transportClient,
                 observer,
@@ -102,7 +109,8 @@
                 /* pmToken */ 0,
                 /* isSystemRestore */ true,
                 /* filterSet */ null,
-                listener);
+                listener,
+                backupEligibilityRules);
     }
 
     /**
@@ -115,7 +123,8 @@
             long token,
             String[] filterSet,
             boolean isSystemRestore,
-            OnTaskFinishedListener listener) {
+            OnTaskFinishedListener listener,
+            BackupEligibilityRules backupEligibilityRules) {
         return new RestoreParams(
                 transportClient,
                 observer,
@@ -125,7 +134,8 @@
                 /* pmToken */ 0,
                 isSystemRestore,
                 filterSet,
-                listener);
+                listener,
+                backupEligibilityRules);
     }
 
     private RestoreParams(
@@ -137,7 +147,8 @@
             int pmToken,
             boolean isSystemRestore,
             @Nullable String[] filterSet,
-            OnTaskFinishedListener listener) {
+            OnTaskFinishedListener listener,
+            BackupEligibilityRules backupEligibilityRules) {
         this.transportClient = transportClient;
         this.observer = observer;
         this.monitor = monitor;
@@ -147,5 +158,6 @@
         this.isSystemRestore = isSystemRestore;
         this.filterSet = filterSet;
         this.listener = listener;
+        this.backupEligibilityRules = backupEligibilityRules;
     }
 }
diff --git a/services/backup/java/com/android/server/backup/restore/ActiveRestoreSession.java b/services/backup/java/com/android/server/backup/restore/ActiveRestoreSession.java
index 5a57cdc..3102b5f 100644
--- a/services/backup/java/com/android/server/backup/restore/ActiveRestoreSession.java
+++ b/services/backup/java/com/android/server/backup/restore/ActiveRestoreSession.java
@@ -42,6 +42,7 @@
 import com.android.server.backup.params.RestoreGetSetsParams;
 import com.android.server.backup.params.RestoreParams;
 import com.android.server.backup.transport.TransportClient;
+import com.android.server.backup.utils.BackupEligibilityRules;
 
 import java.util.function.BiFunction;
 
@@ -55,6 +56,7 @@
     private final String mTransportName;
     private final UserBackupManagerService mBackupManagerService;
     private final int mUserId;
+    private final BackupEligibilityRules mBackupEligibilityRules;
     @Nullable private final String mPackageName;
     public RestoreSet[] mRestoreSets = null;
     boolean mEnded = false;
@@ -63,12 +65,14 @@
     public ActiveRestoreSession(
             UserBackupManagerService backupManagerService,
             @Nullable String packageName,
-            String transportName) {
+            String transportName,
+            BackupEligibilityRules backupEligibilityRules) {
         mBackupManagerService = backupManagerService;
         mPackageName = packageName;
         mTransportManager = backupManagerService.getTransportManager();
         mTransportName = transportName;
         mUserId = backupManagerService.getUserId();
+        mBackupEligibilityRules = backupEligibilityRules;
     }
 
     public void markTimedOut() {
@@ -178,7 +182,8 @@
                                                 observer,
                                                 monitor,
                                                 token,
-                                                listener),
+                                                listener,
+                                                mBackupEligibilityRules),
                                 "RestoreSession.restoreAll()");
                     } finally {
                         Binder.restoreCallingIdentity(oldId);
@@ -271,7 +276,8 @@
                                                 token,
                                                 packages,
                                                 /* isSystemRestore */ packages.length > 1,
-                                                listener),
+                                                listener,
+                                                mBackupEligibilityRules),
                                 "RestoreSession.restorePackages(" + packages.length + " packages)");
                     } finally {
                         Binder.restoreCallingIdentity(oldId);
@@ -363,7 +369,8 @@
                                     monitor,
                                     token,
                                     app,
-                                    listener),
+                                    listener,
+                                    mBackupEligibilityRules),
                     "RestoreSession.restorePackage(" + packageName + ")");
         } finally {
             Binder.restoreCallingIdentity(oldId);
diff --git a/services/backup/java/com/android/server/backup/restore/FullRestoreEngine.java b/services/backup/java/com/android/server/backup/restore/FullRestoreEngine.java
index e42d3bd..b962539 100644
--- a/services/backup/java/com/android/server/backup/restore/FullRestoreEngine.java
+++ b/services/backup/java/com/android/server/backup/restore/FullRestoreEngine.java
@@ -27,6 +27,7 @@
 
 import android.app.ApplicationThreadConstants;
 import android.app.IBackupAgent;
+import android.app.backup.BackupManager;
 import android.app.backup.FullBackup;
 import android.app.backup.IBackupManagerMonitor;
 import android.app.backup.IFullBackupRestoreObserver;
@@ -49,6 +50,7 @@
 import com.android.server.backup.KeyValueAdbRestoreEngine;
 import com.android.server.backup.UserBackupManagerService;
 import com.android.server.backup.fullbackup.FullBackupObbConnection;
+import com.android.server.backup.utils.BackupEligibilityRules;
 import com.android.server.backup.utils.BytesReadListener;
 import com.android.server.backup.utils.FullBackupRestoreObserverUtils;
 import com.android.server.backup.utils.RestoreUtils;
@@ -129,11 +131,13 @@
     private final boolean mIsAdbRestore;
     @GuardedBy("mPipesLock")
     private boolean mPipesClosed;
+    private final BackupEligibilityRules mBackupEligibilityRules;
 
     public FullRestoreEngine(UserBackupManagerService backupManagerService,
             BackupRestoreTask monitorTask, IFullBackupRestoreObserver observer,
             IBackupManagerMonitor monitor, PackageInfo onlyPackage, boolean allowApks,
-            int ephemeralOpToken, boolean isAdbRestore) {
+            int ephemeralOpToken, boolean isAdbRestore,
+            BackupEligibilityRules backupEligibilityRules) {
         mBackupManagerService = backupManagerService;
         mEphemeralOpToken = ephemeralOpToken;
         mMonitorTask = monitorTask;
@@ -147,6 +151,7 @@
                 "Timeout parameters cannot be null");
         mIsAdbRestore = isAdbRestore;
         mUserId = backupManagerService.getUserId();
+        mBackupEligibilityRules = backupEligibilityRules;
     }
 
     public IBackupAgent getAgent() {
@@ -365,7 +370,8 @@
                             mAgent = mBackupManagerService.bindToAgentSynchronous(mTargetApp,
                                     FullBackup.KEY_VALUE_DATA_TOKEN.equals(info.domain)
                                             ? ApplicationThreadConstants.BACKUP_MODE_INCREMENTAL
-                                            : ApplicationThreadConstants.BACKUP_MODE_RESTORE_FULL);
+                                            : ApplicationThreadConstants.BACKUP_MODE_RESTORE_FULL,
+                                    mBackupEligibilityRules.getOperationType());
                             mAgentPackage = pkg;
                         } catch (IOException | NameNotFoundException e) {
                             // fall through to error handling
@@ -628,7 +634,11 @@
         setRunning(false);
     }
 
-    private static boolean isRestorableFile(FileMetadata info) {
+    private boolean isRestorableFile(FileMetadata info) {
+        if (mBackupEligibilityRules.getOperationType() == BackupManager.OperationType.MIGRATION) {
+            // Everything is eligible for device-to-device migration.
+            return true;
+        }
         if (FullBackup.CACHE_TREE_TOKEN.equals(info.domain)) {
             if (MORE_DEBUG) {
                 Slog.i(TAG, "Dropping cache file path " + info.path);
diff --git a/services/backup/java/com/android/server/backup/restore/PerformAdbRestoreTask.java b/services/backup/java/com/android/server/backup/restore/PerformAdbRestoreTask.java
index 923bb08..c94286f 100644
--- a/services/backup/java/com/android/server/backup/restore/PerformAdbRestoreTask.java
+++ b/services/backup/java/com/android/server/backup/restore/PerformAdbRestoreTask.java
@@ -25,12 +25,15 @@
 import static com.android.server.backup.UserBackupManagerService.BACKUP_FILE_VERSION;
 
 import android.app.backup.IFullBackupRestoreObserver;
+import android.content.pm.PackageManagerInternal;
 import android.os.ParcelFileDescriptor;
 import android.util.Slog;
 
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.LocalServices;
 import com.android.server.backup.UserBackupManagerService;
 import com.android.server.backup.fullbackup.FullBackupObbConnection;
+import com.android.server.backup.utils.BackupEligibilityRules;
 import com.android.server.backup.utils.FullBackupRestoreObserverUtils;
 import com.android.server.backup.utils.PasswordUtils;
 
@@ -102,7 +105,10 @@
             }
 
             FullRestoreEngine mEngine = new FullRestoreEngine(mBackupManagerService, null,
-                    mObserver, null, null, true, 0 /*unused*/, true);
+                    mObserver, null, null, true, 0 /*unused*/, true,
+                    BackupEligibilityRules.forBackup(mBackupManagerService.getPackageManager(),
+                                    LocalServices.getService(PackageManagerInternal.class),
+                                    mBackupManagerService.getUserId()));
             FullRestoreEngineThread mEngineThread = new FullRestoreEngineThread(mEngine,
                     tarInputStream);
             mEngineThread.run();
diff --git a/services/backup/java/com/android/server/backup/restore/PerformUnifiedRestoreTask.java b/services/backup/java/com/android/server/backup/restore/PerformUnifiedRestoreTask.java
index a7e3604..abf11bd 100644
--- a/services/backup/java/com/android/server/backup/restore/PerformUnifiedRestoreTask.java
+++ b/services/backup/java/com/android/server/backup/restore/PerformUnifiedRestoreTask.java
@@ -162,6 +162,7 @@
 
     private final int mEphemeralOpToken;
     private final BackupAgentTimeoutParameters mAgentTimeoutParameters;
+    private final BackupEligibilityRules mBackupEligibilityRules;
 
     @VisibleForTesting
     PerformUnifiedRestoreTask(UserBackupManagerService backupManagerService) {
@@ -171,6 +172,7 @@
         mTransportManager = null;
         mEphemeralOpToken = 0;
         mUserId = 0;
+        mBackupEligibilityRules = null;
         this.backupManagerService = backupManagerService;
     }
 
@@ -208,6 +210,7 @@
         mAgentTimeoutParameters = Objects.requireNonNull(
                 backupManagerService.getAgentTimeoutParameters(),
                 "Timeout parameters cannot be null");
+        mBackupEligibilityRules = backupEligibilityRules;
 
         if (targetPackage != null) {
             // Single package restore
@@ -656,7 +659,8 @@
         // Good to go!  Set up and bind the agent...
         mAgent = backupManagerService.bindToAgentSynchronous(
                 mCurrentPackage.applicationInfo,
-                ApplicationThreadConstants.BACKUP_MODE_INCREMENTAL);
+                ApplicationThreadConstants.BACKUP_MODE_INCREMENTAL,
+                mBackupEligibilityRules.getOperationType());
         if (mAgent == null) {
             Slog.w(TAG, "Can't find backup agent for " + packageName);
             mMonitor = BackupManagerMonitorUtils.monitorEvent(mMonitor,
@@ -913,7 +917,8 @@
                     mCurrentPackage.packageName);
 
             mEngine = new FullRestoreEngine(backupManagerService, this, null,
-                    mMonitor, mCurrentPackage, false, mEphemeralOpToken, false);
+                    mMonitor, mCurrentPackage, false, mEphemeralOpToken, false,
+                    mBackupEligibilityRules);
             mEngineThread = new FullRestoreEngineThread(mEngine, mEnginePipes[0]);
 
             ParcelFileDescriptor eWriteEnd = mEnginePipes[1];
@@ -1131,6 +1136,8 @@
         if (mIsSystemRestore && mPmAgent != null) {
             backupManagerService.setAncestralPackages(mPmAgent.getRestoredPackages());
             backupManagerService.setAncestralToken(mToken);
+            backupManagerService.setAncestralOperationType(
+                    mBackupEligibilityRules.getOperationType());
             backupManagerService.writeRestoreTokens();
         }
 
diff --git a/services/backup/java/com/android/server/backup/utils/BackupEligibilityRules.java b/services/backup/java/com/android/server/backup/utils/BackupEligibilityRules.java
index ee05c2b..73ba1f1 100644
--- a/services/backup/java/com/android/server/backup/utils/BackupEligibilityRules.java
+++ b/services/backup/java/com/android/server/backup/utils/BackupEligibilityRules.java
@@ -85,12 +85,15 @@
      *     <li>they run as a system-level uid but do not supply their own backup agent
      *     <li>it is the special shared-storage backup package used for 'adb backup'
      * </ol>
+     *
+     * However, the above eligibility rules are ignored for non-system apps in in case of
+     * device-to-device migration, see {@link OperationType}.
      */
     @VisibleForTesting
     public boolean appIsEligibleForBackup(ApplicationInfo app) {
-        // 1. their manifest states android:allowBackup="false"
-        boolean appAllowsBackup = (app.flags & ApplicationInfo.FLAG_ALLOW_BACKUP) != 0;
-        if (!appAllowsBackup && !forceFullBackup(app.uid, mOperationType)) {
+        // 1. their manifest states android:allowBackup="false" and this is not a device-to-device
+        // migration
+        if (!isAppBackupAllowed(app)) {
             return false;
         }
 
@@ -123,6 +126,23 @@
     }
 
     /**
+    * Check if this app allows backup. Apps can opt out of backup by stating
+    * android:allowBackup="false" in their manifest. However, this flag is ignored for non-system
+    * apps during device-to-device migrations, see {@link OperationType}.
+    *
+    * @param app The app under check.
+    * @return boolean indicating whether backup is allowed.
+    */
+    public boolean isAppBackupAllowed(ApplicationInfo app) {
+        if (mOperationType == OperationType.MIGRATION && !UserHandle.isCore(app.uid)) {
+            // Backup / restore of all apps is force allowed during device-to-device migration.
+            return true;
+        }
+
+        return (app.flags & ApplicationInfo.FLAG_ALLOW_BACKUP) != 0;
+    }
+
+    /**
      * Returns whether an app is eligible for backup at runtime. That is, the app has to:
      * <ol>
      *     <li>Return true for {@link #appIsEligibleForBackup(ApplicationInfo, int)}
@@ -318,4 +338,8 @@
             return true;
         }
     }
+
+    public int getOperationType() {
+        return mOperationType;
+    }
 }
diff --git a/services/backup/java/com/android/server/backup/utils/TarBackupReader.java b/services/backup/java/com/android/server/backup/utils/TarBackupReader.java
index bf8e9c8..3789fa1 100644
--- a/services/backup/java/com/android/server/backup/utils/TarBackupReader.java
+++ b/services/backup/java/com/android/server/backup/utils/TarBackupReader.java
@@ -402,7 +402,7 @@
                     info.packageName, PackageManager.GET_SIGNING_CERTIFICATES, userId);
             // Fall through to IGNORE if the app explicitly disallows backup
             final int flags = pkgInfo.applicationInfo.flags;
-            if ((flags & ApplicationInfo.FLAG_ALLOW_BACKUP) != 0) {
+            if (eligibilityRules.isAppBackupAllowed(pkgInfo.applicationInfo)) {
                 // Restore system-uid-space packages only if they have
                 // defined a custom backup agent
                 if (!UserHandle.isCore(pkgInfo.applicationInfo.uid)
diff --git a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
index 6d8f9a3..eb38f51 100644
--- a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
+++ b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
@@ -28,6 +28,7 @@
 import static java.util.concurrent.TimeUnit.MINUTES;
 
 import android.annotation.CheckResult;
+import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.app.AppOpsManager;
 import android.app.PendingIntent;
@@ -84,6 +85,7 @@
 import com.android.server.FgThread;
 import com.android.server.LocalServices;
 import com.android.server.SystemService;
+import com.android.server.SystemService.TargetUser;
 import com.android.server.wm.ActivityTaskManagerInternal;
 
 import org.xmlpull.v1.XmlPullParser;
@@ -189,7 +191,8 @@
     }
 
     @Override
-    public void onUnlockUser(int userHandle) {
+    public void onUserUnlocking(@NonNull TargetUser user) {
+        int userHandle = user.getUserIdentifier();
         Set<Association> associations = readAllAssociations(userHandle);
         if (associations == null || associations.isEmpty()) {
             return;
diff --git a/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureManagerService.java b/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureManagerService.java
index ea94ad0..e742015 100644
--- a/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureManagerService.java
+++ b/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureManagerService.java
@@ -219,7 +219,7 @@
 
     @Override // from SystemService
     public boolean isUserSupported(TargetUser user) {
-        return user.getUserInfo().isFull() || user.getUserInfo().isManagedProfile();
+        return user.isFull() || user.isManagedProfile();
     }
 
     @Override // from SystemService
diff --git a/services/core/Android.bp b/services/core/Android.bp
index 1093515..addaa65 100644
--- a/services/core/Android.bp
+++ b/services/core/Android.bp
@@ -2,38 +2,25 @@
     name: "services.core-sources",
     srcs: ["java/**/*.java"],
     path: "java",
-    visibility: ["//frameworks/base/services"],
-}
-
-java_library {
-    name: "protolog-common",
-    srcs: [
-        "java/com/android/server/protolog/common/**/*.java",
+    visibility: [
+        "//frameworks/base/services",
+        "//frameworks/base/core/java/com/android/internal/protolog",
     ],
-    host_supported: true,
-}
-
-java_library {
-    name: "services.core.wm.protologgroups",
-    srcs: [
-        "java/com/android/server/wm/ProtoLogGroup.java",
-    ],
-    static_libs: ["protolog-common"],
 }
 
 genrule {
     name: "services.core.protologsrc",
     srcs: [
-        ":services.core.wm.protologgroups",
+        ":protolog-groups",
         ":services.core-sources",
     ],
     tools: ["protologtool"],
     cmd: "$(location protologtool) transform-protolog-calls " +
-      "--protolog-class com.android.server.protolog.common.ProtoLog " +
-      "--protolog-impl-class com.android.server.protolog.ProtoLogImpl " +
-      "--protolog-cache-class 'com.android.server.protolog.ProtoLog$$Cache' " +
-      "--loggroups-class com.android.server.wm.ProtoLogGroup " +
-      "--loggroups-jar $(location :services.core.wm.protologgroups) " +
+      "--protolog-class com.android.internal.protolog.common.ProtoLog " +
+      "--protolog-impl-class com.android.internal.protolog.ProtoLogImpl " +
+      "--protolog-cache-class 'com.android.server.wm.ProtoLogCache' " +
+      "--loggroups-class com.android.internal.protolog.ProtoLogGroup " +
+      "--loggroups-jar $(location :protolog-groups) " +
       "--output-srcjar $(out) " +
       "$(locations :services.core-sources)",
     out: ["services.core.protolog.srcjar"],
@@ -42,14 +29,14 @@
 genrule {
     name: "generate-protolog.json",
     srcs: [
-        ":services.core.wm.protologgroups",
+        ":protolog-groups",
         ":services.core-sources",
     ],
     tools: ["protologtool"],
     cmd: "$(location protologtool) generate-viewer-config " +
-      "--protolog-class com.android.server.protolog.common.ProtoLog " +
-      "--loggroups-class com.android.server.wm.ProtoLogGroup " +
-      "--loggroups-jar $(location :services.core.wm.protologgroups) " +
+      "--protolog-class com.android.internal.protolog.common.ProtoLog " +
+      "--loggroups-class com.android.internal.protolog.ProtoLogGroup " +
+      "--loggroups-jar $(location :protolog-groups) " +
       "--viewer-conf $(out) " +
       "$(locations :services.core-sources)",
     out: ["services.core.protolog.json"],
@@ -79,6 +66,7 @@
         ":framework_native_aidl",
         ":gsiservice_aidl",
         ":idmap2_aidl",
+        ":inputconstants_aidl",
         ":installd_aidl",
         ":storaged_aidl",
         ":vold_aidl",
@@ -108,6 +96,7 @@
     ],
 
     static_libs: [
+        "protolog-lib",
         "time_zone_distro",
         "time_zone_distro_installer",
         "android.hardware.authsecret-V1.0-java",
diff --git a/services/core/java/android/content/pm/PackageManagerInternal.java b/services/core/java/android/content/pm/PackageManagerInternal.java
index 3bed506..cf9324c 100644
--- a/services/core/java/android/content/pm/PackageManagerInternal.java
+++ b/services/core/java/android/content/pm/PackageManagerInternal.java
@@ -69,6 +69,7 @@
     public static final int PACKAGE_WIFI = 13;
     public static final int PACKAGE_COMPANION = 14;
     public static final int PACKAGE_RETAIL_DEMO = 15;
+    public static final int PACKAGE_OVERLAY_CONFIG_SIGNATURE = 16;
 
     @IntDef(flag = true, prefix = "RESOLVE_", value = {
             RESOLVE_NON_BROWSER_ONLY,
diff --git a/services/core/java/android/os/BatteryStatsInternal.java b/services/core/java/android/os/BatteryStatsInternal.java
index 7cf5fd6..b7fed87 100644
--- a/services/core/java/android/os/BatteryStatsInternal.java
+++ b/services/core/java/android/os/BatteryStatsInternal.java
@@ -50,5 +50,5 @@
      * Informs battery stats of binder stats for the given work source UID.
      */
     public abstract void noteBinderCallStats(int workSourceUid, long incrementalBinderCallCount,
-            Collection<BinderCallsStats.CallStat> callStats);
+            Collection<BinderCallsStats.CallStat> callStats,  int[] binderThreadNativeTids);
 }
diff --git a/services/core/java/android/os/UserManagerInternal.java b/services/core/java/android/os/UserManagerInternal.java
index fbe8c04..61e8128 100644
--- a/services/core/java/android/os/UserManagerInternal.java
+++ b/services/core/java/android/os/UserManagerInternal.java
@@ -262,8 +262,7 @@
     public abstract boolean hasUserRestriction(String restriction, int userId);
 
     /**
-     * Gets an {@link UserInfo} for the given {@code userId}, or {@code null} if not
-     * found.
+     * Gets a {@link UserInfo} for the given {@code userId}, or {@code null} if not found.
      */
     public abstract @Nullable UserInfo getUserInfo(@UserIdInt int userId);
 
diff --git a/services/core/java/com/android/server/BinderCallsStatsService.java b/services/core/java/com/android/server/BinderCallsStatsService.java
index a3c04be..c951359 100644
--- a/services/core/java/com/android/server/BinderCallsStatsService.java
+++ b/services/core/java/com/android/server/BinderCallsStatsService.java
@@ -28,7 +28,9 @@
 import android.net.Uri;
 import android.os.BatteryStatsInternal;
 import android.os.Binder;
+import android.os.ParcelFileDescriptor;
 import android.os.Process;
+import android.os.ShellCommand;
 import android.os.SystemProperties;
 import android.os.UserHandle;
 import android.provider.Settings;
@@ -308,50 +310,134 @@
         }
 
         boolean verbose = false;
+        int worksourceUid = Process.INVALID_UID;
         if (args != null) {
-            for (final String arg : args) {
+            for (int i = 0; i < args.length; i++) {
+                String arg = args[i];
                 if ("-a".equals(arg)) {
                     verbose = true;
-                } else if ("--reset".equals(arg)) {
-                    reset();
-                    pw.println("binder_calls_stats reset.");
-                    return;
-                } else if ("--enable".equals(arg)) {
-                    Binder.setObserver(mBinderCallsStats);
-                    return;
-                } else if ("--disable".equals(arg)) {
-                    Binder.setObserver(null);
-                    return;
-                } else if ("--no-sampling".equals(arg)) {
-                    mBinderCallsStats.setSamplingInterval(1);
-                    return;
-                } else if ("--enable-detailed-tracking".equals(arg)) {
-                    SystemProperties.set(PERSIST_SYS_BINDER_CALLS_DETAILED_TRACKING, "1");
-                    mBinderCallsStats.setDetailedTracking(true);
-                    pw.println("Detailed tracking enabled");
-                    return;
-                } else if ("--disable-detailed-tracking".equals(arg)) {
-                    SystemProperties.set(PERSIST_SYS_BINDER_CALLS_DETAILED_TRACKING, "");
-                    mBinderCallsStats.setDetailedTracking(false);
-                    pw.println("Detailed tracking disabled");
-                    return;
-                } else if ("--dump-worksource-provider".equals(arg)) {
-                    mWorkSourceProvider.dump(pw, AppIdToPackageMap.getSnapshot());
-                    return;
                 } else if ("-h".equals(arg)) {
-                    pw.println("binder_calls_stats commands:");
-                    pw.println("  --reset: Reset stats");
-                    pw.println("  --enable: Enable tracking binder calls");
-                    pw.println("  --disable: Disables tracking binder calls");
-                    pw.println("  --no-sampling: Tracks all calls");
-                    pw.println("  --enable-detailed-tracking: Enables detailed tracking");
-                    pw.println("  --disable-detailed-tracking: Disables detailed tracking");
+                    pw.println("dumpsys binder_calls_stats options:");
+                    pw.println("  -a: Verbose");
+                    pw.println("  --work-source-uid <UID>: Dump binder calls from the UID");
                     return;
-                } else {
-                    pw.println("Unknown option: " + arg);
+                } else if ("--work-source-uid".equals(arg)) {
+                    i++;
+                    if (i >= args.length) {
+                        throw new IllegalArgumentException(
+                                "Argument expected after \"" + arg + "\"");
+                    }
+                    String uidArg = args[i];
+                    try {
+                        worksourceUid = Integer.parseInt(uidArg);
+                    } catch (NumberFormatException e) {
+                        pw.println("Invalid UID: " + uidArg);
+                        return;
+                    }
+                }
+            }
+
+            if (args.length > 0 && worksourceUid == Process.INVALID_UID) {
+                // For compatibility, support "cmd"-style commands when passed to "dumpsys".
+                BinderCallsStatsShellCommand command = new BinderCallsStatsShellCommand(pw);
+                int status = command.exec(this, null, FileDescriptor.out, FileDescriptor.err, args);
+                if (status == 0) {
+                    return;
                 }
             }
         }
-        mBinderCallsStats.dump(pw, AppIdToPackageMap.getSnapshot(), verbose);
+        mBinderCallsStats.dump(pw, AppIdToPackageMap.getSnapshot(), worksourceUid, verbose);
+    }
+
+    @Override
+    public int handleShellCommand(ParcelFileDescriptor in, ParcelFileDescriptor out,
+            ParcelFileDescriptor err, String[] args) {
+        ShellCommand command = new BinderCallsStatsShellCommand(null);
+        int status = command.exec(this, in.getFileDescriptor(), out.getFileDescriptor(),
+                err.getFileDescriptor(), args);
+        if (status != 0) {
+            command.onHelp();
+        }
+        return status;
+    }
+
+    private class BinderCallsStatsShellCommand extends ShellCommand {
+        private final PrintWriter mPrintWriter;
+
+        BinderCallsStatsShellCommand(PrintWriter printWriter) {
+            mPrintWriter = printWriter;
+        }
+
+        @Override
+        public PrintWriter getOutPrintWriter() {
+            if (mPrintWriter != null) {
+                return mPrintWriter;
+            }
+            return super.getOutPrintWriter();
+        }
+
+        @Override
+        public int onCommand(String cmd) {
+            PrintWriter pw = getOutPrintWriter();
+            if (cmd == null) {
+                return -1;
+            }
+
+            switch (cmd) {
+                case "--reset":
+                    reset();
+                    pw.println("binder_calls_stats reset.");
+                    break;
+                case "--enable":
+                    Binder.setObserver(mBinderCallsStats);
+                    break;
+                case "--disable":
+                    Binder.setObserver(null);
+                    break;
+                case "--no-sampling":
+                    mBinderCallsStats.setSamplingInterval(1);
+                    break;
+                case "--enable-detailed-tracking":
+                    SystemProperties.set(PERSIST_SYS_BINDER_CALLS_DETAILED_TRACKING, "1");
+                    mBinderCallsStats.setDetailedTracking(true);
+                    pw.println("Detailed tracking enabled");
+                    break;
+                case "--disable-detailed-tracking":
+                    SystemProperties.set(PERSIST_SYS_BINDER_CALLS_DETAILED_TRACKING, "");
+                    mBinderCallsStats.setDetailedTracking(false);
+                    pw.println("Detailed tracking disabled");
+                    break;
+                case "--dump-worksource-provider":
+                    mBinderCallsStats.setDetailedTracking(true);
+                    mWorkSourceProvider.dump(pw, AppIdToPackageMap.getSnapshot());
+                    break;
+                case "--work-source-uid":
+                    String uidArg = getNextArgRequired();
+                    try {
+                        int uid = Integer.parseInt(uidArg);
+                        mBinderCallsStats.recordAllCallsForWorkSourceUid(uid);
+                    } catch (NumberFormatException e) {
+                        pw.println("Invalid UID: " + uidArg);
+                        return -1;
+                    }
+                    break;
+                default:
+                    return handleDefaultCommands(cmd);
+            }
+            return 0;
+        }
+
+        @Override
+        public void onHelp() {
+            PrintWriter pw = getOutPrintWriter();
+            pw.println("binder_calls_stats commands:");
+            pw.println("  --reset: Reset stats");
+            pw.println("  --enable: Enable tracking binder calls");
+            pw.println("  --disable: Disables tracking binder calls");
+            pw.println("  --no-sampling: Tracks all calls");
+            pw.println("  --enable-detailed-tracking: Enables detailed tracking");
+            pw.println("  --disable-detailed-tracking: Disables detailed tracking");
+            pw.println("  --work-source-uid <UID>: Track all binder calls from the UID");
+        }
     }
 }
diff --git a/services/core/java/com/android/server/BluetoothService.java b/services/core/java/com/android/server/BluetoothService.java
index 0bcd937..1a1eecd 100644
--- a/services/core/java/com/android/server/BluetoothService.java
+++ b/services/core/java/com/android/server/BluetoothService.java
@@ -16,10 +16,14 @@
 
 package com.android.server;
 
+import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.bluetooth.BluetoothAdapter;
 import android.content.Context;
 import android.os.UserManager;
 
+import com.android.server.SystemService.TargetUser;
+
 class BluetoothService extends SystemService {
     private BluetoothManagerService mBluetoothManagerService;
     private boolean mInitialized = false;
@@ -52,16 +56,16 @@
     }
 
     @Override
-    public void onSwitchUser(int userHandle) {
+    public void onUserSwitching(@Nullable TargetUser from, @NonNull TargetUser to) {
         if (!mInitialized) {
             initialize();
         } else {
-            mBluetoothManagerService.handleOnSwitchUser(userHandle);
+            mBluetoothManagerService.handleOnSwitchUser(to.getUserIdentifier());
         }
     }
 
     @Override
-    public void onUnlockUser(int userHandle) {
-        mBluetoothManagerService.handleOnUnlockUser(userHandle);
+    public void onUserUnlocking(@NonNull TargetUser user) {
+        mBluetoothManagerService.handleOnUnlockUser(user.getUserIdentifier());
     }
 }
diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java
index 02c08cc..bd590d3 100644
--- a/services/core/java/com/android/server/ConnectivityService.java
+++ b/services/core/java/com/android/server/ConnectivityService.java
@@ -220,6 +220,8 @@
 
 import com.google.android.collect.Lists;
 
+import libcore.io.IoUtils;
+
 import org.xmlpull.v1.XmlPullParser;
 import org.xmlpull.v1.XmlPullParserException;
 
@@ -4964,7 +4966,7 @@
                 Slog.w(TAG, "User " + userId + " has no Vpn configuration");
                 return null;
             }
-            return vpn.getLockdownWhitelist();
+            return vpn.getLockdownAllowlist();
         }
     }
 
@@ -7513,18 +7515,34 @@
     public void startNattKeepaliveWithFd(Network network, FileDescriptor fd, int resourceId,
             int intervalSeconds, ISocketKeepaliveCallback cb, String srcAddr,
             String dstAddr) {
-        mKeepaliveTracker.startNattKeepalive(
-                getNetworkAgentInfoForNetwork(network), fd, resourceId,
-                intervalSeconds, cb,
-                srcAddr, dstAddr, NattSocketKeepalive.NATT_PORT);
+        try {
+            mKeepaliveTracker.startNattKeepalive(
+                    getNetworkAgentInfoForNetwork(network), fd, resourceId,
+                    intervalSeconds, cb,
+                    srcAddr, dstAddr, NattSocketKeepalive.NATT_PORT);
+        } finally {
+            // FileDescriptors coming from AIDL calls must be manually closed to prevent leaks.
+            // startNattKeepalive calls Os.dup(fd) before returning, so we can close immediately.
+            if (fd != null && Binder.getCallingPid() != Process.myPid()) {
+                IoUtils.closeQuietly(fd);
+            }
+        }
     }
 
     @Override
     public void startTcpKeepalive(Network network, FileDescriptor fd, int intervalSeconds,
             ISocketKeepaliveCallback cb) {
-        enforceKeepalivePermission();
-        mKeepaliveTracker.startTcpKeepalive(
-                getNetworkAgentInfoForNetwork(network), fd, intervalSeconds, cb);
+        try {
+            enforceKeepalivePermission();
+            mKeepaliveTracker.startTcpKeepalive(
+                    getNetworkAgentInfoForNetwork(network), fd, intervalSeconds, cb);
+        } finally {
+            // FileDescriptors coming from AIDL calls must be manually closed to prevent leaks.
+            // startTcpKeepalive calls Os.dup(fd) before returning, so we can close immediately.
+            if (fd != null && Binder.getCallingPid() != Process.myPid()) {
+                IoUtils.closeQuietly(fd);
+            }
+        }
     }
 
     @Override
diff --git a/services/core/java/com/android/server/IpSecService.java b/services/core/java/com/android/server/IpSecService.java
index 6402e07..b2f0c83 100644
--- a/services/core/java/com/android/server/IpSecService.java
+++ b/services/core/java/com/android/server/IpSecService.java
@@ -1477,7 +1477,7 @@
     }
 
     /**
-     * Checks an IpSecConfig parcel to ensure that the contents are sane and throws an
+     * Checks an IpSecConfig parcel to ensure that the contents are valid and throws an
      * IllegalArgumentException if they are not.
      */
     private void checkIpSecConfig(IpSecConfig config) {
diff --git a/services/core/java/com/android/server/NetworkManagementService.java b/services/core/java/com/android/server/NetworkManagementService.java
index 0ddfa1c..ee794ba 100644
--- a/services/core/java/com/android/server/NetworkManagementService.java
+++ b/services/core/java/com/android/server/NetworkManagementService.java
@@ -20,14 +20,14 @@
 import static android.Manifest.permission.NETWORK_SETTINGS;
 import static android.Manifest.permission.OBSERVE_NETWORK_POLICY;
 import static android.Manifest.permission.SHUTDOWN;
-import static android.net.INetd.FIREWALL_BLACKLIST;
+import static android.net.INetd.FIREWALL_ALLOWLIST;
 import static android.net.INetd.FIREWALL_CHAIN_DOZABLE;
 import static android.net.INetd.FIREWALL_CHAIN_NONE;
 import static android.net.INetd.FIREWALL_CHAIN_POWERSAVE;
 import static android.net.INetd.FIREWALL_CHAIN_STANDBY;
+import static android.net.INetd.FIREWALL_DENYLIST;
 import static android.net.INetd.FIREWALL_RULE_ALLOW;
 import static android.net.INetd.FIREWALL_RULE_DENY;
-import static android.net.INetd.FIREWALL_WHITELIST;
 import static android.net.NetworkPolicyManager.FIREWALL_CHAIN_NAME_DOZABLE;
 import static android.net.NetworkPolicyManager.FIREWALL_CHAIN_NAME_POWERSAVE;
 import static android.net.NetworkPolicyManager.FIREWALL_CHAIN_NAME_STANDBY;
@@ -185,10 +185,10 @@
     /** Set of interfaces with active alerts. */
     @GuardedBy("mQuotaLock")
     private HashMap<String, Long> mActiveAlerts = Maps.newHashMap();
-    /** Set of UIDs blacklisted on metered networks. */
+    /** Set of UIDs denylisted on metered networks. */
     @GuardedBy("mRulesLock")
     private SparseBooleanArray mUidRejectOnMetered = new SparseBooleanArray();
-    /** Set of UIDs whitelisted on metered networks. */
+    /** Set of UIDs allowlisted on metered networks. */
     @GuardedBy("mRulesLock")
     private SparseBooleanArray mUidAllowOnMetered = new SparseBooleanArray();
     /** Set of UIDs with cleartext penalties. */
@@ -561,27 +561,27 @@
             synchronized (mRulesLock) {
                 size = mUidRejectOnMetered.size();
                 if (size > 0) {
-                    if (DBG) Slog.d(TAG, "Pushing " + size + " UIDs to metered blacklist rules");
+                    if (DBG) Slog.d(TAG, "Pushing " + size + " UIDs to metered denylist rules");
                     uidRejectOnQuota = mUidRejectOnMetered;
                     mUidRejectOnMetered = new SparseBooleanArray();
                 }
 
                 size = mUidAllowOnMetered.size();
                 if (size > 0) {
-                    if (DBG) Slog.d(TAG, "Pushing " + size + " UIDs to metered whitelist rules");
+                    if (DBG) Slog.d(TAG, "Pushing " + size + " UIDs to metered allowlist rules");
                     uidAcceptOnQuota = mUidAllowOnMetered;
                     mUidAllowOnMetered = new SparseBooleanArray();
                 }
             }
             if (uidRejectOnQuota != null) {
                 for (int i = 0; i < uidRejectOnQuota.size(); i++) {
-                    setUidMeteredNetworkBlacklist(uidRejectOnQuota.keyAt(i),
+                    setUidMeteredNetworkDenylist(uidRejectOnQuota.keyAt(i),
                             uidRejectOnQuota.valueAt(i));
                 }
             }
             if (uidAcceptOnQuota != null) {
                 for (int i = 0; i < uidAcceptOnQuota.size(); i++) {
-                    setUidMeteredNetworkWhitelist(uidAcceptOnQuota.keyAt(i),
+                    setUidMeteredNetworkAllowlist(uidAcceptOnQuota.keyAt(i),
                             uidAcceptOnQuota.valueAt(i));
                 }
             }
@@ -1307,14 +1307,14 @@
         }
     }
 
-    private void setUidOnMeteredNetworkList(int uid, boolean blacklist, boolean enable) {
+    private void setUidOnMeteredNetworkList(int uid, boolean denylist, boolean enable) {
         NetworkStack.checkNetworkStackPermission(mContext);
 
         synchronized (mQuotaLock) {
             boolean oldEnable;
             SparseBooleanArray quotaList;
             synchronized (mRulesLock) {
-                quotaList = blacklist ? mUidRejectOnMetered : mUidAllowOnMetered;
+                quotaList = denylist ? mUidRejectOnMetered : mUidAllowOnMetered;
                 oldEnable = quotaList.get(uid, false);
             }
             if (oldEnable == enable) {
@@ -1324,7 +1324,7 @@
 
             Trace.traceBegin(Trace.TRACE_TAG_NETWORK, "inetd bandwidth");
             try {
-                if (blacklist) {
+                if (denylist) {
                     if (enable) {
                         mNetdService.bandwidthAddNaughtyApp(uid);
                     } else {
@@ -1353,12 +1353,12 @@
     }
 
     @Override
-    public void setUidMeteredNetworkBlacklist(int uid, boolean enable) {
+    public void setUidMeteredNetworkDenylist(int uid, boolean enable) {
         setUidOnMeteredNetworkList(uid, true, enable);
     }
 
     @Override
-    public void setUidMeteredNetworkWhitelist(int uid, boolean enable) {
+    public void setUidMeteredNetworkAllowlist(int uid, boolean enable) {
         setUidOnMeteredNetworkList(uid, false, enable);
     }
 
@@ -1575,7 +1575,7 @@
         enforceSystemUid();
         try {
             mNetdService.firewallSetFirewallType(
-                    enabled ? INetd.FIREWALL_WHITELIST : INetd.FIREWALL_BLACKLIST);
+                    enabled ? INetd.FIREWALL_ALLOWLIST : INetd.FIREWALL_DENYLIST);
             mFirewallEnabled = enabled;
         } catch (RemoteException | ServiceSpecificException e) {
             throw new IllegalStateException(e);
@@ -1608,7 +1608,7 @@
 
         int numUids = 0;
         if (DBG) Slog.d(TAG, "Closing sockets after enabling chain " + chainName);
-        if (getFirewallType(chain) == FIREWALL_WHITELIST) {
+        if (getFirewallType(chain) == FIREWALL_ALLOWLIST) {
             // Close all sockets on all non-system UIDs...
             ranges = new UidRangeParcel[] {
                 // TODO: is there a better way of finding all existing users? If so, we could
@@ -1626,7 +1626,7 @@
                     }
                 }
             }
-            // Normally, whitelist chains only contain deny rules, so numUids == exemptUids.length.
+            // Normally, allowlist chains only contain deny rules, so numUids == exemptUids.length.
             // But the code does not guarantee this in any way, and at least in one case - if we add
             // a UID rule to the firewall, and then disable the firewall - the chains can contain
             // the wrong type of rule. In this case, don't close connections that we shouldn't.
@@ -1691,7 +1691,7 @@
             // Close any sockets that were opened by the affected UIDs. This has to be done after
             // disabling network connectivity, in case they react to the socket close by reopening
             // the connection and race with the iptables commands that enable the firewall. All
-            // whitelist and blacklist chains allow RSTs through.
+            // allowlist and denylist chains allow RSTs through.
             if (enable) {
                 closeSocketsForFirewallChainLocked(chain, chainName);
             }
@@ -1714,13 +1714,13 @@
     private int getFirewallType(int chain) {
         switch (chain) {
             case FIREWALL_CHAIN_STANDBY:
-                return FIREWALL_BLACKLIST;
+                return FIREWALL_DENYLIST;
             case FIREWALL_CHAIN_DOZABLE:
-                return FIREWALL_WHITELIST;
+                return FIREWALL_ALLOWLIST;
             case FIREWALL_CHAIN_POWERSAVE:
-                return FIREWALL_WHITELIST;
+                return FIREWALL_ALLOWLIST;
             default:
-                return isFirewallEnabled() ? FIREWALL_WHITELIST : FIREWALL_BLACKLIST;
+                return isFirewallEnabled() ? FIREWALL_ALLOWLIST : FIREWALL_DENYLIST;
         }
     }
 
@@ -1822,13 +1822,13 @@
 
     private @NonNull String getFirewallRuleName(int chain, int rule) {
         String ruleName;
-        if (getFirewallType(chain) == FIREWALL_WHITELIST) {
+        if (getFirewallType(chain) == FIREWALL_ALLOWLIST) {
             if (rule == FIREWALL_RULE_ALLOW) {
                 ruleName = "allow";
             } else {
                 ruleName = "deny";
             }
-        } else { // Blacklist mode
+        } else { // Denylist mode
             if (rule == FIREWALL_RULE_DENY) {
                 ruleName = "deny";
             } else {
@@ -1856,7 +1856,7 @@
 
     private int getFirewallRuleType(int chain, int rule) {
         if (rule == NetworkPolicyManager.FIREWALL_RULE_DEFAULT) {
-            return getFirewallType(chain) == FIREWALL_WHITELIST
+            return getFirewallType(chain) == FIREWALL_ALLOWLIST
                     ? INetd.FIREWALL_RULE_DENY : INetd.FIREWALL_RULE_ALLOW;
         }
         return rule;
@@ -1913,8 +1913,8 @@
             pw.print("Active alert ifaces: "); pw.println(mActiveAlerts.toString());
             pw.print("Data saver mode: "); pw.println(mDataSaverMode);
             synchronized (mRulesLock) {
-                dumpUidRuleOnQuotaLocked(pw, "blacklist", mUidRejectOnMetered);
-                dumpUidRuleOnQuotaLocked(pw, "whitelist", mUidAllowOnMetered);
+                dumpUidRuleOnQuotaLocked(pw, "denylist", mUidRejectOnMetered);
+                dumpUidRuleOnQuotaLocked(pw, "allowlist", mUidAllowOnMetered);
             }
         }
 
@@ -2179,9 +2179,9 @@
             }
         }
 
-        void setUidOnMeteredNetworkList(boolean blacklist, int uid, boolean enable) {
+        void setUidOnMeteredNetworkList(boolean denylist, int uid, boolean enable) {
             synchronized (mRulesLock) {
-                if (blacklist) {
+                if (denylist) {
                     mUidRejectOnMetered.put(uid, enable);
                 } else {
                     mUidAllowOnMetered.put(uid, enable);
diff --git a/services/core/java/com/android/server/PackageWatchdog.java b/services/core/java/com/android/server/PackageWatchdog.java
index 1689656..e675d8d 100644
--- a/services/core/java/com/android/server/PackageWatchdog.java
+++ b/services/core/java/com/android/server/PackageWatchdog.java
@@ -1453,8 +1453,11 @@
             } else {
                 mHealthCheckState = HealthCheckState.ACTIVE;
             }
-            Slog.i(TAG, "Updated health check state for package " + getName() + ": "
-                    + toString(oldState) + " -> " + toString(mHealthCheckState));
+
+            if (oldState != mHealthCheckState) {
+                Slog.i(TAG, "Updated health check state for package " + getName() + ": "
+                        + toString(oldState) + " -> " + toString(mHealthCheckState));
+            }
             return mHealthCheckState;
         }
 
diff --git a/services/core/java/com/android/server/PinnerService.java b/services/core/java/com/android/server/PinnerService.java
index 3148a62..a3bcbbe 100644
--- a/services/core/java/com/android/server/PinnerService.java
+++ b/services/core/java/com/android/server/PinnerService.java
@@ -20,6 +20,7 @@
 import static android.app.ActivityManager.UID_OBSERVER_GONE;
 
 import android.annotation.IntDef;
+import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.app.ActivityManager;
 import android.app.ActivityManagerInternal;
@@ -60,6 +61,7 @@
 import com.android.internal.os.BackgroundThread;
 import com.android.internal.util.DumpUtils;
 import com.android.internal.util.function.pooled.PooledLambda;
+import com.android.server.SystemService.TargetUser;
 import com.android.server.wm.ActivityTaskManagerInternal;
 
 import dalvik.system.DexFile;
@@ -236,16 +238,18 @@
      * individual apps. Make sure that user's preference is pinned into memory.
      */
     @Override
-    public void onSwitchUser(int userHandle) {
-        if (!mUserManager.isManagedProfile(userHandle)) {
-            sendPinAppsMessage(userHandle);
+    public void onUserSwitching(@Nullable TargetUser from, @NonNull TargetUser to) {
+        int userId = to.getUserIdentifier();
+        if (!mUserManager.isManagedProfile(userId)) {
+            sendPinAppsMessage(userId);
         }
     }
 
     @Override
-    public void onUnlockUser(int userHandle) {
-        if (!mUserManager.isManagedProfile(userHandle)) {
-            sendPinAppsMessage(userHandle);
+    public void onUserUnlocking(@NonNull TargetUser user) {
+        int userId = user.getUserIdentifier();
+        if (!mUserManager.isManagedProfile(userId)) {
+            sendPinAppsMessage(userId);
         }
     }
 
diff --git a/services/core/java/com/android/server/ServiceWatcher.java b/services/core/java/com/android/server/ServiceWatcher.java
index 0038dc2..b78b5d9 100644
--- a/services/core/java/com/android/server/ServiceWatcher.java
+++ b/services/core/java/com/android/server/ServiceWatcher.java
@@ -238,24 +238,13 @@
 
         new PackageMonitor() {
             @Override
-            public void onPackageUpdateFinished(String packageName, int uid) {
-                ServiceWatcher.this.onPackageChanged(packageName);
-            }
-
-            @Override
-            public void onPackageAdded(String packageName, int uid) {
-                ServiceWatcher.this.onPackageChanged(packageName);
-            }
-
-            @Override
-            public void onPackageRemoved(String packageName, int uid) {
-                ServiceWatcher.this.onPackageChanged(packageName);
-            }
-
-            @Override
             public boolean onPackageChanged(String packageName, int uid, String[] components) {
-                ServiceWatcher.this.onPackageChanged(packageName);
-                return super.onPackageChanged(packageName, uid, components);
+                return true;
+            }
+
+            @Override
+            public void onSomePackagesChanged() {
+                onBestServiceChanged(false);
             }
         }.register(mContext, UserHandle.ALL, true, mHandler);
 
@@ -320,7 +309,7 @@
 
         if (!mTargetService.equals(ServiceInfo.NONE)) {
             if (D) {
-                Log.i(TAG, "[" + mIntent.getAction() + "] unbinding from " + mTargetService);
+                Log.d(TAG, "[" + mIntent.getAction() + "] unbinding from " + mTargetService);
             }
 
             mContext.unbindService(this);
@@ -335,9 +324,7 @@
 
         Preconditions.checkState(mTargetService.component != null);
 
-        if (D) {
-            Log.i(TAG, getLogPrefix() + " binding to " + mTargetService);
-        }
+        Log.i(TAG, getLogPrefix() + " binding to " + mTargetService);
 
         Intent bindIntent = new Intent(mIntent).setComponent(mTargetService.component);
         if (!mContext.bindServiceAsUser(bindIntent, this,
@@ -355,7 +342,7 @@
         Preconditions.checkState(mBinder == null);
 
         if (D) {
-            Log.i(TAG, getLogPrefix() + " connected to " + component.toShortString());
+            Log.d(TAG, getLogPrefix() + " connected to " + component.toShortString());
         }
 
         mBinder = binder;
@@ -379,7 +366,7 @@
         }
 
         if (D) {
-            Log.i(TAG, getLogPrefix() + " disconnected from " + component.toShortString());
+            Log.d(TAG, getLogPrefix() + " disconnected from " + component.toShortString());
         }
 
         mBinder = null;
@@ -392,13 +379,16 @@
     public final void onBindingDied(ComponentName component) {
         Preconditions.checkState(Looper.myLooper() == mHandler.getLooper());
 
-        if (D) {
-            Log.i(TAG, getLogPrefix() + " " + component.toShortString() + " died");
-        }
+        Log.i(TAG, getLogPrefix() + " " + component.toShortString() + " died");
 
         onBestServiceChanged(true);
     }
 
+    @Override
+    public final void onNullBinding(ComponentName component) {
+        Log.e(TAG, getLogPrefix() + " " + component.toShortString() + " has null binding");
+    }
+
     void onUserSwitched(@UserIdInt int userId) {
         mCurrentUserId = userId;
         onBestServiceChanged(false);
@@ -410,11 +400,6 @@
         }
     }
 
-    void onPackageChanged(String packageName) {
-        // force a rebind if the changed package was the currently connected package
-        onBestServiceChanged(packageName.equals(mTargetService.getPackageName()));
-    }
-
     /**
      * Runs the given function asynchronously if and only if currently connected. Suppresses any
      * RemoteException thrown during execution.
diff --git a/services/core/java/com/android/server/StorageManagerService.java b/services/core/java/com/android/server/StorageManagerService.java
index 63ac825..27c5d4a 100644
--- a/services/core/java/com/android/server/StorageManagerService.java
+++ b/services/core/java/com/android/server/StorageManagerService.java
@@ -55,6 +55,7 @@
 import static org.xmlpull.v1.XmlPullParser.START_TAG;
 
 import android.Manifest;
+import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.app.ActivityManager;
 import android.app.ActivityManagerInternal;
@@ -152,6 +153,7 @@
 import com.android.internal.util.IndentingPrintWriter;
 import com.android.internal.util.Preconditions;
 import com.android.internal.widget.LockPatternUtils;
+import com.android.server.SystemService.TargetUser;
 import com.android.server.pm.Installer;
 import com.android.server.storage.AppFuseBridge;
 import com.android.server.storage.StorageSessionController;
@@ -259,23 +261,23 @@
         }
 
         @Override
-        public void onSwitchUser(int userHandle) {
-            mStorageManagerService.mCurrentUserId = userHandle;
+        public void onUserSwitching(@Nullable TargetUser from, @NonNull TargetUser to) {
+            mStorageManagerService.mCurrentUserId = to.getUserIdentifier();
         }
 
         @Override
-        public void onUnlockUser(int userHandle) {
-            mStorageManagerService.onUnlockUser(userHandle);
+        public void onUserUnlocking(@NonNull TargetUser user) {
+            mStorageManagerService.onUnlockUser(user.getUserIdentifier());
         }
 
         @Override
-        public void onCleanupUser(int userHandle) {
-            mStorageManagerService.onCleanupUser(userHandle);
+        public void onUserStopped(@NonNull TargetUser user) {
+            mStorageManagerService.onCleanupUser(user.getUserIdentifier());
         }
 
         @Override
-        public void onStopUser(int userHandle) {
-            mStorageManagerService.onStopUser(userHandle);
+        public void onUserStopping(@NonNull TargetUser user) {
+            mStorageManagerService.onStopUser(user.getUserIdentifier());
         }
 
         @Override
@@ -1139,13 +1141,6 @@
     }
 
     private void completeUnlockUser(int userId) {
-        // If user 0 has completed unlock, perform a one-time migration of legacy obb data
-        // to its new location. This may take time depending on the size of the data to be copied
-        // so it's done on the StorageManager handler thread.
-        if (userId == 0) {
-            mPmInternal.migrateLegacyObbData();
-        }
-
         onKeyguardStateChanged(false);
 
         // Record user as started so newly mounted volumes kick off events
@@ -1538,9 +1533,21 @@
                 mFuseMountedUser.remove(vol.getMountUserId());
             } else if (mVoldAppDataIsolationEnabled){
                 final int userId = vol.getMountUserId();
-                mFuseMountedUser.add(userId);
                 // Async remount app storage so it won't block the main thread.
                 new Thread(() -> {
+
+                    // If user 0 has completed unlock, perform a one-time migration of legacy
+                    // obb data to its new location. This may take time depending on the size of
+                    // the data to be copied so it's done on the StorageManager worker thread.
+                    // This needs to be finished before start mounting obb directories.
+                    if (userId == 0) {
+                        mPmInternal.migrateLegacyObbData();
+                    }
+
+                    // Add fuse mounted user after migration to prevent ProcessList tries to
+                    // create obb directory before migration is done.
+                    mFuseMountedUser.add(userId);
+
                     Map<Integer, String> pidPkgMap = null;
                     // getProcessesWithPendingBindMounts() could fail when a new app process is
                     // starting and it's not planning to mount storage dirs in zygote, but it's
@@ -3280,7 +3287,7 @@
         final UserManagerInternal umInternal =
                 LocalServices.getService(UserManagerInternal.class);
 
-        for (UserInfo user : um.getUsers(false /* includeDying */)) {
+        for (UserInfo user : um.getUsers()) {
             final int flags;
             if (umInternal.isUserUnlockingOrUnlocked(user.id)) {
                 flags = StorageManager.FLAG_STORAGE_DE | StorageManager.FLAG_STORAGE_CE;
diff --git a/services/core/java/com/android/server/SystemService.java b/services/core/java/com/android/server/SystemService.java
index 45d53a1..84d01ec 100644
--- a/services/core/java/com/android/server/SystemService.java
+++ b/services/core/java/com/android/server/SystemService.java
@@ -132,45 +132,89 @@
      */
     @SystemApi(client = Client.SYSTEM_SERVER)
     public static final class TargetUser {
-        @NonNull
-        private final UserInfo mUserInfo;
+
+        // NOTE: attributes below must be immutable while ther user is running (i.e., from the
+        // moment it's started until after it's shutdown).
+        private final @UserIdInt int mUserId;
+        private final boolean mFull;
+        private final boolean mManagedProfile;
+        private final boolean mPreCreated;
 
         /** @hide */
         public TargetUser(@NonNull UserInfo userInfo) {
-            mUserInfo = userInfo;
+            mUserId = userInfo.id;
+            mFull = userInfo.isFull();
+            mManagedProfile = userInfo.isManagedProfile();
+            mPreCreated = userInfo.preCreated;
         }
 
         /**
-         * @return The information about the user. <b>NOTE: </b> this is a "live" object
-         * referenced by {@link UserManagerService} and hence should not be modified.
+         * Checks if the target user is {@link UserInfo#isFull() full}.
          *
          * @hide
          */
-        @NonNull
-        public UserInfo getUserInfo() {
-            return mUserInfo;
+        public boolean isFull() {
+            return mFull;
         }
 
         /**
-         * @return the target {@link UserHandle}.
+         * Checks if the target user is a managed profile.
+         *
+         * @hide
+         */
+        public boolean isManagedProfile() {
+            return mManagedProfile;
+        }
+
+        /**
+         * Checks if the target user is a pre-created user.
+         *
+         * @hide
+         */
+        public boolean isPreCreated() {
+            return mPreCreated;
+        }
+
+        /**
+         * Gets the target user's {@link UserHandle}.
          */
         @NonNull
         public UserHandle getUserHandle() {
-            return mUserInfo.getUserHandle();
+            return UserHandle.of(mUserId);
         }
 
         /**
-         * @return the integer user id
+         * Gets the target user's id.
          *
          * @hide
          */
-        public int getUserIdentifier() {
-            return mUserInfo.id;
+        public @UserIdInt int getUserIdentifier() {
+            return mUserId;
         }
 
         @Override
         public String toString() {
-            return Integer.toString(getUserIdentifier());
+            return Integer.toString(mUserId);
+        }
+
+        /**
+         * @hide
+         */
+        public void dump(@NonNull StringBuilder builder) {
+            builder.append(getUserIdentifier());
+
+            if (!isFull() && !isManagedProfile()) return;
+
+            builder.append('(');
+            boolean addComma = false;
+            if (isFull()) {
+                builder.append("full");
+            }
+            if (isManagedProfile()) {
+                if (addComma) builder.append(',');
+                builder.append("mp");
+            }
+            builder.append(')');
         }
     }
 
@@ -263,26 +307,6 @@
     }
 
     /**
-     * @deprecated subclasses should extend {@link #onUserStarting(TargetUser)} instead
-     * (which by default calls this method).
-     *
-     * @hide
-     */
-    @Deprecated
-    public void onStartUser(@UserIdInt int userId) {}
-
-    /**
-     * @deprecated subclasses should extend {@link #onUserStarting(TargetUser)} instead
-     * (which by default calls this method).
-     *
-     * @hide
-     */
-    @Deprecated
-    public void onStartUser(@NonNull UserInfo userInfo) {
-        onStartUser(userInfo.id);
-    }
-
-    /**
      * Called when a new user is starting, for system services to initialize any per-user
      * state they maintain for running users.
      *
@@ -292,27 +316,6 @@
      * @param user target user
      */
     public void onUserStarting(@NonNull TargetUser user) {
-        onStartUser(user.getUserInfo());
-    }
-
-    /**
-     * @deprecated subclasses should extend {@link #onUserUnlocking(TargetUser)} instead (which by
-     * default calls this method).
-     *
-     * @hide
-     */
-    @Deprecated
-    public void onUnlockUser(@UserIdInt int userId) {}
-
-    /**
-     * @deprecated subclasses should extend {@link #onUserUnlocking(TargetUser)} instead (which by
-     * default calls this method).
-     *
-     * @hide
-     */
-    @Deprecated
-    public void onUnlockUser(@NonNull UserInfo userInfo) {
-        onUnlockUser(userInfo.id);
     }
 
     /**
@@ -333,7 +336,6 @@
      * @param user target user
      */
     public void onUserUnlocking(@NonNull TargetUser user) {
-        onUnlockUser(user.getUserInfo());
     }
 
     /**
@@ -348,26 +350,6 @@
     }
 
     /**
-     * @deprecated subclasses should extend {@link #onUserSwitching(TargetUser, TargetUser)} instead
-     * (which by default calls this method).
-     *
-     * @hide
-     */
-    @Deprecated
-    public void onSwitchUser(@UserIdInt int toUserId) {}
-
-    /**
-     * @deprecated subclasses should extend {@link #onUserSwitching(TargetUser, TargetUser)} instead
-     * (which by default calls this method).
-     *
-     * @hide
-     */
-    @Deprecated
-    public void onSwitchUser(@Nullable UserInfo from, @NonNull UserInfo to) {
-        onSwitchUser(to.id);
-    }
-
-    /**
      * Called when switching to a different foreground user, for system services that have
      * special behavior for whichever user is currently in the foreground.  This is called
      * before any application processes are aware of the new user.
@@ -382,28 +364,6 @@
      * @param to the user switching to
      */
     public void onUserSwitching(@Nullable TargetUser from, @NonNull TargetUser to) {
-        onSwitchUser((from == null ? null : from.getUserInfo()), to.getUserInfo());
-    }
-
-    /**
-     * @deprecated subclasses should extend {@link #onUserStopping(TargetUser)} instead
-     * (which by default calls this method).
-     *
-     * @hide
-     */
-    @Deprecated
-    public void onStopUser(@UserIdInt int userId) {}
-
-    /**
-     * @deprecated subclasses should extend {@link #onUserStopping(TargetUser)} instead
-     * (which by default calls this method).
-     *
-     * @hide
-     */
-    @Deprecated
-    public void onStopUser(@NonNull UserInfo user) {
-        onStopUser(user.id);
-
     }
 
     /**
@@ -420,27 +380,6 @@
      * @param user target user
      */
     public void onUserStopping(@NonNull TargetUser user) {
-        onStopUser(user.getUserInfo());
-    }
-
-    /**
-     * @deprecated subclasses should extend {@link #onUserStopped(TargetUser)} instead (which by
-     * default calls this method).
-     *
-     * @hide
-     */
-    @Deprecated
-    public void onCleanupUser(@UserIdInt int userId) {}
-
-    /**
-     * @deprecated subclasses should extend {@link #onUserStopped(TargetUser)} instead (which by
-     * default calls this method).
-     *
-     * @hide
-     */
-    @Deprecated
-    public void onCleanupUser(@NonNull UserInfo user) {
-        onCleanupUser(user.id);
     }
 
     /**
@@ -454,7 +393,6 @@
      * @param user target user
      */
     public void onUserStopped(@NonNull TargetUser user) {
-        onCleanupUser(user.getUserInfo());
     }
 
     /**
diff --git a/services/core/java/com/android/server/SystemServiceManager.java b/services/core/java/com/android/server/SystemServiceManager.java
index 74bb7d7..34e6370 100644
--- a/services/core/java/com/android/server/SystemServiceManager.java
+++ b/services/core/java/com/android/server/SystemServiceManager.java
@@ -27,7 +27,10 @@
 import android.os.UserManagerInternal;
 import android.util.ArrayMap;
 import android.util.Slog;
+import android.util.SparseArray;
 
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.util.Preconditions;
 import com.android.server.SystemService.TargetUser;
 import com.android.server.utils.TimingsTraceAndSlog;
 
@@ -44,8 +47,8 @@
  *
  * {@hide}
  */
-public class SystemServiceManager {
-    private static final String TAG = "SystemServiceManager";
+public final class SystemServiceManager {
+    private static final String TAG = SystemServiceManager.class.getSimpleName();
     private static final boolean DEBUG = false;
     private static final int SERVICE_CALL_WARN_TIME_MS = 50;
 
@@ -74,6 +77,13 @@
 
     private UserManagerInternal mUserManagerInternal;
 
+    /**
+     * Map of started {@link TargetUser TargetUsers} by user id; users are added on start and
+     * removed after they're completely shut down.
+     */
+    @GuardedBy("mTargetUsers")
+    private final SparseArray<TargetUser> mTargetUsers = new SparseArray<>();
+
     SystemServiceManager(Context context) {
         mContext = context;
     }
@@ -240,75 +250,85 @@
         mUserManagerInternal = LocalServices.getService(UserManagerInternal.class);
     }
 
-    private @NonNull UserInfo getUserInfo(@UserIdInt int userHandle) {
-        if (mUserManagerInternal == null) {
-            throw new IllegalStateException("mUserManagerInternal not set yet");
+    private @NonNull TargetUser getTargetUser(@UserIdInt int userId) {
+        final TargetUser targetUser;
+        synchronized (mTargetUsers) {
+            targetUser = mTargetUsers.get(userId);
         }
-        final UserInfo userInfo = mUserManagerInternal.getUserInfo(userHandle);
-        if (userInfo == null) {
-            throw new IllegalStateException("No UserInfo for " + userHandle);
-        }
-        return userInfo;
+        Preconditions.checkState(targetUser != null, "No TargetUser for " + userId);
+        return targetUser;
     }
 
     /**
      * Starts the given user.
      */
-    public void startUser(final @NonNull TimingsTraceAndSlog t, final @UserIdInt int userHandle) {
-        onUser(t, START, userHandle);
+    public void startUser(@NonNull TimingsTraceAndSlog t, @UserIdInt int userId) {
+        // Create cached TargetUser
+        final UserInfo userInfo = mUserManagerInternal.getUserInfo(userId);
+        Preconditions.checkState(userInfo != null, "No UserInfo for " + userId);
+        synchronized (mTargetUsers) {
+            mTargetUsers.put(userId, new TargetUser(userInfo));
+        }
+
+        onUser(t, START, userId);
     }
 
     /**
      * Unlocks the given user.
      */
-    public void unlockUser(final @UserIdInt int userHandle) {
-        onUser(UNLOCKING, userHandle);
+    public void unlockUser(@UserIdInt int userId) {
+        onUser(UNLOCKING, userId);
     }
 
     /**
      * Called after the user was unlocked.
      */
-    public void onUserUnlocked(final @UserIdInt int userHandle) {
-        onUser(UNLOCKED, userHandle);
+    public void onUserUnlocked(@UserIdInt int userId) {
+        onUser(UNLOCKED, userId);
     }
 
     /**
      * Switches to the given user.
      */
-    public void switchUser(final @UserIdInt int from, final @UserIdInt int to) {
+    public void switchUser(@UserIdInt int from, @UserIdInt int to) {
         onUser(TimingsTraceAndSlog.newAsyncLog(), SWITCH, to, from);
     }
 
     /**
      * Stops the given user.
      */
-    public void stopUser(final @UserIdInt int userHandle) {
-        onUser(STOP, userHandle);
+    public void stopUser(@UserIdInt int userId) {
+        onUser(STOP, userId);
     }
 
     /**
      * Cleans up the given user.
      */
-    public void cleanupUser(final @UserIdInt int userHandle) {
-        onUser(CLEANUP, userHandle);
+    public void cleanupUser(@UserIdInt int userId) {
+        onUser(CLEANUP, userId);
+
+        // Remove cached TargetUser
+        synchronized (mTargetUsers) {
+            mTargetUsers.remove(userId);
+        }
     }
 
-    private void onUser(@NonNull String onWhat, @UserIdInt int userHandle) {
-        onUser(TimingsTraceAndSlog.newAsyncLog(), onWhat, userHandle);
+    private void onUser(@NonNull String onWhat, @UserIdInt int userId) {
+        onUser(TimingsTraceAndSlog.newAsyncLog(), onWhat, userId);
     }
 
     private void onUser(@NonNull TimingsTraceAndSlog t, @NonNull String onWhat,
-            @UserIdInt int userHandle) {
-        onUser(t, onWhat, userHandle, UserHandle.USER_NULL);
+            @UserIdInt int userId) {
+        onUser(t, onWhat, userId, UserHandle.USER_NULL);
     }
 
     private void onUser(@NonNull TimingsTraceAndSlog t, @NonNull String onWhat,
             @UserIdInt int curUserId, @UserIdInt int prevUserId) {
         t.traceBegin("ssm." + onWhat + "User-" + curUserId);
         Slog.i(TAG, "Calling on" + onWhat + "User " + curUserId);
-        final TargetUser curUser = new TargetUser(getUserInfo(curUserId));
+        final TargetUser curUser = getTargetUser(curUserId);
         final TargetUser prevUser = prevUserId == UserHandle.USER_NULL ? null
-                : new TargetUser(getUserInfo(prevUserId));
+                : getTargetUser(prevUserId);
         final int serviceLen = mServices.size();
         for (int i = 0; i < serviceLen; i++) {
             final SystemService service = mServices.get(i);
@@ -437,7 +457,7 @@
      */
     public void dump() {
         StringBuilder builder = new StringBuilder();
-        builder.append("Current phase: ").append(mCurrentPhase).append("\n");
+        builder.append("Current phase: ").append(mCurrentPhase).append('\n');
         builder.append("Services:\n");
         final int startedLen = mServices.size();
         for (int i = 0; i < startedLen; i++) {
@@ -447,6 +467,14 @@
                     .append("\n");
         }
 
+        builder.append("Target users: ");
+        final int targetUsersSize = mTargetUsers.size();
+        for (int i = 0; i < targetUsersSize; i++) {
+            mTargetUsers.valueAt(i).dump(builder);
+            if (i != targetUsersSize - 1) builder.append(',');
+        }
+        builder.append('\n');
+
         Slog.e(TAG, builder.toString());
     }
 }
diff --git a/services/core/java/com/android/server/TelephonyRegistry.java b/services/core/java/com/android/server/TelephonyRegistry.java
index 7381da1..eb8308b 100644
--- a/services/core/java/com/android/server/TelephonyRegistry.java
+++ b/services/core/java/com/android/server/TelephonyRegistry.java
@@ -34,7 +34,6 @@
 import android.content.Intent;
 import android.content.IntentFilter;
 import android.content.pm.PackageManager;
-import android.net.LinkProperties;
 import android.os.Binder;
 import android.os.Build;
 import android.os.Bundle;
@@ -46,8 +45,6 @@
 import android.os.UserHandle;
 import android.provider.DeviceConfig;
 import android.telephony.Annotation;
-import android.telephony.Annotation.ApnType;
-import android.telephony.Annotation.DataFailureCause;
 import android.telephony.Annotation.RadioPowerState;
 import android.telephony.Annotation.SrvccState;
 import android.telephony.BarringInfo;
@@ -80,7 +77,9 @@
 import android.telephony.data.ApnSetting;
 import android.telephony.emergency.EmergencyNumber;
 import android.telephony.ims.ImsReasonInfo;
+import android.util.ArrayMap;
 import android.util.LocalLog;
+import android.util.Pair;
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.app.IBatteryStats;
@@ -103,6 +102,7 @@
 import java.util.List;
 import java.util.Map;
 import java.util.NoSuchElementException;
+import java.util.Objects;
 
 /**
  * Since phone process can be restarted, this class provides a centralized place
@@ -302,19 +302,23 @@
     @RadioPowerState
     private int mRadioPowerState = TelephonyManager.RADIO_POWER_UNAVAILABLE;
 
-    private final LocalLog mLocalLog = new LocalLog(100);
+    private final LocalLog mLocalLog = new LocalLog(200);
 
-    private final LocalLog mListenLog = new LocalLog(100);
+    private final LocalLog mListenLog = new LocalLog(00);
 
-    // Per-phoneMap of APN Type to DataConnectionState
-    private List<Map<Integer, PreciseDataConnectionState>> mPreciseDataConnectionStates =
-            new ArrayList<Map<Integer, PreciseDataConnectionState>>();
+    /**
+     * Per-phone map of precise data connection state. The key of the map is the pair of transport
+     * type and APN setting. This is the cache to prevent redundant callbacks to the listeners.
+     * A precise data connection with state {@link TelephonyManager#DATA_DISCONNECTED} removes
+     * its entry from the map.
+     */
+    private List<Map<Pair<Integer, ApnSetting>, PreciseDataConnectionState>>
+            mPreciseDataConnectionStates;
 
-    static final int ENFORCE_COARSE_LOCATION_PERMISSION_MASK =
-            PhoneStateListener.LISTEN_REGISTRATION_FAILURE
-                    | PhoneStateListener.LISTEN_BARRING_INFO;
-
-    static final int ENFORCE_FINE_LOCATION_PERMISSION_MASK =
+    // Starting in Q, almost all cellular location requires FINE location enforcement.
+    // Prior to Q, cellular was available with COARSE location enforcement. Bits in this
+    // list will be checked for COARSE on apps targeting P or earlier and FINE on Q or later.
+    static final int ENFORCE_LOCATION_PERMISSION_MASK =
             PhoneStateListener.LISTEN_CELL_LOCATION
                     | PhoneStateListener.LISTEN_CELL_INFO
                     | PhoneStateListener.LISTEN_REGISTRATION_FAILURE
@@ -371,7 +375,7 @@
                                 + " newDefaultPhoneId=" + newDefaultPhoneId);
                     }
 
-                    //Due to possible risk condition,(notify call back using the new
+                    //Due to possible race condition,(notify call back using the new
                     //defaultSubId comes before new defaultSubId update) we need to recall all
                     //possible missed notify callback
                     synchronized (mRecords) {
@@ -521,7 +525,7 @@
             mRingingCallState[i] = PreciseCallState.PRECISE_CALL_STATE_IDLE;
             mForegroundCallState[i] = PreciseCallState.PRECISE_CALL_STATE_IDLE;
             mBackgroundCallState[i] = PreciseCallState.PRECISE_CALL_STATE_IDLE;
-            mPreciseDataConnectionStates.add(new HashMap<Integer, PreciseDataConnectionState>());
+            mPreciseDataConnectionStates.add(new ArrayMap<>());
             mBarringInfo.add(i, new BarringInfo());
             mTelephonyDisplayInfos[i] = null;
         }
@@ -610,7 +614,7 @@
             mRingingCallState[i] = PreciseCallState.PRECISE_CALL_STATE_IDLE;
             mForegroundCallState[i] = PreciseCallState.PRECISE_CALL_STATE_IDLE;
             mBackgroundCallState[i] = PreciseCallState.PRECISE_CALL_STATE_IDLE;
-            mPreciseDataConnectionStates.add(new HashMap<Integer, PreciseDataConnectionState>());
+            mPreciseDataConnectionStates.add(new ArrayMap<>());
             mBarringInfo.add(i, new BarringInfo());
             mTelephonyDisplayInfos[i] = null;
         }
@@ -904,7 +908,8 @@
                     if (validateEventsAndUserLocked(r, PhoneStateListener.LISTEN_CELL_LOCATION)) {
                         try {
                             if (DBG_LOC) log("listen: mCellIdentity = " + mCellIdentity[phoneId]);
-                            if (checkFineLocationAccess(r, Build.VERSION_CODES.Q)) {
+                            if (checkCoarseLocationAccess(r, Build.VERSION_CODES.BASE)
+                                    && checkFineLocationAccess(r, Build.VERSION_CODES.Q)) {
                                 // null will be translated to empty CellLocation object in client.
                                 r.callback.onCellLocationChanged(mCellIdentity[phoneId]);
                             }
@@ -959,7 +964,8 @@
                         try {
                             if (DBG_LOC) log("listen: mCellInfo[" + phoneId + "] = "
                                     + mCellInfo.get(phoneId));
-                            if (checkFineLocationAccess(r, Build.VERSION_CODES.Q)) {
+                            if (checkCoarseLocationAccess(r, Build.VERSION_CODES.BASE)
+                                    && checkFineLocationAccess(r, Build.VERSION_CODES.Q)) {
                                 r.callback.onCellInfoChanged(mCellInfo.get(phoneId));
                             }
                         } catch (RemoteException ex) {
@@ -1513,7 +1519,8 @@
                 for (Record r : mRecords) {
                     if (validateEventsAndUserLocked(r, PhoneStateListener.LISTEN_CELL_INFO) &&
                             idMatch(r.subId, subId, phoneId) &&
-                            checkFineLocationAccess(r, Build.VERSION_CODES.Q)) {
+                            (checkCoarseLocationAccess(r, Build.VERSION_CODES.BASE)
+                                    && checkFineLocationAccess(r, Build.VERSION_CODES.Q))) {
                         try {
                             if (DBG_LOC) {
                                 log("notifyCellInfoForSubscriber: mCellInfo=" + cellInfo
@@ -1687,38 +1694,25 @@
      *
      * @param phoneId the phoneId carrying the data connection
      * @param subId the subscriptionId for the data connection
-     * @param apnType the apn type bitmask, defined with {@code ApnSetting#TYPE_*} flags.
      * @param preciseState a PreciseDataConnectionState that has info about the data connection
      */
     @Override
-    public void notifyDataConnectionForSubscriber(
-            int phoneId, int subId, @ApnType int apnType, PreciseDataConnectionState preciseState) {
+    public void notifyDataConnectionForSubscriber(int phoneId, int subId,
+            @NonNull PreciseDataConnectionState preciseState) {
         if (!checkNotifyPermission("notifyDataConnection()" )) {
             return;
         }
 
-        String apn = "";
-        int state = TelephonyManager.DATA_UNKNOWN;
-        int networkType = TelephonyManager.NETWORK_TYPE_UNKNOWN;
-        LinkProperties linkProps = null;
+        ApnSetting apnSetting = preciseState.getApnSetting();
 
-        if (preciseState != null) {
-            apn = preciseState.getDataConnectionApn();
-            state = preciseState.getState();
-            networkType = preciseState.getNetworkType();
-            linkProps = preciseState.getLinkProperties();
-        }
-        if (VDBG) {
-            log("notifyDataConnectionForSubscriber: subId=" + subId
-                    + " state=" + state + "' apn='" + apn
-                    + "' apnType=" + apnType + " networkType=" + networkType
-                    + "' preciseState=" + preciseState);
-        }
+        int apnTypes = apnSetting.getApnTypeBitmask();
+        int state = preciseState.getState();
+        int networkType = preciseState.getNetworkType();
 
         synchronized (mRecords) {
             if (validatePhoneId(phoneId)) {
                 // We only call the callback when the change is for default APN type.
-                if ((ApnSetting.TYPE_DEFAULT & apnType) != 0
+                if ((ApnSetting.TYPE_DEFAULT & apnTypes) != 0
                         && (mDataConnectionState[phoneId] != state
                         || mDataConnectionNetworkType[phoneId] != networkType)) {
                     String str = "onDataConnectionStateChanged("
@@ -1747,19 +1741,11 @@
                     mDataConnectionNetworkType[phoneId] = networkType;
                 }
 
-                boolean needsNotify = false;
-                // State has been cleared for this APN Type
-                if (preciseState == null) {
-                    // We try clear the state and check if the state was previously not cleared
-                    needsNotify = mPreciseDataConnectionStates.get(phoneId).remove(apnType) != null;
-                } else {
-                    // We need to check to see if the state actually changed
-                    PreciseDataConnectionState oldPreciseState =
-                            mPreciseDataConnectionStates.get(phoneId).put(apnType, preciseState);
-                    needsNotify = !preciseState.equals(oldPreciseState);
-                }
-
-                if (needsNotify) {
+                Pair<Integer, ApnSetting> key = Pair.create(preciseState.getTransportType(),
+                        preciseState.getApnSetting());
+                PreciseDataConnectionState oldState = mPreciseDataConnectionStates.get(phoneId)
+                        .remove(key);
+                if (!Objects.equals(oldState, preciseState)) {
                     for (Record r : mRecords) {
                         if (r.matchPhoneStateListenerEvent(
                                 PhoneStateListener.LISTEN_PRECISE_DATA_CONNECTION_STATE)
@@ -1771,54 +1757,22 @@
                             }
                         }
                     }
+                    handleRemoveListLocked();
+
+                    broadcastDataConnectionStateChanged(phoneId, subId, preciseState);
+
+                    String str = "notifyDataConnectionForSubscriber: phoneId=" + phoneId + " subId="
+                            + subId + " " + preciseState;
+                    log(str);
+                    mLocalLog.log(str);
+                }
+
+                // If the state is disconnected, it would be the end of life cycle of a data
+                // connection, so remove it from the cache.
+                if (preciseState.getState() != TelephonyManager.DATA_DISCONNECTED) {
+                    mPreciseDataConnectionStates.get(phoneId).put(key, preciseState);
                 }
             }
-            handleRemoveListLocked();
-        }
-
-        broadcastDataConnectionStateChanged(state, apn, apnType, subId);
-    }
-
-    /**
-     * Stub to satisfy the ITelephonyRegistry aidl interface; do not use this function.
-     * @see #notifyDataConnectionFailedForSubscriber
-     */
-    public void notifyDataConnectionFailed(String apnType) {
-        loge("This function should not be invoked");
-    }
-
-    private void notifyDataConnectionFailedForSubscriber(int phoneId, int subId, int apnType) {
-        if (!checkNotifyPermission("notifyDataConnectionFailed()")) {
-            return;
-        }
-        if (VDBG) {
-            log("notifyDataConnectionFailedForSubscriber: subId=" + subId
-                    + " apnType=" + apnType);
-        }
-        synchronized (mRecords) {
-            if (validatePhoneId(phoneId)) {
-                mPreciseDataConnectionStates.get(phoneId).put(
-                        apnType,
-                        new PreciseDataConnectionState.Builder()
-                                .setApnSetting(new ApnSetting.Builder()
-                                        .setApnTypeBitmask(apnType)
-                                        .build())
-                                .build());
-                for (Record r : mRecords) {
-                    if (r.matchPhoneStateListenerEvent(
-                            PhoneStateListener.LISTEN_PRECISE_DATA_CONNECTION_STATE)
-                            && idMatch(r.subId, subId, phoneId)) {
-                        try {
-                            r.callback.onPreciseDataConnectionStateChanged(
-                                    mPreciseDataConnectionStates.get(phoneId).get(apnType));
-                        } catch (RemoteException ex) {
-                            mRemoveList.add(r.binder);
-                        }
-                    }
-                }
-            }
-
-            handleRemoveListLocked();
         }
     }
 
@@ -1845,7 +1799,8 @@
                 for (Record r : mRecords) {
                     if (validateEventsAndUserLocked(r, PhoneStateListener.LISTEN_CELL_LOCATION) &&
                             idMatch(r.subId, subId, phoneId) &&
-                            checkFineLocationAccess(r, Build.VERSION_CODES.Q)) {
+                            (checkCoarseLocationAccess(r, Build.VERSION_CODES.BASE)
+                                    && checkFineLocationAccess(r, Build.VERSION_CODES.Q))) {
                         try {
                             if (DBG_LOC) {
                                 log("notifyCellLocation: cellLocation=" + cellLocation
@@ -1972,43 +1927,6 @@
     }
 
     @Override
-    public void notifyPreciseDataConnectionFailed(int phoneId, int subId, @ApnType int apnType,
-            String apn, @DataFailureCause int failCause) {
-        if (!checkNotifyPermission("notifyPreciseDataConnectionFailed()")) {
-            return;
-        }
-
-        // precise notify invokes imprecise notify
-        notifyDataConnectionFailedForSubscriber(phoneId, subId, apnType);
-
-        synchronized (mRecords) {
-            if (validatePhoneId(phoneId)) {
-                mPreciseDataConnectionStates.get(phoneId).put(
-                        apnType,
-                        new PreciseDataConnectionState.Builder()
-                                .setApnSetting(new ApnSetting.Builder()
-                                        .setApnTypeBitmask(apnType)
-                                        .build())
-                                .setFailCause(failCause)
-                                .build());
-                for (Record r : mRecords) {
-                    if (r.matchPhoneStateListenerEvent(
-                            PhoneStateListener.LISTEN_PRECISE_DATA_CONNECTION_STATE)
-                            && idMatch(r.subId, subId, phoneId)) {
-                        try {
-                            r.callback.onPreciseDataConnectionStateChanged(
-                                    mPreciseDataConnectionStates.get(phoneId).get(apnType));
-                        } catch (RemoteException ex) {
-                            mRemoveList.add(r.binder);
-                        }
-                    }
-                }
-            }
-            handleRemoveListLocked();
-        }
-    }
-
-    @Override
     public void notifySrvccStateChanged(int subId, @SrvccState int state) {
         if (!checkNotifyPermission("notifySrvccStateChanged()")) {
             return;
@@ -2188,20 +2106,20 @@
         synchronized (mRecords) {
             if (validatePhoneId(phoneId)) {
                 mOutgoingCallEmergencyNumber[phoneId] = emergencyNumber;
-                for (Record r : mRecords) {
-                    if (r.matchPhoneStateListenerEvent(
-                            PhoneStateListener.LISTEN_OUTGOING_EMERGENCY_CALL)
-                                    && idMatch(r.subId, subId, phoneId)) {
-                        try {
-                            r.callback.onOutgoingEmergencyCall(emergencyNumber);
-                        } catch (RemoteException ex) {
-                            mRemoveList.add(r.binder);
-                        }
+            }
+            for (Record r : mRecords) {
+                // Send to all listeners regardless of subscription
+                if (r.matchPhoneStateListenerEvent(
+                        PhoneStateListener.LISTEN_OUTGOING_EMERGENCY_CALL)) {
+                    try {
+                        r.callback.onOutgoingEmergencyCall(emergencyNumber, subId);
+                    } catch (RemoteException ex) {
+                        mRemoveList.add(r.binder);
                     }
                 }
             }
-            handleRemoveListLocked();
         }
+        handleRemoveListLocked();
     }
 
     @Override
@@ -2578,16 +2496,18 @@
         }
     }
 
-    private void broadcastDataConnectionStateChanged(int state, String apn,
-                                                     int apnType, int subId) {
+    private void broadcastDataConnectionStateChanged(int slotIndex, int subId,
+            @NonNull PreciseDataConnectionState pdcs) {
         // Note: not reporting to the battery stats service here, because the
         // status bar takes care of that after taking into account all of the
         // required info.
         Intent intent = new Intent(ACTION_ANY_DATA_CONNECTION_STATE_CHANGED);
-        intent.putExtra(PHONE_CONSTANTS_STATE_KEY, TelephonyUtils.dataStateToString(state));
-        intent.putExtra(PHONE_CONSTANTS_DATA_APN_KEY, apn);
+        intent.putExtra(PHONE_CONSTANTS_STATE_KEY,
+                TelephonyUtils.dataStateToString(pdcs.getState()));
+        intent.putExtra(PHONE_CONSTANTS_DATA_APN_KEY, pdcs.getApnSetting().getApnName());
         intent.putExtra(PHONE_CONSTANTS_DATA_APN_TYPE_KEY,
-                ApnSetting.getApnTypesStringFromBitmask(apnType));
+                ApnSetting.getApnTypesStringFromBitmask(pdcs.getApnSetting().getApnTypeBitmask()));
+        intent.putExtra(PHONE_CONSTANTS_SLOT_KEY, slotIndex);
         intent.putExtra(PHONE_CONSTANTS_SUBSCRIPTION_KEY, subId);
         mContext.sendBroadcastAsUser(intent, UserHandle.ALL, Manifest.permission.READ_PHONE_STATE);
     }
@@ -2627,16 +2547,11 @@
 
         boolean shouldCheckLocationPermissions = false;
 
-        if ((events & ENFORCE_FINE_LOCATION_PERMISSION_MASK) != 0) {
+        if ((events & ENFORCE_LOCATION_PERMISSION_MASK) != 0) {
             // Everything that requires fine location started in Q. So far...
             locationQueryBuilder.setMinSdkVersionForFine(Build.VERSION_CODES.Q);
-            // If we're enforcing fine starting in Q, we also want to enforce coarse starting in Q.
-            locationQueryBuilder.setMinSdkVersionForCoarse(Build.VERSION_CODES.Q);
-            locationQueryBuilder.setMinSdkVersionForEnforcement(Build.VERSION_CODES.Q);
-            shouldCheckLocationPermissions = true;
-        }
-
-        if ((events & ENFORCE_COARSE_LOCATION_PERMISSION_MASK) != 0) {
+            // If we're enforcing fine starting in Q, we also want to enforce coarse even for
+            // older SDK versions.
             locationQueryBuilder.setMinSdkVersionForCoarse(0);
             locationQueryBuilder.setMinSdkVersionForEnforcement(0);
             shouldCheckLocationPermissions = true;
@@ -2833,8 +2748,16 @@
             try {
                 if (VDBG) log("checkPossibleMissNotify: onServiceStateChanged state=" +
                         mServiceState[phoneId]);
-                r.callback.onServiceStateChanged(
-                        new ServiceState(mServiceState[phoneId]));
+                ServiceState ss = new ServiceState(mServiceState[phoneId]);
+                if (checkFineLocationAccess(r, Build.VERSION_CODES.Q)) {
+                    r.callback.onServiceStateChanged(ss);
+                } else if (checkCoarseLocationAccess(r, Build.VERSION_CODES.Q)) {
+                    r.callback.onServiceStateChanged(
+                            ss.createLocationInfoSanitizedCopy(false));
+                } else {
+                    r.callback.onServiceStateChanged(
+                            ss.createLocationInfoSanitizedCopy(true));
+                }
             } catch (RemoteException ex) {
                 mRemoveList.add(r.binder);
             }
@@ -2879,7 +2802,8 @@
                     log("checkPossibleMissNotify: onCellInfoChanged[" + phoneId + "] = "
                             + mCellInfo.get(phoneId));
                 }
-                if (checkFineLocationAccess(r, Build.VERSION_CODES.Q)) {
+                if (checkCoarseLocationAccess(r, Build.VERSION_CODES.BASE)
+                        && checkFineLocationAccess(r, Build.VERSION_CODES.Q)) {
                     r.callback.onCellInfoChanged(mCellInfo.get(phoneId));
                 }
             } catch (RemoteException ex) {
@@ -2945,7 +2869,8 @@
                     log("checkPossibleMissNotify: onCellLocationChanged mCellIdentity = "
                             + mCellIdentity[phoneId]);
                 }
-                if (checkFineLocationAccess(r, Build.VERSION_CODES.Q)) {
+                if (checkCoarseLocationAccess(r, Build.VERSION_CODES.BASE)
+                        && checkFineLocationAccess(r, Build.VERSION_CODES.Q)) {
                     // null will be translated to empty CellLocation object in client.
                     r.callback.onCellLocationChanged(mCellIdentity[phoneId]);
                 }
@@ -2973,7 +2898,7 @@
     /**
      * Returns a string representation of the radio technology (network type)
      * currently in use on the device.
-     * @param subId for which network type is returned
+     * @param type for which network type is returned
      * @return the name of the radio technology
      *
      */
diff --git a/services/core/java/com/android/server/UiModeManagerService.java b/services/core/java/com/android/server/UiModeManagerService.java
index 915189c..6dbb1e92 100644
--- a/services/core/java/com/android/server/UiModeManagerService.java
+++ b/services/core/java/com/android/server/UiModeManagerService.java
@@ -16,7 +16,15 @@
 
 package com.android.server;
 
+import static android.app.UiModeManager.DEFAULT_PRIORITY;
+import static android.app.UiModeManager.MODE_NIGHT_AUTO;
+import static android.app.UiModeManager.MODE_NIGHT_CUSTOM;
+import static android.app.UiModeManager.MODE_NIGHT_YES;
+import static android.os.UserHandle.USER_SYSTEM;
+import static android.util.TimeUtils.isTimeBetween;
+
 import android.annotation.IntRange;
+import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.app.Activity;
 import android.app.ActivityManager;
@@ -64,6 +72,7 @@
 import com.android.internal.messages.nano.SystemMessageProto.SystemMessage;
 import com.android.internal.notification.SystemNotificationChannels;
 import com.android.internal.util.DumpUtils;
+import com.android.server.SystemService.TargetUser;
 import com.android.server.twilight.TwilightListener;
 import com.android.server.twilight.TwilightManager;
 import com.android.server.twilight.TwilightState;
@@ -81,13 +90,6 @@
 import java.util.Map;
 import java.util.Set;
 
-import static android.app.UiModeManager.DEFAULT_PRIORITY;
-import static android.app.UiModeManager.MODE_NIGHT_AUTO;
-import static android.app.UiModeManager.MODE_NIGHT_CUSTOM;
-import static android.app.UiModeManager.MODE_NIGHT_YES;
-import static android.os.UserHandle.USER_SYSTEM;
-import static android.util.TimeUtils.isTimeBetween;
-
 final class UiModeManagerService extends SystemService {
     private static final String TAG = UiModeManager.class.getSimpleName();
     private static final boolean LOG = false;
@@ -135,6 +137,7 @@
     int mCurUiMode = 0;
     private int mSetUiMode = 0;
     private boolean mHoldingConfiguration = false;
+    private int mCurrentUser;
 
     private Configuration mConfiguration = new Configuration();
     boolean mSystemReady;
@@ -322,8 +325,8 @@
     }
 
     @Override
-    public void onSwitchUser(int userHandle) {
-        super.onSwitchUser(userHandle);
+    public void onUserSwitching(@Nullable TargetUser from, @NonNull TargetUser to) {
+        mCurrentUser = to.getUserIdentifier();
         getContext().getContentResolver().unregisterContentObserver(mSetupWizardObserver);
         verifySetupWizardCompleted();
     }
@@ -726,16 +729,30 @@
 
         @Override
         public boolean setNightModeActivated(boolean active) {
+            if (isNightModeLocked() && (getContext().checkCallingOrSelfPermission(
+                    android.Manifest.permission.MODIFY_DAY_NIGHT_MODE)
+                    != PackageManager.PERMISSION_GRANTED)) {
+                Slog.e(TAG, "Night mode locked, requires MODIFY_DAY_NIGHT_MODE permission");
+                return false;
+            }
+            final int user = Binder.getCallingUserHandle().getIdentifier();
+            if (user != mCurrentUser && getContext().checkCallingOrSelfPermission(
+                    android.Manifest.permission.INTERACT_ACROSS_USERS)
+                    != PackageManager.PERMISSION_GRANTED) {
+                Slog.e(TAG, "Target user is not current user,"
+                        + " INTERACT_ACROSS_USERS permission is required");
+                return false;
+
+            }
             synchronized (mLock) {
-                final int user = UserHandle.getCallingUserId();
                 final long ident = Binder.clearCallingIdentity();
                 try {
                     if (mNightMode == MODE_NIGHT_AUTO || mNightMode == MODE_NIGHT_CUSTOM) {
                         unregisterScreenOffEventLocked();
                         mOverrideNightModeOff = !active;
                         mOverrideNightModeOn = active;
-                        mOverrideNightModeUser = user;
-                        persistNightModeOverrides(user);
+                        mOverrideNightModeUser = mCurrentUser;
+                        persistNightModeOverrides(mCurrentUser);
                     } else if (mNightMode == UiModeManager.MODE_NIGHT_NO
                             && active) {
                         mNightMode = UiModeManager.MODE_NIGHT_YES;
@@ -745,7 +762,7 @@
                     }
                     updateConfigurationLocked();
                     applyConfigurationExternallyLocked();
-                    persistNightMode(user);
+                    persistNightMode(mCurrentUser);
                     return true;
                 } finally {
                     Binder.restoreCallingIdentity(ident);
diff --git a/services/core/java/com/android/server/VibratorService.java b/services/core/java/com/android/server/VibratorService.java
index 72f29b4..96d973e 100644
--- a/services/core/java/com/android/server/VibratorService.java
+++ b/services/core/java/com/android/server/VibratorService.java
@@ -76,6 +76,8 @@
 import com.android.internal.util.DumpUtils;
 import com.android.internal.util.FrameworkStatsLog;
 
+import libcore.util.NativeAllocationRegistry;
+
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
 import java.util.ArrayList;
@@ -112,10 +114,6 @@
     private static final VibrationAttributes DEFAULT_ATTRIBUTES =
             new VibrationAttributes.Builder().build();
 
-    // If HAL supports callbacks set the timeout to ASYNC_TIMEOUT_MULTIPLIER * duration.
-    private static final long ASYNC_TIMEOUT_MULTIPLIER = 2;
-
-
     // A mapping from the intensity adjustment to the scaling to apply, where the intensity
     // adjustment is defined as the delta between the default intensity level and the user selected
     // intensity level. It's important that we apply the scaling on the delta between the two so
@@ -128,9 +126,8 @@
     private final LinkedList<VibrationInfo> mPreviousVibrations;
     private final int mPreviousVibrationsLimit;
     private final boolean mAllowPriorityVibrationsInLowPowerMode;
-    private final boolean mSupportsAmplitudeControl;
-    private final boolean mSupportsExternalControl;
     private final List<Integer> mSupportedEffects;
+    private final List<Integer> mSupportedPrimitives;
     private final long mCapabilities;
     private final int mDefaultVibrationAmplitude;
     private final SparseArray<VibrationEffect> mFallbackEffects;
@@ -168,28 +165,40 @@
     private boolean mIsVibrating;
     @GuardedBy("mLock")
     private final RemoteCallbackList<IVibratorStateListener> mVibratorStateListeners =
-                new RemoteCallbackList<>();
+            new RemoteCallbackList<>();
     private int mHapticFeedbackIntensity;
     private int mNotificationIntensity;
     private int mRingIntensity;
     private SparseArray<Vibration> mAlwaysOnEffects = new SparseArray<>();
 
-    static native boolean vibratorExists();
-    static native void vibratorInit();
-    static native void vibratorOn(long milliseconds);
-    static native void vibratorOff();
-    static native boolean vibratorSupportsAmplitudeControl();
-    static native void vibratorSetAmplitude(int amplitude);
-    static native int[] vibratorGetSupportedEffects();
-    static native long vibratorPerformEffect(long effect, long strength, Vibration vibration,
-            boolean withCallback);
-    static native void vibratorPerformComposedEffect(
+    static native long vibratorInit();
+
+    static native long vibratorGetFinalizer();
+
+    static native boolean vibratorExists(long controllerPtr);
+
+    static native void vibratorOn(long controllerPtr, long milliseconds, Vibration vibration);
+
+    static native void vibratorOff(long controllerPtr);
+
+    static native void vibratorSetAmplitude(long controllerPtr, int amplitude);
+
+    static native int[] vibratorGetSupportedEffects(long controllerPtr);
+
+    static native int[] vibratorGetSupportedPrimitives(long controllerPtr);
+
+    static native long vibratorPerformEffect(
+            long controllerPtr, long effect, long strength, Vibration vibration);
+
+    static native void vibratorPerformComposedEffect(long controllerPtr,
             VibrationEffect.Composition.PrimitiveEffect[] effect, Vibration vibration);
-    static native boolean vibratorSupportsExternalControl();
-    static native void vibratorSetExternalControl(boolean enabled);
-    static native long vibratorGetCapabilities();
-    static native void vibratorAlwaysOnEnable(long id, long effect, long strength);
-    static native void vibratorAlwaysOnDisable(long id);
+
+    static native void vibratorSetExternalControl(long controllerPtr, boolean enabled);
+
+    static native long vibratorGetCapabilities(long controllerPtr);
+    static native void vibratorAlwaysOnEnable(long controllerPtr, long id, long effect,
+            long strength);
+    static native void vibratorAlwaysOnDisable(long controllerPtr, long id);
 
     private final IUidObserver mUidObserver = new IUidObserver.Stub() {
         @Override public void onUidStateChanged(int uid, int procState, long procStateSeq,
@@ -231,8 +240,7 @@
 
         // The actual effect to be played.
         public VibrationEffect effect;
-        // The original effect that was requested. This is non-null only when the original effect
-        // differs from the effect that's being played. Typically these two things differ because
+        // The original effect that was requested. Typically these two things differ because
         // the effect was scaled based on the users vibration intensity settings.
         public VibrationEffect originalEffect;
 
@@ -248,9 +256,13 @@
             this.reason = reason;
         }
 
+        @Override
         public void binderDied() {
             synchronized (mLock) {
                 if (this == mCurrentVibration) {
+                    if (DEBUG) {
+                        Slog.d(TAG, "Vibration finished because binder died, cleaning up");
+                    }
                     doCancelVibrateLocked();
                 }
             }
@@ -261,6 +273,9 @@
         public void onComplete() {
             synchronized (mLock) {
                 if (this == mCurrentVibration) {
+                    if (DEBUG) {
+                        Slog.d(TAG, "Vibration finished by callback, cleaning up");
+                    }
                     doCancelVibrateLocked();
                 }
             }
@@ -370,14 +385,22 @@
         mNativeWrapper = injector.getNativeWrapper();
         mH = injector.createHandler(Looper.myLooper());
 
-        mNativeWrapper.vibratorInit();
+        long controllerPtr = mNativeWrapper.vibratorInit();
+        long finalizerPtr = mNativeWrapper.vibratorGetFinalizer();
+
+        if (finalizerPtr != 0) {
+            NativeAllocationRegistry registry =
+                    NativeAllocationRegistry.createMalloced(
+                            VibratorService.class.getClassLoader(), finalizerPtr);
+            registry.registerNativeAllocation(this, controllerPtr);
+        }
+
         // Reset the hardware to a default state, in case this is a runtime
         // restart instead of a fresh boot.
         mNativeWrapper.vibratorOff();
 
-        mSupportsAmplitudeControl = mNativeWrapper.vibratorSupportsAmplitudeControl();
-        mSupportsExternalControl = mNativeWrapper.vibratorSupportsExternalControl();
         mSupportedEffects = asList(mNativeWrapper.vibratorGetSupportedEffects());
+        mSupportedPrimitives = asList(mNativeWrapper.vibratorGetSupportedPrimitives());
         mCapabilities = mNativeWrapper.vibratorGetCapabilities();
 
         mContext = context;
@@ -605,7 +628,8 @@
         synchronized (mInputDeviceVibrators) {
             // Input device vibrators don't support amplitude controls yet, but are still used over
             // the system vibrator when connected.
-            return mSupportsAmplitudeControl && mInputDeviceVibrators.isEmpty();
+            return hasCapability(IVibrator.CAP_AMPLITUDE_CONTROL)
+                    && mInputDeviceVibrators.isEmpty();
         }
     }
 
@@ -627,8 +651,11 @@
     @Override // Binder call
     public boolean[] arePrimitivesSupported(int[] primitiveIds) {
         boolean[] supported = new boolean[primitiveIds.length];
-        if (hasCapability(IVibrator.CAP_COMPOSE_EFFECTS)) {
-            Arrays.fill(supported, true);
+        if (!hasCapability(IVibrator.CAP_COMPOSE_EFFECTS) || mSupportedPrimitives == null) {
+            return supported;
+        }
+        for (int i = 0; i < primitiveIds.length; i++) {
+            supported[i] = mSupportedPrimitives.contains(primitiveIds[i]);
         }
         return supported;
     }
@@ -875,19 +902,11 @@
         }
     }
 
-    private final Runnable mVibrationEndRunnable = new Runnable() {
-        @Override
-        public void run() {
-            onVibrationFinished();
-        }
-    };
-
     @GuardedBy("mLock")
     private void doCancelVibrateLocked() {
         Trace.asyncTraceEnd(Trace.TRACE_TAG_VIBRATOR, "vibration", 0);
         Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "doCancelVibrateLocked");
         try {
-            mH.removeCallbacks(mVibrationEndRunnable);
             if (mThread != null) {
                 mThread.cancel();
                 mThread = null;
@@ -907,7 +926,7 @@
     // Callback for whenever the current vibration has finished played out
     public void onVibrationFinished() {
         if (DEBUG) {
-            Slog.e(TAG, "Vibration finished, cleaning up");
+            Slog.d(TAG, "Vibration finished, cleaning up");
         }
         synchronized (mLock) {
             // Make sure the vibration is really done. This also reports that the vibration is
@@ -939,29 +958,19 @@
             if (vib.effect instanceof VibrationEffect.OneShot) {
                 Trace.asyncTraceBegin(Trace.TRACE_TAG_VIBRATOR, "vibration", 0);
                 VibrationEffect.OneShot oneShot = (VibrationEffect.OneShot) vib.effect;
-                doVibratorOn(oneShot.getDuration(), oneShot.getAmplitude(), vib.uid, vib.attrs);
-                mH.postDelayed(mVibrationEndRunnable, oneShot.getDuration());
+                doVibratorOn(oneShot.getDuration(), oneShot.getAmplitude(), vib);
             } else if (vib.effect instanceof VibrationEffect.Waveform) {
                 // mThread better be null here. doCancelVibrate should always be
                 // called before startNextVibrationLocked or startVibrationLocked.
-                Trace.asyncTraceBegin(Trace.TRACE_TAG_VIBRATOR, "vibration", 0);
                 VibrationEffect.Waveform waveform = (VibrationEffect.Waveform) vib.effect;
                 mThread = new VibrateThread(waveform, vib.uid, vib.attrs);
                 mThread.start();
             } else if (vib.effect instanceof VibrationEffect.Prebaked) {
                 Trace.asyncTraceBegin(Trace.TRACE_TAG_VIBRATOR, "vibration", 0);
-                long timeout = doVibratorPrebakedEffectLocked(vib);
-                if (timeout > 0) {
-                    mH.postDelayed(mVibrationEndRunnable, timeout);
-                }
+                doVibratorPrebakedEffectLocked(vib);
             } else if (vib.effect instanceof  VibrationEffect.Composed) {
                 Trace.asyncTraceBegin(Trace.TRACE_TAG_VIBRATOR, "vibration", 0);
                 doVibratorComposedEffectLocked(vib);
-                // FIXME: We rely on the completion callback here, but I don't think we require that
-                // devices which support composition also support the completion callback. If we
-                // ever get a device that supports the former but not the latter, then we have no
-                // real way of knowing how long a given effect should last.
-                mH.postDelayed(mVibrationEndRunnable, 10000);
             } else {
                 Slog.e(TAG, "Unknown vibration type, ignoring");
             }
@@ -1054,18 +1063,18 @@
         return attrs.isFlagSet(VibrationAttributes.FLAG_BYPASS_INTERRUPTION_POLICY);
     }
 
-    private int getAppOpMode(Vibration vib) {
+    private int getAppOpMode(int uid, String packageName, VibrationAttributes attrs) {
         int mode = mAppOps.checkAudioOpNoThrow(AppOpsManager.OP_VIBRATE,
-                vib.attrs.getAudioAttributes().getUsage(), vib.uid, vib.opPkg);
+                attrs.getAudioAttributes().getUsage(), uid, packageName);
         if (mode == AppOpsManager.MODE_ALLOWED) {
-            mode = mAppOps.startOpNoThrow(AppOpsManager.OP_VIBRATE, vib.uid, vib.opPkg);
+            mode = mAppOps.startOpNoThrow(AppOpsManager.OP_VIBRATE, uid, packageName);
         }
 
-        if (mode == AppOpsManager.MODE_IGNORED && shouldBypassDnd(vib.attrs)) {
+        if (mode == AppOpsManager.MODE_IGNORED && shouldBypassDnd(attrs)) {
             // If we're just ignoring the vibration op then this is set by DND and we should ignore
             // if we're asked to bypass. AppOps won't be able to record this operation, so make
             // sure we at least note it in the logs for debugging.
-            Slog.d(TAG, "Bypassing DND for vibration: " + vib);
+            Slog.d(TAG, "Bypassing DND for vibrate from uid " + uid);
             mode = AppOpsManager.MODE_ALLOWED;
         }
         return mode;
@@ -1087,7 +1096,7 @@
             return false;
         }
 
-        final int mode = getAppOpMode(vib);
+        final int mode = getAppOpMode(vib.uid, vib.opPkg, vib.attrs);
         if (mode != AppOpsManager.MODE_ALLOWED) {
             if (mode == AppOpsManager.MODE_ERRORED) {
                 // We might be getting calls from within system_server, so we don't actually
@@ -1257,7 +1266,18 @@
         return mNativeWrapper.vibratorExists();
     }
 
+    /** Vibrates with native callback trigger for {@link Vibration#onComplete()}. */
+    private void doVibratorOn(long millis, int amplitude, Vibration vib) {
+        doVibratorOn(millis, amplitude, vib.uid, vib.attrs, vib);
+    }
+
+    /** Vibrates without native callback. */
     private void doVibratorOn(long millis, int amplitude, int uid, VibrationAttributes attrs) {
+        doVibratorOn(millis, amplitude, uid, attrs, /* vib= */ null);
+    }
+
+    private void doVibratorOn(long millis, int amplitude, int uid, VibrationAttributes attrs,
+            @Nullable Vibration vib) {
         Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "doVibratorOn");
         try {
             synchronized (mInputDeviceVibrators) {
@@ -1272,13 +1292,14 @@
                 final int vibratorCount = mInputDeviceVibrators.size();
                 if (vibratorCount != 0) {
                     for (int i = 0; i < vibratorCount; i++) {
-                        mInputDeviceVibrators.get(i).vibrate(millis, attrs.getAudioAttributes());
+                        Vibrator inputDeviceVibrator = mInputDeviceVibrators.get(i);
+                        inputDeviceVibrator.vibrate(millis, attrs.getAudioAttributes());
                     }
                 } else {
                     // Note: ordering is important here! Many haptic drivers will reset their
                     // amplitude when enabled, so we always have to enable first, then set the
                     // amplitude.
-                    mNativeWrapper.vibratorOn(millis);
+                    mNativeWrapper.vibratorOn(millis, vib);
                     doVibratorSetAmplitude(amplitude);
                 }
             }
@@ -1288,7 +1309,7 @@
     }
 
     private void doVibratorSetAmplitude(int amplitude) {
-        if (mSupportsAmplitudeControl) {
+        if (hasCapability(IVibrator.CAP_AMPLITUDE_CONTROL)) {
             mNativeWrapper.vibratorSetAmplitude(amplitude);
         }
     }
@@ -1316,7 +1337,7 @@
     }
 
     @GuardedBy("mLock")
-    private long doVibratorPrebakedEffectLocked(Vibration vib) {
+    private void doVibratorPrebakedEffectLocked(Vibration vib) {
         Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "doVibratorPrebakedEffectLocked");
         try {
             final VibrationEffect.Prebaked prebaked = (VibrationEffect.Prebaked) vib.effect;
@@ -1326,25 +1347,20 @@
             }
             // Input devices don't support prebaked effect, so skip trying it with them.
             if (!usingInputDeviceVibrators) {
-                long duration = mNativeWrapper.vibratorPerformEffect(prebaked.getId(),
-                        prebaked.getEffectStrength(), vib,
-                        hasCapability(IVibrator.CAP_PERFORM_CALLBACK));
-                long timeout = duration;
-                if (hasCapability(IVibrator.CAP_PERFORM_CALLBACK)) {
-                    timeout *= ASYNC_TIMEOUT_MULTIPLIER;
-                }
-                if (timeout > 0) {
+                long duration = mNativeWrapper.vibratorPerformEffect(
+                        prebaked.getId(), prebaked.getEffectStrength(), vib);
+                if (duration > 0) {
                     noteVibratorOnLocked(vib.uid, duration);
-                    return timeout;
+                    return;
                 }
             }
             if (!prebaked.shouldFallback()) {
-                return 0;
+                return;
             }
             VibrationEffect effect = getFallbackEffect(prebaked.getId());
             if (effect == null) {
                 Slog.w(TAG, "Failed to play prebaked effect, no fallback");
-                return 0;
+                return;
             }
             Vibration fallbackVib = new Vibration(vib.token, effect, vib.attrs, vib.uid,
                     vib.opPkg, vib.reason + " (fallback)");
@@ -1352,7 +1368,7 @@
             linkVibration(fallbackVib);
             applyVibrationIntensityScalingLocked(fallbackVib, intensity);
             startVibrationInnerLocked(fallbackVib);
-            return 0;
+            return;
         } finally {
             Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR);
         }
@@ -1492,6 +1508,7 @@
             pw.println("  mNotificationIntensity=" + mNotificationIntensity);
             pw.println("  mRingIntensity=" + mRingIntensity);
             pw.println("  mSupportedEffects=" + mSupportedEffects);
+            pw.println("  mSupportedPrimitives=" + mSupportedPrimitives);
             pw.println();
             pw.println("  Previous ring vibrations:");
             for (VibrationInfo info : mPreviousRingVibrations) {
@@ -1562,6 +1579,7 @@
         proto.flush();
     }
 
+    /** Thread that plays a single {@link VibrationEffect.Waveform}. */
     private class VibrateThread extends Thread {
         private final VibrationEffect.Waveform mWaveform;
         private final int mUid;
@@ -1708,76 +1726,82 @@
     @VisibleForTesting
     public static class NativeWrapper {
 
+        private long mNativeControllerPtr = 0;
+
         /** Checks if vibrator exists on device. */
         public boolean vibratorExists() {
-            return VibratorService.vibratorExists();
+            return VibratorService.vibratorExists(mNativeControllerPtr);
         }
 
-        /** Initializes connection to vibrator HAL service. */
-        public void vibratorInit() {
-            VibratorService.vibratorInit();
+        /**
+         * Returns native pointer to newly created controller and initializes connection to vibrator
+         * HAL service.
+         */
+        public long vibratorInit() {
+            mNativeControllerPtr = VibratorService.vibratorInit();
+            return mNativeControllerPtr;
+        }
+
+        /** Returns pointer to native finalizer function to be called by GC. */
+        public long vibratorGetFinalizer() {
+            return VibratorService.vibratorGetFinalizer();
         }
 
         /** Turns vibrator on for given time. */
-        public void vibratorOn(long milliseconds) {
-            VibratorService.vibratorOn(milliseconds);
+        public void vibratorOn(long milliseconds, @Nullable Vibration vibration) {
+            VibratorService.vibratorOn(mNativeControllerPtr, milliseconds, vibration);
         }
 
         /** Turns vibrator off. */
         public void vibratorOff() {
-            VibratorService.vibratorOff();
-        }
-
-        /** Returns true if vibrator supports {@link #vibratorSetAmplitude(int)}. */
-        public boolean vibratorSupportsAmplitudeControl() {
-            return VibratorService.vibratorSupportsAmplitudeControl();
+            VibratorService.vibratorOff(mNativeControllerPtr);
         }
 
         /** Sets the amplitude for the vibrator to run. */
         public void vibratorSetAmplitude(int amplitude) {
-            VibratorService.vibratorSetAmplitude(amplitude);
+            VibratorService.vibratorSetAmplitude(mNativeControllerPtr, amplitude);
         }
 
         /** Returns all predefined effects supported by the device vibrator. */
         public int[] vibratorGetSupportedEffects() {
-            return VibratorService.vibratorGetSupportedEffects();
+            return VibratorService.vibratorGetSupportedEffects(mNativeControllerPtr);
+        }
+
+        /** Returns all compose primitives supported by the device vibrator. */
+        public int[] vibratorGetSupportedPrimitives() {
+            return VibratorService.vibratorGetSupportedPrimitives(mNativeControllerPtr);
         }
 
         /** Turns vibrator on to perform one of the supported effects. */
-        public long vibratorPerformEffect(long effect, long strength, Vibration vibration,
-                boolean withCallback) {
-            return VibratorService.vibratorPerformEffect(effect, strength, vibration, withCallback);
+        public long vibratorPerformEffect(long effect, long strength, Vibration vibration) {
+            return VibratorService.vibratorPerformEffect(
+                    mNativeControllerPtr, effect, strength, vibration);
         }
 
         /** Turns vibrator on to perform one of the supported composed effects. */
         public void vibratorPerformComposedEffect(
                 VibrationEffect.Composition.PrimitiveEffect[] effect, Vibration vibration) {
-            VibratorService.vibratorPerformComposedEffect(effect, vibration);
-        }
-
-        /** Returns true if vibrator supports {@link #vibratorSetExternalControl(boolean)}. */
-        public boolean vibratorSupportsExternalControl() {
-            return VibratorService.vibratorSupportsExternalControl();
+            VibratorService.vibratorPerformComposedEffect(mNativeControllerPtr, effect, vibration);
         }
 
         /** Enabled the device vibrator to be controlled by another service. */
         public void vibratorSetExternalControl(boolean enabled) {
-            VibratorService.vibratorSetExternalControl(enabled);
+            VibratorService.vibratorSetExternalControl(mNativeControllerPtr, enabled);
         }
 
         /** Returns all capabilities of the device vibrator. */
         public long vibratorGetCapabilities() {
-            return VibratorService.vibratorGetCapabilities();
+            return VibratorService.vibratorGetCapabilities(mNativeControllerPtr);
         }
 
         /** Enable always-on vibration with given id and effect. */
         public void vibratorAlwaysOnEnable(long id, long effect, long strength) {
-            VibratorService.vibratorAlwaysOnEnable(id, effect, strength);
+            VibratorService.vibratorAlwaysOnEnable(mNativeControllerPtr, id, effect, strength);
         }
 
         /** Disable always-on vibration for given id. */
         public void vibratorAlwaysOnDisable(long id) {
-            VibratorService.vibratorAlwaysOnDisable(id);
+            VibratorService.vibratorAlwaysOnDisable(mNativeControllerPtr, id);
         }
     }
 
@@ -1852,11 +1876,11 @@
 
         @Override
         public int onExternalVibrationStart(ExternalVibration vib) {
-            if (!mSupportsExternalControl) {
+            if (!hasCapability(IVibrator.CAP_EXTERNAL_CONTROL)) {
                 return SCALE_MUTE;
             }
             if (ActivityManager.checkComponentPermission(android.Manifest.permission.VIBRATE,
-                        vib.getUid(), -1 /*owningUid*/, true /*exported*/)
+                    vib.getUid(), -1 /*owningUid*/, true /*exported*/)
                     != PackageManager.PERMISSION_GRANTED) {
                 Slog.w(TAG, "pkg=" + vib.getPackage() + ", uid=" + vib.getUid()
                         + " tried to play externally controlled vibration"
@@ -1864,6 +1888,14 @@
                 return SCALE_MUTE;
             }
 
+            int mode = getAppOpMode(vib.getUid(), vib.getPackage(), vib.getVibrationAttributes());
+            if (mode != AppOpsManager.MODE_ALLOWED) {
+                if (mode == AppOpsManager.MODE_ERRORED) {
+                    Slog.w(TAG, "Would be an error: external vibrate from uid " + vib.getUid());
+                }
+                return SCALE_MUTE;
+            }
+
             final int scaleLevel;
             synchronized (mLock) {
                 if (!vib.equals(mCurrentExternalVibration)) {
@@ -2142,10 +2174,10 @@
                 if (hasCapability(IVibrator.CAP_COMPOSE_EFFECTS)) {
                     pw.println("  Compose effects");
                 }
-                if (mSupportsAmplitudeControl || hasCapability(IVibrator.CAP_AMPLITUDE_CONTROL)) {
+                if (hasCapability(IVibrator.CAP_AMPLITUDE_CONTROL)) {
                     pw.println("  Amplitude control");
                 }
-                if (mSupportsExternalControl || hasCapability(IVibrator.CAP_EXTERNAL_CONTROL)) {
+                if (hasCapability(IVibrator.CAP_EXTERNAL_CONTROL)) {
                     pw.println("  External control");
                 }
                 if (hasCapability(IVibrator.CAP_EXTERNAL_AMPLITUDE_CONTROL)) {
diff --git a/services/core/java/com/android/server/Watchdog.java b/services/core/java/com/android/server/Watchdog.java
index 1815dac..990a547 100644
--- a/services/core/java/com/android/server/Watchdog.java
+++ b/services/core/java/com/android/server/Watchdog.java
@@ -99,6 +99,7 @@
             "android.hardware.audio@4.0::IDevicesFactory",
             "android.hardware.audio@5.0::IDevicesFactory",
             "android.hardware.audio@6.0::IDevicesFactory",
+            "android.hardware.audio@7.0::IDevicesFactory",
             "android.hardware.biometrics.face@1.0::IBiometricsFace",
             "android.hardware.biometrics.fingerprint@2.1::IBiometricsFingerprint",
             "android.hardware.bluetooth@1.0::IBluetoothHci",
diff --git a/services/core/java/com/android/server/accounts/AccountManagerService.java b/services/core/java/com/android/server/accounts/AccountManagerService.java
index 7dc0b3a..35e88eb 100644
--- a/services/core/java/com/android/server/accounts/AccountManagerService.java
+++ b/services/core/java/com/android/server/accounts/AccountManagerService.java
@@ -106,6 +106,7 @@
 import com.android.server.LocalServices;
 import com.android.server.ServiceThread;
 import com.android.server.SystemService;
+import com.android.server.SystemService.TargetUser;
 
 import com.google.android.collect.Lists;
 import com.google.android.collect.Sets;
@@ -161,14 +162,14 @@
         }
 
         @Override
-        public void onUnlockUser(int userHandle) {
-            mService.onUnlockUser(userHandle);
+        public void onUserUnlocking(@NonNull TargetUser user) {
+            mService.onUnlockUser(user.getUserIdentifier());
         }
 
         @Override
-        public void onStopUser(int userHandle) {
-            Slog.i(TAG, "onStopUser " + userHandle);
-            mService.purgeUserData(userHandle);
+        public void onUserStopping(@NonNull TargetUser user) {
+            Slog.i(TAG, "onStopUser " + user);
+            mService.purgeUserData(user.getUserIdentifier());
         }
     }
 
diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java
index 1680963..c31d732 100644
--- a/services/core/java/com/android/server/am/ActiveServices.java
+++ b/services/core/java/com/android/server/am/ActiveServices.java
@@ -453,16 +453,16 @@
     }
 
     ComponentName startServiceLocked(IApplicationThread caller, Intent service, String resolvedType,
-            int callingPid, int callingUid, boolean fgRequired, String callingPackage,
-            @Nullable String callingFeatureId, final int userId)
+            int callingPid, int callingUid, boolean fgRequired, boolean hideFgNotification,
+            String callingPackage, @Nullable String callingFeatureId, final int userId)
             throws TransactionTooLargeException {
         return startServiceLocked(caller, service, resolvedType, callingPid, callingUid, fgRequired,
-                callingPackage, callingFeatureId, userId, false);
+                hideFgNotification, callingPackage, callingFeatureId, userId, false);
     }
 
     ComponentName startServiceLocked(IApplicationThread caller, Intent service, String resolvedType,
-            int callingPid, int callingUid, boolean fgRequired, String callingPackage,
-            @Nullable String callingFeatureId, final int userId,
+            int callingPid, int callingUid, boolean fgRequired, boolean hideFgNotification,
+            String callingPackage, @Nullable String callingFeatureId, final int userId,
             boolean allowBackgroundActivityStarts) throws TransactionTooLargeException {
         if (DEBUG_DELAYED_STARTS) Slog.v(TAG_SERVICE, "startService: " + service
                 + " type=" + resolvedType + " args=" + service.getExtras());
@@ -493,6 +493,8 @@
         }
 
         ServiceRecord r = res.record;
+        setFgsRestrictionLocked(callingPackage, callingPid, callingUid, service, r,
+                allowBackgroundActivityStarts);
 
         if (!mAm.mUserController.exists(r.userId)) {
             Slog.w(TAG, "Trying to start service with non-existent user! " + r.userId);
@@ -516,6 +518,21 @@
             forcedStandby = true;
         }
 
+        if (fgRequired) {
+            if (!r.mAllowStartForeground) {
+                if (!r.mLoggedInfoAllowStartForeground) {
+                    Slog.wtf(TAG, "Background started FGS " + r.mInfoAllowStartForeground);
+                    r.mLoggedInfoAllowStartForeground = true;
+                }
+                if (mAm.mConstants.mFlagFgsStartRestrictionEnabled) {
+                    Slog.w(TAG, "startForegroundService() not allowed due to "
+                                    + " mAllowStartForeground false: service "
+                                    + r.shortInstanceName);
+                    forcedStandby = true;
+                }
+            }
+        }
+
         // If this is a direct-to-foreground start, make sure it is allowed as per the app op.
         boolean forceSilentAbort = false;
         if (fgRequired) {
@@ -609,6 +626,7 @@
         r.startRequested = true;
         r.delayedStop = false;
         r.fgRequired = fgRequired;
+        r.hideFgNotification = hideFgNotification;
         r.pendingStarts.add(new ServiceRecord.StartItem(r, false, r.makeNextStartId(),
                 service, neededGrants, callingUid));
 
@@ -688,18 +706,10 @@
                         "Not potential delay (user " + r.userId + " not started): " + r);
             }
         }
-
         if (allowBackgroundActivityStarts) {
             r.allowBgActivityStartsOnServiceStart();
         }
         ComponentName cmp = startServiceInnerLocked(smap, service, r, callerFg, addToStarting);
-
-        if (!r.mAllowWhileInUsePermissionInFgs) {
-            r.mAllowWhileInUsePermissionInFgs =
-                    shouldAllowWhileInUsePermissionInFgsLocked(callingPackage, callingPid,
-                            callingUid, service, r, allowBackgroundActivityStarts);
-        }
-
         return cmp;
     }
 
@@ -1369,6 +1379,7 @@
                                     + r.shortInstanceName);
                 }
             }
+
             boolean alreadyStartedOp = false;
             boolean stopProcStatsOp = false;
             if (r.fgRequired) {
@@ -1415,6 +1426,24 @@
                     ignoreForeground = true;
                 }
 
+                if (!ignoreForeground) {
+                    if (!r.mAllowStartForeground) {
+                        if (!r.mLoggedInfoAllowStartForeground) {
+                            Slog.wtf(TAG, "Background started FGS "
+                                    + r.mInfoAllowStartForeground);
+                            r.mLoggedInfoAllowStartForeground = true;
+                        }
+                        if (mAm.mConstants.mFlagFgsStartRestrictionEnabled) {
+                            Slog.w(TAG,
+                                    "Service.startForeground() not allowed due to "
+                                            + "mAllowStartForeground false: service "
+                                            + r.shortInstanceName);
+                            updateServiceForegroundLocked(r.app, true);
+                            ignoreForeground = true;
+                        }
+                    }
+                }
+
                 // Apps under strict background restrictions simply don't get to have foreground
                 // services, so now that we've enforced the startForegroundService() contract
                 // we only do the machinery of making the service foreground when the app
@@ -1836,11 +1865,13 @@
         if (DEBUG_SERVICE) Slog.v(TAG_SERVICE, "bindService: " + service
                 + " type=" + resolvedType + " conn=" + connection.asBinder()
                 + " flags=0x" + Integer.toHexString(flags));
+        final int callingPid = Binder.getCallingPid();
+        final int callingUid = Binder.getCallingUid();
         final ProcessRecord callerApp = mAm.getRecordForAppLocked(caller);
         if (callerApp == null) {
             throw new SecurityException(
                     "Unable to find app for caller " + caller
-                    + " (pid=" + Binder.getCallingPid()
+                    + " (pid=" + callingPid
                     + ") when binding service " + service);
         }
 
@@ -1880,19 +1911,19 @@
         }
 
         if ((flags & Context.BIND_SCHEDULE_LIKE_TOP_APP) != 0 && !isCallerSystem) {
-            throw new SecurityException("Non-system caller (pid=" + Binder.getCallingPid()
+            throw new SecurityException("Non-system caller (pid=" + callingPid
                     + ") set BIND_SCHEDULE_LIKE_TOP_APP when binding service " + service);
         }
 
         if ((flags & Context.BIND_ALLOW_WHITELIST_MANAGEMENT) != 0 && !isCallerSystem) {
             throw new SecurityException(
-                    "Non-system caller " + caller + " (pid=" + Binder.getCallingPid()
+                    "Non-system caller " + caller + " (pid=" + callingPid
                     + ") set BIND_ALLOW_WHITELIST_MANAGEMENT when binding service " + service);
         }
 
         if ((flags & Context.BIND_ALLOW_INSTANT) != 0 && !isCallerSystem) {
             throw new SecurityException(
-                    "Non-system caller " + caller + " (pid=" + Binder.getCallingPid()
+                    "Non-system caller " + caller + " (pid=" + callingPid
                             + ") set BIND_ALLOW_INSTANT when binding service " + service);
         }
 
@@ -1908,7 +1939,7 @@
 
         ServiceLookupResult res =
             retrieveServiceLocked(service, instanceName, resolvedType, callingPackage,
-                    Binder.getCallingPid(), Binder.getCallingUid(), userId, true,
+                    callingPid, callingUid, userId, true,
                     callerFg, isBindExternal, allowInstant);
         if (res == null) {
             return 0;
@@ -2065,12 +2096,7 @@
                 }
             }
 
-            if (!s.mAllowWhileInUsePermissionInFgs) {
-                s.mAllowWhileInUsePermissionInFgs =
-                        shouldAllowWhileInUsePermissionInFgsLocked(callingPackage,
-                                Binder.getCallingPid(), Binder.getCallingUid(),
-                                service, s, false);
-            }
+            setFgsRestrictionLocked(callingPackage, callingPid, callingUid, service, s, false);
 
             if (s.app != null) {
                 if ((flags&Context.BIND_TREAT_LIKE_ACTIVITY) != 0) {
@@ -2559,12 +2585,12 @@
 
     private int getAllowMode(Intent service, @Nullable String callingPackage) {
         if (callingPackage == null || service.getComponent() == null) {
-            return ActivityManagerInternal.ALLOW_NON_FULL_IN_PROFILE_OR_FULL;
+            return ActivityManagerInternal.ALLOW_NON_FULL_IN_PROFILE;
         }
         if (callingPackage.equals(service.getComponent().getPackageName())) {
-            return ActivityManagerInternal.ALLOW_ACROSS_PROFILES_IN_PROFILE_OR_FULL;
+            return ActivityManagerInternal.ALLOW_ALL_PROFILE_PERMISSIONS_IN_PROFILE;
         } else {
-            return ActivityManagerInternal.ALLOW_NON_FULL_IN_PROFILE_OR_FULL;
+            return ActivityManagerInternal.ALLOW_NON_FULL_IN_PROFILE;
         }
     }
 
@@ -4838,21 +4864,48 @@
     }
 
     /**
-     * Should allow while-in-use permissions in foreground service or not.
-     * while-in-use permissions in FGS started from background might be restricted.
+     * There are two FGS restrictions:
+     * In R, mAllowWhileInUsePermissionInFgs is to allow while-in-use permissions in foreground
+     *  service or not. while-in-use permissions in FGS started from background might be restricted.
+     * In S, mAllowStartForeground is to allow FGS to startForeground or not. Service started
+     * from background may not become a FGS.
      * @param callingPackage caller app's package name.
      * @param callingUid caller app's uid.
      * @param intent intent to start/bind service.
      * @param r the service to start.
      * @return true if allow, false otherwise.
      */
-    private boolean shouldAllowWhileInUsePermissionInFgsLocked(String callingPackage,
+    private void setFgsRestrictionLocked(String callingPackage,
             int callingPid, int callingUid, Intent intent, ServiceRecord r,
             boolean allowBackgroundActivityStarts) {
-        // Is the background FGS start restriction turned on?
+        // Check DeviceConfig flag.
         if (!mAm.mConstants.mFlagBackgroundFgsStartRestrictionEnabled) {
-            return true;
+            r.mAllowWhileInUsePermissionInFgs = true;
         }
+
+        if (!r.mAllowWhileInUsePermissionInFgs || !r.mAllowStartForeground) {
+            final boolean temp = shouldAllowFgsFeatureLocked(callingPackage, callingPid,
+                    callingUid, intent, r, allowBackgroundActivityStarts);
+            if (!r.mAllowWhileInUsePermissionInFgs) {
+                r.mAllowWhileInUsePermissionInFgs = temp;
+            }
+            if (!r.mAllowStartForeground) {
+                r.mAllowStartForeground = temp;
+            }
+        }
+    }
+
+    /**
+     * Should allow FGS feature or not.
+     * @param callingPackage caller app's package name.
+     * @param callingUid caller app's uid.
+     * @param intent intent to start/bind service.
+     * @param r the service to start.
+     * @return true if allow, false otherwise.
+     */
+    private boolean shouldAllowFgsFeatureLocked(String callingPackage,
+            int callingPid, int callingUid, Intent intent, ServiceRecord r,
+            boolean allowBackgroundActivityStarts) {
         // Is the allow activity background start flag on?
         if (allowBackgroundActivityStarts) {
             return true;
@@ -4892,10 +4945,11 @@
         }
 
         // Is the calling UID at PROCESS_STATE_TOP or above?
-        final boolean isCallingUidTopApp = appIsTopLocked(callingUid);
-        if (isCallingUidTopApp) {
+        final int uidState = mAm.getUidState(callingUid);
+        if (uidState <= ActivityManager.PROCESS_STATE_TOP) {
             return true;
         }
+
         // Does the calling UID have any visible activity?
         final boolean isCallingUidVisible = mAm.mAtmInternal.isUidForeground(callingUid);
         if (isCallingUidVisible) {
@@ -4913,6 +4967,19 @@
         if (isDeviceOwner) {
             return true;
         }
+
+        final String info =
+                "[callingPackage: " + callingPackage
+                        + "; callingUid: " + callingUid
+                        + "; uidState: " + ProcessList.makeProcStateString(uidState)
+                        + "; intent: " + intent
+                        + "; targetSdkVersion:" + r.appInfo.targetSdkVersion
+                        + "]";
+        if (!info.equals(r.mInfoAllowStartForeground)) {
+            r.mLoggedInfoAllowStartForeground = false;
+            r.mInfoAllowStartForeground = info;
+        }
+
         return false;
     }
 }
diff --git a/services/core/java/com/android/server/am/ActivityManagerConstants.java b/services/core/java/com/android/server/am/ActivityManagerConstants.java
index 775119c..fede1d2 100644
--- a/services/core/java/com/android/server/am/ActivityManagerConstants.java
+++ b/services/core/java/com/android/server/am/ActivityManagerConstants.java
@@ -89,6 +89,7 @@
     static final String KEY_MEMORY_INFO_THROTTLE_TIME = "memory_info_throttle_time";
     static final String KEY_TOP_TO_FGS_GRACE_DURATION = "top_to_fgs_grace_duration";
     static final String KEY_PENDINGINTENT_WARNING_THRESHOLD = "pendingintent_warning_threshold";
+    static final String KEY_MIN_CRASH_INTERVAL = "min_crash_interval";
 
     private static final int DEFAULT_MAX_CACHED_PROCESSES = 32;
     private static final long DEFAULT_BACKGROUND_SETTLE_TIME = 60*1000;
@@ -122,6 +123,8 @@
     private static final long DEFAULT_MEMORY_INFO_THROTTLE_TIME = 5*60*1000;
     private static final long DEFAULT_TOP_TO_FGS_GRACE_DURATION = 15 * 1000;
     private static final int DEFAULT_PENDINGINTENT_WARNING_THRESHOLD = 2000;
+    private static final int DEFAULT_MIN_CRASH_INTERVAL = 2 * 60 * 1000;
+
 
     // Flag stored in the DeviceConfig API.
     /**
@@ -144,6 +147,13 @@
     private static final String KEY_DEFAULT_BACKGROUND_FGS_STARTS_RESTRICTION_ENABLED =
             "default_background_fgs_starts_restriction_enabled";
 
+    /**
+     * Default value for mFlagFgsStartRestrictionEnabled if not explicitly set in
+     * Settings.Global.
+     */
+    private static final String KEY_DEFAULT_FGS_STARTS_RESTRICTION_ENABLED =
+            "default_fgs_starts_restriction_enabled";
+
     // Maximum number of cached processes we will allow.
     public int MAX_CACHED_PROCESSES = DEFAULT_MAX_CACHED_PROCESSES;
 
@@ -274,6 +284,12 @@
     // this long.
     public long TOP_TO_FGS_GRACE_DURATION = DEFAULT_TOP_TO_FGS_GRACE_DURATION;
 
+    /**
+     * The minimum time we allow between crashes, for us to consider this
+     * application to be bad and stop its services and reject broadcasts.
+     */
+    public static int MIN_CRASH_INTERVAL = DEFAULT_MIN_CRASH_INTERVAL;
+
     // Indicates whether the activity starts logging is enabled.
     // Controlled by Settings.Global.ACTIVITY_STARTS_LOGGING_ENABLED
     volatile boolean mFlagActivityStartsLoggingEnabled;
@@ -293,6 +309,11 @@
     // started, the restriction is on while-in-use permissions.)
     volatile boolean mFlagBackgroundFgsStartRestrictionEnabled = true;
 
+    // Indicates whether the foreground service background start restriction is enabled.
+    // When the restriction is enabled, service is not allowed to startForeground from background
+    // at all.
+    volatile boolean mFlagFgsStartRestrictionEnabled = false;
+
     private final ActivityManagerService mService;
     private ContentResolver mResolver;
     private final KeyValueListParser mParser = new KeyValueListParser(',');
@@ -436,6 +457,9 @@
                             case KEY_DEFAULT_BACKGROUND_FGS_STARTS_RESTRICTION_ENABLED:
                                 updateBackgroundFgsStartsRestriction();
                                 break;
+                            case KEY_DEFAULT_FGS_STARTS_RESTRICTION_ENABLED:
+                                updateFgsStartsRestriction();
+                                break;
                             case KEY_OOMADJ_UPDATE_POLICY:
                                 updateOomAdjUpdatePolicy();
                                 break;
@@ -635,6 +659,8 @@
                     DEFAULT_MEMORY_INFO_THROTTLE_TIME);
             TOP_TO_FGS_GRACE_DURATION = mParser.getDurationMillis(KEY_TOP_TO_FGS_GRACE_DURATION,
                     DEFAULT_TOP_TO_FGS_GRACE_DURATION);
+            MIN_CRASH_INTERVAL = mParser.getInt(KEY_MIN_CRASH_INTERVAL,
+                    DEFAULT_MIN_CRASH_INTERVAL);
             PENDINGINTENT_WARNING_THRESHOLD = mParser.getInt(KEY_PENDINGINTENT_WARNING_THRESHOLD,
                     DEFAULT_PENDINGINTENT_WARNING_THRESHOLD);
 
@@ -659,6 +685,7 @@
         mFlagForegroundServiceStartsLoggingEnabled = Settings.Global.getInt(mResolver,
                 Settings.Global.FOREGROUND_SERVICE_STARTS_LOGGING_ENABLED, 1) == 1;
     }
+
     private void updateBackgroundFgsStartsRestriction() {
         mFlagBackgroundFgsStartRestrictionEnabled = DeviceConfig.getBoolean(
                 DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
@@ -666,6 +693,13 @@
                 /*defaultValue*/ true);
     }
 
+    private void updateFgsStartsRestriction() {
+        mFlagFgsStartRestrictionEnabled = DeviceConfig.getBoolean(
+                DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+                KEY_DEFAULT_FGS_STARTS_RESTRICTION_ENABLED,
+                /*defaultValue*/ false);
+    }
+
     private void updateOomAdjUpdatePolicy() {
         OOMADJ_UPDATE_QUICK = DeviceConfig.getInt(
                 DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
@@ -843,6 +877,8 @@
         pw.println(MEMORY_INFO_THROTTLE_TIME);
         pw.print("  "); pw.print(KEY_TOP_TO_FGS_GRACE_DURATION); pw.print("=");
         pw.println(TOP_TO_FGS_GRACE_DURATION);
+        pw.print("  "); pw.print(KEY_MIN_CRASH_INTERVAL); pw.print("=");
+        pw.println(MIN_CRASH_INTERVAL);
         pw.print("  "); pw.print(KEY_IMPERCEPTIBLE_KILL_EXEMPT_PROC_STATES); pw.print("=");
         pw.println(Arrays.toString(IMPERCEPTIBLE_KILL_EXEMPT_PROC_STATES.toArray()));
         pw.print("  "); pw.print(KEY_IMPERCEPTIBLE_KILL_EXEMPT_PACKAGES); pw.print("=");
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index a54d6b4..6e1e3d0 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -41,6 +41,7 @@
 import static android.content.pm.PackageManager.MATCH_UNINSTALLED_PACKAGES;
 import static android.content.pm.PackageManager.PERMISSION_GRANTED;
 import static android.os.FactoryTest.FACTORY_TEST_OFF;
+import static android.os.IInputConstants.DEFAULT_DISPATCHING_TIMEOUT_MILLIS;
 import static android.os.IServiceManager.DUMP_FLAG_PRIORITY_CRITICAL;
 import static android.os.IServiceManager.DUMP_FLAG_PRIORITY_HIGH;
 import static android.os.IServiceManager.DUMP_FLAG_PRIORITY_NORMAL;
@@ -97,7 +98,6 @@
 import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_PROCESS_OBSERVERS;
 import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_PSS;
 import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_SERVICE;
-import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_UID_OBSERVERS;
 import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_WHITELISTS;
 import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_BACKUP;
 import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_BROADCAST;
@@ -130,10 +130,10 @@
 import static com.android.server.wm.ActivityTaskManagerService.DUMP_RECENTS_CMD;
 import static com.android.server.wm.ActivityTaskManagerService.DUMP_RECENTS_SHORT_CMD;
 import static com.android.server.wm.ActivityTaskManagerService.DUMP_STARTER_CMD;
-import static com.android.server.wm.ActivityTaskManagerService.KEY_DISPATCHING_TIMEOUT_MS;
 import static com.android.server.wm.ActivityTaskManagerService.RELAUNCH_REASON_NONE;
 import static com.android.server.wm.ActivityTaskManagerService.relaunchReasonToString;
 
+
 import android.Manifest;
 import android.Manifest.permission;
 import android.annotation.BroadcastBehavior;
@@ -145,7 +145,6 @@
 import android.app.ActivityManager.RunningTaskInfo;
 import android.app.ActivityManager.StackInfo;
 import android.app.ActivityManagerInternal;
-import android.app.ActivityManagerProto;
 import android.app.ActivityThread;
 import android.app.AppGlobals;
 import android.app.AppOpsManager;
@@ -175,6 +174,7 @@
 import android.app.ProcessMemoryState;
 import android.app.ProfilerInfo;
 import android.app.WaitResult;
+import android.app.backup.BackupManager.OperationType;
 import android.app.backup.IBackupManager;
 import android.app.usage.UsageEvents;
 import android.app.usage.UsageEvents.Event;
@@ -341,11 +341,11 @@
 import com.android.server.ServiceThread;
 import com.android.server.SystemConfig;
 import com.android.server.SystemService;
+import com.android.server.SystemService.TargetUser;
 import com.android.server.SystemServiceManager;
 import com.android.server.ThreadPriorityBooster;
 import com.android.server.UserspaceRebootLogger;
 import com.android.server.Watchdog;
-import com.android.server.am.ActivityManagerServiceDumpProcessesProto.UidObserverRegistrationProto;
 import com.android.server.appop.AppOpsService;
 import com.android.server.compat.PlatformCompat;
 import com.android.server.contentcapture.ContentCaptureManagerInternal;
@@ -486,9 +486,6 @@
     // as one line, but close enough for now.
     static final int RESERVED_BYTES_PER_LOGCAT_LINE = 100;
 
-    /** If a UID observer takes more than this long, send a WTF. */
-    private static final int SLOW_UID_OBSERVER_THRESHOLD_MS = 20;
-
     // Necessary ApplicationInfo flags to mark an app as persistent
     static final int PERSISTENT_MASK =
             ApplicationInfo.FLAG_SYSTEM|ApplicationInfo.FLAG_PERSISTENT;
@@ -640,9 +637,6 @@
     @VisibleForTesting
     long mWaitForNetworkTimeoutMs;
 
-    /** Total # of UID change events dispatched, shown in dumpsys. */
-    int mUidChangeDispatchCount;
-
     /**
      * Uids of apps with current active camera sessions.  Access synchronized on
      * the IntArray instance itself, and no other locks must be acquired while that
@@ -1010,12 +1004,6 @@
             };
 
     /**
-     * This is for verifying the UID report flow.
-     */
-    static final boolean VALIDATE_UID_STATES = true;
-    final ActiveUids mValidateUids = new ActiveUids(this, false /* postChangesToAtm */);
-
-    /**
      * Fingerprints (hashCode()) of stack traces that we've
      * already logged DropBox entries for.  Guarded by itself.  If
      * something (rogue user app) forces this over
@@ -1382,6 +1370,10 @@
 
     final Injector mInjector;
 
+    /** The package verifier app. */
+    private String mPackageVerifier;
+    private int mPackageVerifierUid = UserHandle.USER_NULL;
+
     static final class ProcessChangeItem {
         static final int CHANGE_ACTIVITIES = 1<<0;
         static final int CHANGE_FOREGROUND_SERVICES = 1<<1;
@@ -1395,69 +1387,6 @@
         int foregroundServiceTypes;
     }
 
-    static final class UidObserverRegistration {
-        final int uid;
-        final String pkg;
-        final int which;
-        final int cutpoint;
-
-        /**
-         * Total # of callback calls that took more than {@link #SLOW_UID_OBSERVER_THRESHOLD_MS}.
-         * We show it in dumpsys.
-         */
-        int mSlowDispatchCount;
-
-        /** Max time it took for each dispatch. */
-        int mMaxDispatchTime;
-
-        final SparseIntArray lastProcStates;
-
-        // Please keep the enum lists in sync
-        private static int[] ORIG_ENUMS = new int[]{
-                ActivityManager.UID_OBSERVER_IDLE,
-                ActivityManager.UID_OBSERVER_ACTIVE,
-                ActivityManager.UID_OBSERVER_GONE,
-                ActivityManager.UID_OBSERVER_PROCSTATE,
-        };
-        private static int[] PROTO_ENUMS = new int[]{
-                ActivityManagerProto.UID_OBSERVER_FLAG_IDLE,
-                ActivityManagerProto.UID_OBSERVER_FLAG_ACTIVE,
-                ActivityManagerProto.UID_OBSERVER_FLAG_GONE,
-                ActivityManagerProto.UID_OBSERVER_FLAG_PROCSTATE,
-        };
-
-        UidObserverRegistration(int _uid, String _pkg, int _which, int _cutpoint) {
-            uid = _uid;
-            pkg = _pkg;
-            which = _which;
-            cutpoint = _cutpoint;
-            if (cutpoint >= ActivityManager.MIN_PROCESS_STATE) {
-                lastProcStates = new SparseIntArray();
-            } else {
-                lastProcStates = null;
-            }
-        }
-
-        void dumpDebug(ProtoOutputStream proto, long fieldId) {
-            final long token = proto.start(fieldId);
-            proto.write(UidObserverRegistrationProto.UID, uid);
-            proto.write(UidObserverRegistrationProto.PACKAGE, pkg);
-            ProtoUtils.writeBitWiseFlagsToProtoEnum(proto, UidObserverRegistrationProto.FLAGS,
-                    which, ORIG_ENUMS, PROTO_ENUMS);
-            proto.write(UidObserverRegistrationProto.CUT_POINT, cutpoint);
-            if (lastProcStates != null) {
-                final int NI = lastProcStates.size();
-                for (int i=0; i<NI; i++) {
-                    final long pToken = proto.start(UidObserverRegistrationProto.LAST_PROC_STATES);
-                    proto.write(UidObserverRegistrationProto.ProcState.UID, lastProcStates.keyAt(i));
-                    proto.write(UidObserverRegistrationProto.ProcState.STATE, lastProcStates.valueAt(i));
-                    proto.end(pToken);
-                }
-            }
-            proto.end(token);
-        }
-    }
-
     // TODO: Move below 4 members and code to ProcessList
     final RemoteCallbackList<IProcessObserver> mProcessObservers = new RemoteCallbackList<>();
     ProcessChangeItem[] mActiveProcessChanges = new ProcessChangeItem[5];
@@ -1465,12 +1394,6 @@
     final ArrayList<ProcessChangeItem> mPendingProcessChanges = new ArrayList<>();
     final ArrayList<ProcessChangeItem> mAvailProcessChanges = new ArrayList<>();
 
-    final RemoteCallbackList<IUidObserver> mUidObservers = new RemoteCallbackList<>();
-    UidRecord.ChangeItem[] mActiveUidChanges = new UidRecord.ChangeItem[5];
-
-    final ArrayList<UidRecord.ChangeItem> mPendingUidChanges = new ArrayList<>();
-    final ArrayList<UidRecord.ChangeItem> mAvailUidChanges = new ArrayList<>();
-
     OomAdjObserver mCurOomAdjObserver;
     int mCurOomAdjUid;
 
@@ -1521,6 +1444,8 @@
     public final ActivityManagerInternal mInternal;
     final ActivityThread mSystemThread;
 
+    final UidObserverController mUidObserverController;
+
     private final class AppDeathRecipient implements IBinder.DeathRecipient {
         final ProcessRecord mApp;
         final int mPid;
@@ -1566,7 +1491,6 @@
     static final int NOTIFY_CLEARTEXT_NETWORK_MSG = 49;
     static final int POST_DUMP_HEAP_NOTIFICATION_MSG = 50;
     static final int ABORT_DUMPHEAP_MSG = 51;
-    static final int DISPATCH_UIDS_CHANGED_UI_MSG = 53;
     static final int SHUTDOWN_UI_AUTOMATION_CONNECTION_MSG = 56;
     static final int CONTENT_PROVIDER_PUBLISH_TIMEOUT_MSG = 57;
     static final int IDLE_UIDS_MSG = 58;
@@ -1577,6 +1501,7 @@
     static final int DISPATCH_OOM_ADJ_OBSERVER_MSG = 70;
     static final int KILL_APP_ZYGOTE_MSG = 71;
     static final int BINDER_HEAVYHITTER_AUTOSAMPLER_TIMEOUT_MSG = 72;
+    static final int WAIT_FOR_CONTENT_PROVIDER_TIMEOUT_MSG = 73;
 
     static final int FIRST_BROADCAST_QUEUE_MSG = 200;
 
@@ -1694,17 +1619,14 @@
                     break;
                 }
                 case DISPATCH_PROCESS_DIED_UI_MSG: {
+                    if (false) { // DO NOT SUBMIT WITH TRUE
+                        maybeTriggerWatchdog();
+                    }
                     final int pid = msg.arg1;
                     final int uid = msg.arg2;
                     dispatchProcessDied(pid, uid);
                     break;
                 }
-                case DISPATCH_UIDS_CHANGED_UI_MSG: {
-                    if (false) { // DO NOT SUBMIT WITH TRUE
-                        maybeTriggerWatchdog();
-                    }
-                    dispatchUidsChanged();
-                } break;
                 case DISPATCH_OOM_ADJ_OBSERVER_MSG: {
                     dispatchOomAdjObserver((String) msg.obj);
                 } break;
@@ -1790,12 +1712,10 @@
                     }
                 } break;
             case CHECK_EXCESSIVE_POWER_USE_MSG: {
-                synchronized (ActivityManagerService.this) {
-                    checkExcessivePowerUsageLocked();
-                    removeMessages(CHECK_EXCESSIVE_POWER_USE_MSG);
-                    Message nmsg = obtainMessage(CHECK_EXCESSIVE_POWER_USE_MSG);
-                    sendMessageDelayed(nmsg, mConstants.POWER_CHECK_INTERVAL);
-                }
+                checkExcessivePowerUsage();
+                removeMessages(CHECK_EXCESSIVE_POWER_USE_MSG);
+                Message nmsg = obtainMessage(CHECK_EXCESSIVE_POWER_USE_MSG);
+                sendMessageDelayed(nmsg, mConstants.POWER_CHECK_INTERVAL);
             } break;
             case REPORT_MEM_USAGE_MSG: {
                 final ArrayList<ProcessMemInfo> memInfos = (ArrayList<ProcessMemInfo>)msg.obj;
@@ -1913,6 +1833,11 @@
                 case BINDER_HEAVYHITTER_AUTOSAMPLER_TIMEOUT_MSG: {
                     handleBinderHeavyHitterAutoSamplerTimeOut();
                 } break;
+                case WAIT_FOR_CONTENT_PROVIDER_TIMEOUT_MSG: {
+                    synchronized (ActivityManagerService.this) {
+                        ((ContentProviderRecord) msg.obj).onProviderPublishStatusLocked(false);
+                    }
+                } break;
             }
         }
     }
@@ -1954,7 +1879,7 @@
                         nativeTotalPss += Debug.getPss(stats.get(j).pid, null, null);
                     }
                     memInfo.readMemInfo();
-                    synchronized (ActivityManagerService.this) {
+                    synchronized (mProcessStats.mLock) {
                         if (DEBUG_PSS) Slog.d(TAG_PSS, "Collected native and kernel memory in "
                                 + (SystemClock.uptimeMillis()-start) + "ms");
                         final long cachedKb = memInfo.getCachedSizeKb();
@@ -2325,6 +2250,18 @@
             if (phase == PHASE_SYSTEM_SERVICES_READY) {
                 mService.mBatteryStatsService.systemServicesReady();
                 mService.mServices.systemServicesReady();
+                mService.mPackageVerifier = ArrayUtils.firstOrNull(
+                        LocalServices.getService(PackageManagerInternal.class).getKnownPackageNames(
+                                PackageManagerInternal.PACKAGE_VERIFIER, UserHandle.USER_SYSTEM));
+                if (mService.mPackageVerifier != null) {
+                    try {
+                        mService.mPackageVerifierUid =
+                                getContext().getPackageManager().getPackageUid(
+                                        mService.mPackageVerifier, UserHandle.USER_SYSTEM);
+                    } catch (NameNotFoundException e) {
+                        Slog.wtf(TAG, "Package manager couldn't get package verifier uid", e);
+                    }
+                }
             } else if (phase == PHASE_ACTIVITY_MANAGER_READY) {
                 mService.startBroadcastObservers();
             } else if (phase == PHASE_THIRD_PARTY_APPS_CAN_START) {
@@ -2333,8 +2270,8 @@
         }
 
         @Override
-        public void onCleanupUser(int userId) {
-            mService.mBatteryStatsService.onCleanupUser(userId);
+        public void onUserStopped(@NonNull TargetUser user) {
+            mService.mBatteryStatsService.onCleanupUser(user.getUserIdentifier());
         }
 
         public ActivityManagerService getService() {
@@ -2443,7 +2380,7 @@
                             ? Collections.emptyList()
                             : Arrays.asList(exemptions.split(","));
                 }
-                if (!ZYGOTE_PROCESS.setApiBlacklistExemptions(mExemptions)) {
+                if (!ZYGOTE_PROCESS.setApiDenylistExemptions(mExemptions)) {
                   Slog.e(TAG, "Failed to set API blacklist exemptions!");
                   // leave mExemptionsStr as is, so we don't try to send the same list again.
                   mExemptions = Collections.emptyList();
@@ -2501,6 +2438,7 @@
         mConstants = hasHandlerThread
                 ? new ActivityManagerConstants(mContext, this, mHandler) : null;
         final ActiveUids activeUids = new ActiveUids(this, false /* postChangesToAtm */);
+        mUidObserverController = new UidObserverController(this);
         mPlatformCompat = null;
         mProcessList = injector.getProcessList(this);
         mProcessList.init(this, activeUids, mPlatformCompat);
@@ -2596,6 +2534,7 @@
         mCpHelper = new ContentProviderHelper(this, true);
         mPackageWatchdog = PackageWatchdog.getInstance(mUiContext);
         mAppErrors = new AppErrors(mUiContext, this, mPackageWatchdog);
+        mUidObserverController = new UidObserverController(this);
 
         final File systemDir = SystemServiceManager.ensureSystemDir();
 
@@ -3412,153 +3351,6 @@
         mProcessObservers.finishBroadcast();
     }
 
-    @VisibleForTesting
-    void dispatchUidsChanged() {
-        int N;
-        synchronized (this) {
-            N = mPendingUidChanges.size();
-            if (mActiveUidChanges.length < N) {
-                mActiveUidChanges = new UidRecord.ChangeItem[N];
-            }
-            for (int i=0; i<N; i++) {
-                final UidRecord.ChangeItem change = mPendingUidChanges.get(i);
-                mActiveUidChanges[i] = change;
-                if (change.uidRecord != null) {
-                    change.uidRecord.pendingChange = null;
-                    change.uidRecord = null;
-                }
-            }
-            mPendingUidChanges.clear();
-            if (DEBUG_UID_OBSERVERS) Slog.i(TAG_UID_OBSERVERS,
-                    "*** Delivering " + N + " uid changes");
-        }
-
-        mUidChangeDispatchCount += N;
-        int i = mUidObservers.beginBroadcast();
-        while (i > 0) {
-            i--;
-            dispatchUidsChangedForObserver(mUidObservers.getBroadcastItem(i),
-                    (UidObserverRegistration) mUidObservers.getBroadcastCookie(i), N);
-        }
-        mUidObservers.finishBroadcast();
-
-        if (VALIDATE_UID_STATES && mUidObservers.getRegisteredCallbackCount() > 0) {
-            for (int j = 0; j < N; ++j) {
-                final UidRecord.ChangeItem item = mActiveUidChanges[j];
-                if ((item.change & UidRecord.CHANGE_GONE) != 0) {
-                    mValidateUids.remove(item.uid);
-                } else {
-                    UidRecord validateUid = mValidateUids.get(item.uid);
-                    if (validateUid == null) {
-                        validateUid = new UidRecord(item.uid);
-                        mValidateUids.put(item.uid, validateUid);
-                    }
-                    if ((item.change & UidRecord.CHANGE_IDLE) != 0) {
-                        validateUid.idle = true;
-                    } else if ((item.change & UidRecord.CHANGE_ACTIVE) != 0) {
-                        validateUid.idle = false;
-                    }
-                    validateUid.setCurProcState(validateUid.setProcState = item.processState);
-                    validateUid.curCapability = validateUid.setCapability = item.capability;
-                    validateUid.lastDispatchedProcStateSeq = item.procStateSeq;
-                }
-            }
-        }
-
-        synchronized (this) {
-            for (int j = 0; j < N; j++) {
-                mAvailUidChanges.add(mActiveUidChanges[j]);
-            }
-        }
-    }
-
-    private void dispatchUidsChangedForObserver(IUidObserver observer,
-            UidObserverRegistration reg, int changesSize) {
-        if (observer == null) {
-            return;
-        }
-        try {
-            for (int j = 0; j < changesSize; j++) {
-                UidRecord.ChangeItem item = mActiveUidChanges[j];
-                final int change = item.change;
-                if (change == UidRecord.CHANGE_PROCSTATE &&
-                        (reg.which & ActivityManager.UID_OBSERVER_PROCSTATE) == 0) {
-                    // No-op common case: no significant change, the observer is not
-                    // interested in all proc state changes.
-                    continue;
-                }
-                final long start = SystemClock.uptimeMillis();
-                if ((change & UidRecord.CHANGE_IDLE) != 0) {
-                    if ((reg.which & ActivityManager.UID_OBSERVER_IDLE) != 0) {
-                        if (DEBUG_UID_OBSERVERS) Slog.i(TAG_UID_OBSERVERS,
-                                "UID idle uid=" + item.uid);
-                        observer.onUidIdle(item.uid, item.ephemeral);
-                    }
-                } else if ((change & UidRecord.CHANGE_ACTIVE) != 0) {
-                    if ((reg.which & ActivityManager.UID_OBSERVER_ACTIVE) != 0) {
-                        if (DEBUG_UID_OBSERVERS) Slog.i(TAG_UID_OBSERVERS,
-                                "UID active uid=" + item.uid);
-                        observer.onUidActive(item.uid);
-                    }
-                }
-                if ((reg.which & ActivityManager.UID_OBSERVER_CACHED) != 0) {
-                    if ((change & UidRecord.CHANGE_CACHED) != 0) {
-                        if (DEBUG_UID_OBSERVERS) Slog.i(TAG_UID_OBSERVERS,
-                                "UID cached uid=" + item.uid);
-                        observer.onUidCachedChanged(item.uid, true);
-                    } else if ((change & UidRecord.CHANGE_UNCACHED) != 0) {
-                        if (DEBUG_UID_OBSERVERS) Slog.i(TAG_UID_OBSERVERS,
-                                "UID active uid=" + item.uid);
-                        observer.onUidCachedChanged(item.uid, false);
-                    }
-                }
-                if ((change & UidRecord.CHANGE_GONE) != 0) {
-                    if ((reg.which & ActivityManager.UID_OBSERVER_GONE) != 0) {
-                        if (DEBUG_UID_OBSERVERS) Slog.i(TAG_UID_OBSERVERS,
-                                "UID gone uid=" + item.uid);
-                        observer.onUidGone(item.uid, item.ephemeral);
-                    }
-                    if (reg.lastProcStates != null) {
-                        reg.lastProcStates.delete(item.uid);
-                    }
-                } else {
-                    if ((reg.which & ActivityManager.UID_OBSERVER_PROCSTATE) != 0) {
-                        if (DEBUG_UID_OBSERVERS) Slog.i(TAG_UID_OBSERVERS,
-                                "UID CHANGED uid=" + item.uid
-                                        + ": " + item.processState + ": " + item.capability);
-                        boolean doReport = true;
-                        if (reg.cutpoint >= ActivityManager.MIN_PROCESS_STATE) {
-                            final int lastState = reg.lastProcStates.get(item.uid,
-                                    ActivityManager.PROCESS_STATE_UNKNOWN);
-                            if (lastState != ActivityManager.PROCESS_STATE_UNKNOWN) {
-                                final boolean lastAboveCut = lastState <= reg.cutpoint;
-                                final boolean newAboveCut = item.processState <= reg.cutpoint;
-                                doReport = lastAboveCut != newAboveCut;
-                            } else {
-                                doReport = item.processState != PROCESS_STATE_NONEXISTENT;
-                            }
-                        }
-                        if (doReport) {
-                            if (reg.lastProcStates != null) {
-                                reg.lastProcStates.put(item.uid, item.processState);
-                            }
-                            observer.onUidStateChanged(item.uid, item.processState,
-                                    item.procStateSeq, item.capability);
-                        }
-                    }
-                }
-                final int duration = (int) (SystemClock.uptimeMillis() - start);
-                if (reg.mMaxDispatchTime < duration) {
-                    reg.mMaxDispatchTime = duration;
-                }
-                if (duration >= SLOW_UID_OBSERVER_THRESHOLD_MS) {
-                    reg.mSlowDispatchCount++;
-                }
-            }
-        } catch (RemoteException e) {
-        }
-    }
-
     void dispatchOomAdjObserver(String msg) {
         OomAdjObserver observer;
         synchronized (this) {
@@ -4599,10 +4391,12 @@
                     proc.lastMemInfoTime = SystemClock.uptimeMillis();
                     if (proc.thread != null && proc.setAdj == oomAdj) {
                         // Record this for posterity if the process has been stable.
-                        proc.baseProcessTracker.addPss(infos[i].getTotalPss(),
-                                infos[i].getTotalUss(), infos[i].getTotalRss(), false,
-                                ProcessStats.ADD_PSS_EXTERNAL_SLOW, endTime - startTime,
-                                proc.pkgList.mPkgList);
+                        synchronized (mProcessStats.mLock) {
+                            proc.baseProcessTracker.addPss(infos[i].getTotalPss(),
+                                    infos[i].getTotalUss(), infos[i].getTotalRss(), false,
+                                    ProcessStats.ADD_PSS_EXTERNAL_SLOW, endTime - startTime,
+                                    proc.pkgList.mPkgList);
+                        }
                         for (int ipkg = proc.pkgList.size() - 1; ipkg >= 0; ipkg--) {
                             ProcessStats.ProcessStateHolder holder = proc.pkgList.valueAt(ipkg);
                             FrameworkStatsLog.write(FrameworkStatsLog.PROCESS_MEMORY_STAT_REPORTED,
@@ -4659,8 +4453,11 @@
                 synchronized (this) {
                     if (proc.thread != null && proc.setAdj == oomAdj) {
                         // Record this for posterity if the process has been stable.
-                        proc.baseProcessTracker.addPss(pss[i], tmpUss[0], tmpUss[2], false,
-                                ProcessStats.ADD_PSS_EXTERNAL, endTime-startTime, proc.pkgList.mPkgList);
+                        synchronized (mProcessStats.mLock) {
+                            proc.baseProcessTracker.addPss(pss[i], tmpUss[0], tmpUss[2], false,
+                                    ProcessStats.ADD_PSS_EXTERNAL, endTime - startTime,
+                                    proc.pkgList.mPkgList);
+                        }
                         for (int ipkg = proc.pkgList.size() - 1; ipkg >= 0; ipkg--) {
                             ProcessStats.ProcessStateHolder holder = proc.pkgList.valueAt(ipkg);
                             FrameworkStatsLog.write(FrameworkStatsLog.PROCESS_MEMORY_STAT_REPORTED,
@@ -5372,7 +5169,7 @@
             try {
                 thread.scheduleCreateBackupAgent(backupTarget.appInfo,
                         compatibilityInfoForPackage(backupTarget.appInfo),
-                        backupTarget.backupMode, backupTarget.userId);
+                        backupTarget.backupMode, backupTarget.userId, backupTarget.operationType);
             } catch (Exception e) {
                 Slog.wtf(TAG, "Exception thrown creating backup agent in " + app, e);
                 badApp = true;
@@ -7046,9 +6843,7 @@
             mUsageStatsService.prepareShutdown();
         }
         mBatteryStatsService.shutdown();
-        synchronized (this) {
-            mProcessStats.shutdownLocked();
-        }
+        mProcessStats.shutdown();
 
         return timedout;
     }
@@ -7492,15 +7287,15 @@
                     "registerUidObserver");
         }
         synchronized (this) {
-            mUidObservers.register(observer, new UidObserverRegistration(Binder.getCallingUid(),
-                    callingPackage, which, cutpoint));
+            mUidObserverController.register(observer, which, cutpoint, callingPackage,
+                    Binder.getCallingUid());
         }
     }
 
     @Override
     public void unregisterUidObserver(IUidObserver observer) {
         synchronized (this) {
-            mUidObservers.unregister(observer);
+            mUidObserverController.unregister(observer);
         }
     }
 
@@ -10119,9 +9914,9 @@
         }
 
         if (dumpAll) {
-            if (mValidateUids.size() > 0) {
-                if (dumpUids(pw, dumpPackage, dumpAppId, mValidateUids, "UID validation:",
-                        needSep)) {
+            if (mUidObserverController.mValidateUids.size() > 0) {
+                if (dumpUids(pw, dumpPackage, dumpAppId, mUidObserverController.mValidateUids,
+                                "UID validation:", needSep)) {
                     needSep = true;
                 }
             }
@@ -10253,45 +10048,8 @@
             }
         }
         if (dumpAll) {
-            final int NI = mUidObservers.getRegisteredCallbackCount();
-            boolean printed = false;
-            for (int i=0; i<NI; i++) {
-                final UidObserverRegistration reg = (UidObserverRegistration)
-                        mUidObservers.getRegisteredCallbackCookie(i);
-                if (dumpPackage == null || dumpPackage.equals(reg.pkg)) {
-                    if (!printed) {
-                        pw.println("  mUidObservers:");
-                        printed = true;
-                    }
-                    pw.print("    "); UserHandle.formatUid(pw, reg.uid);
-                    pw.print(" "); pw.print(reg.pkg);
-                    final IUidObserver observer = mUidObservers.getRegisteredCallbackItem(i);
-                    pw.print(" "); pw.print(observer.getClass().getTypeName()); pw.print(":");
-                    if ((reg.which&ActivityManager.UID_OBSERVER_IDLE) != 0) {
-                        pw.print(" IDLE");
-                    }
-                    if ((reg.which&ActivityManager.UID_OBSERVER_ACTIVE) != 0) {
-                        pw.print(" ACT" );
-                    }
-                    if ((reg.which&ActivityManager.UID_OBSERVER_GONE) != 0) {
-                        pw.print(" GONE");
-                    }
-                    if ((reg.which&ActivityManager.UID_OBSERVER_PROCSTATE) != 0) {
-                        pw.print(" STATE");
-                        pw.print(" (cut="); pw.print(reg.cutpoint);
-                        pw.print(")");
-                    }
-                    pw.println();
-                    if (reg.lastProcStates != null) {
-                        final int NJ = reg.lastProcStates.size();
-                        for (int j=0; j<NJ; j++) {
-                            pw.print("      Last ");
-                            UserHandle.formatUid(pw, reg.lastProcStates.keyAt(j));
-                            pw.print(": "); pw.println(reg.lastProcStates.valueAt(j));
-                        }
-                    }
-                }
-            }
+            mUidObserverController.dump(pw, dumpPackage);
+
             pw.println("  mDeviceIdleWhitelist=" + Arrays.toString(mDeviceIdleWhitelist));
             pw.println("  mDeviceIdleExceptIdleWhitelist="
                     + Arrays.toString(mDeviceIdleExceptIdleWhitelist));
@@ -10419,25 +10177,6 @@
                         pw.print(" mLowRamSinceLastIdle=");
                         TimeUtils.formatDuration(getLowRamTimeSinceIdle(now), pw);
                         pw.println();
-                pw.println();
-                pw.print("  mUidChangeDispatchCount=");
-                pw.print(mUidChangeDispatchCount);
-                pw.println();
-
-                pw.println("  Slow UID dispatches:");
-                final int N = mUidObservers.beginBroadcast();
-                for (int i = 0; i < N; i++) {
-                    UidObserverRegistration r =
-                            (UidObserverRegistration) mUidObservers.getBroadcastCookie(i);
-                    pw.print("    ");
-                    pw.print(mUidObservers.getBroadcastItem(i).getClass().getTypeName());
-                    pw.print(": ");
-                    pw.print(r.mSlowDispatchCount);
-                    pw.print(" / Max ");
-                    pw.print(r.mMaxDispatchTime);
-                    pw.println("ms");
-                }
-                mUidObservers.finishBroadcast();
 
                 pw.println();
                 pw.println("  ServiceManager statistics:");
@@ -10505,8 +10244,8 @@
             uidRec.dumpDebug(proto, ActivityManagerServiceDumpProcessesProto.ACTIVE_UIDS);
         }
 
-        for (int i = 0; i < mValidateUids.size(); i++) {
-            UidRecord uidRec = mValidateUids.valueAt(i);
+        for (int i = 0; i < mUidObserverController.mValidateUids.size(); i++) {
+            UidRecord uidRec = mUidObserverController.mValidateUids.valueAt(i);
             if (dumpPackage != null && UserHandle.getAppId(uidRec.uid) != whichAppId) {
                 continue;
             }
@@ -10591,14 +10330,7 @@
             ActivityManagerServiceDumpProcessesProto.USER_CONTROLLER);
         }
 
-        final int NI = mUidObservers.getRegisteredCallbackCount();
-        for (int i=0; i<NI; i++) {
-            final UidObserverRegistration reg = (UidObserverRegistration)
-                    mUidObservers.getRegisteredCallbackCookie(i);
-            if (dumpPackage == null || dumpPackage.equals(reg.pkg)) {
-                reg.dumpDebug(proto, ActivityManagerServiceDumpProcessesProto.UID_OBSERVERS);
-            }
-        }
+        mUidObserverController.dumpDebug(proto, dumpPackage);
 
         for (int v : mDeviceIdleWhitelist) {
             proto.write(ActivityManagerServiceDumpProcessesProto.DEVICE_IDLE_WHITELIST, v);
@@ -12099,8 +11831,10 @@
                 synchronized (this) {
                     if (r.thread != null && oomAdj == r.getSetAdjWithServices()) {
                         // Record this for posterity if the process has been stable.
-                        r.baseProcessTracker.addPss(myTotalPss, myTotalUss, myTotalRss, true,
-                                reportType, endTime-startTime, r.pkgList.mPkgList);
+                        synchronized (mProcessStats.mLock) {
+                            r.baseProcessTracker.addPss(myTotalPss, myTotalUss, myTotalRss, true,
+                                    reportType, endTime - startTime, r.pkgList.mPkgList);
+                        }
                         for (int ipkg = r.pkgList.size() - 1; ipkg >= 0; ipkg--) {
                             ProcessStats.ProcessStateHolder holder = r.pkgList.valueAt(ipkg);
                             FrameworkStatsLog.write(FrameworkStatsLog.PROCESS_MEMORY_STAT_REPORTED,
@@ -12350,7 +12084,7 @@
             MemInfoReader memInfo = new MemInfoReader();
             memInfo.readMemInfo();
             if (nativeProcTotalPss > 0) {
-                synchronized (this) {
+                synchronized (mProcessStats.mLock) {
                     final long cachedKb = memInfo.getCachedSizeKb();
                     final long freeKb = memInfo.getFreeSizeKb();
                     final long zramKb = memInfo.getZramTotalSizeKb();
@@ -12705,8 +12439,10 @@
             synchronized (this) {
                 if (r.thread != null && oomAdj == r.getSetAdjWithServices()) {
                     // Record this for posterity if the process has been stable.
-                    r.baseProcessTracker.addPss(myTotalPss, myTotalUss, myTotalRss, true,
-                            reportType, endTime-startTime, r.pkgList.mPkgList);
+                    synchronized (mProcessStats.mLock) {
+                        r.baseProcessTracker.addPss(myTotalPss, myTotalUss, myTotalRss, true,
+                                reportType, endTime - startTime, r.pkgList.mPkgList);
+                    }
                     for (int ipkg = r.pkgList.size() - 1; ipkg >= 0; ipkg--) {
                         ProcessStats.ProcessStateHolder holder = r.pkgList.valueAt(ipkg);
                         FrameworkStatsLog.write(FrameworkStatsLog.PROCESS_MEMORY_STAT_REPORTED,
@@ -12932,7 +12668,7 @@
             MemInfoReader memInfo = new MemInfoReader();
             memInfo.readMemInfo();
             if (nativeProcTotalPss > 0) {
-                synchronized (this) {
+                synchronized (mProcessStats.mLock) {
                     final long cachedKb = memInfo.getCachedSizeKb();
                     final long freeKb = memInfo.getFreeSizeKb();
                     final long zramKb = memInfo.getZramTotalSizeKb();
@@ -13543,8 +13279,8 @@
 
     @Override
     public ComponentName startService(IApplicationThread caller, Intent service,
-            String resolvedType, boolean requireForeground, String callingPackage,
-            String callingFeatureId, int userId)
+            String resolvedType, boolean requireForeground, boolean hideForegroundNotification,
+            String callingPackage, String callingFeatureId, int userId)
             throws TransactionTooLargeException {
         enforceNotIsolatedCaller("startService");
         // Refuse possible leaked file descriptors
@@ -13556,17 +13292,27 @@
             throw new IllegalArgumentException("callingPackage cannot be null");
         }
 
+        final int callingUid = Binder.getCallingUid();
+        if (requireForeground && hideForegroundNotification) {
+            if (!UserHandle.isSameApp(callingUid, mPackageVerifierUid)
+                    || !callingPackage.equals(mPackageVerifier)) {
+                throw new IllegalArgumentException(
+                        "Only the package verifier can hide its foreground service notification");
+            }
+            Slog.i(TAG, "Foreground service notification hiding requested by " + callingPackage);
+        }
+
         if (DEBUG_SERVICE) Slog.v(TAG_SERVICE,
                 "*** startService: " + service + " type=" + resolvedType + " fg=" + requireForeground);
         synchronized(this) {
             final int callingPid = Binder.getCallingPid();
-            final int callingUid = Binder.getCallingUid();
             final long origId = Binder.clearCallingIdentity();
             ComponentName res;
             try {
                 res = mServices.startServiceLocked(caller, service,
                         resolvedType, callingPid, callingUid,
-                        requireForeground, callingPackage, callingFeatureId, userId);
+                        requireForeground, hideForegroundNotification,
+                        callingPackage, callingFeatureId, userId);
             } finally {
                 Binder.restoreCallingIdentity(origId);
             }
@@ -13776,7 +13522,8 @@
     // Cause the target app to be launched if necessary and its backup agent
     // instantiated.  The backup agent will invoke backupAgentCreated() on the
     // activity manager to announce its creation.
-    public boolean bindBackupAgent(String packageName, int backupMode, int targetUserId) {
+    public boolean bindBackupAgent(String packageName, int backupMode, int targetUserId,
+            @OperationType int operationType) {
         if (DEBUG_BACKUP) {
             Slog.v(TAG, "bindBackupAgent: app=" + packageName + " mode=" + backupMode
                     + " targetUserId=" + targetUserId + " callingUid = " + Binder.getCallingUid()
@@ -13819,7 +13566,7 @@
                         + app.packageName + ": " + e);
             }
 
-            BackupRecord r = new BackupRecord(app, backupMode, targetUserId);
+            BackupRecord r = new BackupRecord(app, backupMode, targetUserId, operationType);
             ComponentName hostingName =
                     (backupMode == ApplicationThreadConstants.BACKUP_MODE_INCREMENTAL)
                             ? new ComponentName(app.packageName, app.backupAgentName)
@@ -13858,7 +13605,8 @@
                 if (DEBUG_BACKUP) Slog.v(TAG_BACKUP, "Agent proc already running: " + proc);
                 try {
                     proc.thread.scheduleCreateBackupAgent(app,
-                            compatibilityInfoForPackage(app), backupMode, targetUserId);
+                            compatibilityInfoForPackage(app), backupMode, targetUserId,
+                            operationType);
                 } catch (RemoteException e) {
                     // Will time out on the backup manager side
                 }
@@ -14051,13 +13799,13 @@
     public Intent registerReceiver(IApplicationThread caller, String callerPackage,
             IIntentReceiver receiver, IntentFilter filter, String permission, int userId,
             int flags) {
-        return registerReceiverWithFeature(caller, callerPackage, null, receiver, filter,
-                permission, userId, flags);
+        return registerReceiverWithFeature(caller, callerPackage, null, null,
+                receiver, filter, permission, userId, flags);
     }
 
     public Intent registerReceiverWithFeature(IApplicationThread caller, String callerPackage,
-            String callerFeatureId, IIntentReceiver receiver, IntentFilter filter,
-            String permission, int userId, int flags) {
+            String callerFeatureId, String receiverId, IIntentReceiver receiver,
+            IntentFilter filter, String permission, int userId, int flags) {
         enforceNotIsolatedCaller("registerReceiver");
         ArrayList<Intent> stickyIntents = null;
         ProcessRecord callerApp = null;
@@ -14194,7 +13942,7 @@
                         + " callerPackage is " + callerPackage);
             }
             BroadcastFilter bf = new BroadcastFilter(filter, rl, callerPackage, callerFeatureId,
-                    permission, callingUid, userId, instantApp, visibleToInstantApps);
+                    receiverId, permission, callingUid, userId, instantApp, visibleToInstantApps);
             if (rl.containsFilter(filter)) {
                 Slog.w(TAG, "Receiver with filter " + filter
                         + " already registered for pid " + rl.pid
@@ -14466,7 +14214,7 @@
                 resolvedType, resultTo, resultCode, resultData, resultExtras, requiredPermissions,
                 appOp, bOptions, ordered, sticky, callingPid, callingUid, realCallingUid,
                 realCallingPid, userId, false /* allowBackgroundActivityStarts */,
-                null /* tokenNeededForBackgroundActivityStarts */, null /* broadcastWhitelist */);
+                null /* tokenNeededForBackgroundActivityStarts */, null /* broadcastAllowList */);
     }
 
     @GuardedBy("this")
@@ -15310,7 +15058,7 @@
                         OP_NONE, bOptions, serialized, sticky, -1, uid, realCallingUid,
                         realCallingPid, userId, allowBackgroundActivityStarts,
                         backgroundActivityStartsToken,
-                        null /*broadcastWhitelist*/);
+                        null /* broadcastAllowList */);
             } finally {
                 Binder.restoreCallingIdentity(origId);
             }
@@ -15917,8 +15665,10 @@
         EventLogTags.writeAmPss(proc.pid, proc.uid, proc.processName, pss * 1024, uss * 1024,
                 swapPss * 1024, rss * 1024, statType, procState, pssDuration);
         proc.lastPssTime = now;
-        proc.baseProcessTracker.addPss(
-                pss, uss, rss, true, statType, pssDuration, proc.pkgList.mPkgList);
+        synchronized (mProcessStats.mLock) {
+            proc.baseProcessTracker.addPss(
+                    pss, uss, rss, true, statType, pssDuration, proc.pkgList.mPkgList);
+        }
         for (int ipkg = proc.pkgList.mPkgList.size() - 1; ipkg >= 0; ipkg--) {
             ProcessStats.ProcessStateHolder holder = proc.pkgList.valueAt(ipkg);
             FrameworkStatsLog.write(FrameworkStatsLog.PROCESS_MEMORY_STAT_REPORTED,
@@ -16220,182 +15970,94 @@
         }
     }
 
-    final void checkExcessivePowerUsageLocked() {
+    private void checkExcessivePowerUsage() {
         updateCpuStatsNow();
 
-        BatteryStatsImpl stats = mBatteryStatsService.getActiveStatistics();
-        boolean doCpuKills = true;
-        if (mLastPowerCheckUptime == 0) {
-            doCpuKills = false;
-        }
-        final long curUptime = SystemClock.uptimeMillis();
-        final long uptimeSince = curUptime - mLastPowerCheckUptime;
-        mLastPowerCheckUptime = curUptime;
-        int i = mProcessList.mLruProcesses.size();
-        while (i > 0) {
-            i--;
-            ProcessRecord app = mProcessList.mLruProcesses.get(i);
-            if (app.setProcState >= ActivityManager.PROCESS_STATE_HOME) {
-                if (app.lastCpuTime <= 0) {
-                    continue;
-                }
-                long cputimeUsed = app.curCpuTime - app.lastCpuTime;
-                if (DEBUG_POWER) {
-                    StringBuilder sb = new StringBuilder(128);
-                    sb.append("CPU for ");
-                    app.toShortString(sb);
-                    sb.append(": over ");
-                    TimeUtils.formatDuration(uptimeSince, sb);
-                    sb.append(" used ");
-                    TimeUtils.formatDuration(cputimeUsed, sb);
-                    sb.append(" (");
-                    sb.append((cputimeUsed*100)/uptimeSince);
-                    sb.append("%)");
-                    Slog.i(TAG_POWER, sb.toString());
-                }
-                // If the process has used too much CPU over the last duration, the
-                // user probably doesn't want this, so kill!
-                if (doCpuKills && uptimeSince > 0) {
-                    // What is the limit for this process?
-                    int cpuLimit;
-                    long checkDur = curUptime - app.getWhenUnimportant();
-                    if (checkDur <= mConstants.POWER_CHECK_INTERVAL) {
-                        cpuLimit = mConstants.POWER_CHECK_MAX_CPU_1;
-                    } else if (checkDur <= (mConstants.POWER_CHECK_INTERVAL*2)
-                            || app.setProcState <= ActivityManager.PROCESS_STATE_HOME) {
-                        cpuLimit = mConstants.POWER_CHECK_MAX_CPU_2;
-                    } else if (checkDur <= (mConstants.POWER_CHECK_INTERVAL*3)) {
-                        cpuLimit = mConstants.POWER_CHECK_MAX_CPU_3;
-                    } else {
-                        cpuLimit = mConstants.POWER_CHECK_MAX_CPU_4;
+        synchronized (this) {
+            BatteryStatsImpl stats = mBatteryStatsService.getActiveStatistics();
+            boolean doCpuKills = true;
+            if (mLastPowerCheckUptime == 0) {
+                doCpuKills = false;
+            }
+            final long curUptime = SystemClock.uptimeMillis();
+            final long uptimeSince = curUptime - mLastPowerCheckUptime;
+            mLastPowerCheckUptime = curUptime;
+            int i = mProcessList.mLruProcesses.size();
+            while (i > 0) {
+                i--;
+                ProcessRecord app = mProcessList.mLruProcesses.get(i);
+                if (app.setProcState >= ActivityManager.PROCESS_STATE_HOME) {
+                    if (app.lastCpuTime <= 0) {
+                        continue;
                     }
-                    if (((cputimeUsed*100)/uptimeSince) >= cpuLimit) {
-                        synchronized (stats) {
-                            stats.reportExcessiveCpuLocked(app.info.uid, app.processName,
-                                    uptimeSince, cputimeUsed);
+                    long cputimeUsed = app.curCpuTime - app.lastCpuTime;
+                    if (DEBUG_POWER) {
+                        StringBuilder sb = new StringBuilder(128);
+                        sb.append("CPU for ");
+                        app.toShortString(sb);
+                        sb.append(": over ");
+                        TimeUtils.formatDuration(uptimeSince, sb);
+                        sb.append(" used ");
+                        TimeUtils.formatDuration(cputimeUsed, sb);
+                        sb.append(" (");
+                        sb.append((cputimeUsed * 100) / uptimeSince);
+                        sb.append("%)");
+                        Slog.i(TAG_POWER, sb.toString());
+                    }
+                    // If the process has used too much CPU over the last duration, the
+                    // user probably doesn't want this, so kill!
+                    if (doCpuKills && uptimeSince > 0) {
+                        // What is the limit for this process?
+                        int cpuLimit;
+                        long checkDur = curUptime - app.getWhenUnimportant();
+                        if (checkDur <= mConstants.POWER_CHECK_INTERVAL) {
+                            cpuLimit = mConstants.POWER_CHECK_MAX_CPU_1;
+                        } else if (checkDur <= (mConstants.POWER_CHECK_INTERVAL * 2)
+                                || app.setProcState <= ActivityManager.PROCESS_STATE_HOME) {
+                            cpuLimit = mConstants.POWER_CHECK_MAX_CPU_2;
+                        } else if (checkDur <= (mConstants.POWER_CHECK_INTERVAL * 3)) {
+                            cpuLimit = mConstants.POWER_CHECK_MAX_CPU_3;
+                        } else {
+                            cpuLimit = mConstants.POWER_CHECK_MAX_CPU_4;
                         }
-                        app.kill("excessive cpu " + cputimeUsed + " during " + uptimeSince
-                                + " dur=" + checkDur + " limit=" + cpuLimit,
-                                ApplicationExitInfo.REASON_EXCESSIVE_RESOURCE_USAGE,
-                                ApplicationExitInfo.SUBREASON_EXCESSIVE_CPU,
-                                true);
-                        app.baseProcessTracker.reportExcessiveCpu(app.pkgList.mPkgList);
-                        for (int ipkg = app.pkgList.size() - 1; ipkg >= 0; ipkg--) {
-                            ProcessStats.ProcessStateHolder holder = app.pkgList.valueAt(ipkg);
-                            FrameworkStatsLog.write(FrameworkStatsLog.EXCESSIVE_CPU_USAGE_REPORTED,
-                                    app.info.uid,
-                                    holder.state.getName(),
-                                    holder.state.getPackage(),
-                                    holder.appVersion);
+                        if (((cputimeUsed * 100) / uptimeSince) >= cpuLimit) {
+                            synchronized (stats) {
+                                stats.reportExcessiveCpuLocked(app.info.uid, app.processName,
+                                        uptimeSince, cputimeUsed);
+                            }
+                            app.kill("excessive cpu " + cputimeUsed + " during " + uptimeSince
+                                    + " dur=" + checkDur + " limit=" + cpuLimit,
+                                    ApplicationExitInfo.REASON_EXCESSIVE_RESOURCE_USAGE,
+                                    ApplicationExitInfo.SUBREASON_EXCESSIVE_CPU,
+                                    true);
+                            synchronized (mProcessStats.mLock) {
+                                app.baseProcessTracker.reportExcessiveCpu(app.pkgList.mPkgList);
+                            }
+                            for (int ipkg = app.pkgList.size() - 1; ipkg >= 0; ipkg--) {
+                                ProcessStats.ProcessStateHolder holder = app.pkgList.valueAt(ipkg);
+                                FrameworkStatsLog.write(
+                                        FrameworkStatsLog.EXCESSIVE_CPU_USAGE_REPORTED,
+                                        app.info.uid,
+                                        holder.state.getName(),
+                                        holder.state.getPackage(),
+                                        holder.appVersion);
+                            }
                         }
                     }
+                    app.lastCpuTime = app.curCpuTime;
                 }
-                app.lastCpuTime = app.curCpuTime;
-            }
-        }
-    }
-
-    private boolean isEphemeralLocked(int uid) {
-        String packages[] = mContext.getPackageManager().getPackagesForUid(uid);
-        if (packages == null || packages.length != 1) { // Ephemeral apps cannot share uid
-            return false;
-        }
-        return getPackageManagerInternalLocked().isPackageEphemeral(UserHandle.getUserId(uid),
-                packages[0]);
-    }
-
-    @VisibleForTesting
-    final void enqueueUidChangeLocked(UidRecord uidRec, int uid, int change) {
-        final UidRecord.ChangeItem pendingChange;
-        if (uidRec == null || uidRec.pendingChange == null) {
-            if (mPendingUidChanges.size() == 0) {
-                if (DEBUG_UID_OBSERVERS) Slog.i(TAG_UID_OBSERVERS,
-                        "*** Enqueueing dispatch uid changed!");
-                mUiHandler.obtainMessage(DISPATCH_UIDS_CHANGED_UI_MSG).sendToTarget();
-            }
-            final int NA = mAvailUidChanges.size();
-            if (NA > 0) {
-                pendingChange = mAvailUidChanges.remove(NA-1);
-                if (DEBUG_UID_OBSERVERS) Slog.i(TAG_UID_OBSERVERS,
-                        "Retrieving available item: " + pendingChange);
-            } else {
-                pendingChange = new UidRecord.ChangeItem();
-                if (DEBUG_UID_OBSERVERS) Slog.i(TAG_UID_OBSERVERS,
-                        "Allocating new item: " + pendingChange);
-            }
-            if (uidRec != null) {
-                uidRec.pendingChange = pendingChange;
-                if ((change & UidRecord.CHANGE_GONE) != 0 && !uidRec.idle) {
-                    // If this uid is going away, and we haven't yet reported it is gone,
-                    // then do so now.
-                    change |= UidRecord.CHANGE_IDLE;
-                }
-            } else if (uid < 0) {
-                throw new IllegalArgumentException("No UidRecord or uid");
-            }
-            pendingChange.uidRecord = uidRec;
-            pendingChange.uid = uidRec != null ? uidRec.uid : uid;
-            mPendingUidChanges.add(pendingChange);
-        } else {
-            pendingChange = uidRec.pendingChange;
-            // If there is no change in idle or active state, then keep whatever was pending.
-            if ((change & (UidRecord.CHANGE_IDLE | UidRecord.CHANGE_ACTIVE)) == 0) {
-                change |= (pendingChange.change & (UidRecord.CHANGE_IDLE
-                        | UidRecord.CHANGE_ACTIVE));
-            }
-            // If there is no change in cached or uncached state, then keep whatever was pending.
-            if ((change & (UidRecord.CHANGE_CACHED | UidRecord.CHANGE_UNCACHED)) == 0) {
-                change |= (pendingChange.change & (UidRecord.CHANGE_CACHED
-                        | UidRecord.CHANGE_UNCACHED));
-            }
-            // If this is a report of the UID being gone, then we shouldn't keep any previous
-            // report of it being active or cached.  (That is, a gone uid is never active,
-            // and never cached.)
-            if ((change & UidRecord.CHANGE_GONE) != 0) {
-                change &= ~(UidRecord.CHANGE_ACTIVE | UidRecord.CHANGE_CACHED);
-                if (!uidRec.idle) {
-                    // If this uid is going away, and we haven't yet reported it is gone,
-                    // then do so now.
-                    change |= UidRecord.CHANGE_IDLE;
-                }
-            }
-        }
-        pendingChange.change = change;
-        pendingChange.processState = uidRec != null ? uidRec.setProcState : PROCESS_STATE_NONEXISTENT;
-        pendingChange.capability = uidRec != null ? uidRec.setCapability : 0;
-        pendingChange.ephemeral = uidRec != null ? uidRec.ephemeral : isEphemeralLocked(uid);
-        pendingChange.procStateSeq = uidRec != null ? uidRec.curProcStateSeq : 0;
-        if (uidRec != null) {
-            uidRec.lastReportedChange = change;
-            uidRec.updateLastDispatchedProcStateSeq(change);
-        }
-
-        // Directly update the power manager, since we sit on top of it and it is critical
-        // it be kept in sync (so wake locks will be held as soon as appropriate).
-        if (mLocalPowerManager != null) {
-            // TO DO: dispatch cached/uncached changes here, so we don't need to report
-            // all proc state changes.
-            if ((change & UidRecord.CHANGE_ACTIVE) != 0) {
-                mLocalPowerManager.uidActive(pendingChange.uid);
-            }
-            if ((change & UidRecord.CHANGE_IDLE) != 0) {
-                mLocalPowerManager.uidIdle(pendingChange.uid);
-            }
-            if ((change & UidRecord.CHANGE_GONE) != 0) {
-                mLocalPowerManager.uidGone(pendingChange.uid);
-            } else {
-                mLocalPowerManager.updateUidProcState(pendingChange.uid,
-                        pendingChange.processState);
             }
         }
     }
 
     final void setProcessTrackerStateLocked(ProcessRecord proc, int memFactor, long now) {
-        if (proc.thread != null && proc.baseProcessTracker != null) {
-            final int procState = proc.getReportedProcState();
-            if (procState != PROCESS_STATE_NONEXISTENT) {
-                proc.baseProcessTracker.setState(
-                        procState, memFactor, now, proc.pkgList.mPkgList);
+        synchronized (mProcessStats.mLock) {
+            if (proc.thread != null && proc.baseProcessTracker != null) {
+                final int procState = proc.getReportedProcState();
+                if (procState != PROCESS_STATE_NONEXISTENT) {
+                    proc.baseProcessTracker.setState(
+                            procState, memFactor, now, proc.pkgList.mPkgList);
+                }
             }
         }
     }
@@ -16503,9 +16165,7 @@
         }
 
         @Override public void run() {
-            synchronized (mService) {
-                mProcessStats.writeStateAsyncLocked();
-            }
+            mProcessStats.writeStateAsync();
         }
     }
 
@@ -16555,9 +16215,13 @@
         }
         mLastMemoryLevel = memFactor;
         mLastNumProcesses = mProcessList.getLruSizeLocked();
-        boolean allChanged = mProcessStats.setMemFactorLocked(
-                memFactor, mAtmInternal != null ? !mAtmInternal.isSleeping() : true, now);
-        final int trackerMemFactor = mProcessStats.getMemFactorLocked();
+        boolean allChanged;
+        int trackerMemFactor;
+        synchronized (mProcessStats.mLock) {
+            allChanged = mProcessStats.setMemFactorLocked(
+                    memFactor, mAtmInternal != null ? !mAtmInternal.isSleeping() : true, now);
+            trackerMemFactor = mProcessStats.getMemFactorLocked();
+        }
         if (memFactor != ProcessStats.ADJ_MEM_FACTOR_NORMAL) {
             if (mLowRamStartTime == 0) {
                 mLowRamStartTime = now;
@@ -16806,7 +16470,7 @@
     @GuardedBy("this")
     final void doStopUidLocked(int uid, final UidRecord uidRec) {
         mServices.stopInBackgroundLocked(uid);
-        enqueueUidChangeLocked(uidRec, uid, UidRecord.CHANGE_IDLE);
+        mUidObserverController.enqueueUidChangeLocked(uidRec, uid, UidRecord.CHANGE_IDLE);
     }
 
     /**
@@ -18032,7 +17696,7 @@
                 ComponentName res;
                 try {
                     res = mServices.startServiceLocked(null, service,
-                            resolvedType, -1, uid, fgRequired, callingPackage,
+                            resolvedType, -1, uid, fgRequired, false, callingPackage,
                             callingFeatureId, userId, allowBackgroundActivityStarts);
                 } finally {
                     Binder.restoreCallingIdentity(origId);
@@ -18352,19 +18016,20 @@
             throw new SecurityException("Requires permission " + FILTER_EVENTS);
         }
         ProcessRecord proc;
-        long timeout;
+        long timeoutMillis;
         synchronized (this) {
             synchronized (mPidsSelfLocked) {
                 proc = mPidsSelfLocked.get(pid);
             }
-            timeout = proc != null ? proc.getInputDispatchingTimeout() : KEY_DISPATCHING_TIMEOUT_MS;
+            timeoutMillis = proc != null ? proc.getInputDispatchingTimeoutMillis() :
+                    DEFAULT_DISPATCHING_TIMEOUT_MILLIS;
         }
 
         if (inputDispatchingTimedOut(proc, null, null, null, null, aboveSystem, reason)) {
-            return -1;
+            return 0;
         }
 
-        return timeout;
+        return timeoutMillis;
     }
 
     /**
@@ -18466,7 +18131,8 @@
                     Slog.w(TAG_NETWORK, "Total time waited for network rules to get updated: "
                             + totalTime + ". Uid: " + callingUid + " procStateSeq: "
                             + procStateSeq + " UidRec: " + record
-                            + " validateUidRec: " + mValidateUids.get(callingUid));
+                            + " validateUidRec: "
+                            + mUidObserverController.mValidateUids.get(callingUid));
                 }
             } catch (InterruptedException e) {
                 Thread.currentThread().interrupt();
diff --git a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
index 149e3ba..a512cca 100644
--- a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
+++ b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
@@ -654,7 +654,7 @@
         pw.println("Starting service: " + intent);
         pw.flush();
         ComponentName cn = mInterface.startService(null, intent, intent.getType(),
-                asForeground, SHELL_PACKAGE_NAME, null, mUserId);
+                asForeground, false, SHELL_PACKAGE_NAME, null, mUserId);
         if (cn == null) {
             err.println("Error: Not found; no service started.");
             return -1;
diff --git a/services/core/java/com/android/server/am/AppErrors.java b/services/core/java/com/android/server/am/AppErrors.java
index 2e92ac0..5268359 100644
--- a/services/core/java/com/android/server/am/AppErrors.java
+++ b/services/core/java/com/android/server/am/AppErrors.java
@@ -716,7 +716,7 @@
             // back in the pending list.
             ServiceRecord sr = app.getRunningServiceAt(i);
             // If the service was restarted a while ago, then reset crash count, else increment it.
-            if (now > sr.restartTime + ProcessList.MIN_CRASH_INTERVAL) {
+            if (now > sr.restartTime + ActivityManagerConstants.MIN_CRASH_INTERVAL) {
                 sr.crashCount = 1;
             } else {
                 sr.crashCount++;
@@ -729,7 +729,7 @@
             }
         }
 
-        if (crashTime != null && now < crashTime + ProcessList.MIN_CRASH_INTERVAL) {
+        if (crashTime != null && now < crashTime + ActivityManagerConstants.MIN_CRASH_INTERVAL) {
             // The process crashed again very quickly. If it was a bound foreground service, let's
             // try to restart again in a while, otherwise the process loses!
             Slog.w(TAG, "Process " + app.processName
@@ -771,7 +771,7 @@
                 data.taskId = affectedTaskId;
             }
             if (data != null && crashTimePersistent != null
-                    && now < crashTimePersistent + ProcessList.MIN_CRASH_INTERVAL) {
+                    && now < crashTimePersistent + ActivityManagerConstants.MIN_CRASH_INTERVAL) {
                 data.repeating = true;
             }
         }
@@ -853,7 +853,7 @@
                     mAppsNotReportingCrashes.contains(proc.info.packageName);
             final long now = SystemClock.uptimeMillis();
             final boolean shouldThottle = crashShowErrorTime != null
-                    && now < crashShowErrorTime + ProcessList.MIN_CRASH_INTERVAL;
+                    && now < crashShowErrorTime + ActivityManagerConstants.MIN_CRASH_INTERVAL;
             if ((mService.mAtmInternal.canShowErrorDialogs() || showBackground)
                     && !crashSilenced && !shouldThottle
                     && (showFirstCrash || showFirstCrashDevOption || data.repeating)) {
diff --git a/services/core/java/com/android/server/am/BackupRecord.java b/services/core/java/com/android/server/am/BackupRecord.java
index 37b4be4..d419856 100644
--- a/services/core/java/com/android/server/am/BackupRecord.java
+++ b/services/core/java/com/android/server/am/BackupRecord.java
@@ -16,6 +16,8 @@
 
 package com.android.server.am;
 
+import android.app.backup.BackupManager;
+import android.app.backup.BackupManager.OperationType;
 import android.content.pm.ApplicationInfo;
 
 /** @hide */
@@ -30,14 +32,16 @@
     final ApplicationInfo appInfo;         // information about BackupAgent's app
     final int userId;                      // user for which backup is performed
     final int backupMode;                  // full backup / incremental / restore
+    @OperationType  final int operationType; // see BackupManager#OperationType
     ProcessRecord app;                     // where this agent is running or null
 
     // ----- Implementation -----
 
-    BackupRecord(ApplicationInfo _appInfo, int _backupMode, int _userId) {
+    BackupRecord(ApplicationInfo _appInfo, int _backupMode, int _userId, int _operationType) {
         appInfo = _appInfo;
         backupMode = _backupMode;
         userId = _userId;
+        operationType = _operationType;
     }
 
     public String toString() {
diff --git a/services/core/java/com/android/server/am/BatteryStatsService.java b/services/core/java/com/android/server/am/BatteryStatsService.java
index 5081b36..d72998b 100644
--- a/services/core/java/com/android/server/am/BatteryStatsService.java
+++ b/services/core/java/com/android/server/am/BatteryStatsService.java
@@ -227,8 +227,9 @@
 
         @Override
         public void noteBinderCallStats(int workSourceUid, long incrementatCallCount,
-                Collection<BinderCallsStats.CallStat> callStats) {
-            mStats.noteBinderCallStats(workSourceUid, incrementatCallCount, callStats);
+                Collection<BinderCallsStats.CallStat> callStats, int[] binderThreadNativeTids) {
+            mStats.noteBinderCallStats(workSourceUid, incrementatCallCount, callStats,
+                    binderThreadNativeTids);
         }
     }
 
diff --git a/services/core/java/com/android/server/am/BroadcastFilter.java b/services/core/java/com/android/server/am/BroadcastFilter.java
index 578db6f..50e06c3 100644
--- a/services/core/java/com/android/server/am/BroadcastFilter.java
+++ b/services/core/java/com/android/server/am/BroadcastFilter.java
@@ -28,6 +28,7 @@
     final ReceiverList receiverList;
     final String packageName;
     final String featureId;
+    final String receiverId;
     final String requiredPermission;
     final int owningUid;
     final int owningUserId;
@@ -35,12 +36,13 @@
     final boolean visibleToInstantApp;
 
     BroadcastFilter(IntentFilter _filter, ReceiverList _receiverList,
-            String _packageName, String _featureId, String _requiredPermission, int _owningUid, int _userId,
-            boolean _instantApp, boolean _visibleToInstantApp) {
+            String _packageName, String _featureId, String _receiverId, String _requiredPermission,
+            int _owningUid, int _userId, boolean _instantApp, boolean _visibleToInstantApp) {
         super(_filter);
         receiverList = _receiverList;
         packageName = _packageName;
         featureId = _featureId;
+        receiverId = _receiverId;
         requiredPermission = _requiredPermission;
         owningUid = _owningUid;
         owningUserId = _userId;
diff --git a/services/core/java/com/android/server/am/BroadcastQueue.java b/services/core/java/com/android/server/am/BroadcastQueue.java
index 12937b9..960d26b 100644
--- a/services/core/java/com/android/server/am/BroadcastQueue.java
+++ b/services/core/java/com/android/server/am/BroadcastQueue.java
@@ -639,7 +639,7 @@
                 final int opCode = AppOpsManager.permissionToOpCode(filter.requiredPermission);
                 if (opCode != AppOpsManager.OP_NONE
                         && mService.getAppOpsManager().noteOpNoThrow(opCode, r.callingUid,
-                        r.callerPackage, r.callerFeatureId, "")
+                        r.callerPackage, r.callerFeatureId, "Broadcast sent to protected receiver")
                         != AppOpsManager.MODE_ALLOWED) {
                     Slog.w(TAG, "Appop Denial: broadcasting "
                             + r.intent.toString()
@@ -672,7 +672,8 @@
                 int appOp = AppOpsManager.permissionToOpCode(requiredPermission);
                 if (appOp != AppOpsManager.OP_NONE && appOp != r.appOp
                         && mService.getAppOpsManager().noteOpNoThrow(appOp,
-                        filter.receiverList.uid, filter.packageName, filter.featureId, "")
+                        filter.receiverList.uid, filter.packageName, filter.featureId,
+                        "Broadcast delivered to registered receiver " + filter.receiverId)
                         != AppOpsManager.MODE_ALLOWED) {
                     Slog.w(TAG, "Appop Denial: receiving "
                             + r.intent.toString()
@@ -704,7 +705,8 @@
         }
         if (!skip && r.appOp != AppOpsManager.OP_NONE
                 && mService.getAppOpsManager().noteOpNoThrow(r.appOp,
-                filter.receiverList.uid, filter.packageName, filter.featureId, "")
+                filter.receiverList.uid, filter.packageName, filter.featureId,
+                "Broadcast delivered to registered receiver " + filter.receiverId)
                 != AppOpsManager.MODE_ALLOWED) {
             Slog.w(TAG, "Appop Denial: receiving "
                     + r.intent.toString()
@@ -1366,9 +1368,10 @@
             skip = true;
         } else if (!skip && info.activityInfo.permission != null) {
             final int opCode = AppOpsManager.permissionToOpCode(info.activityInfo.permission);
-            if (opCode != AppOpsManager.OP_NONE
-                    && mService.getAppOpsManager().noteOpNoThrow(opCode, r.callingUid, r.callerPackage,
-                    r.callerFeatureId, "") != AppOpsManager.MODE_ALLOWED) {
+            if (opCode != AppOpsManager.OP_NONE && mService.getAppOpsManager().noteOpNoThrow(opCode,
+                    r.callingUid, r.callerPackage, r.callerFeatureId,
+                    "Broadcast delivered to " + info.activityInfo.name)
+                    != AppOpsManager.MODE_ALLOWED) {
                 Slog.w(TAG, "Appop Denial: broadcasting "
                         + r.intent.toString()
                         + " from " + r.callerPackage + " (pid="
@@ -1407,7 +1410,8 @@
                 if (appOp != AppOpsManager.OP_NONE && appOp != r.appOp
                         && mService.getAppOpsManager().noteOpNoThrow(appOp,
                         info.activityInfo.applicationInfo.uid, info.activityInfo.packageName,
-                        null /* default featureId */, "")
+                        null /* default featureId */,
+                        "Broadcast delivered to " + info.activityInfo.name)
                         != AppOpsManager.MODE_ALLOWED) {
                     Slog.w(TAG, "Appop Denial: receiving "
                             + r.intent + " to "
@@ -1424,7 +1428,7 @@
         if (!skip && r.appOp != AppOpsManager.OP_NONE
                 && mService.getAppOpsManager().noteOpNoThrow(r.appOp,
                 info.activityInfo.applicationInfo.uid, info.activityInfo.packageName,
-                null  /* default featureId */, "")
+                null  /* default featureId */, "Broadcast delivered to " + info.activityInfo.name)
                 != AppOpsManager.MODE_ALLOWED) {
             Slog.w(TAG, "Appop Denial: receiving "
                     + r.intent + " to "
diff --git a/services/core/java/com/android/server/am/ContentProviderHelper.java b/services/core/java/com/android/server/am/ContentProviderHelper.java
index 5cc7aba..bfba4af 100644
--- a/services/core/java/com/android/server/am/ContentProviderHelper.java
+++ b/services/core/java/com/android/server/am/ContentProviderHelper.java
@@ -50,6 +50,7 @@
 import android.os.Bundle;
 import android.os.Debug;
 import android.os.IBinder;
+import android.os.Message;
 import android.os.Process;
 import android.os.RemoteCallback;
 import android.os.RemoteException;
@@ -218,7 +219,7 @@
                     // of being published...  but it is also allowed to run
                     // in the caller's process, so don't make a connection
                     // and just let the caller instantiate its own instance.
-                    ContentProviderHolder holder = cpr.newHolder(null);
+                    ContentProviderHolder holder = cpr.newHolder(null, true);
                     // don't give caller the provider object, it needs to make its own.
                     holder.provider = null;
                     return holder;
@@ -415,7 +416,7 @@
                     // info and allow the caller to instantiate it.  Only do
                     // this if the provider is the same user as the caller's
                     // process, or can run as root (so can be in any process).
-                    return cpr.newHolder(null);
+                    return cpr.newHolder(null, true);
                 }
 
                 if (ActivityManagerDebugConfig.DEBUG_PROVIDER) {
@@ -513,6 +514,38 @@
                     UserHandle.getAppId(cpi.applicationInfo.uid));
         }
 
+        if (caller != null) {
+            // The client will be waiting, and we'll notify it when the provider is ready.
+            synchronized (cpr) {
+                if (cpr.provider == null) {
+                    if (cpr.launchingApp == null) {
+                        Slog.w(TAG, "Unable to launch app "
+                                + cpi.applicationInfo.packageName + "/"
+                                + cpi.applicationInfo.uid + " for provider "
+                                + name + ": launching app became null");
+                        EventLogTags.writeAmProviderLostProcess(
+                                UserHandle.getUserId(cpi.applicationInfo.uid),
+                                cpi.applicationInfo.packageName,
+                                cpi.applicationInfo.uid, name);
+                        return null;
+                    }
+
+                    if (conn != null) {
+                        conn.waiting = true;
+                    }
+                    Message msg = mService.mHandler.obtainMessage(
+                            ActivityManagerService.WAIT_FOR_CONTENT_PROVIDER_TIMEOUT_MSG);
+                    msg.obj = cpr;
+                    mService.mHandler.sendMessageDelayed(msg,
+                            ContentResolver.CONTENT_PROVIDER_READY_TIMEOUT_MILLIS);
+                }
+            }
+            // Return a holder instance even if we are waiting for the publishing of the provider,
+            // client will check for the holder.provider to see if it needs to wait for it.
+            return cpr.newHolder(conn, false);
+        }
+
+        // Because of the provider's external client (i.e., SHELL), we'll have to wait right here.
         // Wait for the provider to be published...
         final long timeout =
                 SystemClock.uptimeMillis() + ContentResolver.CONTENT_PROVIDER_READY_TIMEOUT_MILLIS;
@@ -569,7 +602,7 @@
                     + " caller=" + callerName + "/" + Binder.getCallingUid());
             return null;
         }
-        return cpr.newHolder(conn);
+        return cpr.newHolder(conn, false);
     }
 
     void publishContentProviders(IApplicationThread caller, List<ContentProviderHolder> providers) {
@@ -622,6 +655,8 @@
                 }
                 if (wasInLaunchingProviders) {
                     mService.mHandler.removeMessages(
+                            ActivityManagerService.WAIT_FOR_CONTENT_PROVIDER_TIMEOUT_MSG, dst);
+                    mService.mHandler.removeMessages(
                             ActivityManagerService.CONTENT_PROVIDER_PUBLISH_TIMEOUT_MSG, r);
                 }
                 // Make sure the package is associated with the process.
@@ -635,6 +670,7 @@
                     dst.provider = src.provider;
                     dst.setProcess(r);
                     dst.notifyAll();
+                    dst.onProviderPublishStatusLocked(true);
                 }
                 dst.mRestartCount = 0;
                 mService.updateOomAdjLocked(r, true, OomAdjuster.OOM_ADJ_REASON_GET_PROVIDER);
@@ -1504,6 +1540,9 @@
             synchronized (cpr) {
                 cpr.launchingApp = null;
                 cpr.notifyAll();
+                cpr.onProviderPublishStatusLocked(false);
+                mService.mHandler.removeMessages(
+                        ActivityManagerService.WAIT_FOR_CONTENT_PROVIDER_TIMEOUT_MSG, cpr);
             }
             final int userId = UserHandle.getUserId(cpr.uid);
             // Don't remove from provider map if it doesn't match
diff --git a/services/core/java/com/android/server/am/ContentProviderRecord.java b/services/core/java/com/android/server/am/ContentProviderRecord.java
index d8d8ccc..fb8b5d4 100644
--- a/services/core/java/com/android/server/am/ContentProviderRecord.java
+++ b/services/core/java/com/android/server/am/ContentProviderRecord.java
@@ -85,11 +85,12 @@
         noReleaseNeeded = cpr.noReleaseNeeded;
     }
 
-    public ContentProviderHolder newHolder(ContentProviderConnection conn) {
+    public ContentProviderHolder newHolder(ContentProviderConnection conn, boolean local) {
         ContentProviderHolder holder = new ContentProviderHolder(info);
         holder.provider = provider;
         holder.noReleaseNeeded = noReleaseNeeded;
         holder.connection = conn;
+        holder.mLocal = local;
         return holder;
     }
 
@@ -179,6 +180,50 @@
         return !connections.isEmpty() || hasExternalProcessHandles();
     }
 
+    /**
+     * Notify all clients that the provider has been published and ready to use,
+     * or timed out.
+     *
+     * @param status true: successfully published; false: timed out
+     */
+    void onProviderPublishStatusLocked(boolean status) {
+        final int numOfConns = connections.size();
+        final int userId = UserHandle.getUserId(appInfo.uid);
+        for (int i = 0; i < numOfConns; i++) {
+            final ContentProviderConnection conn = connections.get(i);
+            if (conn.waiting && conn.client != null) {
+                final ProcessRecord client = conn.client;
+                if (!status) {
+                    if (launchingApp == null) {
+                        Slog.w(TAG_AM, "Unable to launch app "
+                                + appInfo.packageName + "/"
+                                + appInfo.uid + " for provider "
+                                + info.authority + ": launching app became null");
+                        EventLogTags.writeAmProviderLostProcess(
+                                userId,
+                                appInfo.packageName,
+                                appInfo.uid, info.authority);
+                    } else {
+                        Slog.wtf(TAG_AM, "Timeout waiting for provider "
+                                + appInfo.packageName + "/"
+                                + appInfo.uid + " for provider "
+                                + info.authority
+                                + " caller=" + client);
+                    }
+                }
+                if (client.thread != null) {
+                    try {
+                        client.thread.notifyContentProviderPublishStatus(
+                                newHolder(status ? conn : null, false),
+                                info.authority, userId, status);
+                    } catch (RemoteException e) {
+                    }
+                }
+            }
+            conn.waiting = false;
+        }
+    }
+
     void dump(PrintWriter pw, String prefix, boolean full) {
         if (full) {
             pw.print(prefix); pw.print("package=");
diff --git a/services/core/java/com/android/server/am/CoreSettingsObserver.java b/services/core/java/com/android/server/am/CoreSettingsObserver.java
index 8970ec4..757b4e8 100644
--- a/services/core/java/com/android/server/am/CoreSettingsObserver.java
+++ b/services/core/java/com/android/server/am/CoreSettingsObserver.java
@@ -100,15 +100,20 @@
         sGlobalSettingToTypeMap.put(Settings.Global.GPU_DEBUG_LAYERS, String.class);
         sGlobalSettingToTypeMap.put(Settings.Global.GPU_DEBUG_LAYERS_GLES, String.class);
         sGlobalSettingToTypeMap.put(Settings.Global.GPU_DEBUG_LAYER_APP, String.class);
-        sGlobalSettingToTypeMap.put(Settings.Global.GAME_DRIVER_ALL_APPS, int.class);
-        sGlobalSettingToTypeMap.put(Settings.Global.GAME_DRIVER_OPT_IN_APPS, String.class);
+        sGlobalSettingToTypeMap.put(Settings.Global.UPDATABLE_DRIVER_ALL_APPS, int.class);
         sGlobalSettingToTypeMap.put(
-                Settings.Global.GAME_DRIVER_PRERELEASE_OPT_IN_APPS, String.class);
-        sGlobalSettingToTypeMap.put(Settings.Global.GAME_DRIVER_OPT_OUT_APPS, String.class);
-        sGlobalSettingToTypeMap.put(Settings.Global.GAME_DRIVER_DENYLIST, String.class);
-        sGlobalSettingToTypeMap.put(Settings.Global.GAME_DRIVER_ALLOWLIST, String.class);
-        sGlobalSettingToTypeMap.put(Settings.Global.GAME_DRIVER_DENYLISTS, String.class);
-        sGlobalSettingToTypeMap.put(Settings.Global.GAME_DRIVER_SPHAL_LIBRARIES, String.class);
+                Settings.Global.UPDATABLE_DRIVER_PRODUCTION_OPT_IN_APPS, String.class);
+        sGlobalSettingToTypeMap.put(
+                Settings.Global.UPDATABLE_DRIVER_PRERELEASE_OPT_IN_APPS, String.class);
+        sGlobalSettingToTypeMap.put(
+                Settings.Global.UPDATABLE_DRIVER_PRODUCTION_OPT_OUT_APPS, String.class);
+        sGlobalSettingToTypeMap.put(
+                Settings.Global.UPDATABLE_DRIVER_PRODUCTION_DENYLIST, String.class);
+        sGlobalSettingToTypeMap.put(
+                Settings.Global.UPDATABLE_DRIVER_PRODUCTION_ALLOWLIST, String.class);
+        sGlobalSettingToTypeMap.put(
+                Settings.Global.UPDATABLE_DRIVER_PRODUCTION_DENYLISTS, String.class);
+        sGlobalSettingToTypeMap.put(Settings.Global.UPDATABLE_DRIVER_SPHAL_LIBRARIES, String.class);
         // add other global settings here...
 
         sDeviceConfigEntries.add(new DeviceConfigEntry<Boolean>(
@@ -116,6 +121,10 @@
                 WidgetFlags.KEY_ENABLE_CURSOR_DRAG_FROM_ANYWHERE, boolean.class,
                 WidgetFlags.ENABLE_CURSOR_DRAG_FROM_ANYWHERE_DEFAULT));
         sDeviceConfigEntries.add(new DeviceConfigEntry<Integer>(
+                DeviceConfig.NAMESPACE_WIDGET, WidgetFlags.CURSOR_DRAG_MIN_ANGLE_FROM_VERTICAL,
+                WidgetFlags.KEY_CURSOR_DRAG_MIN_ANGLE_FROM_VERTICAL, int.class,
+                WidgetFlags.CURSOR_DRAG_MIN_ANGLE_FROM_VERTICAL_DEFAULT));
+        sDeviceConfigEntries.add(new DeviceConfigEntry<Integer>(
                 DeviceConfig.NAMESPACE_WIDGET, WidgetFlags.FINGER_TO_CURSOR_DISTANCE,
                 WidgetFlags.KEY_FINGER_TO_CURSOR_DISTANCE, int.class,
                 WidgetFlags.FINGER_TO_CURSOR_DISTANCE_DEFAULT));
diff --git a/services/core/java/com/android/server/am/OomAdjuster.java b/services/core/java/com/android/server/am/OomAdjuster.java
index f0343e1..bad042c 100644
--- a/services/core/java/com/android/server/am/OomAdjuster.java
+++ b/services/core/java/com/android/server/am/OomAdjuster.java
@@ -659,13 +659,15 @@
 
         updateUidsLocked(activeUids, nowElapsed);
 
-        if (mService.mProcessStats.shouldWriteNowLocked(now)) {
-            mService.mHandler.post(new ActivityManagerService.ProcStatsRunnable(mService,
-                    mService.mProcessStats));
-        }
+        synchronized (mService.mProcessStats.mLock) {
+            if (mService.mProcessStats.shouldWriteNowLocked(now)) {
+                mService.mHandler.post(new ActivityManagerService.ProcStatsRunnable(mService,
+                        mService.mProcessStats));
+            }
 
-        // Run this after making sure all procstates are updated.
-        mService.mProcessStats.updateTrackingAssociationsLocked(mAdjSeq, now);
+            // Run this after making sure all procstates are updated.
+            mService.mProcessStats.updateTrackingAssociationsLocked(mAdjSeq, now);
+        }
 
         if (DEBUG_OOM_ADJ) {
             final long duration = SystemClock.uptimeMillis() - now;
@@ -985,7 +987,7 @@
                 uidRec.setWhitelist = uidRec.curWhitelist;
                 uidRec.setIdle = uidRec.idle;
                 mService.mAtmInternal.onUidProcStateChanged(uidRec.uid, uidRec.setProcState);
-                mService.enqueueUidChangeLocked(uidRec, -1, uidChange);
+                mService.mUidObserverController.enqueueUidChangeLocked(uidRec, -1, uidChange);
                 mService.noteUidProcessState(uidRec.uid, uidRec.getCurProcState(),
                         uidRec.curCapability);
                 if (uidRec.foregroundServices) {
diff --git a/services/core/java/com/android/server/am/PendingTempWhitelists.java b/services/core/java/com/android/server/am/PendingTempWhitelists.java
index b36e3c7..50d58f0 100644
--- a/services/core/java/com/android/server/am/PendingTempWhitelists.java
+++ b/services/core/java/com/android/server/am/PendingTempWhitelists.java
@@ -32,13 +32,13 @@
 
     void put(int uid, ActivityManagerService.PendingTempWhitelist value) {
         mPendingTempWhitelist.put(uid, value);
-        mService.mAtmInternal.onUidAddedToPendingTempWhitelist(uid, value.tag);
+        mService.mAtmInternal.onUidAddedToPendingTempAllowlist(uid, value.tag);
     }
 
     void removeAt(int index) {
         final int uid = mPendingTempWhitelist.keyAt(index);
         mPendingTempWhitelist.removeAt(index);
-        mService.mAtmInternal.onUidRemovedFromPendingTempWhitelist(uid);
+        mService.mAtmInternal.onUidRemovedFromPendingTempAllowlist(uid);
     }
 
     ActivityManagerService.PendingTempWhitelist get(int uid) {
diff --git a/services/core/java/com/android/server/am/ProcessList.java b/services/core/java/com/android/server/am/ProcessList.java
index 5721fb7..76089f8 100644
--- a/services/core/java/com/android/server/am/ProcessList.java
+++ b/services/core/java/com/android/server/am/ProcessList.java
@@ -154,10 +154,6 @@
     static final String ANDROID_VOLD_APP_DATA_ISOLATION_ENABLED_PROPERTY =
             "persist.sys.vold_app_data_isolation_enabled";
 
-    // The minimum time we allow between crashes, for us to consider this
-    // application to be bad and stop and its services and reject broadcasts.
-    static final int MIN_CRASH_INTERVAL = 60 * 1000;
-
     // OOM adjustments for processes in various states:
 
     // Uninitialized value for any major or minor adj fields
@@ -2222,7 +2218,8 @@
                 // access /mnt/user anyway.
                 && app.mountMode != Zygote.MOUNT_EXTERNAL_ANDROID_WRITABLE
                 && app.mountMode != Zygote.MOUNT_EXTERNAL_PASS_THROUGH
-                && app.mountMode != Zygote.MOUNT_EXTERNAL_INSTALLER;
+                && app.mountMode != Zygote.MOUNT_EXTERNAL_INSTALLER
+                && app.mountMode != Zygote.MOUNT_EXTERNAL_NONE;
     }
 
     private Process.ProcessStartResult startProcess(HostingRecord hostingRecord, String entryPoint,
@@ -2960,7 +2957,8 @@
                 // No more processes using this uid, tell clients it is gone.
                 if (DEBUG_UID_OBSERVERS) Slog.i(TAG_UID_OBSERVERS,
                         "No more processes in " + uidRecord);
-                mService.enqueueUidChangeLocked(uidRecord, -1, UidRecord.CHANGE_GONE);
+                mService.mUidObserverController.enqueueUidChangeLocked(uidRecord, -1,
+                        UidRecord.CHANGE_GONE);
                 EventLogTags.writeAmUidStopped(uid);
                 mActiveUids.remove(uid);
                 mService.noteUidProcessState(uid, ActivityManager.PROCESS_STATE_NONEXISTENT,
@@ -4068,7 +4066,8 @@
         boolean enqueueLocked(ProcessRecord app, String reason, int requester) {
             // Throttle the killing request for potential bad app to avoid cpu thrashing
             Long last = app.isolated ? null : mLastProcessKillTimes.get(app.processName, app.uid);
-            if (last != null && SystemClock.uptimeMillis() < last + MIN_CRASH_INTERVAL) {
+            if ((last != null) && (SystemClock.uptimeMillis()
+                    < (last + ActivityManagerConstants.MIN_CRASH_INTERVAL))) {
                 return false;
             }
 
diff --git a/services/core/java/com/android/server/am/ProcessRecord.java b/services/core/java/com/android/server/am/ProcessRecord.java
index cd4302b..14bc6cb 100644
--- a/services/core/java/com/android/server/am/ProcessRecord.java
+++ b/services/core/java/com/android/server/am/ProcessRecord.java
@@ -673,30 +673,33 @@
 
     public void makeActive(IApplicationThread _thread, ProcessStatsService tracker) {
         if (thread == null) {
-            final ProcessState origBase = baseProcessTracker;
-            if (origBase != null) {
-                origBase.setState(ProcessStats.STATE_NOTHING,
-                        tracker.getMemFactorLocked(), SystemClock.uptimeMillis(), pkgList.mPkgList);
-                for (int ipkg = pkgList.size() - 1; ipkg >= 0; ipkg--) {
-                    FrameworkStatsLog.write(FrameworkStatsLog.PROCESS_STATE_CHANGED,
-                            uid, processName, pkgList.keyAt(ipkg),
-                            ActivityManager.processStateAmToProto(ProcessStats.STATE_NOTHING),
-                            pkgList.valueAt(ipkg).appVersion);
+            synchronized (tracker.mLock) {
+                final ProcessState origBase = baseProcessTracker;
+                if (origBase != null) {
+                    origBase.setState(ProcessStats.STATE_NOTHING,
+                            tracker.getMemFactorLocked(), SystemClock.uptimeMillis(),
+                            pkgList.mPkgList);
+                    for (int ipkg = pkgList.size() - 1; ipkg >= 0; ipkg--) {
+                        FrameworkStatsLog.write(FrameworkStatsLog.PROCESS_STATE_CHANGED,
+                                uid, processName, pkgList.keyAt(ipkg),
+                                ActivityManager.processStateAmToProto(ProcessStats.STATE_NOTHING),
+                                pkgList.valueAt(ipkg).appVersion);
+                    }
+                    origBase.makeInactive();
                 }
-                origBase.makeInactive();
-            }
-            baseProcessTracker = tracker.getProcessStateLocked(info.packageName, info.uid,
-                    info.longVersionCode, processName);
-            baseProcessTracker.makeActive();
-            for (int i=0; i<pkgList.size(); i++) {
-                ProcessStats.ProcessStateHolder holder = pkgList.valueAt(i);
-                if (holder.state != null && holder.state != origBase) {
-                    holder.state.makeInactive();
-                }
-                tracker.updateProcessStateHolderLocked(holder, pkgList.keyAt(i), info.uid,
+                baseProcessTracker = tracker.getProcessStateLocked(info.packageName, info.uid,
                         info.longVersionCode, processName);
-                if (holder.state != baseProcessTracker) {
-                    holder.state.makeActive();
+                baseProcessTracker.makeActive();
+                for (int i = 0, ipkg = pkgList.size(); i < ipkg; i++) {
+                    ProcessStats.ProcessStateHolder holder = pkgList.valueAt(i);
+                    if (holder.state != null && holder.state != origBase) {
+                        holder.state.makeInactive();
+                    }
+                    tracker.updateProcessStateHolderLocked(holder, pkgList.keyAt(i), info.uid,
+                            info.longVersionCode, processName);
+                    if (holder.state != baseProcessTracker) {
+                        holder.state.makeActive();
+                    }
                 }
             }
         }
@@ -707,27 +710,30 @@
     public void makeInactive(ProcessStatsService tracker) {
         thread = null;
         mWindowProcessController.setThread(null);
-        final ProcessState origBase = baseProcessTracker;
-        if (origBase != null) {
+        synchronized (tracker.mLock) {
+            final ProcessState origBase = baseProcessTracker;
             if (origBase != null) {
-                origBase.setState(ProcessStats.STATE_NOTHING,
-                        tracker.getMemFactorLocked(), SystemClock.uptimeMillis(), pkgList.mPkgList);
-                for (int ipkg = pkgList.size() - 1; ipkg >= 0; ipkg--) {
-                    FrameworkStatsLog.write(FrameworkStatsLog.PROCESS_STATE_CHANGED,
-                            uid, processName, pkgList.keyAt(ipkg),
-                            ActivityManager.processStateAmToProto(ProcessStats.STATE_NOTHING),
-                            pkgList.valueAt(ipkg).appVersion);
+                if (origBase != null) {
+                    origBase.setState(ProcessStats.STATE_NOTHING,
+                            tracker.getMemFactorLocked(), SystemClock.uptimeMillis(),
+                            pkgList.mPkgList);
+                    for (int ipkg = pkgList.size() - 1; ipkg >= 0; ipkg--) {
+                        FrameworkStatsLog.write(FrameworkStatsLog.PROCESS_STATE_CHANGED,
+                                uid, processName, pkgList.keyAt(ipkg),
+                                ActivityManager.processStateAmToProto(ProcessStats.STATE_NOTHING),
+                                pkgList.valueAt(ipkg).appVersion);
+                    }
+                    origBase.makeInactive();
                 }
-                origBase.makeInactive();
-            }
-            baseProcessTracker = null;
-            for (int i=0; i<pkgList.size(); i++) {
-                ProcessStats.ProcessStateHolder holder = pkgList.valueAt(i);
-                if (holder.state != null && holder.state != origBase) {
-                    holder.state.makeInactive();
+                baseProcessTracker = null;
+                for (int i = 0, ipkg = pkgList.size(); i < ipkg; i++) {
+                    ProcessStats.ProcessStateHolder holder = pkgList.valueAt(i);
+                    if (holder.state != null && holder.state != origBase) {
+                        holder.state.makeInactive();
+                    }
+                    holder.pkg = null;
+                    holder.state = null;
                 }
-                holder.pkg = null;
-                holder.state = null;
             }
         }
     }
@@ -1026,17 +1032,19 @@
      */
     public boolean addPackage(String pkg, long versionCode, ProcessStatsService tracker) {
         if (!pkgList.containsKey(pkg)) {
-            ProcessStats.ProcessStateHolder holder = new ProcessStats.ProcessStateHolder(
-                    versionCode);
-            if (baseProcessTracker != null) {
-                tracker.updateProcessStateHolderLocked(holder, pkg, info.uid, versionCode,
-                        processName);
-                pkgList.put(pkg, holder);
-                if (holder.state != baseProcessTracker) {
-                    holder.state.makeActive();
+            synchronized (tracker.mLock) {
+                ProcessStats.ProcessStateHolder holder = new ProcessStats.ProcessStateHolder(
+                        versionCode);
+                if (baseProcessTracker != null) {
+                    tracker.updateProcessStateHolderLocked(holder, pkg, info.uid, versionCode,
+                            processName);
+                    pkgList.put(pkg, holder);
+                    if (holder.state != baseProcessTracker) {
+                        holder.state.makeActive();
+                    }
+                } else {
+                    pkgList.put(pkg, holder);
                 }
-            } else {
-                pkgList.put(pkg, holder);
             }
             return true;
         }
@@ -1071,37 +1079,40 @@
      */
     public void resetPackageList(ProcessStatsService tracker) {
         final int N = pkgList.size();
-        if (baseProcessTracker != null) {
-            long now = SystemClock.uptimeMillis();
-            baseProcessTracker.setState(ProcessStats.STATE_NOTHING,
-                    tracker.getMemFactorLocked(), now, pkgList.mPkgList);
-            for (int ipkg = pkgList.size() - 1; ipkg >= 0; ipkg--) {
-                FrameworkStatsLog.write(FrameworkStatsLog.PROCESS_STATE_CHANGED,
-                        uid, processName, pkgList.keyAt(ipkg),
-                        ActivityManager.processStateAmToProto(ProcessStats.STATE_NOTHING),
-                        pkgList.valueAt(ipkg).appVersion);
-            }
-            if (N != 1) {
-                for (int i=0; i<N; i++) {
-                    ProcessStats.ProcessStateHolder holder = pkgList.valueAt(i);
-                    if (holder.state != null && holder.state != baseProcessTracker) {
-                        holder.state.makeInactive();
-                    }
+        synchronized (tracker.mLock) {
+            if (baseProcessTracker != null) {
+                long now = SystemClock.uptimeMillis();
+                baseProcessTracker.setState(ProcessStats.STATE_NOTHING,
+                        tracker.getMemFactorLocked(), now, pkgList.mPkgList);
+                for (int ipkg = pkgList.size() - 1; ipkg >= 0; ipkg--) {
+                    FrameworkStatsLog.write(FrameworkStatsLog.PROCESS_STATE_CHANGED,
+                            uid, processName, pkgList.keyAt(ipkg),
+                            ActivityManager.processStateAmToProto(ProcessStats.STATE_NOTHING),
+                            pkgList.valueAt(ipkg).appVersion);
+                }
+                if (N != 1) {
+                    for (int i = 0; i < N; i++) {
+                        ProcessStats.ProcessStateHolder holder = pkgList.valueAt(i);
+                        if (holder.state != null && holder.state != baseProcessTracker) {
+                            holder.state.makeInactive();
+                        }
 
+                    }
+                    pkgList.clear();
+                    ProcessStats.ProcessStateHolder holder = new ProcessStats.ProcessStateHolder(
+                            info.longVersionCode);
+                    tracker.updateProcessStateHolderLocked(holder, info.packageName, info.uid,
+                            info.longVersionCode, processName);
+                    pkgList.put(info.packageName, holder);
+                    if (holder.state != baseProcessTracker) {
+                        holder.state.makeActive();
+                    }
                 }
+            } else if (N != 1) {
                 pkgList.clear();
-                ProcessStats.ProcessStateHolder holder = new ProcessStats.ProcessStateHolder(
-                        info.longVersionCode);
-                tracker.updateProcessStateHolderLocked(holder, info.packageName, info.uid,
-                        info.longVersionCode, processName);
-                pkgList.put(info.packageName, holder);
-                if (holder.state != baseProcessTracker) {
-                    holder.state.makeActive();
-                }
+                pkgList.put(info.packageName,
+                        new ProcessStats.ProcessStateHolder(info.longVersionCode));
             }
-        } else if (N != 1) {
-            pkgList.clear();
-            pkgList.put(info.packageName, new ProcessStats.ProcessStateHolder(info.longVersionCode));
         }
     }
 
@@ -1522,8 +1533,8 @@
         }
     }
 
-    public long getInputDispatchingTimeout() {
-        return mWindowProcessController.getInputDispatchingTimeout();
+    public long getInputDispatchingTimeoutMillis() {
+        return mWindowProcessController.getInputDispatchingTimeoutMillis();
     }
 
     public int getProcessClassEnum() {
diff --git a/services/core/java/com/android/server/am/ProcessStatsService.java b/services/core/java/com/android/server/am/ProcessStatsService.java
index a168af5a..4e8c386 100644
--- a/services/core/java/com/android/server/am/ProcessStatsService.java
+++ b/services/core/java/com/android/server/am/ProcessStatsService.java
@@ -71,33 +71,62 @@
 
     final ActivityManagerService mAm;
     final File mBaseDir;
-    ProcessStats mProcessStats;
+
+    // Note: The locking order of the below 3 locks should be:
+    // mLock, mPendingWriteLock, mFileLock
+
+    // The lock object to protect the internal state/structures
+    final Object mLock = new Object();
+
+    // The lock object to protect the access to pending writes
+    final Object mPendingWriteLock = new Object();
+
+    // The lock object to protect the access to all of the file read/write
+    final ReentrantLock mFileLock = new ReentrantLock();
+
+    @GuardedBy("mLock")
+    final ProcessStats mProcessStats;
+
+    @GuardedBy("mFileLock")
     AtomicFile mFile;
+
+    @GuardedBy("mLock")
     boolean mCommitPending;
+
+    @GuardedBy("mLock")
     boolean mShuttingDown;
+
+    @GuardedBy("mLock")
     int mLastMemOnlyState = -1;
     boolean mMemFactorLowered;
 
-    final ReentrantLock mWriteLock = new ReentrantLock();
-    final Object mPendingWriteLock = new Object();
+    @GuardedBy("mPendingWriteLock")
     AtomicFile mPendingWriteFile;
+
+    @GuardedBy("mPendingWriteLock")
     Parcel mPendingWrite;
+
+    @GuardedBy("mPendingWriteLock")
     boolean mPendingWriteCommitted;
+
+    @GuardedBy("mLock")
     long mLastWriteTime;
 
     /** For CTS to inject the screen state. */
-    @GuardedBy("mAm")
+    @GuardedBy("mLock")
     Boolean mInjectedScreenState;
 
     public ProcessStatsService(ActivityManagerService am, File file) {
         mAm = am;
         mBaseDir = file;
         mBaseDir.mkdirs();
-        mProcessStats = new ProcessStats(true);
-        updateFile();
+        synchronized (mLock) {
+            mProcessStats = new ProcessStats(true);
+            updateFileLocked();
+        }
         SystemProperties.addChangeCallback(new Runnable() {
             @Override public void run() {
-                synchronized (mAm) {
+                synchronized (mLock) {
                     if (mProcessStats.evaluateSystemProperties(false)) {
                         mProcessStats.mFlags |= ProcessStats.FLAG_SYSPROPS;
                         writeStateLocked(true, true);
@@ -121,32 +150,33 @@
         }
     }
 
-    @GuardedBy("mAm")
-    public void updateProcessStateHolderLocked(ProcessStats.ProcessStateHolder holder,
+    @GuardedBy("mLock")
+    void updateProcessStateHolderLocked(ProcessStats.ProcessStateHolder holder,
             String packageName, int uid, long versionCode, String processName) {
         holder.pkg = mProcessStats.getPackageStateLocked(packageName, uid, versionCode);
         holder.state = mProcessStats.getProcessStateLocked(holder.pkg, processName);
     }
 
-    @GuardedBy("mAm")
-    public ProcessState getProcessStateLocked(String packageName,
+    @GuardedBy("mLock")
+    ProcessState getProcessStateLocked(String packageName,
             int uid, long versionCode, String processName) {
         return mProcessStats.getProcessStateLocked(packageName, uid, versionCode, processName);
     }
 
-    @GuardedBy("mAm")
-    public ServiceState getServiceStateLocked(String packageName, int uid,
+    ServiceState getServiceState(String packageName, int uid,
             long versionCode, String processName, String className) {
-        return mProcessStats.getServiceStateLocked(packageName, uid, versionCode, processName,
-                className);
+        synchronized (mLock) {
+            return mProcessStats.getServiceStateLocked(packageName, uid, versionCode, processName,
+                    className);
+        }
     }
 
-    public boolean isMemFactorLowered() {
+    boolean isMemFactorLowered() {
         return mMemFactorLowered;
     }
 
-    @GuardedBy("mAm")
-    public boolean setMemFactorLocked(int memFactor, boolean screenOn, long now) {
+    @GuardedBy("mLock")
+    boolean setMemFactorLocked(int memFactor, boolean screenOn, long now) {
         mMemFactorLowered = memFactor < mLastMemOnlyState;
         mLastMemOnlyState = memFactor;
         if (mInjectedScreenState != null) {
@@ -184,24 +214,24 @@
         return false;
     }
 
-    @GuardedBy("mAm")
-    public int getMemFactorLocked() {
+    @GuardedBy("mLock")
+    int getMemFactorLocked() {
         return mProcessStats.mMemFactor != ProcessStats.STATE_NOTHING ? mProcessStats.mMemFactor : 0;
     }
 
-    @GuardedBy("mAm")
-    public void addSysMemUsageLocked(long cachedMem, long freeMem, long zramMem, long kernelMem,
+    @GuardedBy("mLock")
+    void addSysMemUsageLocked(long cachedMem, long freeMem, long zramMem, long kernelMem,
             long nativeMem) {
         mProcessStats.addSysMemUsage(cachedMem, freeMem, zramMem, kernelMem, nativeMem);
     }
 
-    @GuardedBy("mAm")
-    public void updateTrackingAssociationsLocked(int curSeq, long now) {
+    @GuardedBy("mLock")
+    void updateTrackingAssociationsLocked(int curSeq, long now) {
         mProcessStats.updateTrackingAssociationsLocked(curSeq, now);
     }
 
-    @GuardedBy("mAm")
-    public boolean shouldWriteNowLocked(long now) {
+    @GuardedBy("mLock")
+    boolean shouldWriteNowLocked(long now) {
         if (now > (mLastWriteTime+WRITE_PERIOD)) {
             if (SystemClock.elapsedRealtime()
                     > (mProcessStats.mTimePeriodStartRealtime+ProcessStats.COMMIT_PERIOD) &&
@@ -214,25 +244,27 @@
         return false;
     }
 
-    @GuardedBy("mAm")
-    public void shutdownLocked() {
+    void shutdown() {
         Slog.w(TAG, "Writing process stats before shutdown...");
-        mProcessStats.mFlags |= ProcessStats.FLAG_SHUTDOWN;
-        writeStateSyncLocked();
-        mShuttingDown = true;
+        synchronized (mLock) {
+            mProcessStats.mFlags |= ProcessStats.FLAG_SHUTDOWN;
+            writeStateSyncLocked();
+            mShuttingDown = true;
+        }
     }
 
-    @GuardedBy("mAm")
-    public void writeStateAsyncLocked() {
-        writeStateLocked(false);
+    void writeStateAsync() {
+        synchronized (mLock) {
+            writeStateLocked(false);
+        }
     }
 
-    @GuardedBy("mAm")
-    public void writeStateSyncLocked() {
+    @GuardedBy("mLock")
+    private void writeStateSyncLocked() {
         writeStateLocked(true);
     }
 
-    @GuardedBy("mAm")
+    @GuardedBy("mLock")
     private void writeStateLocked(boolean sync) {
         if (mShuttingDown) {
             return;
@@ -242,8 +274,8 @@
         writeStateLocked(sync, commitPending);
     }
 
-    @GuardedBy("mAm")
-    public void writeStateLocked(boolean sync, final boolean commit) {
+    @GuardedBy("mLock")
+    private void writeStateLocked(boolean sync, final boolean commit) {
         final long totalTime;
         synchronized (mPendingWriteLock) {
             final long now = SystemClock.uptimeMillis();
@@ -255,13 +287,13 @@
                     mProcessStats.mFlags |= ProcessStats.FLAG_COMPLETE;
                 }
                 mProcessStats.writeToParcel(mPendingWrite, 0);
-                mPendingWriteFile = new AtomicFile(mFile.getBaseFile());
+                mPendingWriteFile = new AtomicFile(getCurrentFile());
                 mPendingWriteCommitted = commit;
             }
             if (commit) {
                 mProcessStats.resetSafely();
-                updateFile();
-                mAm.requestPssAllProcsLocked(SystemClock.uptimeMillis(), true, false);
+                updateFileLocked();
+                scheduleRequestPssAllProcs(true, false);
             }
             mLastWriteTime = SystemClock.uptimeMillis();
             totalTime = SystemClock.uptimeMillis() - now;
@@ -279,14 +311,37 @@
         performWriteState(totalTime);
     }
 
-    private void updateFile() {
-        mFile = new AtomicFile(new File(mBaseDir, STATE_FILE_PREFIX
-                + mProcessStats.mTimePeriodStartClockStr + STATE_FILE_SUFFIX));
+    private void scheduleRequestPssAllProcs(boolean always, boolean memLowered) {
+        mAm.mHandler.post(() -> {
+            synchronized (mAm) {
+                mAm.requestPssAllProcsLocked(SystemClock.uptimeMillis(), always, memLowered);
+            }
+        });
+    }
+
+    @GuardedBy("mLock")
+    private void updateFileLocked() {
+        mFileLock.lock();
+        try {
+            mFile = new AtomicFile(new File(mBaseDir, STATE_FILE_PREFIX
+                    + mProcessStats.mTimePeriodStartClockStr + STATE_FILE_SUFFIX));
+        } finally {
+            mFileLock.unlock();
+        }
         mLastWriteTime = SystemClock.uptimeMillis();
     }
 
-    void performWriteState(long initialTime) {
-        if (DEBUG) Slog.d(TAG, "Performing write to " + mFile.getBaseFile());
+    private File getCurrentFile() {
+        mFileLock.lock();
+        try {
+            return mFile.getBaseFile();
+        } finally {
+            mFileLock.unlock();
+        }
+    }
+
+    private void performWriteState(long initialTime) {
+        if (DEBUG) Slog.d(TAG, "Performing write to " + getCurrentFile());
         Parcel data;
         AtomicFile file;
         synchronized (mPendingWriteLock) {
@@ -298,7 +353,7 @@
             }
             mPendingWrite = null;
             mPendingWriteFile = null;
-            mWriteLock.lock();
+            mFileLock.lock();
         }
 
         final long startTime = SystemClock.uptimeMillis();
@@ -316,13 +371,13 @@
             file.failWrite(stream);
         } finally {
             data.recycle();
-            trimHistoricStatesWriteLocked();
-            mWriteLock.unlock();
+            trimHistoricStatesWriteLF();
+            mFileLock.unlock();
         }
     }
 
-    @GuardedBy("mAm")
-    boolean readLocked(ProcessStats stats, AtomicFile file) {
+    @GuardedBy("mFileLock")
+    private boolean readLF(ProcessStats stats, AtomicFile file) {
         try {
             FileInputStream stream = file.openRead();
             stats.read(stream);
@@ -387,7 +442,8 @@
         return true;
     }
 
-    private ArrayList<String> getCommittedFiles(int minNum, boolean inclCurrent,
+    @GuardedBy("mFileLock")
+    private ArrayList<String> getCommittedFilesLF(int minNum, boolean inclCurrent,
             boolean inclCheckedIn) {
         File[] files = mBaseDir.listFiles();
         if (files == null || files.length <= minNum) {
@@ -414,9 +470,9 @@
         return filesArray;
     }
 
-    @GuardedBy("mAm")
-    public void trimHistoricStatesWriteLocked() {
-        ArrayList<String> filesArray = getCommittedFiles(MAX_HISTORIC_STATES, false, true);
+    @GuardedBy("mFileLock")
+    private void trimHistoricStatesWriteLF() {
+        ArrayList<String> filesArray = getCommittedFilesLF(MAX_HISTORIC_STATES, false, true);
         if (filesArray == null) {
             return;
         }
@@ -427,8 +483,8 @@
         }
     }
 
-    @GuardedBy("mAm")
-    boolean dumpFilteredProcessesCsvLocked(PrintWriter pw, String header,
+    @GuardedBy("mLock")
+    private boolean dumpFilteredProcessesCsvLocked(PrintWriter pw, String header,
             boolean sepScreenStates, int[] screenStates, boolean sepMemStates, int[] memStates,
             boolean sepProcStates, int[] procStates, long now, String reqPackage) {
         ArrayList<ProcessState> procs = mProcessStats.collectProcessesLocked(
@@ -502,20 +558,21 @@
         return res;
     }
 
+    @Override
     public byte[] getCurrentStats(List<ParcelFileDescriptor> historic) {
         mAm.mContext.enforceCallingOrSelfPermission(
                 android.Manifest.permission.PACKAGE_USAGE_STATS, null);
         Parcel current = Parcel.obtain();
-        synchronized (mAm) {
+        synchronized (mLock) {
             long now = SystemClock.uptimeMillis();
             mProcessStats.mTimePeriodEndRealtime = SystemClock.elapsedRealtime();
             mProcessStats.mTimePeriodEndUptime = now;
             mProcessStats.writeToParcel(current, now, 0);
         }
-        mWriteLock.lock();
+        mFileLock.lock();
         try {
             if (historic != null) {
-                ArrayList<String> files = getCommittedFiles(0, false, true);
+                ArrayList<String> files = getCommittedFilesLF(0, false, true);
                 if (files != null) {
                     for (int i=files.size()-1; i>=0; i--) {
                         try {
@@ -529,7 +586,7 @@
                 }
             }
         } finally {
-            mWriteLock.unlock();
+            mFileLock.unlock();
         }
         return current.marshall();
     }
@@ -563,9 +620,9 @@
                 android.Manifest.permission.PACKAGE_USAGE_STATS, null);
 
         long newHighWaterMark = highWaterMarkMs;
-        mWriteLock.lock();
+        mFileLock.lock();
         try {
-            ArrayList<String> files = getCommittedFiles(0, false, true);
+            ArrayList<String> files = getCommittedFilesLF(0, false, true);
             if (files != null) {
                 String highWaterMarkStr =
                         DateFormat.format("yyyy-MM-dd-HH-mm-ss", highWaterMarkMs).toString();
@@ -612,7 +669,7 @@
         } catch (IOException e) {
             Slog.w(TAG, "Failure opening procstat file", e);
         } finally {
-            mWriteLock.unlock();
+            mFileLock.unlock();
         }
         return newHighWaterMark;
     }
@@ -625,7 +682,7 @@
         return mAm.mConstants.MIN_ASSOC_LOG_DURATION;
     }
 
-    private ParcelFileDescriptor protoToParcelFileDescriptor(ProcessStats stats, int section)
+    private static ParcelFileDescriptor protoToParcelFileDescriptor(ProcessStats stats, int section)
             throws IOException {
         final ParcelFileDescriptor[] fds = ParcelFileDescriptor.createPipe();
         Thread thr = new Thread("ProcessStats pipe output") {
@@ -645,12 +702,13 @@
         return fds[0];
     }
 
+    @Override
     public ParcelFileDescriptor getStatsOverTime(long minTime) {
         mAm.mContext.enforceCallingOrSelfPermission(
                 android.Manifest.permission.PACKAGE_USAGE_STATS, null);
         Parcel current = Parcel.obtain();
         long curTime;
-        synchronized (mAm) {
+        synchronized (mLock) {
             long now = SystemClock.uptimeMillis();
             mProcessStats.mTimePeriodEndRealtime = SystemClock.elapsedRealtime();
             mProcessStats.mTimePeriodEndUptime = now;
@@ -658,11 +716,11 @@
             curTime = mProcessStats.mTimePeriodEndRealtime
                     - mProcessStats.mTimePeriodStartRealtime;
         }
-        mWriteLock.lock();
+        mFileLock.lock();
         try {
             if (curTime < minTime) {
                 // Need to add in older stats to reach desired time.
-                ArrayList<String> files = getCommittedFiles(0, false, true);
+                ArrayList<String> files = getCommittedFilesLF(0, false, true);
                 if (files != null && files.size() > 0) {
                     current.setDataPosition(0);
                     ProcessStats stats = ProcessStats.CREATOR.createFromParcel(current);
@@ -673,7 +731,7 @@
                         AtomicFile file = new AtomicFile(new File(files.get(i)));
                         i--;
                         ProcessStats moreStats = new ProcessStats(false);
-                        readLocked(moreStats, file);
+                        readLF(moreStats, file);
                         if (moreStats.mReadError == null) {
                             stats.add(moreStats);
                             StringBuilder sb = new StringBuilder();
@@ -712,13 +770,14 @@
         } catch (IOException e) {
             Slog.w(TAG, "Failed building output pipe", e);
         } finally {
-            mWriteLock.unlock();
+            mFileLock.unlock();
         }
         return null;
     }
 
+    @Override
     public int getCurrentMemoryState() {
-        synchronized (mAm) {
+        synchronized (mLock) {
             return mLastMemOnlyState;
         }
     }
@@ -947,7 +1006,7 @@
                 } else if ("--current".equals(arg)) {
                     currentOnly = true;
                 } else if ("--commit".equals(arg)) {
-                    synchronized (mAm) {
+                    synchronized (mLock) {
                         mProcessStats.mFlags |= ProcessStats.FLAG_COMPLETE;
                         writeStateLocked(true, true);
                         pw.println("Process stats committed.");
@@ -962,29 +1021,39 @@
                     }
                     section = parseSectionOptions(args[i]);
                 } else if ("--clear".equals(arg)) {
-                    synchronized (mAm) {
+                    synchronized (mLock) {
                         mProcessStats.resetSafely();
-                        mAm.requestPssAllProcsLocked(SystemClock.uptimeMillis(), true, false);
-                        ArrayList<String> files = getCommittedFiles(0, true, true);
-                        if (files != null) {
-                            for (int fi=0; fi<files.size(); fi++) {
-                                (new File(files.get(fi))).delete();
+                        scheduleRequestPssAllProcs(true, false);
+                        mFileLock.lock();
+                        try {
+                            ArrayList<String> files = getCommittedFilesLF(0, true, true);
+                            if (files != null) {
+                                for (int fi = files.size() - 1; fi >= 0; fi--) {
+                                    (new File(files.get(fi))).delete();
+                                }
                             }
+                        } finally {
+                            mFileLock.unlock();
                         }
                         pw.println("All process stats cleared.");
                         quit = true;
                     }
                 } else if ("--write".equals(arg)) {
-                    synchronized (mAm) {
+                    synchronized (mLock) {
                         writeStateSyncLocked();
                         pw.println("Process stats written.");
                         quit = true;
                     }
                 } else if ("--read".equals(arg)) {
-                    synchronized (mAm) {
-                        readLocked(mProcessStats, mFile);
-                        pw.println("Process stats read.");
-                        quit = true;
+                    synchronized (mLock) {
+                        mFileLock.lock();
+                        try {
+                            readLF(mProcessStats, mFile);
+                            pw.println("Process stats read.");
+                            quit = true;
+                        } finally {
+                            mFileLock.unlock();
+                        }
                     }
                 } else if ("--start-testing".equals(arg)) {
                     synchronized (mAm) {
@@ -999,17 +1068,17 @@
                         quit = true;
                     }
                 } else if ("--pretend-screen-on".equals(arg)) {
-                    synchronized (mAm) {
+                    synchronized (mLock) {
                         mInjectedScreenState = true;
                     }
                     quit = true;
                 } else if ("--pretend-screen-off".equals(arg)) {
-                    synchronized (mAm) {
+                    synchronized (mLock) {
                         mInjectedScreenState = false;
                     }
                     quit = true;
                 } else if ("--stop-pretend-screen".equals(arg)) {
-                    synchronized (mAm) {
+                    synchronized (mLock) {
                         mInjectedScreenState = null;
                     }
                     quit = true;
@@ -1060,7 +1129,7 @@
                 }
             }
             pw.println();
-            synchronized (mAm) {
+            synchronized (mLock) {
                 dumpFilteredProcessesCsvLocked(pw, null,
                         csvSepScreenStats, csvScreenStats, csvSepMemStats, csvMemStats,
                         csvSepProcStats, csvProcStats, now, reqPackage);
@@ -1090,14 +1159,26 @@
             return;
         } else if (lastIndex > 0) {
             pw.print("LAST STATS AT INDEX "); pw.print(lastIndex); pw.println(":");
-            ArrayList<String> files = getCommittedFiles(0, false, true);
-            if (lastIndex >= files.size()) {
-                pw.print("Only have "); pw.print(files.size()); pw.println(" data sets");
-                return;
+
+            ArrayList<String> files;
+            AtomicFile file;
+            ProcessStats processStats;
+
+            mFileLock.lock();
+            try {
+                files = getCommittedFilesLF(0, false, true);
+                if (lastIndex >= files.size()) {
+                    pw.print("Only have "); pw.print(files.size()); pw.println(" data sets");
+                    return;
+                }
+                file = new AtomicFile(new File(files.get(lastIndex)));
+                processStats = new ProcessStats(false);
+                readLF(processStats, file);
+            } finally {
+                mFileLock.unlock();
             }
-            AtomicFile file = new AtomicFile(new File(files.get(lastIndex)));
-            ProcessStats processStats = new ProcessStats(false);
-            readLocked(processStats, file);
+
+            // No lock is needed now, since only us have the access to the 'processStats'.
             if (processStats.mReadError != null) {
                 if (isCheckin || isCompact) pw.print("err,");
                 pw.print("Failure reading "); pw.print(files.get(lastIndex));
@@ -1118,7 +1199,7 @@
                     processStats.dumpLocked(pw, reqPackage, now, !dumpFullDetails, dumpDetails,
                             dumpAll, activeOnly, section);
                     if (dumpAll) {
-                        pw.print("  mFile="); pw.println(mFile.getBaseFile());
+                        pw.print("  mFile="); pw.println(getCurrentFile());
                     }
                 } else {
                     processStats.dumpSummaryLocked(pw, reqPackage, now, activeOnly);
@@ -1129,9 +1210,9 @@
 
         boolean sepNeeded = false;
         if ((dumpAll || isCheckin) && !currentOnly) {
-            mWriteLock.lock();
+            mFileLock.lock();
             try {
-                ArrayList<String> files = getCommittedFiles(0, false, !isCheckin);
+                ArrayList<String> files = getCommittedFilesLF(0, false, !isCheckin);
                 if (files != null) {
                     int start = isCheckin ? 0 : (files.size() - maxNum);
                     if (start < 0) {
@@ -1142,7 +1223,7 @@
                         try {
                             AtomicFile file = new AtomicFile(new File(files.get(i)));
                             ProcessStats processStats = new ProcessStats(false);
-                            readLocked(processStats, file);
+                            readLF(processStats, file);
                             if (processStats.mReadError != null) {
                                 if (isCheckin || isCompact) pw.print("err,");
                                 pw.print("Failure reading "); pw.print(files.get(i));
@@ -1188,11 +1269,11 @@
                     }
                 }
             } finally {
-                mWriteLock.unlock();
+                mFileLock.unlock();
             }
         }
         if (!isCheckin) {
-            synchronized (mAm) {
+            synchronized (mLock) {
                 if (isCompact) {
                     mProcessStats.dumpCheckinLocked(pw, reqPackage, section);
                 } else {
@@ -1204,7 +1285,7 @@
                         mProcessStats.dumpLocked(pw, reqPackage, now, !dumpFullDetails, dumpDetails,
                                 dumpAll, activeOnly, section);
                         if (dumpAll) {
-                            pw.print("  mFile="); pw.println(mFile.getBaseFile());
+                            pw.print("  mFile="); pw.println(getCurrentFile());
                         }
                     } else {
                         mProcessStats.dumpSummaryLocked(pw, reqPackage, now, activeOnly);
@@ -1249,7 +1330,7 @@
 
         // dump current procstats
         long now;
-        synchronized (mAm) {
+        synchronized (mLock) {
             now = SystemClock.uptimeMillis();
             final long token = proto.start(ProcessStatsServiceDumpProto.PROCSTATS_NOW);
             mProcessStats.dumpDebug(proto, now, ProcessStats.REPORT_ALL);
diff --git a/services/core/java/com/android/server/am/ServiceRecord.java b/services/core/java/com/android/server/am/ServiceRecord.java
index 022b04d..5b12c8c 100644
--- a/services/core/java/com/android/server/am/ServiceRecord.java
+++ b/services/core/java/com/android/server/am/ServiceRecord.java
@@ -104,6 +104,7 @@
     boolean whitelistManager; // any bindings to this service have BIND_ALLOW_WHITELIST_MANAGEMENT?
     boolean delayed;        // are we waiting to start this service in the background?
     boolean fgRequired;     // is the service required to go foreground after starting?
+    boolean hideFgNotification; // Hide the fg service notification
     boolean fgWaiting;      // is a timeout for going foreground already scheduled?
     boolean isForeground;   // is service currently in foreground mode?
     int foregroundId;       // Notification ID of last foreground req.
@@ -146,6 +147,12 @@
     // the most recent package that start/bind this service.
     String mRecentCallingPackage;
 
+    // allow the service becomes foreground service? Service started from background may not be
+    // allowed to become a foreground service.
+    boolean mAllowStartForeground;
+    String mInfoAllowStartForeground;
+    boolean mLoggedInfoAllowStartForeground;
+
     String stringName;      // caching of toString
 
     private int lastStartId;    // identifier of most recent start request.
@@ -408,6 +415,10 @@
                 pw.println(mAllowWhileInUsePermissionInFgs);
         pw.print(prefix); pw.print("recentCallingPackage=");
                 pw.println(mRecentCallingPackage);
+        pw.print(prefix); pw.print("allowStartForeground=");
+        pw.println(mAllowStartForeground);
+        pw.print(prefix); pw.print("infoAllowStartForeground=");
+        pw.println(mInfoAllowStartForeground);
         if (delayed) {
             pw.print(prefix); pw.print("delayed="); pw.println(delayed);
         }
@@ -528,8 +539,9 @@
             return tracker;
         }
         if ((serviceInfo.applicationInfo.flags&ApplicationInfo.FLAG_PERSISTENT) == 0) {
-            tracker = ams.mProcessStats.getServiceStateLocked(serviceInfo.packageName,
-                    serviceInfo.applicationInfo.uid, serviceInfo.applicationInfo.longVersionCode,
+            tracker = ams.mProcessStats.getServiceState(serviceInfo.packageName,
+                    serviceInfo.applicationInfo.uid,
+                    serviceInfo.applicationInfo.longVersionCode,
                     serviceInfo.processName, serviceInfo.name);
             tracker.applyNewOwner(this);
         }
@@ -546,7 +558,8 @@
     public void makeRestarting(int memFactor, long now) {
         if (restartTracker == null) {
             if ((serviceInfo.applicationInfo.flags&ApplicationInfo.FLAG_PERSISTENT) == 0) {
-                restartTracker = ams.mProcessStats.getServiceStateLocked(serviceInfo.packageName,
+                restartTracker = ams.mProcessStats.getServiceState(
+                        serviceInfo.packageName,
                         serviceInfo.applicationInfo.uid,
                         serviceInfo.applicationInfo.longVersionCode,
                         serviceInfo.processName, serviceInfo.name);
@@ -824,6 +837,9 @@
     }
 
     public void postNotification() {
+        if (hideFgNotification) {
+            return;
+        }
         final int appUid = appInfo.uid;
         final int appPid = app.pid;
         if (foregroundId != 0 && foregroundNoti != null) {
@@ -916,7 +932,7 @@
                         }
                         if (localForegroundNoti.getSmallIcon() == null) {
                             // Notifications whose icon is 0 are defined to not show
-                            // a notification, silently ignoring it.  We don't want to
+                            // a notification.  We don't want to
                             // just ignore it, we want to prevent the service from
                             // being foreground.
                             throw new RuntimeException("invalid service notification: "
diff --git a/services/core/java/com/android/server/am/UidObserverController.java b/services/core/java/com/android/server/am/UidObserverController.java
new file mode 100644
index 0000000..4d9260a
--- /dev/null
+++ b/services/core/java/com/android/server/am/UidObserverController.java
@@ -0,0 +1,470 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.server.am;
+
+import static android.app.ActivityManager.PROCESS_STATE_NONEXISTENT;
+
+import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_UID_OBSERVERS;
+import static com.android.server.am.ActivityManagerService.TAG_UID_OBSERVERS;
+
+import android.app.ActivityManager;
+import android.app.ActivityManagerProto;
+import android.app.IUidObserver;
+import android.os.RemoteCallbackList;
+import android.os.RemoteException;
+import android.os.SystemClock;
+import android.os.UserHandle;
+import android.util.Slog;
+import android.util.SparseIntArray;
+import android.util.proto.ProtoOutputStream;
+import android.util.proto.ProtoUtils;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.am.ActivityManagerServiceDumpProcessesProto.UidObserverRegistrationProto;
+
+import java.io.PrintWriter;
+import java.util.ArrayList;
+
+public class UidObserverController {
+    private final ActivityManagerService mService;
+    final RemoteCallbackList<IUidObserver> mUidObservers = new RemoteCallbackList<>();
+
+    UidRecord.ChangeItem[] mActiveUidChanges = new UidRecord.ChangeItem[5];
+    final ArrayList<UidRecord.ChangeItem> mPendingUidChanges = new ArrayList<>();
+    final ArrayList<UidRecord.ChangeItem> mAvailUidChanges = new ArrayList<>();
+
+    /** Total # of UID change events dispatched, shown in dumpsys. */
+    int mUidChangeDispatchCount;
+
+    /** If a UID observer takes more than this long, send a WTF. */
+    private static final int SLOW_UID_OBSERVER_THRESHOLD_MS = 20;
+
+    /**
+     * This is for verifying the UID report flow.
+     */
+    static final boolean VALIDATE_UID_STATES = true;
+    final ActiveUids mValidateUids;
+
+    UidObserverController(ActivityManagerService service) {
+        mService = service;
+        mValidateUids = new ActiveUids(mService, false /* postChangesToAtm */);
+    }
+
+    @GuardedBy("mService")
+    void register(IUidObserver observer, int which, int cutpoint, String callingPackage,
+            int callingUid) {
+        mUidObservers.register(observer, new UidObserverRegistration(callingUid,
+                callingPackage, which, cutpoint));
+    }
+
+    @GuardedBy("mService")
+    void unregister(IUidObserver observer) {
+        mUidObservers.unregister(observer);
+    }
+
+    @GuardedBy("mService")
+    final void enqueueUidChangeLocked(UidRecord uidRec, int uid, int change) {
+        final UidRecord.ChangeItem pendingChange;
+        if (uidRec == null || uidRec.pendingChange == null) {
+            if (mPendingUidChanges.size() == 0) {
+                if (DEBUG_UID_OBSERVERS) {
+                    Slog.i(TAG_UID_OBSERVERS, "*** Enqueueing dispatch uid changed!");
+                }
+                mService.mUiHandler.post(this::dispatchUidsChanged);
+            }
+            final int size = mAvailUidChanges.size();
+            if (size > 0) {
+                pendingChange = mAvailUidChanges.remove(size - 1);
+                if (DEBUG_UID_OBSERVERS) {
+                    Slog.i(TAG_UID_OBSERVERS, "Retrieving available item: " + pendingChange);
+                }
+            } else {
+                pendingChange = new UidRecord.ChangeItem();
+                if (DEBUG_UID_OBSERVERS) {
+                    Slog.i(TAG_UID_OBSERVERS, "Allocating new item: " + pendingChange);
+                }
+            }
+            if (uidRec != null) {
+                uidRec.pendingChange = pendingChange;
+                if ((change & UidRecord.CHANGE_GONE) != 0 && !uidRec.idle) {
+                    // If this uid is going away, and we haven't yet reported it is gone,
+                    // then do so now.
+                    change |= UidRecord.CHANGE_IDLE;
+                }
+            } else if (uid < 0) {
+                throw new IllegalArgumentException("No UidRecord or uid");
+            }
+            pendingChange.uidRecord = uidRec;
+            pendingChange.uid = uidRec != null ? uidRec.uid : uid;
+            mPendingUidChanges.add(pendingChange);
+        } else {
+            pendingChange = uidRec.pendingChange;
+            // If there is no change in idle or active state, then keep whatever was pending.
+            if ((change & (UidRecord.CHANGE_IDLE | UidRecord.CHANGE_ACTIVE)) == 0) {
+                change |= (pendingChange.change & (UidRecord.CHANGE_IDLE
+                        | UidRecord.CHANGE_ACTIVE));
+            }
+            // If there is no change in cached or uncached state, then keep whatever was pending.
+            if ((change & (UidRecord.CHANGE_CACHED | UidRecord.CHANGE_UNCACHED)) == 0) {
+                change |= (pendingChange.change & (UidRecord.CHANGE_CACHED
+                        | UidRecord.CHANGE_UNCACHED));
+            }
+            // If this is a report of the UID being gone, then we shouldn't keep any previous
+            // report of it being active or cached.  (That is, a gone uid is never active,
+            // and never cached.)
+            if ((change & UidRecord.CHANGE_GONE) != 0) {
+                change &= ~(UidRecord.CHANGE_ACTIVE | UidRecord.CHANGE_CACHED);
+                if (!uidRec.idle) {
+                    // If this uid is going away, and we haven't yet reported it is gone,
+                    // then do so now.
+                    change |= UidRecord.CHANGE_IDLE;
+                }
+            }
+        }
+        pendingChange.change = change;
+        pendingChange.processState = uidRec != null
+                ? uidRec.setProcState : PROCESS_STATE_NONEXISTENT;
+        pendingChange.capability = uidRec != null ? uidRec.setCapability : 0;
+        pendingChange.ephemeral = uidRec != null ? uidRec.ephemeral : isEphemeralLocked(uid);
+        pendingChange.procStateSeq = uidRec != null ? uidRec.curProcStateSeq : 0;
+        if (uidRec != null) {
+            uidRec.lastReportedChange = change;
+            uidRec.updateLastDispatchedProcStateSeq(change);
+        }
+
+        // Directly update the power manager, since we sit on top of it and it is critical
+        // it be kept in sync (so wake locks will be held as soon as appropriate).
+        if (mService.mLocalPowerManager != null) {
+            // TO DO: dispatch cached/uncached changes here, so we don't need to report
+            // all proc state changes.
+            if ((change & UidRecord.CHANGE_ACTIVE) != 0) {
+                mService.mLocalPowerManager.uidActive(pendingChange.uid);
+            }
+            if ((change & UidRecord.CHANGE_IDLE) != 0) {
+                mService.mLocalPowerManager.uidIdle(pendingChange.uid);
+            }
+            if ((change & UidRecord.CHANGE_GONE) != 0) {
+                mService.mLocalPowerManager.uidGone(pendingChange.uid);
+            } else {
+                mService.mLocalPowerManager.updateUidProcState(pendingChange.uid,
+                        pendingChange.processState);
+            }
+        }
+    }
+
+    @VisibleForTesting
+    void dispatchUidsChanged() {
+        int numUidChanges;
+        synchronized (mService) {
+            numUidChanges = mPendingUidChanges.size();
+            if (mActiveUidChanges.length < numUidChanges) {
+                mActiveUidChanges = new UidRecord.ChangeItem[numUidChanges];
+            }
+            for (int i = 0; i < numUidChanges; i++) {
+                final UidRecord.ChangeItem change = mPendingUidChanges.get(i);
+                mActiveUidChanges[i] = change;
+                if (change.uidRecord != null) {
+                    change.uidRecord.pendingChange = null;
+                    change.uidRecord = null;
+                }
+            }
+            mPendingUidChanges.clear();
+            if (DEBUG_UID_OBSERVERS) {
+                Slog.i(TAG_UID_OBSERVERS, "*** Delivering " + numUidChanges + " uid changes");
+            }
+        }
+
+        mUidChangeDispatchCount += numUidChanges;
+        int i = mUidObservers.beginBroadcast();
+        while (i > 0) {
+            i--;
+            dispatchUidsChangedForObserver(mUidObservers.getBroadcastItem(i),
+                    (UidObserverRegistration) mUidObservers.getBroadcastCookie(i), numUidChanges);
+        }
+        mUidObservers.finishBroadcast();
+
+        if (VALIDATE_UID_STATES && mUidObservers.getRegisteredCallbackCount() > 0) {
+            for (int j = 0; j < numUidChanges; ++j) {
+                final UidRecord.ChangeItem item = mActiveUidChanges[j];
+                if ((item.change & UidRecord.CHANGE_GONE) != 0) {
+                    mValidateUids.remove(item.uid);
+                } else {
+                    UidRecord validateUid = mValidateUids.get(item.uid);
+                    if (validateUid == null) {
+                        validateUid = new UidRecord(item.uid);
+                        mValidateUids.put(item.uid, validateUid);
+                    }
+                    if ((item.change & UidRecord.CHANGE_IDLE) != 0) {
+                        validateUid.idle = true;
+                    } else if ((item.change & UidRecord.CHANGE_ACTIVE) != 0) {
+                        validateUid.idle = false;
+                    }
+                    validateUid.setCurProcState(validateUid.setProcState = item.processState);
+                    validateUid.curCapability = validateUid.setCapability = item.capability;
+                    validateUid.lastDispatchedProcStateSeq = item.procStateSeq;
+                }
+            }
+        }
+
+        synchronized (mService) {
+            for (int j = 0; j < numUidChanges; j++) {
+                mAvailUidChanges.add(mActiveUidChanges[j]);
+            }
+        }
+    }
+
+    private void dispatchUidsChangedForObserver(IUidObserver observer,
+            UidObserverRegistration reg, int changesSize) {
+        if (observer == null) {
+            return;
+        }
+        try {
+            for (int j = 0; j < changesSize; j++) {
+                UidRecord.ChangeItem item = mActiveUidChanges[j];
+                final int change = item.change;
+                if (change == UidRecord.CHANGE_PROCSTATE
+                        && (reg.mWhich & ActivityManager.UID_OBSERVER_PROCSTATE) == 0) {
+                    // No-op common case: no significant change, the observer is not
+                    // interested in all proc state changes.
+                    continue;
+                }
+                final long start = SystemClock.uptimeMillis();
+                if ((change & UidRecord.CHANGE_IDLE) != 0) {
+                    if ((reg.mWhich & ActivityManager.UID_OBSERVER_IDLE) != 0) {
+                        if (DEBUG_UID_OBSERVERS) {
+                            Slog.i(TAG_UID_OBSERVERS, "UID idle uid=" + item.uid);
+                        }
+                        observer.onUidIdle(item.uid, item.ephemeral);
+                    }
+                } else if ((change & UidRecord.CHANGE_ACTIVE) != 0) {
+                    if ((reg.mWhich & ActivityManager.UID_OBSERVER_ACTIVE) != 0) {
+                        if (DEBUG_UID_OBSERVERS) {
+                            Slog.i(TAG_UID_OBSERVERS, "UID active uid=" + item.uid);
+                        }
+                        observer.onUidActive(item.uid);
+                    }
+                }
+                if ((reg.mWhich & ActivityManager.UID_OBSERVER_CACHED) != 0) {
+                    if ((change & UidRecord.CHANGE_CACHED) != 0) {
+                        if (DEBUG_UID_OBSERVERS) {
+                            Slog.i(TAG_UID_OBSERVERS, "UID cached uid=" + item.uid);
+                        }
+                        observer.onUidCachedChanged(item.uid, true);
+                    } else if ((change & UidRecord.CHANGE_UNCACHED) != 0) {
+                        if (DEBUG_UID_OBSERVERS) {
+                            Slog.i(TAG_UID_OBSERVERS, "UID active uid=" + item.uid);
+                        }
+                        observer.onUidCachedChanged(item.uid, false);
+                    }
+                }
+                if ((change & UidRecord.CHANGE_GONE) != 0) {
+                    if ((reg.mWhich & ActivityManager.UID_OBSERVER_GONE) != 0) {
+                        if (DEBUG_UID_OBSERVERS) {
+                            Slog.i(TAG_UID_OBSERVERS, "UID gone uid=" + item.uid);
+                        }
+                        observer.onUidGone(item.uid, item.ephemeral);
+                    }
+                    if (reg.mLastProcStates != null) {
+                        reg.mLastProcStates.delete(item.uid);
+                    }
+                } else {
+                    if ((reg.mWhich & ActivityManager.UID_OBSERVER_PROCSTATE) != 0) {
+                        if (DEBUG_UID_OBSERVERS) {
+                            Slog.i(TAG_UID_OBSERVERS, "UID CHANGED uid=" + item.uid
+                                    + ": " + item.processState + ": " + item.capability);
+                        }
+                        boolean doReport = true;
+                        if (reg.mCutpoint >= ActivityManager.MIN_PROCESS_STATE) {
+                            final int lastState = reg.mLastProcStates.get(item.uid,
+                                    ActivityManager.PROCESS_STATE_UNKNOWN);
+                            if (lastState != ActivityManager.PROCESS_STATE_UNKNOWN) {
+                                final boolean lastAboveCut = lastState <= reg.mCutpoint;
+                                final boolean newAboveCut = item.processState <= reg.mCutpoint;
+                                doReport = lastAboveCut != newAboveCut;
+                            } else {
+                                doReport = item.processState != PROCESS_STATE_NONEXISTENT;
+                            }
+                        }
+                        if (doReport) {
+                            if (reg.mLastProcStates != null) {
+                                reg.mLastProcStates.put(item.uid, item.processState);
+                            }
+                            observer.onUidStateChanged(item.uid, item.processState,
+                                    item.procStateSeq, item.capability);
+                        }
+                    }
+                }
+                final int duration = (int) (SystemClock.uptimeMillis() - start);
+                if (reg.mMaxDispatchTime < duration) {
+                    reg.mMaxDispatchTime = duration;
+                }
+                if (duration >= SLOW_UID_OBSERVER_THRESHOLD_MS) {
+                    reg.mSlowDispatchCount++;
+                }
+            }
+        } catch (RemoteException e) {
+        }
+    }
+
+    private boolean isEphemeralLocked(int uid) {
+        final String[] packages = mService.mContext.getPackageManager().getPackagesForUid(uid);
+        if (packages == null || packages.length != 1) { // Ephemeral apps cannot share uid
+            return false;
+        }
+        return mService.getPackageManagerInternalLocked().isPackageEphemeral(
+                UserHandle.getUserId(uid), packages[0]);
+    }
+
+    @GuardedBy("mService")
+    void dump(PrintWriter pw, String dumpPackage) {
+        final int count = mUidObservers.getRegisteredCallbackCount();
+        boolean printed = false;
+        for (int i = 0; i < count; i++) {
+            final UidObserverRegistration reg = (UidObserverRegistration)
+                    mUidObservers.getRegisteredCallbackCookie(i);
+            if (dumpPackage == null || dumpPackage.equals(reg.mPkg)) {
+                if (!printed) {
+                    pw.println("  mUidObservers:");
+                    printed = true;
+                }
+                pw.print("    "); UserHandle.formatUid(pw, reg.mUid);
+                pw.print(" "); pw.print(reg.mPkg);
+                final IUidObserver observer = mUidObservers.getRegisteredCallbackItem(i);
+                pw.print(" "); pw.print(observer.getClass().getTypeName()); pw.print(":");
+                if ((reg.mWhich & ActivityManager.UID_OBSERVER_IDLE) != 0) {
+                    pw.print(" IDLE");
+                }
+                if ((reg.mWhich & ActivityManager.UID_OBSERVER_ACTIVE) != 0) {
+                    pw.print(" ACT");
+                }
+                if ((reg.mWhich & ActivityManager.UID_OBSERVER_GONE) != 0) {
+                    pw.print(" GONE");
+                }
+                if ((reg.mWhich & ActivityManager.UID_OBSERVER_PROCSTATE) != 0) {
+                    pw.print(" STATE");
+                    pw.print(" (cut="); pw.print(reg.mCutpoint);
+                    pw.print(")");
+                }
+                pw.println();
+                if (reg.mLastProcStates != null) {
+                    final int size = reg.mLastProcStates.size();
+                    for (int j = 0; j < size; j++) {
+                        pw.print("      Last ");
+                        UserHandle.formatUid(pw, reg.mLastProcStates.keyAt(j));
+                        pw.print(": "); pw.println(reg.mLastProcStates.valueAt(j));
+                    }
+                }
+            }
+        }
+
+        pw.println();
+        pw.print("  mUidChangeDispatchCount=");
+        pw.print(mUidChangeDispatchCount);
+        pw.println();
+        pw.println("  Slow UID dispatches:");
+        final int size = mUidObservers.beginBroadcast();
+        for (int i = 0; i < size; i++) {
+            UidObserverRegistration r =
+                    (UidObserverRegistration) mUidObservers.getBroadcastCookie(i);
+            pw.print("    ");
+            pw.print(mUidObservers.getBroadcastItem(i).getClass().getTypeName());
+            pw.print(": ");
+            pw.print(r.mSlowDispatchCount);
+            pw.print(" / Max ");
+            pw.print(r.mMaxDispatchTime);
+            pw.println("ms");
+        }
+        mUidObservers.finishBroadcast();
+    }
+
+    @GuardedBy("mService")
+    void dumpDebug(ProtoOutputStream proto, String dumpPackage) {
+        final int count = mUidObservers.getRegisteredCallbackCount();
+        for (int i = 0; i < count; i++) {
+            final UidObserverRegistration reg = (UidObserverRegistration)
+                    mUidObservers.getRegisteredCallbackCookie(i);
+            if (dumpPackage == null || dumpPackage.equals(reg.mPkg)) {
+                reg.dumpDebug(proto, ActivityManagerServiceDumpProcessesProto.UID_OBSERVERS);
+            }
+        }
+    }
+
+    private static final class UidObserverRegistration {
+        final int mUid;
+        final String mPkg;
+        final int mWhich;
+        final int mCutpoint;
+
+        /**
+         * Total # of callback calls that took more than {@link #SLOW_UID_OBSERVER_THRESHOLD_MS}.
+         * We show it in dumpsys.
+         */
+        int mSlowDispatchCount;
+
+        /** Max time it took for each dispatch. */
+        int mMaxDispatchTime;
+
+        final SparseIntArray mLastProcStates;
+
+        // Please keep the enum lists in sync
+        private static final int[] ORIG_ENUMS = new int[]{
+                ActivityManager.UID_OBSERVER_IDLE,
+                ActivityManager.UID_OBSERVER_ACTIVE,
+                ActivityManager.UID_OBSERVER_GONE,
+                ActivityManager.UID_OBSERVER_PROCSTATE,
+        };
+        private static final int[] PROTO_ENUMS = new int[]{
+                ActivityManagerProto.UID_OBSERVER_FLAG_IDLE,
+                ActivityManagerProto.UID_OBSERVER_FLAG_ACTIVE,
+                ActivityManagerProto.UID_OBSERVER_FLAG_GONE,
+                ActivityManagerProto.UID_OBSERVER_FLAG_PROCSTATE,
+        };
+
+        UidObserverRegistration(int uid, String pkg, int which, int cutpoint) {
+            this.mUid = uid;
+            this.mPkg = pkg;
+            this.mWhich = which;
+            this.mCutpoint = cutpoint;
+            if (cutpoint >= ActivityManager.MIN_PROCESS_STATE) {
+                mLastProcStates = new SparseIntArray();
+            } else {
+                mLastProcStates = null;
+            }
+        }
+
+        void dumpDebug(ProtoOutputStream proto, long fieldId) {
+            final long token = proto.start(fieldId);
+            proto.write(UidObserverRegistrationProto.UID, mUid);
+            proto.write(UidObserverRegistrationProto.PACKAGE, mPkg);
+            ProtoUtils.writeBitWiseFlagsToProtoEnum(proto, UidObserverRegistrationProto.FLAGS,
+                    mWhich, ORIG_ENUMS, PROTO_ENUMS);
+            proto.write(UidObserverRegistrationProto.CUT_POINT, mCutpoint);
+            if (mLastProcStates != null) {
+                final int size = mLastProcStates.size();
+                for (int i = 0; i < size; i++) {
+                    final long pToken = proto.start(UidObserverRegistrationProto.LAST_PROC_STATES);
+                    proto.write(UidObserverRegistrationProto.ProcState.UID,
+                            mLastProcStates.keyAt(i));
+                    proto.write(UidObserverRegistrationProto.ProcState.STATE,
+                            mLastProcStates.valueAt(i));
+                    proto.end(pToken);
+                }
+            }
+            proto.end(token);
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/am/UserController.java b/services/core/java/com/android/server/am/UserController.java
index 19b671e..83bf28e 100644
--- a/services/core/java/com/android/server/am/UserController.java
+++ b/services/core/java/com/android/server/am/UserController.java
@@ -23,11 +23,10 @@
 import static android.app.ActivityManager.USER_OP_ERROR_RELATED_USERS_CANNOT_STOP;
 import static android.app.ActivityManager.USER_OP_IS_CURRENT;
 import static android.app.ActivityManager.USER_OP_SUCCESS;
-import static android.app.ActivityManagerInternal.ALLOW_ACROSS_PROFILES_IN_PROFILE_OR_NON_FULL;
-import static android.app.ActivityManagerInternal.ALLOW_ACROSS_PROFILES_IN_PROFILE_OR_FULL;
+import static android.app.ActivityManagerInternal.ALLOW_ALL_PROFILE_PERMISSIONS_IN_PROFILE;
 import static android.app.ActivityManagerInternal.ALLOW_FULL_ONLY;
 import static android.app.ActivityManagerInternal.ALLOW_NON_FULL;
-import static android.app.ActivityManagerInternal.ALLOW_NON_FULL_IN_PROFILE_OR_FULL;
+import static android.app.ActivityManagerInternal.ALLOW_NON_FULL_IN_PROFILE;
 import static android.os.Process.SHELL_UID;
 import static android.os.Process.SYSTEM_UID;
 
@@ -989,6 +988,8 @@
         final ArrayList<IStopUserCallback> stopCallbacks;
         final ArrayList<KeyEvictedCallback> keyEvictedCallbacks;
         int userIdToLock = userId;
+        // Must get a reference to UserInfo before it's removed
+        final UserInfo userInfo = getUserInfo(userId);
         synchronized (mLock) {
             stopCallbacks = new ArrayList<>(uss.mStopCallbacks);
             keyEvictedCallbacks = new ArrayList<>(uss.mKeyEvictedCallbacks);
@@ -1031,8 +1032,8 @@
         if (stopped) {
             mInjector.systemServiceManagerCleanupUser(userId);
             mInjector.stackSupervisorRemoveUser(userId);
+
             // Remove the user if it is ephemeral.
-            UserInfo userInfo = getUserInfo(userId);
             if (userInfo.isEphemeral() && !userInfo.preCreated) {
                 mInjector.getUserManager().removeUserEvenWhenDisallowed(userId);
             }
@@ -1631,7 +1632,7 @@
             UserInfo currentUserInfo = getUserInfo(currentUserId);
             Pair<UserInfo, UserInfo> userNames = new Pair<>(currentUserInfo, targetUserInfo);
             mUiHandler.removeMessages(START_USER_SWITCH_UI_MSG);
-            mUiHandler.sendMessage(mHandler.obtainMessage(
+            mUiHandler.sendMessage(mUiHandler.obtainMessage(
                     START_USER_SWITCH_UI_MSG, userNames));
         } else {
             mHandler.removeMessages(START_USER_SWITCH_FG_MSG);
@@ -1910,12 +1911,11 @@
                     callingUid, -1, true) != PackageManager.PERMISSION_GRANTED) {
                 // If the caller does not have either permission, they are always doomed.
                 allow = false;
-            } else if (allowMode == ALLOW_NON_FULL
-                    || allowMode == ALLOW_ACROSS_PROFILES_IN_PROFILE_OR_NON_FULL) {
+            } else if (allowMode == ALLOW_NON_FULL) {
                 // We are blanket allowing non-full access, you lucky caller!
                 allow = true;
-            } else if (allowMode == ALLOW_NON_FULL_IN_PROFILE_OR_FULL
-                        || allowMode == ALLOW_ACROSS_PROFILES_IN_PROFILE_OR_FULL) {
+            } else if (allowMode == ALLOW_NON_FULL_IN_PROFILE
+                        || allowMode == ALLOW_ALL_PROFILE_PERMISSIONS_IN_PROFILE) {
                 // We may or may not allow this depending on whether the two users are
                 // in the same profile.
                 allow = isSameProfileGroup;
@@ -1942,15 +1942,12 @@
                     builder.append("; this requires ");
                     builder.append(INTERACT_ACROSS_USERS_FULL);
                     if (allowMode != ALLOW_FULL_ONLY) {
-                        if (allowMode == ALLOW_NON_FULL
-                                || allowMode == ALLOW_ACROSS_PROFILES_IN_PROFILE_OR_NON_FULL
-                                || isSameProfileGroup) {
+                        if (allowMode == ALLOW_NON_FULL || isSameProfileGroup) {
                             builder.append(" or ");
                             builder.append(INTERACT_ACROSS_USERS);
                         }
                         if (isSameProfileGroup
-                                && (allowMode == ALLOW_ACROSS_PROFILES_IN_PROFILE_OR_FULL
-                                || allowMode == ALLOW_ACROSS_PROFILES_IN_PROFILE_OR_NON_FULL)) {
+                                && allowMode == ALLOW_ALL_PROFILE_PERMISSIONS_IN_PROFILE) {
                             builder.append(" or ");
                             builder.append(INTERACT_ACROSS_PROFILES);
                         }
@@ -1977,8 +1974,7 @@
     private boolean canInteractWithAcrossProfilesPermission(
             int allowMode, boolean isSameProfileGroup, int callingPid, int callingUid,
             String callingPackage) {
-        if (allowMode != ALLOW_ACROSS_PROFILES_IN_PROFILE_OR_FULL
-                && allowMode != ALLOW_ACROSS_PROFILES_IN_PROFILE_OR_NON_FULL) {
+        if (allowMode != ALLOW_ALL_PROFILE_PERMISSIONS_IN_PROFILE) {
             return false;
         }
         if (!isSameProfileGroup) {
@@ -2893,13 +2889,18 @@
 
         void showUserSwitchingDialog(UserInfo fromUser, UserInfo toUser,
                 String switchingFromSystemUserMessage, String switchingToSystemUserMessage) {
-            if (!mService.mContext.getPackageManager()
+            if (mService.mContext.getPackageManager()
                     .hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE)) {
-                final Dialog d = new UserSwitchingDialog(mService, mService.mContext, fromUser,
-                        toUser, true /* above system */, switchingFromSystemUserMessage,
-                        switchingToSystemUserMessage);
-                d.show();
+                // config_customUserSwitchUi is set to true on Automotive as CarSystemUI is
+                // responsible to show the UI; OEMs should not change that, but if they do, we
+                // should at least warn the user...
+                Slog.w(TAG, "Showing user switch dialog on UserController, it could cause a race "
+                        + "condition if it's shown by CarSystemUI as well");
             }
+            final Dialog d = new UserSwitchingDialog(mService, mService.mContext, fromUser,
+                    toUser, true /* above system */, switchingFromSystemUserMessage,
+                    switchingToSystemUserMessage);
+            d.show();
         }
 
         void reportGlobalUsageEventLocked(int event) {
diff --git a/services/core/java/com/android/server/appbinding/AppBindingService.java b/services/core/java/com/android/server/appbinding/AppBindingService.java
index 7e63e72..5db6dc7 100644
--- a/services/core/java/com/android/server/appbinding/AppBindingService.java
+++ b/services/core/java/com/android/server/appbinding/AppBindingService.java
@@ -45,6 +45,7 @@
 import com.android.internal.os.BackgroundThread;
 import com.android.internal.util.DumpUtils;
 import com.android.server.SystemService;
+import com.android.server.SystemService.TargetUser;
 import com.android.server.am.PersistentConnection;
 import com.android.server.appbinding.finders.AppServiceFinder;
 import com.android.server.appbinding.finders.CarrierMessagingClientServiceFinder;
@@ -125,18 +126,18 @@
         }
 
         @Override
-        public void onStartUser(int userHandle) {
-            mService.onStartUser(userHandle);
+        public void onUserStarting(@NonNull TargetUser user) {
+            mService.onStartUser(user.getUserIdentifier());
         }
 
         @Override
-        public void onUnlockUser(int userId) {
-            mService.onUnlockUser(userId);
+        public void onUserUnlocking(@NonNull TargetUser user) {
+            mService.onUnlockUser(user.getUserIdentifier());
         }
 
         @Override
-        public void onStopUser(int userHandle) {
-            mService.onStopUser(userHandle);
+        public void onUserStopping(@NonNull TargetUser user) {
+            mService.onStopUser(user.getUserIdentifier());
         }
     }
 
diff --git a/services/core/java/com/android/server/appop/AppOpsService.java b/services/core/java/com/android/server/appop/AppOpsService.java
index 74f3daf..dfe8af1 100644
--- a/services/core/java/com/android/server/appop/AppOpsService.java
+++ b/services/core/java/com/android/server/appop/AppOpsService.java
@@ -19,7 +19,6 @@
 import static android.app.ActivityManager.PROCESS_CAPABILITY_FOREGROUND_CAMERA;
 import static android.app.ActivityManager.PROCESS_CAPABILITY_FOREGROUND_LOCATION;
 import static android.app.ActivityManager.PROCESS_CAPABILITY_FOREGROUND_MICROPHONE;
-import static android.app.ActivityManagerInternal.ALLOW_ACROSS_PROFILES_IN_PROFILE_OR_NON_FULL;
 import static android.app.AppOpsManager.CALL_BACK_ON_SWITCHED_OP;
 import static android.app.AppOpsManager.FILTER_BY_ATTRIBUTION_TAG;
 import static android.app.AppOpsManager.FILTER_BY_OP_NAMES;
@@ -129,7 +128,6 @@
 import android.util.ArrayMap;
 import android.util.ArraySet;
 import android.util.AtomicFile;
-import android.util.EventLog;
 import android.util.KeyValueListParser;
 import android.util.LongSparseArray;
 import android.util.Pair;
@@ -163,7 +161,6 @@
 import com.android.server.LockGuard;
 import com.android.server.SystemServerInitThreadPool;
 import com.android.server.SystemServiceManager;
-import com.android.server.am.ActivityManagerService;
 import com.android.server.pm.PackageList;
 import com.android.server.pm.parsing.pkg.AndroidPackage;
 
@@ -1892,6 +1889,7 @@
         synchronized (this) {
             if (mWriteScheduled) {
                 mWriteScheduled = false;
+                mHandler.removeCallbacks(mWriteRunner);
                 doWrite = true;
             }
         }
@@ -2200,11 +2198,8 @@
                     + " by uid " + Binder.getCallingUid());
         }
 
-        int userId = UserHandle.getUserId(uid);
-
         enforceManageAppOpsModes(Binder.getCallingPid(), Binder.getCallingUid(), uid);
         verifyIncomingOp(code);
-        verifyIncomingUser(userId);
         code = AppOpsManager.opToSwitch(code);
 
         if (permissionPolicyCallback == null) {
@@ -2449,12 +2444,8 @@
     private void setMode(int code, int uid, @NonNull String packageName, int mode,
             @Nullable IAppOpsCallback permissionPolicyCallback) {
         enforceManageAppOpsModes(Binder.getCallingPid(), Binder.getCallingUid(), uid);
-
-        int userId = UserHandle.getUserId(uid);
-
         verifyIncomingOp(code);
-        verifyIncomingUser(userId);
-        verifyIncomingPackage(packageName, userId);
+        verifyIncomingPackage(packageName, UserHandle.getUserId(uid));
 
         ArraySet<ModeCallback> repCbs = null;
         code = AppOpsManager.opToSwitch(code);
@@ -2867,11 +2858,8 @@
 
     private int checkOperationImpl(int code, int uid, String packageName,
                 boolean raw) {
-        int userId = UserHandle.getUserId(uid);
-
         verifyIncomingOp(code);
-        verifyIncomingUser(userId);
-        verifyIncomingPackage(packageName, userId);
+        verifyIncomingPackage(packageName, UserHandle.getUserId(uid));
 
         String resolvedPackageName = resolvePackageName(uid, packageName);
         if (resolvedPackageName == null) {
@@ -2990,15 +2978,10 @@
             String proxiedAttributionTag, int proxyUid, String proxyPackageName,
             String proxyAttributionTag, boolean shouldCollectAsyncNotedOp, String message,
             boolean shouldCollectMessage) {
-        int proxiedUserId = UserHandle.getUserId(proxiedUid);
-        int proxyUserId = UserHandle.getUserId(proxyUid);
-
         verifyIncomingUid(proxyUid);
         verifyIncomingOp(code);
-        verifyIncomingUser(proxiedUserId);
-        verifyIncomingUser(proxyUserId);
-        verifyIncomingPackage(proxiedPackageName, proxiedUserId);
-        verifyIncomingPackage(proxyPackageName, proxyUserId);
+        verifyIncomingPackage(proxiedPackageName, UserHandle.getUserId(proxiedUid));
+        verifyIncomingPackage(proxyPackageName, UserHandle.getUserId(proxyUid));
 
         String resolveProxyPackageName = resolvePackageName(proxyUid, proxyPackageName);
         if (resolveProxyPackageName == null) {
@@ -3048,12 +3031,9 @@
     private int noteOperationImpl(int code, int uid, @Nullable String packageName,
             @Nullable String attributionTag, boolean shouldCollectAsyncNotedOp,
             @Nullable String message, boolean shouldCollectMessage) {
-        int userId = UserHandle.getUserId(uid);
-
         verifyIncomingUid(uid);
         verifyIncomingOp(code);
-        verifyIncomingUser(userId);
-        verifyIncomingPackage(packageName, userId);
+        verifyIncomingPackage(packageName, UserHandle.getUserId(uid));
 
         String resolvedPackageName = resolvePackageName(uid, packageName);
         if (resolvedPackageName == null) {
@@ -3430,12 +3410,9 @@
     public int startOperation(IBinder clientId, int code, int uid, String packageName,
             String attributionTag, boolean startIfModeDefault, boolean shouldCollectAsyncNotedOp,
             String message, boolean shouldCollectMessage) {
-        int userId = UserHandle.getUserId(uid);
-
         verifyIncomingUid(uid);
         verifyIncomingOp(code);
-        verifyIncomingUser(userId);
-        verifyIncomingPackage(packageName, userId);
+        verifyIncomingPackage(packageName, UserHandle.getUserId(uid));
 
         String resolvedPackageName = resolvePackageName(uid, packageName);
         if (resolvedPackageName == null) {
@@ -3515,12 +3492,9 @@
     @Override
     public void finishOperation(IBinder clientId, int code, int uid, String packageName,
             String attributionTag) {
-        int userId = UserHandle.getUserId(uid);
-
         verifyIncomingUid(uid);
         verifyIncomingOp(code);
-        verifyIncomingUser(userId);
-        verifyIncomingPackage(packageName, userId);
+        verifyIncomingPackage(packageName, UserHandle.getUserId(uid));
 
         String resolvedPackageName = resolvePackageName(uid, packageName);
         if (resolvedPackageName == null) {
@@ -3749,33 +3723,6 @@
         }
     }
 
-    private void verifyIncomingUser(@UserIdInt int userId) {
-        int callingUid = Binder.getCallingUid();
-        int callingUserId = UserHandle.getUserId(callingUid);
-        int callingPid = Binder.getCallingPid();
-
-        if (callingUserId != userId) {
-            // Prevent endless loop between when checking appops inside of handleIncomingUser
-            if (Binder.getCallingPid() == ActivityManagerService.MY_PID) {
-                return;
-            }
-            long token = Binder.clearCallingIdentity();
-            try {
-                try {
-                    LocalServices.getService(ActivityManagerInternal.class).handleIncomingUser(
-                            callingPid, callingUid, userId, /* allowAll */ false,
-                            ALLOW_ACROSS_PROFILES_IN_PROFILE_OR_NON_FULL, "appop operation", null);
-                } catch (Exception e) {
-                    EventLog.writeEvent(0x534e4554, "153996875", "appop", userId);
-
-                    throw e;
-                }
-            } finally {
-                Binder.restoreCallingIdentity(token);
-            }
-        }
-    }
-
     private @Nullable UidState getUidStateLocked(int uid, boolean edit) {
         UidState uidState = mUidStates.get(uid);
         if (uidState == null) {
@@ -5855,11 +5802,8 @@
                 return false;
             }
         }
-        int userId = UserHandle.getUserId(uid);
-
         verifyIncomingOp(code);
-        verifyIncomingUser(userId);
-        verifyIncomingPackage(packageName, userId);
+        verifyIncomingPackage(packageName, UserHandle.getUserId(uid));
 
         final String resolvedPackageName = resolvePackageName(uid, packageName);
         if (resolvedPackageName == null) {
@@ -6278,7 +6222,9 @@
 
             int[] users;
             if (userId == UserHandle.USER_ALL) {
-                List<UserInfo> liveUsers = UserManager.get(mContext).getUsers(false);
+                // TODO(b/157921703): 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++) {
diff --git a/services/core/java/com/android/server/appop/TEST_MAPPING b/services/core/java/com/android/server/appop/TEST_MAPPING
index a3e1b7a..84de25c 100644
--- a/services/core/java/com/android/server/appop/TEST_MAPPING
+++ b/services/core/java/com/android/server/appop/TEST_MAPPING
@@ -7,9 +7,6 @@
             "name": "CtsAppOps2TestCases"
         },
         {
-            "name": "CtsAppOpHostTestCases"
-        },
-        {
             "name": "FrameworksServicesTests",
             "options": [
                 {
diff --git a/services/core/java/com/android/server/audio/AudioDeviceBroker.java b/services/core/java/com/android/server/audio/AudioDeviceBroker.java
index 45f95fd..06ef58f 100644
--- a/services/core/java/com/android/server/audio/AudioDeviceBroker.java
+++ b/services/core/java/com/android/server/audio/AudioDeviceBroker.java
@@ -28,7 +28,7 @@
 import android.media.AudioRoutesInfo;
 import android.media.AudioSystem;
 import android.media.IAudioRoutesObserver;
-import android.media.IStrategyPreferredDeviceDispatcher;
+import android.media.IStrategyPreferredDevicesDispatcher;
 import android.media.MediaMetrics;
 import android.os.Binder;
 import android.os.Handler;
@@ -47,6 +47,7 @@
 import java.io.PrintWriter;
 import java.util.ArrayList;
 import java.util.HashSet;
+import java.util.List;
 import java.util.NoSuchElementException;
 import java.util.Set;
 import java.util.concurrent.atomic.AtomicBoolean;
@@ -224,12 +225,35 @@
             if (!addSpeakerphoneClient(cb, pid, on)) {
                 return false;
             }
+            if (on) {
+                // Cancel BT SCO ON request by this same client: speakerphone and BT SCO routes
+                // are mutually exclusive.
+                // See symmetrical operation for startBluetoothScoForClient_Sync().
+                mBtHelper.stopBluetoothScoForPid(pid);
+            }
             final boolean wasOn = isSpeakerphoneOn();
             updateSpeakerphoneOn(eventSource);
             return (wasOn != isSpeakerphoneOn());
         }
     }
 
+    /**
+     * Turns speakerphone off for a given pid and update speakerphone state.
+     * @param pid
+     */
+    @GuardedBy("mDeviceStateLock")
+    private void setSpeakerphoneOffForPid(int pid) {
+        SpeakerphoneClient client = getSpeakerphoneClientForPid(pid);
+        if (client == null) {
+            return;
+        }
+        client.unregisterDeathRecipient();
+        mSpeakerphoneClients.remove(client);
+        final String eventSource = new StringBuilder("setSpeakerphoneOffForPid(")
+                .append(pid).append(")").toString();
+        updateSpeakerphoneOn(eventSource);
+    }
+
     @GuardedBy("mDeviceStateLock")
     private void updateSpeakerphoneOn(String eventSource) {
         if (isSpeakerphoneOnRequested()) {
@@ -488,6 +512,10 @@
     /*package*/ void startBluetoothScoForClient_Sync(IBinder cb, int scoAudioMode,
                 @NonNull String eventSource) {
         synchronized (mDeviceStateLock) {
+            // Cancel speakerphone ON request by this same client: speakerphone and BT SCO routes
+            // are mutually exclusive.
+            // See symmetrical operation for setSpeakerphoneOn(true).
+            setSpeakerphoneOffForPid(Binder.getCallingPid());
             mBtHelper.startBluetoothScoForClient(cb, scoAudioMode, eventSource);
         }
     }
@@ -499,23 +527,23 @@
         }
     }
 
-    /*package*/ int setPreferredDeviceForStrategySync(int strategy,
-                                                      @NonNull AudioDeviceAttributes device) {
-        return mDeviceInventory.setPreferredDeviceForStrategySync(strategy, device);
+    /*package*/ int setPreferredDevicesForStrategySync(int strategy,
+            @NonNull List<AudioDeviceAttributes> devices) {
+        return mDeviceInventory.setPreferredDevicesForStrategySync(strategy, devices);
     }
 
-    /*package*/ int removePreferredDeviceForStrategySync(int strategy) {
-        return mDeviceInventory.removePreferredDeviceForStrategySync(strategy);
+    /*package*/ int removePreferredDevicesForStrategySync(int strategy) {
+        return mDeviceInventory.removePreferredDevicesForStrategySync(strategy);
     }
 
-    /*package*/ void registerStrategyPreferredDeviceDispatcher(
-            @NonNull IStrategyPreferredDeviceDispatcher dispatcher) {
-        mDeviceInventory.registerStrategyPreferredDeviceDispatcher(dispatcher);
+    /*package*/ void registerStrategyPreferredDevicesDispatcher(
+            @NonNull IStrategyPreferredDevicesDispatcher dispatcher) {
+        mDeviceInventory.registerStrategyPreferredDevicesDispatcher(dispatcher);
     }
 
-    /*package*/ void unregisterStrategyPreferredDeviceDispatcher(
-            @NonNull IStrategyPreferredDeviceDispatcher dispatcher) {
-        mDeviceInventory.unregisterStrategyPreferredDeviceDispatcher(dispatcher);
+    /*package*/ void unregisterStrategyPreferredDevicesDispatcher(
+            @NonNull IStrategyPreferredDevicesDispatcher dispatcher) {
+        mDeviceInventory.unregisterStrategyPreferredDevicesDispatcher(dispatcher);
     }
 
     //---------------------------------------------------------------------
@@ -656,14 +684,14 @@
         sendLMsgNoDelay(MSG_L_SPEAKERPHONE_CLIENT_DIED, SENDMSG_QUEUE, obj);
     }
 
-    /*package*/ void postSaveSetPreferredDeviceForStrategy(int strategy,
-                                                           AudioDeviceAttributes device)
+    /*package*/ void postSaveSetPreferredDevicesForStrategy(int strategy,
+                                                            List<AudioDeviceAttributes> devices)
     {
-        sendILMsgNoDelay(MSG_IL_SAVE_PREF_DEVICE_FOR_STRATEGY, SENDMSG_QUEUE, strategy, device);
+        sendILMsgNoDelay(MSG_IL_SAVE_PREF_DEVICES_FOR_STRATEGY, SENDMSG_QUEUE, strategy, devices);
     }
 
-    /*package*/ void postSaveRemovePreferredDeviceForStrategy(int strategy) {
-        sendIMsgNoDelay(MSG_I_SAVE_REMOVE_PREF_DEVICE_FOR_STRATEGY, SENDMSG_QUEUE, strategy);
+    /*package*/ void postSaveRemovePreferredDevicesForStrategy(int strategy) {
+        sendIMsgNoDelay(MSG_I_SAVE_REMOVE_PREF_DEVICES_FOR_STRATEGY, SENDMSG_QUEUE, strategy);
     }
 
     //---------------------------------------------------------------------
@@ -1055,14 +1083,15 @@
                                 info.mDevice, info.mState, info.mSupprNoisy, info.mMusicDevice);
                     }
                 } break;
-                case MSG_IL_SAVE_PREF_DEVICE_FOR_STRATEGY: {
+                case MSG_IL_SAVE_PREF_DEVICES_FOR_STRATEGY: {
                     final int strategy = msg.arg1;
-                    final AudioDeviceAttributes device = (AudioDeviceAttributes) msg.obj;
-                    mDeviceInventory.onSaveSetPreferredDevice(strategy, device);
+                    final List<AudioDeviceAttributes> devices =
+                            (List<AudioDeviceAttributes>) msg.obj;
+                    mDeviceInventory.onSaveSetPreferredDevices(strategy, devices);
                 } break;
-                case MSG_I_SAVE_REMOVE_PREF_DEVICE_FOR_STRATEGY: {
+                case MSG_I_SAVE_REMOVE_PREF_DEVICES_FOR_STRATEGY: {
                     final int strategy = msg.arg1;
-                    mDeviceInventory.onSaveRemovePreferredDevice(strategy);
+                    mDeviceInventory.onSaveRemovePreferredDevices(strategy);
                 } break;
                 case MSG_CHECK_MUTE_MUSIC:
                     checkMessagesMuteMusic(0);
@@ -1136,8 +1165,8 @@
 
     // a ScoClient died in BtHelper
     private static final int MSG_L_SCOCLIENT_DIED = 32;
-    private static final int MSG_IL_SAVE_PREF_DEVICE_FOR_STRATEGY = 33;
-    private static final int MSG_I_SAVE_REMOVE_PREF_DEVICE_FOR_STRATEGY = 34;
+    private static final int MSG_IL_SAVE_PREF_DEVICES_FOR_STRATEGY = 33;
+    private static final int MSG_I_SAVE_REMOVE_PREF_DEVICES_FOR_STRATEGY = 34;
 
     private static final int MSG_L_SPEAKERPHONE_CLIENT_DIED = 35;
     private static final int MSG_CHECK_MUTE_MUSIC = 36;
@@ -1379,6 +1408,16 @@
         return false;
     }
 
+    @GuardedBy("mDeviceStateLock")
+    private SpeakerphoneClient getSpeakerphoneClientForPid(int pid) {
+        for (SpeakerphoneClient cl : mSpeakerphoneClients) {
+            if (cl.getPid() == pid) {
+                return cl;
+            }
+        }
+        return null;
+    }
+
     // List of clients requesting speakerPhone ON
     @GuardedBy("mDeviceStateLock")
     private final @NonNull ArrayList<SpeakerphoneClient> mSpeakerphoneClients =
diff --git a/services/core/java/com/android/server/audio/AudioDeviceInventory.java b/services/core/java/com/android/server/audio/AudioDeviceInventory.java
index 02a846e..fbf07cc 100644
--- a/services/core/java/com/android/server/audio/AudioDeviceInventory.java
+++ b/services/core/java/com/android/server/audio/AudioDeviceInventory.java
@@ -16,7 +16,6 @@
 package com.android.server.audio;
 
 import android.annotation.NonNull;
-import android.annotation.Nullable;
 import android.app.ActivityManager;
 import android.bluetooth.BluetoothA2dp;
 import android.bluetooth.BluetoothAdapter;
@@ -32,7 +31,7 @@
 import android.media.AudioRoutesInfo;
 import android.media.AudioSystem;
 import android.media.IAudioRoutesObserver;
-import android.media.IStrategyPreferredDeviceDispatcher;
+import android.media.IStrategyPreferredDevicesDispatcher;
 import android.media.MediaMetrics;
 import android.os.Binder;
 import android.os.RemoteCallbackList;
@@ -51,6 +50,7 @@
 import java.util.ArrayList;
 import java.util.HashSet;
 import java.util.LinkedHashMap;
+import java.util.List;
 import java.util.Set;
 
 /**
@@ -137,7 +137,8 @@
     private final ArrayMap<Integer, String> mApmConnectedDevices = new ArrayMap<>();
 
     // List of preferred devices for strategies
-    private final ArrayMap<Integer, AudioDeviceAttributes> mPreferredDevices = new ArrayMap<>();
+    private final ArrayMap<Integer, List<AudioDeviceAttributes>> mPreferredDevices =
+            new ArrayMap<>();
 
     // the wrapper for AudioSystem static methods, allows us to spy AudioSystem
     private final @NonNull AudioSystemAdapter mAudioSystem;
@@ -150,8 +151,8 @@
             new RemoteCallbackList<IAudioRoutesObserver>();
 
     // Monitoring of strategy-preferred device
-    final RemoteCallbackList<IStrategyPreferredDeviceDispatcher> mPrefDevDispatchers =
-            new RemoteCallbackList<IStrategyPreferredDeviceDispatcher>();
+    final RemoteCallbackList<IStrategyPreferredDevicesDispatcher> mPrefDevDispatchers =
+            new RemoteCallbackList<IStrategyPreferredDevicesDispatcher>();
 
     /*package*/ AudioDeviceInventory(@NonNull AudioDeviceBroker broker) {
         mDeviceBroker = broker;
@@ -265,8 +266,9 @@
             }
         }
         synchronized (mPreferredDevices) {
-            mPreferredDevices.forEach((strategy, device) -> {
-                mAudioSystem.setPreferredDeviceForStrategy(strategy, device); });
+            mPreferredDevices.forEach((strategy, devices) -> {
+                mAudioSystem.setDevicesRoleForStrategy(
+                        strategy, AudioSystem.DEVICE_ROLE_PREFERRED, devices); });
         }
     }
 
@@ -600,49 +602,52 @@
         mmi.record();
     }
 
-    /*package*/ void onSaveSetPreferredDevice(int strategy, @NonNull AudioDeviceAttributes device) {
-        mPreferredDevices.put(strategy, device);
-        dispatchPreferredDevice(strategy, device);
+    /*package*/ void onSaveSetPreferredDevices(int strategy,
+                                               @NonNull List<AudioDeviceAttributes> devices) {
+        mPreferredDevices.put(strategy, devices);
+        dispatchPreferredDevice(strategy, devices);
     }
 
-    /*package*/ void onSaveRemovePreferredDevice(int strategy) {
+    /*package*/ void onSaveRemovePreferredDevices(int strategy) {
         mPreferredDevices.remove(strategy);
-        dispatchPreferredDevice(strategy, null);
+        dispatchPreferredDevice(strategy, new ArrayList<AudioDeviceAttributes>());
     }
 
     //------------------------------------------------------------
     //
 
-    /*package*/ int setPreferredDeviceForStrategySync(int strategy,
-                                                      @NonNull AudioDeviceAttributes device) {
+    /*package*/ int setPreferredDevicesForStrategySync(int strategy,
+            @NonNull List<AudioDeviceAttributes> devices) {
         final long identity = Binder.clearCallingIdentity();
-        final int status = mAudioSystem.setPreferredDeviceForStrategy(strategy, device);
+        final int status = mAudioSystem.setDevicesRoleForStrategy(
+                strategy, AudioSystem.DEVICE_ROLE_PREFERRED, devices);
         Binder.restoreCallingIdentity(identity);
 
         if (status == AudioSystem.SUCCESS) {
-            mDeviceBroker.postSaveSetPreferredDeviceForStrategy(strategy, device);
+            mDeviceBroker.postSaveSetPreferredDevicesForStrategy(strategy, devices);
         }
         return status;
     }
 
-    /*package*/ int removePreferredDeviceForStrategySync(int strategy) {
+    /*package*/ int removePreferredDevicesForStrategySync(int strategy) {
         final long identity = Binder.clearCallingIdentity();
-        final int status = mAudioSystem.removePreferredDeviceForStrategy(strategy);
+        final int status = mAudioSystem.removeDevicesRoleForStrategy(
+                strategy, AudioSystem.DEVICE_ROLE_PREFERRED);
         Binder.restoreCallingIdentity(identity);
 
         if (status == AudioSystem.SUCCESS) {
-            mDeviceBroker.postSaveRemovePreferredDeviceForStrategy(strategy);
+            mDeviceBroker.postSaveRemovePreferredDevicesForStrategy(strategy);
         }
         return status;
     }
 
-    /*package*/ void registerStrategyPreferredDeviceDispatcher(
-            @NonNull IStrategyPreferredDeviceDispatcher dispatcher) {
+    /*package*/ void registerStrategyPreferredDevicesDispatcher(
+            @NonNull IStrategyPreferredDevicesDispatcher dispatcher) {
         mPrefDevDispatchers.register(dispatcher);
     }
 
-    /*package*/ void unregisterStrategyPreferredDeviceDispatcher(
-            @NonNull IStrategyPreferredDeviceDispatcher dispatcher) {
+    /*package*/ void unregisterStrategyPreferredDevicesDispatcher(
+            @NonNull IStrategyPreferredDevicesDispatcher dispatcher) {
         mPrefDevDispatchers.unregister(dispatcher);
     }
 
@@ -1288,11 +1293,13 @@
         }
     }
 
-    private void dispatchPreferredDevice(int strategy, @Nullable AudioDeviceAttributes device) {
+    private void dispatchPreferredDevice(int strategy,
+                                         @NonNull List<AudioDeviceAttributes> devices) {
         final int nbDispatchers = mPrefDevDispatchers.beginBroadcast();
         for (int i = 0; i < nbDispatchers; i++) {
             try {
-                mPrefDevDispatchers.getBroadcastItem(i).dispatchPrefDeviceChanged(strategy, device);
+                mPrefDevDispatchers.getBroadcastItem(i).dispatchPrefDevicesChanged(
+                        strategy, devices);
             } catch (RemoteException e) {
             }
         }
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index 8ebb606..673ca1f 100755
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -83,7 +83,7 @@
 import android.media.IPlaybackConfigDispatcher;
 import android.media.IRecordingConfigDispatcher;
 import android.media.IRingtonePlayer;
-import android.media.IStrategyPreferredDeviceDispatcher;
+import android.media.IStrategyPreferredDevicesDispatcher;
 import android.media.IVolumeController;
 import android.media.MediaExtractor;
 import android.media.MediaFormat;
@@ -167,6 +167,7 @@
 import java.util.Set;
 import java.util.concurrent.Executor;
 import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.stream.Collectors;
 
 /**
  * The implementation of the audio service for volume, audio focus, device management...
@@ -284,6 +285,8 @@
     private static final int MSG_PLAYBACK_CONFIG_CHANGE = 29;
     private static final int MSG_BROADCAST_MICROPHONE_MUTE = 30;
     private static final int MSG_CHECK_MODE_FOR_UID = 31;
+    private static final int MSG_STREAM_DEVICES_CHANGED = 32;
+    private static final int MSG_UPDATE_VOLUME_STATES_FOR_DEVICE = 33;
     // start of messages handled under wakelock
     //   these messages can only be queued, i.e. sent with queueMsgUnderWakeLock(),
     //   and not with sendMsg(..., ..., SENDMSG_QUEUE, ...)
@@ -414,13 +417,6 @@
         AppOpsManager.OP_AUDIO_MEDIA_VOLUME             // STREAM_ASSISTANT
     };
 
-    private static Set<Integer> sDeviceVolumeBehaviorSupportedDeviceOutSet = new HashSet<>(
-            Arrays.asList(
-                    AudioSystem.DEVICE_OUT_HDMI,
-                    AudioSystem.DEVICE_OUT_HDMI_ARC,
-                    AudioSystem.DEVICE_OUT_SPDIF,
-                    AudioSystem.DEVICE_OUT_LINE));
-
     private final boolean mUseFixedVolume;
 
     // If absolute volume is supported in AVRCP device
@@ -1289,7 +1285,6 @@
             }
 
             if (isPlatformTelevision()) {
-                checkAddAllFixedVolumeDevices(AudioSystem.DEVICE_OUT_HDMI, caller);
                 synchronized (mHdmiClientLock) {
                     if (mHdmiManager != null && mHdmiPlaybackClient != null) {
                         updateHdmiCecSinkLocked(mHdmiCecSink | false);
@@ -1309,22 +1304,71 @@
         }
     }
 
-    private void checkAddAllFixedVolumeDevices(int device, String caller) {
-        final int numStreamTypes = AudioSystem.getNumStreamTypes();
-        for (int streamType = 0; streamType < numStreamTypes; streamType++) {
-            if (!mStreamStates[streamType].hasIndexForDevice(device)) {
-                // set the default value, if device is affected by a full/fix/abs volume rule, it
-                // will taken into account in checkFixedVolumeDevices()
-                mStreamStates[streamType].setIndex(
-                        mStreamStates[mStreamVolumeAlias[streamType]]
-                                .getIndex(AudioSystem.DEVICE_OUT_DEFAULT),
-                        device, caller, true /*hasModifyAudioSettings*/);
-            }
-            mStreamStates[streamType].checkFixedVolumeDevices();
+    /**
+     * Asynchronously update volume states for the given device.
+     *
+     * @param device a single audio device, ensure that this is not a devices bitmask
+     * @param caller caller of this method
+     */
+    private void postUpdateVolumeStatesForAudioDevice(int device, String caller) {
+        sendMsg(mAudioHandler,
+                MSG_UPDATE_VOLUME_STATES_FOR_DEVICE,
+                SENDMSG_QUEUE, device /*arg1*/, 0 /*arg2*/, caller /*obj*/,
+                0 /*delay*/);
+    }
 
-            // Unmute streams if device is full volume
-            if (mFullVolumeDevices.contains(device)) {
-                mStreamStates[streamType].mute(false);
+    /**
+     * Update volume states for the given device.
+     *
+     * This will initialize the volume index if no volume index is available.
+     * If the device is the currently routed device, fixed/full volume policies will be applied.
+     *
+     * @param device a single audio device, ensure that this is not a devices bitmask
+     * @param caller caller of this method
+     */
+    private void onUpdateVolumeStatesForAudioDevice(int device, String caller) {
+        final int numStreamTypes = AudioSystem.getNumStreamTypes();
+        synchronized (mSettingsLock) {
+            synchronized (VolumeStreamState.class) {
+                for (int streamType = 0; streamType < numStreamTypes; streamType++) {
+                    updateVolumeStates(device, streamType, caller);
+                }
+            }
+        }
+    }
+
+    /**
+     * Update volume states for the given device and given stream.
+     *
+     * This will initialize the volume index if no volume index is available.
+     * If the device is the currently routed device, fixed/full volume policies will be applied.
+     *
+     * @param device a single audio device, ensure that this is not a devices bitmask
+     * @param streamType streamType to be updated
+     * @param caller caller of this method
+     */
+    private void updateVolumeStates(int device, int streamType, String caller) {
+        if (!mStreamStates[streamType].hasIndexForDevice(device)) {
+            // set the default value, if device is affected by a full/fix/abs volume rule, it
+            // will taken into account in checkFixedVolumeDevices()
+            mStreamStates[streamType].setIndex(
+                    mStreamStates[mStreamVolumeAlias[streamType]]
+                            .getIndex(AudioSystem.DEVICE_OUT_DEFAULT),
+                    device, caller, true /*hasModifyAudioSettings*/);
+        }
+
+        // Check if device to be updated is routed for the given audio stream
+        List<AudioDeviceAttributes> devicesForAttributes = getDevicesForAttributesInt(
+                new AudioAttributes.Builder().setInternalLegacyStreamType(streamType).build());
+        for (AudioDeviceAttributes deviceAttributes : devicesForAttributes) {
+            if (deviceAttributes.getType() == AudioDeviceInfo.convertInternalDeviceToDeviceType(
+                    device)) {
+                mStreamStates[streamType].checkFixedVolumeDevices();
+
+                // Unmute streams if required and device is full volume
+                if (isStreamMute(streamType) && mFullVolumeDevices.contains(device)) {
+                    mStreamStates[streamType].mute(false);
+                }
             }
         }
     }
@@ -1784,22 +1828,28 @@
     ///////////////////////////////////////////////////////////////////////////
     // IPC methods
     ///////////////////////////////////////////////////////////////////////////
-    /** @see AudioManager#setPreferredDeviceForStrategy(AudioProductStrategy, AudioDeviceInfo) */
-    public int setPreferredDeviceForStrategy(int strategy, AudioDeviceAttributes device) {
-        if (device == null) {
+    /**
+     * @see AudioManager#setPreferredDeviceForStrategy(AudioProductStrategy, AudioDeviceAttributes)
+     * @see AudioManager#setPreferredDevicesForStrategy(AudioProductStrategy,
+     *                                                  List<AudioDeviceAttributes>)
+     */
+    public int setPreferredDevicesForStrategy(int strategy, List<AudioDeviceAttributes> devices) {
+        if (devices == null) {
             return AudioSystem.ERROR;
         }
         enforceModifyAudioRoutingPermission();
         final String logString = String.format(
                 "setPreferredDeviceForStrategy u/pid:%d/%d strat:%d dev:%s",
-                Binder.getCallingUid(), Binder.getCallingPid(), strategy, device.toString());
+                Binder.getCallingUid(), Binder.getCallingPid(), strategy,
+                devices.stream().map(e -> e.toString()).collect(Collectors.joining(",")));
         sDeviceLogger.log(new AudioEventLogger.StringEvent(logString).printLog(TAG));
-        if (device.getRole() == AudioDeviceAttributes.ROLE_INPUT) {
+        if (devices.stream().anyMatch(device ->
+                device.getRole() == AudioDeviceAttributes.ROLE_INPUT)) {
             Log.e(TAG, "Unsupported input routing in " + logString);
             return AudioSystem.ERROR;
         }
 
-        final int status = mDeviceBroker.setPreferredDeviceForStrategySync(strategy, device);
+        final int status = mDeviceBroker.setPreferredDevicesForStrategySync(strategy, devices);
         if (status != AudioSystem.SUCCESS) {
             Log.e(TAG, String.format("Error %d in %s)", status, logString));
         }
@@ -1808,60 +1858,73 @@
     }
 
     /** @see AudioManager#removePreferredDeviceForStrategy(AudioProductStrategy) */
-    public int removePreferredDeviceForStrategy(int strategy) {
+    public int removePreferredDevicesForStrategy(int strategy) {
         enforceModifyAudioRoutingPermission();
         final String logString =
                 String.format("removePreferredDeviceForStrategy strat:%d", strategy);
         sDeviceLogger.log(new AudioEventLogger.StringEvent(logString).printLog(TAG));
 
-        final int status = mDeviceBroker.removePreferredDeviceForStrategySync(strategy);
+        final int status = mDeviceBroker.removePreferredDevicesForStrategySync(strategy);
         if (status != AudioSystem.SUCCESS) {
             Log.e(TAG, String.format("Error %d in %s)", status, logString));
         }
         return status;
     }
 
-    /** @see AudioManager#getPreferredDeviceForStrategy(AudioProductStrategy) */
-    public AudioDeviceAttributes getPreferredDeviceForStrategy(int strategy) {
+    /**
+     * @see AudioManager#getPreferredDeviceForStrategy(AudioProductStrategy)
+     * @see AudioManager#getPreferredDevicesForStrategy(AudioProductStrategy)
+     */
+    public List<AudioDeviceAttributes> getPreferredDevicesForStrategy(int strategy) {
         enforceModifyAudioRoutingPermission();
-        AudioDeviceAttributes[] devices = new AudioDeviceAttributes[1];
+        List<AudioDeviceAttributes> devices = new ArrayList<>();
         final long identity = Binder.clearCallingIdentity();
-        final int status = AudioSystem.getPreferredDeviceForStrategy(strategy, devices);
+        final int status = AudioSystem.getDevicesForRoleAndStrategy(
+                strategy, AudioSystem.DEVICE_ROLE_PREFERRED, devices);
         Binder.restoreCallingIdentity(identity);
         if (status != AudioSystem.SUCCESS) {
             Log.e(TAG, String.format("Error %d in getPreferredDeviceForStrategy(%d)",
                     status, strategy));
-            return null;
+            return new ArrayList<AudioDeviceAttributes>();
         } else {
-            return devices[0];
+            return devices;
         }
     }
 
-    /** @see AudioManager#addOnPreferredDeviceForStrategyChangedListener(Executor, AudioManager.OnPreferredDeviceForStrategyChangedListener) */
-    public void registerStrategyPreferredDeviceDispatcher(
-            @Nullable IStrategyPreferredDeviceDispatcher dispatcher) {
+    /** @see AudioManager#addOnPreferredDevicesForStrategyChangedListener(
+     *               Executor, AudioManager.OnPreferredDevicesForStrategyChangedListener)
+     */
+    public void registerStrategyPreferredDevicesDispatcher(
+            @Nullable IStrategyPreferredDevicesDispatcher dispatcher) {
         if (dispatcher == null) {
             return;
         }
         enforceModifyAudioRoutingPermission();
-        mDeviceBroker.registerStrategyPreferredDeviceDispatcher(dispatcher);
+        mDeviceBroker.registerStrategyPreferredDevicesDispatcher(dispatcher);
     }
 
-    /** @see AudioManager#removeOnPreferredDeviceForStrategyChangedListener(AudioManager.OnPreferredDeviceForStrategyChangedListener) */
-    public void unregisterStrategyPreferredDeviceDispatcher(
-            @Nullable IStrategyPreferredDeviceDispatcher dispatcher) {
+    /** @see AudioManager#removeOnPreferredDevicesForStrategyChangedListener(
+     *               AudioManager.OnPreferredDevicesForStrategyChangedListener)
+     */
+    public void unregisterStrategyPreferredDevicesDispatcher(
+            @Nullable IStrategyPreferredDevicesDispatcher dispatcher) {
         if (dispatcher == null) {
             return;
         }
         enforceModifyAudioRoutingPermission();
-        mDeviceBroker.unregisterStrategyPreferredDeviceDispatcher(dispatcher);
+        mDeviceBroker.unregisterStrategyPreferredDevicesDispatcher(dispatcher);
     }
 
     /** @see AudioManager#getDevicesForAttributes(AudioAttributes) */
     public @NonNull ArrayList<AudioDeviceAttributes> getDevicesForAttributes(
             @NonNull AudioAttributes attributes) {
-        Objects.requireNonNull(attributes);
         enforceModifyAudioRoutingPermission();
+        return getDevicesForAttributesInt(attributes);
+    }
+
+    protected @NonNull ArrayList<AudioDeviceAttributes> getDevicesForAttributesInt(
+            @NonNull AudioAttributes attributes) {
+        Objects.requireNonNull(attributes);
         return AudioSystem.getDevicesForAttributes(attributes);
     }
 
@@ -4904,11 +4967,21 @@
         }
     }
 
-    private void observeDevicesForStreams(int skipStream) {
-        synchronized (VolumeStreamState.class) {
-            for (int stream = 0; stream < mStreamStates.length; stream++) {
-                if (stream != skipStream) {
-                    mStreamStates[stream].observeDevicesForStream_syncVSS(false /*checkOthers*/);
+    private void onObserveDevicesForAllStreams(int skipStream) {
+        synchronized (mSettingsLock) {
+            synchronized (VolumeStreamState.class) {
+                for (int stream = 0; stream < mStreamStates.length; stream++) {
+                    if (stream != skipStream) {
+                        int devices = mStreamStates[stream].observeDevicesForStream_syncVSS(
+                                false /*checkOthers*/);
+
+                        Set<Integer> devicesSet = AudioSystem.generateAudioDeviceTypesSet(devices);
+                        for (Integer device : devicesSet) {
+                            // Update volume states for devices routed for the stream
+                            updateVolumeStates(device, stream,
+                                    "AudioService#onObserveDevicesForAllStreams");
+                        }
+                    }
                 }
             }
         }
@@ -4917,14 +4990,16 @@
     /** only public for mocking/spying, do not call outside of AudioService */
     @VisibleForTesting
     public void postObserveDevicesForAllStreams() {
-        sendMsg(mAudioHandler,
-                MSG_OBSERVE_DEVICES_FOR_ALL_STREAMS,
-                SENDMSG_QUEUE, 0 /*arg1*/, 0 /*arg2*/, null /*obj*/,
-                0 /*delay*/);
+        postObserveDevicesForAllStreams(-1);
     }
 
-    private void onObserveDevicesForAllStreams() {
-        observeDevicesForStreams(-1);
+    /** only public for mocking/spying, do not call outside of AudioService */
+    @VisibleForTesting
+    public void postObserveDevicesForAllStreams(int skipStream) {
+        sendMsg(mAudioHandler,
+                MSG_OBSERVE_DEVICES_FOR_ALL_STREAMS,
+                SENDMSG_QUEUE, skipStream /*arg1*/, 0 /*arg2*/, null /*obj*/,
+                0 /*delay*/);
     }
 
     /**
@@ -4952,11 +5027,6 @@
 
     private void setDeviceVolumeBehaviorInternal(int audioSystemDeviceOut,
             @AudioManager.DeviceVolumeBehavior int deviceVolumeBehavior, @NonNull String caller) {
-        if (!sDeviceVolumeBehaviorSupportedDeviceOutSet.contains(audioSystemDeviceOut)) {
-            // unsupported for now
-            throw new IllegalArgumentException("Unsupported device type " + audioSystemDeviceOut);
-        }
-
         // update device masks based on volume behavior
         switch (deviceVolumeBehavior) {
             case AudioManager.DEVICE_VOLUME_BEHAVIOR_VARIABLE:
@@ -4982,7 +5052,8 @@
                       + Integer.toHexString(audioSystemDeviceOut) + " from:" + caller));
         // make sure we have a volume entry for this device, and that volume is updated according
         // to volume behavior
-        checkAddAllFixedVolumeDevices(audioSystemDeviceOut, "setDeviceVolumeBehavior:" + caller);
+        postUpdateVolumeStatesForAudioDevice(audioSystemDeviceOut,
+                "setDeviceVolumeBehavior:" + caller);
     }
 
     /**
@@ -4990,20 +5061,14 @@
      * @param device the audio output device type
      * @return the volume behavior for the device
      */
-    public @AudioManager.DeviceVolumeBehaviorState int getDeviceVolumeBehavior(
-            @NonNull AudioDeviceAttributes device) {
+    public @AudioManager.DeviceVolumeBehavior
+    int getDeviceVolumeBehavior(@NonNull AudioDeviceAttributes device) {
         // verify permissions
         enforceModifyAudioRoutingPermission();
 
         // translate Java device type to native device type (for the devices masks for full / fixed)
         final int audioSystemDeviceOut = AudioDeviceInfo.convertDeviceTypeToInternalDevice(
                 device.getType());
-        if (!sDeviceVolumeBehaviorSupportedDeviceOutSet.contains(audioSystemDeviceOut)
-                && audioSystemDeviceOut != AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP
-                && audioSystemDeviceOut != AudioSystem.DEVICE_OUT_HEARING_AID) {
-            throw new IllegalArgumentException("Unsupported volume behavior "
-                    + audioSystemDeviceOut);
-        }
 
         int setDeviceVolumeBehavior = retrieveStoredDeviceVolumeBehavior(audioSystemDeviceOut);
         if (setDeviceVolumeBehavior != AudioManager.DEVICE_VOLUME_BEHAVIOR_UNSET) {
@@ -5619,6 +5684,7 @@
             }
         }
 
+        @GuardedBy("VolumeStreamState.class")
         public int observeDevicesForStream_syncVSS(boolean checkOthers) {
             if (!mSystemServer.isPrivileged()) {
                 return AudioSystem.DEVICE_NONE;
@@ -5631,15 +5697,19 @@
             mObservedDevices = devices;
             if (checkOthers) {
                 // one stream's devices have changed, check the others
-                observeDevicesForStreams(mStreamType);
+                postObserveDevicesForAllStreams(mStreamType);
             }
             // log base stream changes to the event log
             if (mStreamVolumeAlias[mStreamType] == mStreamType) {
                 EventLogTags.writeStreamDevicesChanged(mStreamType, prevDevices, devices);
             }
-            sendBroadcastToAll(mStreamDevicesChanged
-                    .putExtra(AudioManager.EXTRA_PREV_VOLUME_STREAM_DEVICES, prevDevices)
-                    .putExtra(AudioManager.EXTRA_VOLUME_STREAM_DEVICES, devices));
+            // send STREAM_DEVICES_CHANGED_ACTION on the message handler so it is scheduled after
+            // the postObserveDevicesForStreams is handled
+            sendMsg(mAudioHandler,
+                    MSG_STREAM_DEVICES_CHANGED,
+                    SENDMSG_QUEUE, prevDevices /*arg1*/, devices /*arg2*/,
+                    // ok to send reference to this object, it is final
+                    mStreamDevicesChanged /*obj*/, 0 /*delay*/);
             return devices;
         }
 
@@ -6411,7 +6481,7 @@
                     break;
 
                 case MSG_OBSERVE_DEVICES_FOR_ALL_STREAMS:
-                    onObserveDevicesForAllStreams();
+                    onObserveDevicesForAllStreams(/*skipStream*/ msg.arg1);
                     break;
 
                 case MSG_HDMI_VOLUME_CHECK:
@@ -6454,6 +6524,16 @@
                         mModeLogger.log(new PhoneStateEvent(h.getPackage(), h.getPid()));
                     }
                     break;
+
+                case MSG_STREAM_DEVICES_CHANGED:
+                    sendBroadcastToAll(((Intent) msg.obj)
+                            .putExtra(AudioManager.EXTRA_PREV_VOLUME_STREAM_DEVICES, msg.arg1)
+                            .putExtra(AudioManager.EXTRA_VOLUME_STREAM_DEVICES, msg.arg2));
+                    break;
+
+                case MSG_UPDATE_VOLUME_STATES_FOR_DEVICE:
+                    onUpdateVolumeStatesForAudioDevice(msg.arg1, (String) msg.obj);
+                    break;
             }
         }
     }
@@ -7210,10 +7290,9 @@
                 // HDMI output
                 removeAudioSystemDeviceOutFromFullVolumeDevices(AudioSystem.DEVICE_OUT_HDMI);
             }
+            postUpdateVolumeStatesForAudioDevice(AudioSystem.DEVICE_OUT_HDMI,
+                    "HdmiPlaybackClient.DisplayStatusCallback");
         }
-
-        checkAddAllFixedVolumeDevices(AudioSystem.DEVICE_OUT_HDMI,
-                "HdmiPlaybackClient.DisplayStatusCallback");
     }
 
     private class MyHdmiControlStatusChangeListenerCallback
@@ -7499,6 +7578,7 @@
         pw.print("  mIsSingleVolume="); pw.println(mIsSingleVolume);
         pw.print("  mUseFixedVolume="); pw.println(mUseFixedVolume);
         pw.print("  mFixedVolumeDevices="); pw.println(dumpDeviceTypes(mFixedVolumeDevices));
+        pw.print("  mFullVolumeDevices="); pw.println(dumpDeviceTypes(mFullVolumeDevices));
         pw.print("  mExtVolumeController="); pw.println(mExtVolumeController);
         pw.print("  mHdmiCecSink="); pw.println(mHdmiCecSink);
         pw.print("  mHdmiAudioSystemClient="); pw.println(mHdmiAudioSystemClient);
@@ -7920,9 +8000,6 @@
             return null;
         }
 
-        mDynPolicyLogger.log((new AudioEventLogger.StringEvent("registerAudioPolicy for "
-                + pcb.asBinder() + " with config:" + policyConfig)).printLog(TAG));
-
         String regId = null;
         synchronized (mAudioPolicies) {
             if (mAudioPolicies.containsKey(pcb.asBinder())) {
@@ -7933,6 +8010,13 @@
                 AudioPolicyProxy app = new AudioPolicyProxy(policyConfig, pcb, hasFocusListener,
                         isFocusPolicy, isTestFocusPolicy, isVolumeController, projection);
                 pcb.asBinder().linkToDeath(app, 0/*flags*/);
+
+                // logging after registration so we have the registration id
+                mDynPolicyLogger.log((new AudioEventLogger.StringEvent("registerAudioPolicy for "
+                        + pcb.asBinder() + " u/pid:" + Binder.getCallingUid() + "/"
+                        + Binder.getCallingPid() + " with config:" + app.toCompactLogString()))
+                        .printLog(TAG));
+
                 regId = app.getRegistrationId();
                 mAudioPolicies.put(pcb.asBinder(), app);
             } catch (RemoteException e) {
@@ -8096,7 +8180,10 @@
      * @param pcb nullable because on service interface
      */
     public void unregisterAudioPolicyAsync(@Nullable IAudioPolicyCallback pcb) {
-        unregisterAudioPolicy(pcb);
+        if (pcb == null) {
+            return;
+        }
+        unregisterAudioPolicyInt(pcb, "unregisterAudioPolicyAsync");
     }
 
     /**
@@ -8107,12 +8194,12 @@
         if (pcb == null) {
             return;
         }
-        unregisterAudioPolicyInt(pcb);
+        unregisterAudioPolicyInt(pcb, "unregisterAudioPolicy");
     }
 
 
-    private void unregisterAudioPolicyInt(@NonNull IAudioPolicyCallback pcb) {
-        mDynPolicyLogger.log((new AudioEventLogger.StringEvent("unregisterAudioPolicyAsync for "
+    private void unregisterAudioPolicyInt(@NonNull IAudioPolicyCallback pcb, String operationName) {
+        mDynPolicyLogger.log((new AudioEventLogger.StringEvent(operationName + " for "
                 + pcb.asBinder()).printLog(TAG)));
         synchronized (mAudioPolicies) {
             AudioPolicyProxy app = mAudioPolicies.remove(pcb.asBinder());
@@ -8587,7 +8674,8 @@
         }
 
         public void binderDied() {
-            Log.i(TAG, "audio policy " + mPolicyCallback + " died");
+            mDynPolicyLogger.log((new AudioEventLogger.StringEvent("AudioPolicy "
+                    + mPolicyCallback.asBinder() + " died").printLog(TAG)));
             release();
         }
 
@@ -9080,7 +9168,7 @@
     }
 
     private void restoreDeviceVolumeBehavior() {
-        for (int deviceType : sDeviceVolumeBehaviorSupportedDeviceOutSet) {
+        for (int deviceType : AudioSystem.DEVICE_OUT_ALL_SET) {
             if (DEBUG_VOL) {
                 Log.d(TAG, "Retrieving Volume Behavior for DeviceType: " + deviceType);
             }
diff --git a/services/core/java/com/android/server/audio/AudioSystemAdapter.java b/services/core/java/com/android/server/audio/AudioSystemAdapter.java
index e60243f..a0e1ca7 100644
--- a/services/core/java/com/android/server/audio/AudioSystemAdapter.java
+++ b/services/core/java/com/android/server/audio/AudioSystemAdapter.java
@@ -20,6 +20,8 @@
 import android.media.AudioDeviceAttributes;
 import android.media.AudioSystem;
 
+import java.util.List;
+
 /**
  * Provides an adapter to access functionality of the android.media.AudioSystem class for device
  * related functionality.
@@ -77,22 +79,25 @@
     }
 
     /**
-     * Same as {@link AudioSystem#setPreferredDeviceForStrategy(int, AudioDeviceAttributes)}
+     * Same as {@link AudioSystem#setDevicesRoleForStrategy(int, int, List)}
      * @param strategy
-     * @param device
+     * @param role
+     * @param devices
      * @return
      */
-    public int setPreferredDeviceForStrategy(int strategy, @NonNull AudioDeviceAttributes device) {
-        return AudioSystem.setPreferredDeviceForStrategy(strategy, device);
+    public int setDevicesRoleForStrategy(int strategy, int role,
+                                         @NonNull List<AudioDeviceAttributes> devices) {
+        return AudioSystem.setDevicesRoleForStrategy(strategy, role, devices);
     }
 
     /**
-     * Same as {@link AudioSystem#removePreferredDeviceForStrategy(int)}
+     * Same as {@link AudioSystem#removeDevicesRoleForStrategy(int, int)}
      * @param strategy
+     * @param role
      * @return
      */
-    public int removePreferredDeviceForStrategy(int strategy) {
-        return AudioSystem.removePreferredDeviceForStrategy(strategy);
+    public int removeDevicesRoleForStrategy(int strategy, int role) {
+        return AudioSystem.removeDevicesRoleForStrategy(strategy, role);
     }
 
     /**
diff --git a/services/core/java/com/android/server/audio/BtHelper.java b/services/core/java/com/android/server/audio/BtHelper.java
index 5e8f1ef..ded0f9a3 100644
--- a/services/core/java/com/android/server/audio/BtHelper.java
+++ b/services/core/java/com/android/server/audio/BtHelper.java
@@ -432,19 +432,35 @@
         // and this must be done on behalf of system server to make sure permissions are granted.
         final long ident = Binder.clearCallingIdentity();
         if (client != null) {
-            AudioService.sDeviceLogger.log(new AudioEventLogger.StringEvent(eventSource));
-            client.requestScoState(BluetoothHeadset.STATE_AUDIO_DISCONNECTED,
-                    SCO_MODE_VIRTUAL_CALL);
-            // If a disconnection is pending, the client will be removed whne clearAllScoClients()
-            // is called form receiveBtEvent()
-            if (mScoAudioState != SCO_STATE_DEACTIVATE_REQ
-                    && mScoAudioState != SCO_STATE_DEACTIVATING) {
-                client.remove(false /*stop */, true /*unregister*/);
-            }
+            stopAndRemoveClient(client, eventSource);
         }
         Binder.restoreCallingIdentity(ident);
     }
 
+    // @GuardedBy("AudioDeviceBroker.mSetModeLock")
+    @GuardedBy("AudioDeviceBroker.mDeviceStateLock")
+    /*package*/ synchronized void stopBluetoothScoForPid(int pid) {
+        ScoClient client = getScoClientForPid(pid);
+        if (client == null) {
+            return;
+        }
+        final String eventSource = new StringBuilder("stopBluetoothScoForPid(")
+                .append(pid).append(")").toString();
+        stopAndRemoveClient(client, eventSource);
+    }
+
+    @GuardedBy("AudioDeviceBroker.mDeviceStateLock")
+    private void stopAndRemoveClient(ScoClient client, @NonNull String eventSource) {
+        AudioService.sDeviceLogger.log(new AudioEventLogger.StringEvent(eventSource));
+        client.requestScoState(BluetoothHeadset.STATE_AUDIO_DISCONNECTED,
+                SCO_MODE_VIRTUAL_CALL);
+        // If a disconnection is pending, the client will be removed when clearAllScoClients()
+        // is called form receiveBtEvent()
+        if (mScoAudioState != SCO_STATE_DEACTIVATE_REQ
+                && mScoAudioState != SCO_STATE_DEACTIVATING) {
+            client.remove(false /*stop */, true /*unregister*/);
+        }
+    }
 
     /*package*/ synchronized void setHearingAidVolume(int index, int streamType) {
         if (mHearingAid == null) {
@@ -982,6 +998,16 @@
         return null;
     }
 
+    @GuardedBy("BtHelper.this")
+    private ScoClient getScoClientForPid(int pid) {
+        for (ScoClient cl : mScoClients) {
+            if (cl.getPid() == pid) {
+                return cl;
+            }
+        }
+        return null;
+    }
+
     // @GuardedBy("AudioDeviceBroker.mSetModeLock")
     //@GuardedBy("AudioDeviceBroker.mDeviceStateLock")
     @GuardedBy("BtHelper.this")
diff --git a/services/core/java/com/android/server/backup/SystemBackupAgent.java b/services/core/java/com/android/server/backup/SystemBackupAgent.java
index 0a30b76..d98298c 100644
--- a/services/core/java/com/android/server/backup/SystemBackupAgent.java
+++ b/services/core/java/com/android/server/backup/SystemBackupAgent.java
@@ -20,6 +20,7 @@
 import android.app.backup.BackupAgentHelper;
 import android.app.backup.BackupDataInput;
 import android.app.backup.BackupHelper;
+import android.app.backup.BackupManager;
 import android.app.backup.FullBackup;
 import android.app.backup.FullBackupDataOutput;
 import android.app.backup.WallpaperBackupHelper;
@@ -87,8 +88,8 @@
     private int mUserId = UserHandle.USER_SYSTEM;
 
     @Override
-    public void onCreate(UserHandle user) {
-        super.onCreate(user);
+    public void onCreate(UserHandle user, @BackupManager.OperationType int operationType) {
+        super.onCreate(user, operationType);
 
         mUserId = user.getIdentifier();
 
diff --git a/services/core/java/com/android/server/biometrics/AuthService.java b/services/core/java/com/android/server/biometrics/AuthService.java
index a47904c..54c1790 100644
--- a/services/core/java/com/android/server/biometrics/AuthService.java
+++ b/services/core/java/com/android/server/biometrics/AuthService.java
@@ -259,17 +259,6 @@
         }
 
         @Override
-        public void resetLockout(int userId, byte[] hardwareAuthToken) throws RemoteException {
-            checkInternalPermission();
-            final long identity = Binder.clearCallingIdentity();
-            try {
-                mBiometricService.resetLockout(userId, hardwareAuthToken);
-            } finally {
-                Binder.restoreCallingIdentity(identity);
-            }
-        }
-
-        @Override
         public long[] getAuthenticatorIds() throws RemoteException {
             // In this method, we're not checking whether the caller is permitted to use face
             // API because current authenticator ID is leaked (in a more contrived way) via Android
diff --git a/services/core/java/com/android/server/biometrics/BiometricService.java b/services/core/java/com/android/server/biometrics/BiometricService.java
index 382bdbb..3e0a40f 100644
--- a/services/core/java/com/android/server/biometrics/BiometricService.java
+++ b/services/core/java/com/android/server/biometrics/BiometricService.java
@@ -638,19 +638,6 @@
         }
 
         @Override // Binder call
-        public void resetLockout(int userId, byte[] hardwareAuthToken) {
-            checkInternalPermission();
-
-            try {
-                for (BiometricSensor sensor : mSensors) {
-                    sensor.impl.resetLockout(userId, hardwareAuthToken);
-                }
-            } catch (RemoteException e) {
-                Slog.e(TAG, "Remote exception", e);
-            }
-        }
-
-        @Override // Binder call
         public long[] getAuthenticatorIds(int callingUserId) {
             checkInternalPermission();
 
diff --git a/services/core/java/com/android/server/biometrics/sensors/AcquisitionClient.java b/services/core/java/com/android/server/biometrics/sensors/AcquisitionClient.java
index ab48fdb..73fc17a 100644
--- a/services/core/java/com/android/server/biometrics/sensors/AcquisitionClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/AcquisitionClient.java
@@ -45,7 +45,7 @@
     private final PowerManager mPowerManager;
     private final VibrationEffect mSuccessVibrationEffect;
     private final VibrationEffect mErrorVibrationEffect;
-    private boolean mShouldSendErrorToClient;
+    private boolean mShouldSendErrorToClient = true;
     private boolean mAlreadyCancelled;
 
     /**
@@ -85,11 +85,11 @@
         // case (success, failure, or error) is received from the HAL (e.g. versions of fingerprint
         // that do not handle lockout under the HAL. In these cases, ensure that the framework only
         // sends errors once per ClientMonitor.
-        if (!mShouldSendErrorToClient) {
+        if (mShouldSendErrorToClient) {
             logOnError(getContext(), errorCode, vendorCode, getTargetUserId());
             try {
                 if (getListener() != null) {
-                    mShouldSendErrorToClient = true;
+                    mShouldSendErrorToClient = false;
                     getListener().onError(getSensorId(), getCookie(), errorCode, vendorCode);
                 }
             } catch (RemoteException e) {
@@ -98,7 +98,7 @@
         }
 
         if (finish) {
-            mFinishCallback.onClientFinished(this, false /* success */);
+            mCallback.onClientFinished(this, false /* success */);
         }
     }
 
@@ -114,7 +114,7 @@
     }
 
     @Override
-    public void cancelWithoutStarting(@NonNull FinishCallback finishCallback) {
+    public void cancelWithoutStarting(@NonNull Callback callback) {
         final int errorCode = BiometricConstants.BIOMETRIC_ERROR_CANCELED;
         try {
             if (getListener() != null) {
@@ -123,7 +123,7 @@
         } catch (RemoteException e) {
             Slog.w(TAG, "Failed to invoke sendError", e);
         }
-        finishCallback.onClientFinished(this, true /* success */);
+        callback.onClientFinished(this, true /* success */);
     }
 
     /**
@@ -155,7 +155,7 @@
             }
         } catch (RemoteException e) {
             Slog.w(TAG, "Failed to invoke sendAcquired", e);
-            mFinishCallback.onClientFinished(this, false /* success */);
+            mCallback.onClientFinished(this, false /* success */);
         }
     }
 
diff --git a/services/core/java/com/android/server/biometrics/sensors/AuthenticationClient.java b/services/core/java/com/android/server/biometrics/sensors/AuthenticationClient.java
index cb2321f..9128359 100644
--- a/services/core/java/com/android/server/biometrics/sensors/AuthenticationClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/AuthenticationClient.java
@@ -99,6 +99,14 @@
         return getCookie() != 0;
     }
 
+    public long getOperationId() {
+        return mOperationId;
+    }
+
+    public boolean isRestricted() {
+        return mIsRestricted;
+    }
+
     @Override
     protected boolean isCryptoOperation() {
         return mOperationId != 0;
@@ -191,7 +199,7 @@
             }
         } catch (RemoteException e) {
             Slog.e(TAG, "Unable to notify listener, finishing", e);
-            mFinishCallback.onClientFinished(this, false /* success */);
+            mCallback.onClientFinished(this, false /* success */);
         }
     }
 
@@ -211,8 +219,8 @@
      * Start authentication
      */
     @Override
-    public void start(@NonNull FinishCallback finishCallback) {
-        super.start(finishCallback);
+    public void start(@NonNull Callback callback) {
+        super.start(callback);
 
         final @LockoutTracker.LockoutMode int lockoutMode =
                 mLockoutTracker.getLockoutModeForUser(getTargetUserId());
diff --git a/services/core/java/com/android/server/biometrics/sensors/BiometricScheduler.java b/services/core/java/com/android/server/biometrics/sensors/BiometricScheduler.java
index 4f37dcc..588e865 100644
--- a/services/core/java/com/android/server/biometrics/sensors/BiometricScheduler.java
+++ b/services/core/java/com/android/server/biometrics/sensors/BiometricScheduler.java
@@ -85,7 +85,7 @@
         static final int STATE_WAITING_FOR_COOKIE = 4;
 
         /**
-         * The {@link ClientMonitor.FinishCallback} has been invoked and the client is finished.
+         * The {@link ClientMonitor.Callback} has been invoked and the client is finished.
          */
         static final int STATE_FINISHED = 5;
 
@@ -99,13 +99,13 @@
         @interface OperationState {}
 
         @NonNull final ClientMonitor<?> clientMonitor;
-        @Nullable final ClientMonitor.FinishCallback clientFinishCallback;
+        @Nullable final ClientMonitor.Callback mClientCallback;
         @OperationState int state;
 
         Operation(@NonNull ClientMonitor<?> clientMonitor,
-                @Nullable ClientMonitor.FinishCallback finishCallback) {
+                @Nullable ClientMonitor.Callback callback) {
             this.clientMonitor = clientMonitor;
-            this.clientFinishCallback = finishCallback;
+            this.mClientCallback = callback;
             state = STATE_WAITING_IN_QUEUE;
         }
 
@@ -133,7 +133,7 @@
         public void run() {
             if (operation.state != Operation.STATE_FINISHED) {
                 Slog.e(tag, "[Watchdog Triggered]: " + operation);
-                operation.clientMonitor.mFinishCallback
+                operation.clientMonitor.mCallback
                         .onClientFinished(operation.clientMonitor, false /* success */);
             }
         }
@@ -175,17 +175,25 @@
     @Nullable private final GestureAvailabilityDispatcher mGestureAvailabilityDispatcher;
     @NonNull private final IBiometricService mBiometricService;
     @NonNull private final Handler mHandler = new Handler(Looper.getMainLooper());
-    @NonNull private final InternalFinishCallback mInternalFinishCallback;
+    @NonNull private final InternalCallback mInternalCallback;
     @NonNull private final Queue<Operation> mPendingOperations;
     @Nullable private Operation mCurrentOperation;
     @NonNull private final ArrayDeque<CrashState> mCrashStates;
 
-    // Internal finish callback, notified when an operation is complete. Notifies the requester
+    // Internal callback, notified when an operation is complete. Notifies the requester
     // that the operation is complete, before performing internal scheduler work (such as
     // starting the next client).
-    private class InternalFinishCallback implements ClientMonitor.FinishCallback {
+    public class InternalCallback implements ClientMonitor.Callback {
         @Override
-        public void onClientFinished(ClientMonitor<?> clientMonitor, boolean success) {
+        public void onClientStarted(@NonNull ClientMonitor<?> clientMonitor) {
+            Slog.d(getTag(), "[Started] " + clientMonitor);
+            if (mCurrentOperation.mClientCallback != null) {
+                mCurrentOperation.mClientCallback.onClientStarted(clientMonitor);
+            }
+        }
+
+        @Override
+        public void onClientFinished(@NonNull ClientMonitor<?> clientMonitor, boolean success) {
             mHandler.post(() -> {
                 if (mCurrentOperation == null) {
                     Slog.e(getTag(), "[Finishing] " + clientMonitor
@@ -203,8 +211,8 @@
                 Slog.d(getTag(), "[Finishing] " + clientMonitor + ", success: " + success);
                 mCurrentOperation.state = Operation.STATE_FINISHED;
 
-                if (mCurrentOperation.clientFinishCallback != null) {
-                    mCurrentOperation.clientFinishCallback.onClientFinished(clientMonitor, success);
+                if (mCurrentOperation.mClientCallback != null) {
+                    mCurrentOperation.mClientCallback.onClientFinished(clientMonitor, success);
                 }
 
                 if (mGestureAvailabilityDispatcher != null) {
@@ -227,7 +235,7 @@
     public BiometricScheduler(@NonNull String tag,
             @Nullable GestureAvailabilityDispatcher gestureAvailabilityDispatcher) {
         mBiometricTag = tag;
-        mInternalFinishCallback = new InternalFinishCallback();
+        mInternalCallback = new InternalCallback();
         mGestureAvailabilityDispatcher = gestureAvailabilityDispatcher;
         mPendingOperations = new ArrayDeque<>();
         mBiometricService = IBiometricService.Stub.asInterface(
@@ -235,6 +243,14 @@
         mCrashStates = new ArrayDeque<>();
     }
 
+    /**
+     * @return A reference to the internal callback that should be invoked whenever the scheduler
+     *         needs to (e.g. client started, client finished).
+     */
+    @NonNull protected InternalCallback getInternalCallback() {
+        return mInternalCallback;
+    }
+
     private String getTag() {
         return BASE_TAG + "/" + mBiometricTag;
     }
@@ -262,7 +278,7 @@
             }
 
             final Interruptable interruptable = (Interruptable) currentClient;
-            interruptable.cancelWithoutStarting(mInternalFinishCallback);
+            interruptable.cancelWithoutStarting(getInternalCallback());
             // Now we wait for the client to send its FinishCallback, which kicks off the next
             // operation.
             return;
@@ -280,7 +296,7 @@
         final boolean shouldStartNow = currentClient.getCookie() == 0;
         if (shouldStartNow) {
             Slog.d(getTag(), "[Starting] " + mCurrentOperation);
-            currentClient.start(mInternalFinishCallback);
+            currentClient.start(getInternalCallback());
             mCurrentOperation.state = Operation.STATE_STARTED;
         } else {
             try {
@@ -324,7 +340,7 @@
 
         Slog.d(getTag(), "[Starting] Prepared client: " + mCurrentOperation);
         mCurrentOperation.state = Operation.STATE_STARTED;
-        mCurrentOperation.clientMonitor.start(mInternalFinishCallback);
+        mCurrentOperation.clientMonitor.start(getInternalCallback());
     }
 
     /**
@@ -340,11 +356,11 @@
      * Adds a {@link ClientMonitor} to the pending queue
      *
      * @param clientMonitor        operation to be scheduled
-     * @param clientFinishCallback optional callback, invoked when the client is finished, but
+     * @param clientCallback optional callback, invoked when the client is finished, but
      *                             before it has been removed from the queue.
      */
     public void scheduleClientMonitor(@NonNull ClientMonitor<?> clientMonitor,
-            @Nullable ClientMonitor.FinishCallback clientFinishCallback) {
+            @Nullable ClientMonitor.Callback clientCallback) {
         // Mark any interruptable pending clients as canceling. Once they reach the head of the
         // queue, the scheduler will send ERROR_CANCELED and skip the operation.
         for (Operation operation : mPendingOperations) {
@@ -356,7 +372,7 @@
             }
         }
 
-        mPendingOperations.add(new Operation(clientMonitor, clientFinishCallback));
+        mPendingOperations.add(new Operation(clientMonitor, clientCallback));
         Slog.d(getTag(), "[Added] " + clientMonitor
                 + ", new queue size: " + mPendingOperations.size());
 
diff --git a/services/core/java/com/android/server/biometrics/sensors/ClientMonitor.java b/services/core/java/com/android/server/biometrics/sensors/ClientMonitor.java
index 8b27781..dec40e3 100644
--- a/services/core/java/com/android/server/biometrics/sensors/ClientMonitor.java
+++ b/services/core/java/com/android/server/biometrics/sensors/ClientMonitor.java
@@ -42,7 +42,15 @@
     /**
      * Interface that ClientMonitor holders should use to receive callbacks.
      */
-    public interface FinishCallback {
+    public interface Callback {
+        /**
+         * Invoked when the ClientMonitor operation has been started (e.g. reached the head of
+         * the queue and becomes the current operation).
+         *
+         * @param clientMonitor Reference of the ClientMonitor that is starting.
+         */
+        default void onClientStarted(@NonNull ClientMonitor<?> clientMonitor) {}
+
         /**
          * Invoked when the ClientMonitor operation is complete. This abstracts away asynchronous
          * (i.e. Authenticate, Enroll, Enumerate, Remove) and synchronous (i.e. generateChallenge,
@@ -52,7 +60,7 @@
          * @param clientMonitor Reference of the ClientMonitor that finished.
          * @param success True if the operation completed successfully.
          */
-        void onClientFinished(ClientMonitor<?> clientMonitor, boolean success);
+        default void onClientFinished(@NonNull ClientMonitor<?> clientMonitor, boolean success) {}
     }
 
     /**
@@ -79,7 +87,7 @@
     private final int mCookie;
     boolean mAlreadyDone;
 
-    @NonNull protected FinishCallback mFinishCallback;
+    @NonNull protected Callback mCallback;
 
     /**
      * @param context    system_server context
@@ -125,17 +133,18 @@
     /**
      * Invoked if the scheduler is unable to start the ClientMonitor (for example the HAL is null).
      * If such a problem is detected, the scheduler will not invoke
-     * {@link #start(FinishCallback)}.
+     * {@link #start(Callback)}.
      */
     public abstract void unableToStart();
 
     /**
      * Starts the ClientMonitor's lifecycle. Invokes {@link #startHalOperation()} when internal book
      * keeping is complete.
-     * @param finishCallback invoked when the operation is complete (succeeds, fails, etc)
+     * @param callback invoked when the operation is complete (succeeds, fails, etc)
      */
-    public void start(@NonNull FinishCallback finishCallback) {
-        mFinishCallback = finishCallback;
+    public void start(@NonNull Callback callback) {
+        mCallback = callback;
+        mCallback.onClientStarted(this);
     }
 
     /**
diff --git a/services/core/java/com/android/server/biometrics/sensors/ClientMonitorCallbackConverter.java b/services/core/java/com/android/server/biometrics/sensors/ClientMonitorCallbackConverter.java
index 3cef666..cb7db92 100644
--- a/services/core/java/com/android/server/biometrics/sensors/ClientMonitorCallbackConverter.java
+++ b/services/core/java/com/android/server/biometrics/sensors/ClientMonitorCallbackConverter.java
@@ -128,11 +128,11 @@
         }
     }
 
-    public void onChallengeGenerated(long challenge) throws RemoteException {
+    public void onChallengeGenerated(int sensorId, long challenge) throws RemoteException {
         if (mFaceServiceReceiver != null) {
-            mFaceServiceReceiver.onChallengeGenerated(challenge);
+            mFaceServiceReceiver.onChallengeGenerated(sensorId, challenge);
         } else if (mFingerprintServiceReceiver != null) {
-            mFingerprintServiceReceiver.onChallengeGenerated(challenge);
+            mFingerprintServiceReceiver.onChallengeGenerated(sensorId, challenge);
         }
     }
 
@@ -142,10 +142,21 @@
         }
     }
 
-    public void onFeatureGet(boolean success, int feature, boolean value)
-            throws RemoteException {
+    public void onFeatureGet(boolean success, int feature, boolean value) throws RemoteException {
         if (mFaceServiceReceiver != null) {
             mFaceServiceReceiver.onFeatureGet(success, feature, value);
         }
     }
+
+    public void onChallengeInterrupted(int sensorId) throws RemoteException {
+        if (mFaceServiceReceiver != null) {
+            mFaceServiceReceiver.onChallengeInterrupted(sensorId);
+        }
+    }
+
+    public void onChallengeInterruptFinished(int sensorId) throws RemoteException {
+        if (mFaceServiceReceiver != null) {
+            mFaceServiceReceiver.onChallengeInterruptFinished(sensorId);
+        }
+    }
 }
diff --git a/services/core/java/com/android/server/biometrics/sensors/EnrollClient.java b/services/core/java/com/android/server/biometrics/sensors/EnrollClient.java
index add5829..8bf9680 100644
--- a/services/core/java/com/android/server/biometrics/sensors/EnrollClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/EnrollClient.java
@@ -77,18 +77,18 @@
             mBiometricUtils.addBiometricForUser(getContext(), getTargetUserId(), identifier);
             logOnEnrolled(getTargetUserId(), System.currentTimeMillis() - mEnrollmentStartTimeMs,
                     true /* enrollSuccessful */);
-            mFinishCallback.onClientFinished(this, true /* success */);
+            mCallback.onClientFinished(this, true /* success */);
         }
         notifyUserActivity();
     }
 
     @Override
-    public void start(@NonNull FinishCallback finishCallback) {
-        super.start(finishCallback);
+    public void start(@NonNull Callback callback) {
+        super.start(callback);
 
         if (hasReachedEnrollmentLimit()) {
             Slog.e(TAG, "Reached enrollment limit");
-            finishCallback.onClientFinished(this, false /* success */);
+            callback.onClientFinished(this, false /* success */);
             return;
         }
 
diff --git a/services/core/java/com/android/server/biometrics/sensors/GenerateChallengeClient.java b/services/core/java/com/android/server/biometrics/sensors/GenerateChallengeClient.java
index dad5cad..92c498c 100644
--- a/services/core/java/com/android/server/biometrics/sensors/GenerateChallengeClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/GenerateChallengeClient.java
@@ -40,23 +40,23 @@
     @Override
     public void unableToStart() {
         try {
-            getListener().onChallengeGenerated(0L);
+            getListener().onChallengeGenerated(getSensorId(), 0L);
         } catch (RemoteException e) {
             Slog.e(TAG, "Unable to send error", e);
         }
     }
 
     @Override
-    public void start(@NonNull FinishCallback finishCallback) {
-        super.start(finishCallback);
+    public void start(@NonNull Callback callback) {
+        super.start(callback);
 
         startHalOperation();
         try {
-            getListener().onChallengeGenerated(mChallenge);
-            mFinishCallback.onClientFinished(this, true /* success */);
+            getListener().onChallengeGenerated(getSensorId(), mChallenge);
+            mCallback.onClientFinished(this, true /* success */);
         } catch (RemoteException e) {
             Slog.e(TAG, "Remote exception", e);
-            mFinishCallback.onClientFinished(this, false /* success */);
+            mCallback.onClientFinished(this, false /* success */);
         }
     }
 }
diff --git a/services/core/java/com/android/server/biometrics/sensors/InternalCleanupClient.java b/services/core/java/com/android/server/biometrics/sensors/InternalCleanupClient.java
index 6d7b0fd..5c08bce 100644
--- a/services/core/java/com/android/server/biometrics/sensors/InternalCleanupClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/InternalCleanupClient.java
@@ -62,29 +62,35 @@
     private final List<S> mEnrolledList;
     private ClientMonitor<T> mCurrentTask;
 
-    private final FinishCallback mEnumerateFinishCallback = (clientMonitor, success) -> {
-        final List<BiometricAuthenticator.Identifier> unknownHALTemplates =
-                ((InternalEnumerateClient<T>) mCurrentTask).getUnknownHALTemplates();
+    private final Callback mEnumerateCallback = new Callback() {
+        @Override
+        public void onClientFinished(@NonNull ClientMonitor<?> clientMonitor, boolean success) {
+            final List<BiometricAuthenticator.Identifier> unknownHALTemplates =
+                    ((InternalEnumerateClient<T>) mCurrentTask).getUnknownHALTemplates();
 
-        if (!unknownHALTemplates.isEmpty()) {
-            Slog.w(TAG, "Adding " + unknownHALTemplates.size() + " templates for deletion");
-        }
-        for (BiometricAuthenticator.Identifier unknownHALTemplate : unknownHALTemplates) {
-            mUnknownHALTemplates.add(new UserTemplate(unknownHALTemplate,
-                    mCurrentTask.getTargetUserId()));
-        }
+            if (!unknownHALTemplates.isEmpty()) {
+                Slog.w(TAG, "Adding " + unknownHALTemplates.size() + " templates for deletion");
+            }
+            for (BiometricAuthenticator.Identifier unknownHALTemplate : unknownHALTemplates) {
+                mUnknownHALTemplates.add(new UserTemplate(unknownHALTemplate,
+                        mCurrentTask.getTargetUserId()));
+            }
 
-        if (mUnknownHALTemplates.isEmpty()) {
-            // No unknown HAL templates. Unknown framework templates are already cleaned up in
-            // InternalEnumerateClient. Finish this client.
-            mFinishCallback.onClientFinished(this, success);
-        } else {
-            startCleanupUnknownHalTemplates();
+            if (mUnknownHALTemplates.isEmpty()) {
+                // No unknown HAL templates. Unknown framework templates are already cleaned up in
+                // InternalEnumerateClient. Finish this client.
+                mCallback.onClientFinished(InternalCleanupClient.this, success);
+            } else {
+                startCleanupUnknownHalTemplates();
+            }
         }
     };
 
-    private final FinishCallback mRemoveFinishCallback = (clientMonitor, success) -> {
-        mFinishCallback.onClientFinished(this, success);
+    private final Callback mRemoveCallback = new Callback() {
+        @Override
+        public void onClientFinished(@NonNull ClientMonitor<?> clientMonitor, boolean success) {
+            mCallback.onClientFinished(InternalCleanupClient.this, success);
+        }
     };
 
     protected abstract InternalEnumerateClient<T> getEnumerateClient(Context context,
@@ -116,7 +122,7 @@
         FrameworkStatsLog.write(FrameworkStatsLog.BIOMETRIC_SYSTEM_HEALTH_ISSUE_DETECTED,
                 mStatsModality,
                 BiometricsProtoEnums.ISSUE_UNKNOWN_TEMPLATE_ENROLLED_HAL);
-        mCurrentTask.start(mRemoveFinishCallback);
+        mCurrentTask.start(mRemoveCallback);
     }
 
     @Override
@@ -125,13 +131,13 @@
     }
 
     @Override
-    public void start(@NonNull FinishCallback finishCallback) {
-        super.start(finishCallback);
+    public void start(@NonNull Callback callback) {
+        super.start(callback);
 
         // Start enumeration. Removal will start if necessary, when enumeration is completed.
         mCurrentTask = getEnumerateClient(getContext(), mLazyDaemon, getToken(), getTargetUserId(),
                 getOwnerString(), mEnrolledList, mBiometricUtils, getSensorId());
-        mCurrentTask.start(mEnumerateFinishCallback);
+        mCurrentTask.start(mEnumerateCallback);
     }
 
     @Override
@@ -147,7 +153,7 @@
                     + mCurrentTask.getClass().getSimpleName());
             return;
         }
-        ((RemovalClient) mCurrentTask).onRemoved(identifier, remaining);
+        ((RemovalClient<T>) mCurrentTask).onRemoved(identifier, remaining);
     }
 
     @Override
diff --git a/services/core/java/com/android/server/biometrics/sensors/InternalEnumerateClient.java b/services/core/java/com/android/server/biometrics/sensors/InternalEnumerateClient.java
index 3f73cd5..e07f712 100644
--- a/services/core/java/com/android/server/biometrics/sensors/InternalEnumerateClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/InternalEnumerateClient.java
@@ -62,7 +62,7 @@
         handleEnumeratedTemplate(identifier);
         if (remaining == 0) {
             doTemplateCleanup();
-            mFinishCallback.onClientFinished(this, true /* success */);
+            mCallback.onClientFinished(this, true /* success */);
         }
     }
 
@@ -72,8 +72,8 @@
     }
 
     @Override
-    public void start(@NonNull FinishCallback finishCallback) {
-        super.start(finishCallback);
+    public void start(@NonNull Callback callback) {
+        super.start(callback);
 
         // The biometric template ids will be removed when we get confirmation from the HAL
         startHalOperation();
diff --git a/services/core/java/com/android/server/biometrics/sensors/Interruptable.java b/services/core/java/com/android/server/biometrics/sensors/Interruptable.java
index 35d9177..28e0117 100644
--- a/services/core/java/com/android/server/biometrics/sensors/Interruptable.java
+++ b/services/core/java/com/android/server/biometrics/sensors/Interruptable.java
@@ -37,10 +37,10 @@
 
     /**
      * Notifies the client that it needs to finish before
-     * {@link ClientMonitor#start(ClientMonitor.FinishCallback)} was invoked. This usually happens
+     * {@link ClientMonitor#start(ClientMonitor.Callback)} was invoked. This usually happens
      * if the client is still waiting in the pending queue and got notified that a subsequent
      * operation is preempting it.
-     * @param finishCallback invoked when the operation is completed.
+     * @param callback invoked when the operation is completed.
      */
-    void cancelWithoutStarting(@NonNull ClientMonitor.FinishCallback finishCallback);
+    void cancelWithoutStarting(@NonNull ClientMonitor.Callback callback);
 }
diff --git a/services/core/java/com/android/server/biometrics/sensors/LoggableMonitor.java b/services/core/java/com/android/server/biometrics/sensors/LoggableMonitor.java
index 1a4216f..d85ab25 100644
--- a/services/core/java/com/android/server/biometrics/sensors/LoggableMonitor.java
+++ b/services/core/java/com/android/server/biometrics/sensors/LoggableMonitor.java
@@ -58,6 +58,10 @@
         mStatsClient = statsClient;
     }
 
+    public int getStatsClient() {
+        return mStatsClient;
+    }
+
     private boolean isAnyFieldUnknown() {
         return mStatsModality == BiometricsProtoEnums.MODALITY_UNKNOWN
                 || mStatsAction == BiometricsProtoEnums.ACTION_UNKNOWN
diff --git a/services/core/java/com/android/server/biometrics/sensors/RemovalClient.java b/services/core/java/com/android/server/biometrics/sensors/RemovalClient.java
index 1c49bcdb..1348f79 100644
--- a/services/core/java/com/android/server/biometrics/sensors/RemovalClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/RemovalClient.java
@@ -55,8 +55,8 @@
     }
 
     @Override
-    public void start(@NonNull FinishCallback finishCallback) {
-        super.start(finishCallback);
+    public void start(@NonNull Callback callback) {
+        super.start(callback);
 
         // The biometric template ids will be removed when we get confirmation from the HAL
         startHalOperation();
@@ -85,7 +85,7 @@
                 // cleanup).
                 mAuthenticatorIds.put(getTargetUserId(), 0L);
             }
-            mFinishCallback.onClientFinished(this, true /* success */);
+            mCallback.onClientFinished(this, true /* success */);
         }
     }
 }
diff --git a/services/core/java/com/android/server/biometrics/sensors/RevokeChallengeClient.java b/services/core/java/com/android/server/biometrics/sensors/RevokeChallengeClient.java
index b78ee49..5deb8fa 100644
--- a/services/core/java/com/android/server/biometrics/sensors/RevokeChallengeClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/RevokeChallengeClient.java
@@ -36,10 +36,10 @@
     }
 
     @Override
-    public void start(@NonNull FinishCallback finishCallback) {
-        super.start(finishCallback);
+    public void start(@NonNull Callback callback) {
+        super.start(callback);
 
         startHalOperation();
-        mFinishCallback.onClientFinished(this, true /* success */);
+        mCallback.onClientFinished(this, true /* success */);
     }
 }
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/Face10.java b/services/core/java/com/android/server/biometrics/sensors/face/Face10.java
index 6c57208..32bb2db 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/Face10.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/Face10.java
@@ -29,6 +29,7 @@
 import android.hardware.biometrics.face.V1_0.IBiometricsFace;
 import android.hardware.biometrics.face.V1_0.IBiometricsFaceClientCallback;
 import android.hardware.face.Face;
+import android.hardware.face.FaceManager;
 import android.hardware.face.FaceSensorProperties;
 import android.hardware.face.IFaceServiceReceiver;
 import android.os.Build;
@@ -44,6 +45,7 @@
 import android.provider.Settings;
 import android.util.Slog;
 
+import com.android.internal.R;
 import com.android.internal.util.FrameworkStatsLog;
 import com.android.server.biometrics.Utils;
 import com.android.server.biometrics.sensors.AcquisitionClient;
@@ -98,6 +100,11 @@
 
     @Nullable private IBiometricsFace mDaemon;
     private int mCurrentUserId = UserHandle.USER_NULL;
+    // If a challenge is generated, keep track of its owner. Since IBiometricsFace@1.0 only
+    // supports a single in-flight challenge, we must notify the interrupted owner that its
+    // challenge is no longer valid. The interrupted owner will be notified when the interrupter
+    // has finished.
+    @Nullable private FaceGenerateChallengeClient mCurrentChallengeOwner;
 
     private final UserSwitchObserver mUserSwitchObserver = new SynchronousUserSwitchObserver() {
         @Override
@@ -269,7 +276,10 @@
 
     Face10(@NonNull Context context, int sensorId,
             @NonNull LockoutResetDispatcher lockoutResetDispatcher) {
-        mFaceSensorProperties = new FaceSensorProperties(sensorId, false /* supportsFaceDetect */);
+        final boolean supportsSelfIllumination = context.getResources()
+                .getBoolean(R.bool.config_faceAuthSupportsSelfIllumination);
+        mFaceSensorProperties = new FaceSensorProperties(sensorId, false /* supportsFaceDetect */,
+                supportsSelfIllumination);
         mContext = context;
         mSensorId = sensorId;
         mScheduler = new BiometricScheduler(TAG, null /* gestureAvailabilityTracker */);
@@ -394,9 +404,12 @@
         final FaceUpdateActiveUserClient client = new FaceUpdateActiveUserClient(mContext,
                 mLazyDaemon, targetUserId, mContext.getOpPackageName(), mSensorId, mCurrentUserId,
                 hasEnrolled, mAuthenticatorIds);
-        mScheduler.scheduleClientMonitor(client, (clientMonitor, success) -> {
-            if (success) {
-                mCurrentUserId = targetUserId;
+        mScheduler.scheduleClientMonitor(client, new ClientMonitor.Callback() {
+            @Override
+            public void onClientFinished(@NonNull ClientMonitor<?> clientMonitor, boolean success) {
+                if (success) {
+                    mCurrentUserId = targetUserId;
+                }
             }
         });
     }
@@ -456,21 +469,87 @@
         });
     }
 
+    /**
+     * {@link IBiometricsFace} only supports a single in-flight challenge. In cases where two
+     * callers both need challenges (e.g. resetLockout right before enrollment), we need to ensure
+     * that either:
+     * 1) generateChallenge/operation/revokeChallenge is complete before the next generateChallenge
+     *    is processed by the scheduler, or
+     * 2) the generateChallenge callback provides a mechanism for notifying the caller that its
+     *    challenge has been invalidated by a subsequent caller, as well as a mechanism for
+     *    notifying the previous caller that the interrupting operation is complete (e.g. the
+     *    interrupting client's challenge has been revoked, so that the interrupted client can
+     *    start retry logic if necessary). See
+     *    {@link FaceManager.GenerateChallengeCallback#onChallengeInterruptFinished(int)}
+     * The only case of conflicting challenges is currently resetLockout --> enroll. So, the second
+     * option seems better as it prioritizes the new operation, which is user-facing.
+     */
     void scheduleGenerateChallenge(@NonNull IBinder token, @NonNull IFaceServiceReceiver receiver,
             @NonNull String opPackageName) {
         mHandler.post(() -> {
+            if (mCurrentChallengeOwner != null) {
+                Slog.w(TAG, "Current challenge owner: " + mCurrentChallengeOwner
+                        + ", interrupted by: " + opPackageName);
+                try {
+                    mCurrentChallengeOwner.getListener().onChallengeInterrupted(mSensorId);
+                } catch (RemoteException e) {
+                    Slog.e(TAG, "Unable to notify challenge interrupted", e);
+                }
+            }
+
             final FaceGenerateChallengeClient client = new FaceGenerateChallengeClient(mContext,
                     mLazyDaemon, token, new ClientMonitorCallbackConverter(receiver), opPackageName,
-                    mSensorId);
-            mScheduler.scheduleClientMonitor(client);
+                    mSensorId, mCurrentChallengeOwner);
+            mScheduler.scheduleClientMonitor(client, new ClientMonitor.Callback() {
+                @Override
+                public void onClientStarted(@NonNull ClientMonitor<?> clientMonitor) {
+                    if (client != clientMonitor) {
+                        Slog.e(TAG, "scheduleGenerateChallenge, mismatched client."
+                                + " Expecting: " + client + ", received: " + clientMonitor);
+                        return;
+                    }
+                    Slog.d(TAG, "Current challenge owner: " + client);
+                    mCurrentChallengeOwner = client;
+                }
+            });
         });
     }
 
     void scheduleRevokeChallenge(@NonNull IBinder token, @NonNull String owner) {
         mHandler.post(() -> {
+            if (!mCurrentChallengeOwner.getOwnerString().contentEquals(owner)) {
+                Slog.e(TAG, "scheduleRevokeChallenge, package: " + owner
+                        + " attempting to revoke challenge owned by: "
+                        + mCurrentChallengeOwner.getOwnerString());
+                return;
+            }
+
             final FaceRevokeChallengeClient client = new FaceRevokeChallengeClient(mContext,
                     mLazyDaemon, token, owner, mSensorId);
-            mScheduler.scheduleClientMonitor(client);
+            mScheduler.scheduleClientMonitor(client, new ClientMonitor.Callback() {
+                @Override
+                public void onClientFinished(@NonNull ClientMonitor<?> clientMonitor,
+                        boolean success) {
+                    if (client != clientMonitor) {
+                        Slog.e(TAG, "scheduleRevokeChallenge, mismatched client."
+                                + "Expecting: " + client + ", received: " + clientMonitor);
+                        return;
+                    }
+
+                    final FaceGenerateChallengeClient previousChallengeOwner =
+                            mCurrentChallengeOwner.getInterruptedClient();
+                    mCurrentChallengeOwner = null;
+                    Slog.d(TAG, "Previous challenge owner: " + previousChallengeOwner);
+                    if (previousChallengeOwner != null) {
+                        try {
+                            previousChallengeOwner.getListener()
+                                    .onChallengeInterruptFinished(mSensorId);
+                        } catch (RemoteException e) {
+                            Slog.e(TAG, "Unable to notify interrupt finished", e);
+                        }
+                    }
+                }
+            });
         });
     }
 
@@ -488,12 +567,16 @@
                     opPackageName, FaceUtils.getInstance(), disabledFeatures, ENROLL_TIMEOUT_SEC,
                     surfaceHandle, mSensorId);
 
-            mScheduler.scheduleClientMonitor(client, ((clientMonitor, success) -> {
-                if (success) {
-                    // Update authenticatorIds
-                    scheduleUpdateActiveUserWithoutHandler(client.getTargetUserId());
+            mScheduler.scheduleClientMonitor(client, new ClientMonitor.Callback() {
+                @Override
+                public void onClientFinished(@NonNull ClientMonitor<?> clientMonitor,
+                        boolean success) {
+                    if (success) {
+                        // Update authenticatorIds
+                        scheduleUpdateActiveUserWithoutHandler(client.getTargetUserId());
+                    }
                 }
-            }));
+            });
         });
     }
 
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/FaceAuthenticationClient.java b/services/core/java/com/android/server/biometrics/sensors/face/FaceAuthenticationClient.java
index 21bda74..892d6a4 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/FaceAuthenticationClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/FaceAuthenticationClient.java
@@ -92,7 +92,7 @@
         } catch (RemoteException e) {
             Slog.e(TAG, "Remote exception when requesting auth", e);
             onError(BiometricFaceConstants.FACE_ERROR_HW_UNAVAILABLE, 0 /* vendorCode */);
-            mFinishCallback.onClientFinished(this, false /* success */);
+            mCallback.onClientFinished(this, false /* success */);
         }
     }
 
@@ -103,7 +103,7 @@
         } catch (RemoteException e) {
             Slog.e(TAG, "Remote exception when requesting cancel", e);
             onError(BiometricFaceConstants.FACE_ERROR_HW_UNAVAILABLE, 0 /* vendorCode */);
-            mFinishCallback.onClientFinished(this, false /* success */);
+            mCallback.onClientFinished(this, false /* success */);
         }
     }
 
@@ -131,7 +131,7 @@
         // 1) Authenticated == true
         // 2) Error occurred
         // 3) Authenticated == false
-        mFinishCallback.onClientFinished(this, true /* success */);
+        mCallback.onClientFinished(this, true /* success */);
     }
 
     @Override
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/FaceAuthenticator.java b/services/core/java/com/android/server/biometrics/sensors/face/FaceAuthenticator.java
index 33244b8..bbee6cd 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/FaceAuthenticator.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/FaceAuthenticator.java
@@ -75,11 +75,6 @@
     }
 
     @Override
-    public void resetLockout(int userId, byte[] hardwareAuthToken) throws RemoteException {
-        mFaceService.resetLockout(userId, hardwareAuthToken);
-    }
-
-    @Override
     public long getAuthenticatorId(int callingUserId) throws RemoteException {
         return mFaceService.getAuthenticatorId(callingUserId);
     }
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/FaceEnrollClient.java b/services/core/java/com/android/server/biometrics/sensors/face/FaceEnrollClient.java
index 52a8226..3e843ca 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/FaceEnrollClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/FaceEnrollClient.java
@@ -116,12 +116,12 @@
             }
             if (status != Status.OK) {
                 onError(BiometricFaceConstants.FACE_ERROR_UNABLE_TO_PROCESS, 0 /* vendorCode */);
-                mFinishCallback.onClientFinished(this, false /* success */);
+                mCallback.onClientFinished(this, false /* success */);
             }
         } catch (RemoteException e) {
             Slog.e(TAG, "Remote exception when requesting enroll", e);
             onError(BiometricFaceConstants.FACE_ERROR_UNABLE_TO_PROCESS, 0 /* vendorCode */);
-            mFinishCallback.onClientFinished(this, false /* success */);
+            mCallback.onClientFinished(this, false /* success */);
         }
     }
 
@@ -132,7 +132,7 @@
         } catch (RemoteException e) {
             Slog.e(TAG, "Remote exception when requesting cancel", e);
             onError(BiometricFaceConstants.FACE_ERROR_HW_UNAVAILABLE, 0 /* vendorCode */);
-            mFinishCallback.onClientFinished(this, false /* success */);
+            mCallback.onClientFinished(this, false /* success */);
         }
     }
 }
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/FaceGenerateChallengeClient.java b/services/core/java/com/android/server/biometrics/sensors/face/FaceGenerateChallengeClient.java
index 67f2712..ba401f2 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/FaceGenerateChallengeClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/FaceGenerateChallengeClient.java
@@ -17,12 +17,14 @@
 package com.android.server.biometrics.sensors.face;
 
 import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.content.Context;
 import android.hardware.biometrics.face.V1_0.IBiometricsFace;
 import android.os.IBinder;
 import android.os.RemoteException;
 import android.util.Slog;
 
+import com.android.server.biometrics.sensors.ClientMonitor;
 import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter;
 import com.android.server.biometrics.sensors.GenerateChallengeClient;
 
@@ -36,10 +38,22 @@
     private static final String TAG = "FaceGenerateChallengeClient";
     private static final int CHALLENGE_TIMEOUT_SEC = 600; // 10 minutes
 
+    // If `this` FaceGenerateChallengeClient was invoked while an existing in-flight challenge
+    // was not revoked yet, store a reference to the interrupted client here. Notify the interrupted
+    // client when `this` challenge is revoked.
+    @Nullable private final FaceGenerateChallengeClient mInterruptedClient;
+
     FaceGenerateChallengeClient(@NonNull Context context,
             @NonNull LazyDaemon<IBiometricsFace> lazyDaemon, @NonNull IBinder token,
-            @NonNull ClientMonitorCallbackConverter listener, @NonNull String owner, int sensorId) {
+            @NonNull ClientMonitorCallbackConverter listener, @NonNull String owner, int sensorId,
+            @Nullable FaceGenerateChallengeClient interruptedClient) {
         super(context, lazyDaemon, token, listener, owner, sensorId);
+        mInterruptedClient = interruptedClient;
+    }
+
+    @Nullable
+    public FaceGenerateChallengeClient getInterruptedClient() {
+        return mInterruptedClient;
     }
 
     @Override
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/FaceGetFeatureClient.java b/services/core/java/com/android/server/biometrics/sensors/face/FaceGetFeatureClient.java
index ce57cb7..8c7b99d 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/FaceGetFeatureClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/FaceGetFeatureClient.java
@@ -61,8 +61,8 @@
     }
 
     @Override
-    public void start(@NonNull FinishCallback finishCallback) {
-        super.start(finishCallback);
+    public void start(@NonNull Callback callback) {
+        super.start(callback);
         startHalOperation();
     }
 
@@ -71,10 +71,10 @@
         try {
             final OptionalBool result = getFreshDaemon().getFeature(mFeature, mFaceId);
             getListener().onFeatureGet(result.status == Status.OK, mFeature, result.value);
-            mFinishCallback.onClientFinished(this, true /* success */);
+            mCallback.onClientFinished(this, true /* success */);
         } catch (RemoteException e) {
             Slog.e(TAG, "Unable to getFeature", e);
-            mFinishCallback.onClientFinished(this, false /* success */);
+            mCallback.onClientFinished(this, false /* success */);
         }
     }
 }
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/FaceInternalEnumerateClient.java b/services/core/java/com/android/server/biometrics/sensors/face/FaceInternalEnumerateClient.java
index f25242e..32d4832 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/FaceInternalEnumerateClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/FaceInternalEnumerateClient.java
@@ -52,7 +52,7 @@
             getFreshDaemon().enumerate();
         } catch (RemoteException e) {
             Slog.e(TAG, "Remote exception when requesting enumerate", e);
-            mFinishCallback.onClientFinished(this, false /* success */);
+            mCallback.onClientFinished(this, false /* success */);
         }
     }
 }
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/FaceRemovalClient.java b/services/core/java/com/android/server/biometrics/sensors/face/FaceRemovalClient.java
index 00d5f50..dde5aba 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/FaceRemovalClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/FaceRemovalClient.java
@@ -51,7 +51,7 @@
             getFreshDaemon().remove(mBiometricId);
         } catch (RemoteException e) {
             Slog.e(TAG, "Remote exception when requesting remove", e);
-            mFinishCallback.onClientFinished(this, false /* success */);
+            mCallback.onClientFinished(this, false /* success */);
         }
     }
 }
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/FaceResetLockoutClient.java b/services/core/java/com/android/server/biometrics/sensors/face/FaceResetLockoutClient.java
index 69070da..f4324be 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/FaceResetLockoutClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/FaceResetLockoutClient.java
@@ -56,8 +56,8 @@
     }
 
     @Override
-    public void start(@NonNull FinishCallback finishCallback) {
-        super.start(finishCallback);
+    public void start(@NonNull Callback callback) {
+        super.start(callback);
         startHalOperation();
     }
 
@@ -65,10 +65,10 @@
     protected void startHalOperation() {
         try {
             getFreshDaemon().resetLockout(mHardwareAuthToken);
-            mFinishCallback.onClientFinished(this, true /* success */);
+            mCallback.onClientFinished(this, true /* success */);
         } catch (RemoteException e) {
             Slog.e(TAG, "Unable to reset lockout", e);
-            mFinishCallback.onClientFinished(this, false /* success */);
+            mCallback.onClientFinished(this, false /* success */);
         }
     }
 }
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/FaceService.java b/services/core/java/com/android/server/biometrics/sensors/face/FaceService.java
index b832a09..b689106 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/FaceService.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/FaceService.java
@@ -80,16 +80,27 @@
         }
 
         @Override // Binder call
-        public void generateChallenge(IBinder token, IFaceServiceReceiver receiver,
+        public void generateChallenge(IBinder token, int sensorId, IFaceServiceReceiver receiver,
                 String opPackageName) {
             Utils.checkPermission(getContext(), MANAGE_BIOMETRIC);
-            mFace10.scheduleGenerateChallenge(token, receiver, opPackageName);
+            if (sensorId == mFace10.getFaceSensorProperties().sensorId) {
+                mFace10.scheduleGenerateChallenge(token, receiver, opPackageName);
+                return;
+            }
+
+            Slog.w(TAG, "No matching sensor for generateChallenge, sensorId: " + sensorId);
         }
 
         @Override // Binder call
-        public void revokeChallenge(IBinder token, String owner) {
+        public void revokeChallenge(IBinder token, int sensorId, String owner) {
             Utils.checkPermission(getContext(), MANAGE_BIOMETRIC);
-            mFace10.scheduleRevokeChallenge(token, owner);
+
+            if (sensorId == mFace10.getFaceSensorProperties().sensorId) {
+                mFace10.scheduleRevokeChallenge(token, owner);
+                return;
+            }
+
+            Slog.w(TAG, "No matching sensor for revokeChallenge, sensorId: " + sensorId);
         }
 
         @Override // Binder call
@@ -267,9 +278,16 @@
         }
 
         @Override // Binder call
-        public void resetLockout(int userId, byte[] hardwareAuthToken) {
+        public void resetLockout(IBinder token, int sensorId, int userId, byte[] hardwareAuthToken,
+                String opPackageName) {
             Utils.checkPermission(getContext(), USE_BIOMETRIC_INTERNAL);
-            mFace10.scheduleResetLockout(userId, hardwareAuthToken);
+
+            if (sensorId == mFace10.getFaceSensorProperties().sensorId) {
+                mFace10.scheduleResetLockout(userId, hardwareAuthToken);
+                return;
+            }
+
+            Slog.w(TAG, "No matching sensor for resetLockout, sensorId: " + sensorId);
         }
 
         @Override
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/FaceSetFeatureClient.java b/services/core/java/com/android/server/biometrics/sensors/face/FaceSetFeatureClient.java
index e7d041a..94abb7f 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/FaceSetFeatureClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/FaceSetFeatureClient.java
@@ -70,8 +70,8 @@
     }
 
     @Override
-    public void start(@NonNull FinishCallback finishCallback) {
-        super.start(finishCallback);
+    public void start(@NonNull Callback callback) {
+        super.start(callback);
 
         startHalOperation();
     }
@@ -82,10 +82,10 @@
             final int result = getFreshDaemon()
                     .setFeature(mFeature, mEnabled, mHardwareAuthToken, mFaceId);
             getListener().onFeatureSet(result == Status.OK, mFeature);
-            mFinishCallback.onClientFinished(this, true /* success */);
+            mCallback.onClientFinished(this, true /* success */);
         } catch (RemoteException e) {
             Slog.e(TAG, "Unable to set feature: " + mFeature + " to enabled: " + mEnabled, e);
-            mFinishCallback.onClientFinished(this, false /* success */);
+            mCallback.onClientFinished(this, false /* success */);
         }
     }
 }
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/FaceUpdateActiveUserClient.java b/services/core/java/com/android/server/biometrics/sensors/face/FaceUpdateActiveUserClient.java
index bcf304e..05b176d 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/FaceUpdateActiveUserClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/FaceUpdateActiveUserClient.java
@@ -50,8 +50,8 @@
     }
 
     @Override
-    public void start(@NonNull FinishCallback finishCallback) {
-        super.start(finishCallback);
+    public void start(@NonNull Callback callback) {
+        super.start(callback);
 
         if (mCurrentUserId == getTargetUserId()) {
             Slog.d(TAG, "Already user: " + mCurrentUserId + ", refreshing authenticatorId");
@@ -61,7 +61,7 @@
             } catch (RemoteException e) {
                 Slog.e(TAG, "Unable to refresh authenticatorId", e);
             }
-            finishCallback.onClientFinished(this, true /* success */);
+            callback.onClientFinished(this, true /* success */);
             return;
         }
 
@@ -79,16 +79,16 @@
                 FACE_DATA_DIR);
         if (!storePath.exists()) {
             Slog.e(TAG, "vold has not created the directory?");
-            mFinishCallback.onClientFinished(this, false /* success */);
+            mCallback.onClientFinished(this, false /* success */);
             return;
         }
 
         try {
             getFreshDaemon().setActiveUser(getTargetUserId(), storePath.getAbsolutePath());
-            mFinishCallback.onClientFinished(this, true /* success */);
+            mCallback.onClientFinished(this, true /* success */);
         } catch (RemoteException e) {
             Slog.e(TAG, "Failed to setActiveUser: " + e);
-            mFinishCallback.onClientFinished(this, false /* success */);
+            mCallback.onClientFinished(this, false /* success */);
         }
     }
 }
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/Fingerprint21.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/Fingerprint21.java
index dad0386..c5c2822 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/Fingerprint21.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/Fingerprint21.java
@@ -84,7 +84,7 @@
     private static final String TAG = "Fingerprint21";
     private static final int ENROLL_TIMEOUT_SEC = 60;
 
-    private final Context mContext;
+    final Context mContext;
     private final IActivityTaskManager mActivityTaskManager;
     private final FingerprintSensorProperties mSensorProperties;
     private final BiometricScheduler mScheduler;
@@ -96,6 +96,7 @@
     private final Map<Integer, Long> mAuthenticatorIds;
 
     @Nullable private IBiometricsFingerprint mDaemon;
+    @NonNull private final HalResultController mHalResultController;
     @Nullable private IUdfpsOverlayController mUdfpsOverlayController;
     private int mCurrentUserId = UserHandle.USER_NULL;
 
@@ -146,15 +147,37 @@
         }
     };
 
-    private final IBiometricsFingerprintClientCallback mDaemonCallback =
-            new IBiometricsFingerprintClientCallback.Stub() {
+    public static class HalResultController extends IBiometricsFingerprintClientCallback.Stub {
+
+        /**
+         * Interface to sends results to the HalResultController's owner.
+         */
+        public interface Callback {
+            /**
+             * Invoked when the HAL sends ERROR_HW_UNAVAILABLE.
+             */
+            void onHardwareUnavailable();
+        }
+
+        @NonNull private final Context mContext;
+        @NonNull final Handler mHandler;
+        @NonNull final BiometricScheduler mScheduler;
+        @Nullable private Callback mCallback;
+
+        HalResultController(@NonNull Context context, @NonNull Handler handler,
+                @NonNull BiometricScheduler scheduler) {
+            mContext = context;
+            mHandler = handler;
+            mScheduler = scheduler;
+        }
+
+        public void setCallback(@Nullable Callback callback) {
+            mCallback = callback;
+        }
+
         @Override
         public void onEnrollResult(long deviceId, int fingerId, int groupId, int remaining) {
             mHandler.post(() -> {
-                final CharSequence name = FingerprintUtils.getInstance()
-                        .getUniqueName(mContext, mCurrentUserId);
-                final Fingerprint fingerprint = new Fingerprint(name, groupId, fingerId, deviceId);
-
                 final ClientMonitor<?> client = mScheduler.getCurrentClient();
                 if (!(client instanceof FingerprintEnrollClient)) {
                     Slog.e(TAG, "onEnrollResult for non-enroll client: "
@@ -162,6 +185,11 @@
                     return;
                 }
 
+                final int currentUserId = client.getTargetUserId();
+                final CharSequence name = FingerprintUtils.getInstance()
+                        .getUniqueName(mContext, currentUserId);
+                final Fingerprint fingerprint = new Fingerprint(name, groupId, fingerId, deviceId);
+
                 final FingerprintEnrollClient enrollClient = (FingerprintEnrollClient) client;
                 enrollClient.onEnrollResult(fingerprint, remaining);
             });
@@ -224,8 +252,9 @@
 
                 if (error == BiometricConstants.BIOMETRIC_ERROR_HW_UNAVAILABLE) {
                     Slog.e(TAG, "Got ERROR_HW_UNAVAILABLE");
-                    mDaemon = null;
-                    mCurrentUserId = UserHandle.USER_NULL;
+                    if (mCallback != null) {
+                        mCallback.onHardwareUnavailable();
+                    }
                 }
             });
         }
@@ -262,20 +291,27 @@
                 enumerateConsumer.onEnumerationResult(fp, remaining);
             });
         }
-    };
+    }
 
-    Fingerprint21(@NonNull Context context, int sensorId,
+    Fingerprint21(@NonNull Context context, @NonNull BiometricScheduler scheduler,
+            @NonNull Handler handler, int sensorId,
             @NonNull LockoutResetDispatcher lockoutResetDispatcher,
-            @NonNull GestureAvailabilityDispatcher gestureAvailabilityDispatcher) {
+            @NonNull HalResultController controller) {
         mContext = context;
+        mScheduler = scheduler;
+        mHandler = handler;
         mActivityTaskManager = ActivityTaskManager.getService();
-        mHandler = new Handler(Looper.getMainLooper());
+
         mTaskStackListener = new BiometricTaskStackListener();
         mAuthenticatorIds = Collections.synchronizedMap(new HashMap<>());
         mLazyDaemon = Fingerprint21.this::getDaemon;
         mLockoutResetDispatcher = lockoutResetDispatcher;
         mLockoutTracker = new LockoutFrameworkImpl(context, mLockoutResetCallback);
-        mScheduler = new BiometricScheduler(TAG, gestureAvailabilityDispatcher);
+        mHalResultController = controller;
+        mHalResultController.setCallback(() -> {
+            mDaemon = null;
+            mCurrentUserId = UserHandle.USER_NULL;
+        });
 
         try {
             ActivityManager.getService().registerUserSwitchObserver(mUserSwitchObserver, TAG);
@@ -300,7 +336,21 @@
         final @FingerprintSensorProperties.SensorType int sensorType =
                 isUdfps ? FingerprintSensorProperties.TYPE_UDFPS
                         : FingerprintSensorProperties.TYPE_REAR;
-        mSensorProperties = new FingerprintSensorProperties(sensorId, sensorType);
+        // resetLockout is controlled by the framework, so hardwareAuthToken is not required
+        final boolean resetLockoutRequiresHardwareAuthToken = false;
+        mSensorProperties = new FingerprintSensorProperties(sensorId, sensorType,
+                resetLockoutRequiresHardwareAuthToken);
+    }
+
+    static Fingerprint21 newInstance(@NonNull Context context, int sensorId,
+            @NonNull LockoutResetDispatcher lockoutResetDispatcher,
+            @NonNull GestureAvailabilityDispatcher gestureAvailabilityDispatcher) {
+        final Handler handler = new Handler(Looper.getMainLooper());
+        final BiometricScheduler scheduler =
+                new BiometricScheduler(TAG, gestureAvailabilityDispatcher);
+        final HalResultController controller = new HalResultController(context, handler, scheduler);
+        return new Fingerprint21(context, scheduler, handler, sensorId, lockoutResetDispatcher,
+                controller);
     }
 
     @Override
@@ -355,7 +405,7 @@
         // successfully set.
         long halId = 0;
         try {
-            halId = mDaemon.setNotify(mDaemonCallback);
+            halId = mDaemon.setNotify(mHalResultController);
         } catch (RemoteException e) {
             Slog.e(TAG, "Failed to set callback for fingerprint HAL", e);
             mDaemon = null;
@@ -373,6 +423,9 @@
         return mDaemon;
     }
 
+    @Nullable IUdfpsOverlayController getUdfpsOverlayController() {
+        return mUdfpsOverlayController;
+    }
     @LockoutTracker.LockoutMode int getLockoutModeForUser(int userId) {
         return mLockoutTracker.getLockoutModeForUser(userId);
     }
@@ -409,14 +462,17 @@
                 new FingerprintUpdateActiveUserClient(mContext, mLazyDaemon, targetUserId,
                         mContext.getOpPackageName(), mSensorProperties.sensorId, mCurrentUserId,
                         hasEnrolled, mAuthenticatorIds);
-        mScheduler.scheduleClientMonitor(client, (clientMonitor, success) -> {
-            if (success) {
-                mCurrentUserId = targetUserId;
+        mScheduler.scheduleClientMonitor(client, new ClientMonitor.Callback() {
+            @Override
+            public void onClientFinished(@NonNull ClientMonitor<?> clientMonitor, boolean success) {
+                if (success) {
+                    mCurrentUserId = targetUserId;
+                }
             }
         });
     }
 
-    void scheduleResetLockout(int userId, byte[] hardwareAuthToken) {
+    void scheduleResetLockout(int userId) {
         // Fingerprint2.1 keeps track of lockout in the framework. Let's just do it on the handler
         // thread.
         mHandler.post(() -> {
@@ -453,12 +509,16 @@
                     mLazyDaemon, token, new ClientMonitorCallbackConverter(receiver), userId,
                     hardwareAuthToken, opPackageName, FingerprintUtils.getInstance(),
                     ENROLL_TIMEOUT_SEC, mSensorProperties.sensorId, mUdfpsOverlayController);
-            mScheduler.scheduleClientMonitor(client, ((clientMonitor, success) -> {
-                if (success) {
-                    // Update authenticatorIds
-                    scheduleUpdateActiveUserWithoutHandler(clientMonitor.getTargetUserId());
+            mScheduler.scheduleClientMonitor(client, new ClientMonitor.Callback() {
+                @Override
+                public void onClientFinished(@NonNull ClientMonitor<?> clientMonitor,
+                        boolean success) {
+                    if (success) {
+                        // Update authenticatorIds
+                        scheduleUpdateActiveUserWithoutHandler(clientMonitor.getTargetUserId());
+                    }
                 }
-            }));
+            });
         });
     }
 
@@ -485,7 +545,7 @@
 
     void scheduleAuthenticate(@NonNull IBinder token, long operationId, int userId, int cookie,
             @NonNull ClientMonitorCallbackConverter listener, @NonNull String opPackageName,
-            @Nullable Surface surface, boolean restricted, int statsClient) {
+            boolean restricted, int statsClient, boolean isKeyguard) {
         mHandler.post(() -> {
             scheduleUpdateActiveUserWithoutHandler(userId);
 
@@ -493,8 +553,8 @@
             final FingerprintAuthenticationClient client = new FingerprintAuthenticationClient(
                     mContext, mLazyDaemon, token, listener, userId, operationId, restricted,
                     opPackageName, cookie, false /* requireConfirmation */,
-                    mSensorProperties.sensorId, isStrongBiometric, surface, statsClient,
-                    mTaskStackListener, mLockoutTracker, mUdfpsOverlayController);
+                    mSensorProperties.sensorId, isStrongBiometric, statsClient,
+                    mTaskStackListener, mLockoutTracker, mUdfpsOverlayController, isKeyguard);
             mScheduler.scheduleClientMonitor(client);
         });
     }
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/Fingerprint21UdfpsMock.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/Fingerprint21UdfpsMock.java
new file mode 100644
index 0000000..1a6ad46
--- /dev/null
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/Fingerprint21UdfpsMock.java
@@ -0,0 +1,557 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.biometrics.sensors.fingerprint;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.app.trust.TrustManager;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.hardware.fingerprint.FingerprintManager;
+import android.hardware.fingerprint.FingerprintManager.AuthenticationCallback;
+import android.hardware.fingerprint.FingerprintManager.AuthenticationResult;
+import android.hardware.fingerprint.FingerprintSensorProperties;
+import android.hardware.fingerprint.IUdfpsOverlayController;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Looper;
+import android.os.RemoteException;
+import android.os.UserHandle;
+import android.provider.Settings;
+import android.util.Slog;
+import android.util.SparseBooleanArray;
+
+import com.android.server.biometrics.sensors.AuthenticationConsumer;
+import com.android.server.biometrics.sensors.BiometricScheduler;
+import com.android.server.biometrics.sensors.ClientMonitor;
+import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter;
+import com.android.server.biometrics.sensors.LockoutResetDispatcher;
+
+import java.util.ArrayList;
+import java.util.Random;
+
+/**
+ * A mockable/testable provider of the {@link android.hardware.biometrics.fingerprint.V2_3} HIDL
+ * interface. This class is intended simulate UDFPS logic for devices that do not have an actual
+ * fingerprint@2.3 HAL (where UDFPS starts to become supported)
+ *
+ * UDFPS "accept" can only happen within a set amount of time after a sensor authentication. This is
+ * specified by {@link MockHalResultController#AUTH_VALIDITY_MS}. Touches after this duration will
+ * be treated as "reject".
+ *
+ * This class provides framework logic to emulate, for testing only, the UDFPS functionalies below:
+ *
+ * 1) IF either A) the caller is keyguard, and the device is not in a trusted state (authenticated
+ *    via biometric sensor or unlocked with a trust agent {@see android.app.trust.TrustManager}, OR
+ *    B) the caller is not keyguard, and regardless of trusted state, AND (following applies to both
+ *    (A) and (B) above) {@link FingerprintManager#onFingerDown(int, int, float, float)} is
+ *    received, this class will respond with {@link AuthenticationCallback#onAuthenticationFailed()}
+ *    after a tunable flat_time + variance_time.
+ *
+ *    In the case above (1), callers must not receive a successful authentication event here because
+ *    the sensor has not actually been authenticated.
+ *
+ * 2) IF A) the caller is keyguard and the device is not in a trusted state, OR B) the caller is not
+ *    keyguard and regardless of trusted state, AND (following applies to both (A) and (B)) the
+ *    sensor is touched and the fingerprint is accepted by the HAL, and then
+ *    {@link FingerprintManager#onFingerDown(int, int, float, float)} is received, this class will
+ *    respond with {@link AuthenticationCallback#onAuthenticationSucceeded(AuthenticationResult)}
+ *    after a tunable flat_time + variance_time. Note that the authentication callback from the
+ *    sensor is held until {@link FingerprintManager#onFingerDown(int, int, float, float)} is
+ *    invoked.
+ *
+ *    In the case above (2), callers can receive a successful authentication callback because the
+ *    real sensor was authenticated. Note that even though the real sensor was touched, keyguard
+ *    fingerprint authentication does not put keyguard into a trusted state because the
+ *    authentication callback is held until onFingerDown was invoked. This allows callers such as
+ *    keyguard to simulate a realistic path.
+ *
+ * 3) IF the caller is keyguard AND the device in a trusted state and then
+ *    {@link FingerprintManager#onFingerDown(int, int, float, float)} is received, this class will
+ *    respond with {@link AuthenticationCallback#onAuthenticationSucceeded(AuthenticationResult)}
+ *    after a tunable flat_time + variance time.
+ *
+ *    In the case above (3), since the device is already unlockable via trust agent, it's fine to
+ *    simulate the successful auth path. Non-keyguard clients will fall into either (1) or (2)
+ *    above.
+ *
+ *  This class currently does not simulate false rejection. Conversely, this class relies on the
+ *  real hardware sensor so does not affect false acceptance.
+ */
+@SuppressWarnings("deprecation")
+public class Fingerprint21UdfpsMock extends Fingerprint21 implements TrustManager.TrustListener {
+
+    private static final String TAG = "Fingerprint21UdfpsMock";
+
+    // Secure setting integer. If true, the system will load this class to enable udfps testing.
+    public static final String CONFIG_ENABLE_TEST_UDFPS =
+            "com.android.server.biometrics.sensors.fingerprint.test_udfps.enable";
+    // Secure setting integer. A fixed duration intended to simulate something like the duration
+    // required for image capture.
+    private static final String CONFIG_AUTH_DELAY_PT1 =
+            "com.android.server.biometrics.sensors.fingerprint.test_udfps.auth_delay_pt1";
+    // Secure setting integer. A fixed duration intended to simulate something like the duration
+    // required for template matching to complete.
+    private static final String CONFIG_AUTH_DELAY_PT2 =
+            "com.android.server.biometrics.sensors.fingerprint.test_udfps.auth_delay_pt2";
+    // Secure setting integer. A random value between [-randomness, randomness] will be added to the
+    // capture delay above for each accept/reject.
+    private static final String CONFIG_AUTH_DELAY_RANDOMNESS =
+            "com.android.server.biometrics.sensors.fingerprint.test_udfps.auth_delay_randomness";
+
+    private static final int DEFAULT_AUTH_DELAY_PT1_MS = 300;
+    private static final int DEFAULT_AUTH_DELAY_PT2_MS = 400;
+    private static final int DEFAULT_AUTH_DELAY_RANDOMNESS_MS = 100;
+
+    @NonNull private final TestableBiometricScheduler mScheduler;
+    @NonNull private final Handler mHandler;
+    @NonNull private final FingerprintSensorProperties mSensorProperties;
+    @NonNull private final MockHalResultController mMockHalResultController;
+    @NonNull private final TrustManager mTrustManager;
+    @NonNull private final SparseBooleanArray mUserHasTrust;
+    @NonNull private final Random mRandom;
+    @NonNull private final FakeRejectRunnable mFakeRejectRunnable;
+    @NonNull private final FakeAcceptRunnable mFakeAcceptRunnable;
+    @NonNull private final RestartAuthRunnable mRestartAuthRunnable;
+
+    private static class TestableBiometricScheduler extends BiometricScheduler {
+        @NonNull private final TestableInternalCallback mInternalCallback;
+        @NonNull private Fingerprint21UdfpsMock mFingerprint21;
+
+        TestableBiometricScheduler(@NonNull String tag,
+                @Nullable GestureAvailabilityDispatcher gestureAvailabilityDispatcher) {
+            super(tag, gestureAvailabilityDispatcher);
+            mInternalCallback = new TestableInternalCallback();
+        }
+
+        class TestableInternalCallback extends InternalCallback {
+            @Override
+            public void onClientStarted(ClientMonitor<?> clientMonitor) {
+                super.onClientStarted(clientMonitor);
+                Slog.d(TAG, "Client started: " + clientMonitor);
+                mFingerprint21.setDebugMessage("Started: " + clientMonitor);
+            }
+
+            @Override
+            public void onClientFinished(ClientMonitor<?> clientMonitor, boolean success) {
+                super.onClientFinished(clientMonitor, success);
+                Slog.d(TAG, "Client finished: " + clientMonitor);
+                mFingerprint21.setDebugMessage("Finished: " + clientMonitor);
+            }
+        }
+
+        void init(@NonNull Fingerprint21UdfpsMock fingerprint21) {
+            mFingerprint21 = fingerprint21;
+        }
+
+        /**
+         * Expose the internal finish callback so it can be used for testing
+         */
+        @Override
+        @NonNull protected InternalCallback getInternalCallback() {
+            return mInternalCallback;
+        }
+    }
+
+    /**
+     * All of the mocking/testing should happen in here. This way we don't need to modify the
+     * {@link com.android.server.biometrics.sensors.ClientMonitor} implementations and can run the
+     * real path there.
+     */
+    private static class MockHalResultController extends HalResultController {
+
+        // Duration for which a sensor authentication can be treated as UDFPS success.
+        private static final int AUTH_VALIDITY_MS = 10 * 1000; // 10 seconds
+
+        static class LastAuthArgs {
+            @NonNull final AuthenticationConsumer lastAuthenticatedClient;
+            final long deviceId;
+            final int fingerId;
+            final int groupId;
+            @Nullable final ArrayList<Byte> token;
+
+            LastAuthArgs(@NonNull AuthenticationConsumer authenticationConsumer, long deviceId,
+                    int fingerId, int groupId, @Nullable ArrayList<Byte> token) {
+                lastAuthenticatedClient = authenticationConsumer;
+                this.deviceId = deviceId;
+                this.fingerId = fingerId;
+                this.groupId = groupId;
+                if (token == null) {
+                    this.token = null;
+                } else {
+                    // Store a copy so the owner can be GC'd
+                    this.token = new ArrayList<>(token);
+                }
+            }
+        }
+
+        // Initialized after the constructor, but before it's ever used.
+        @NonNull private RestartAuthRunnable mRestartAuthRunnable;
+        @NonNull private Fingerprint21UdfpsMock mFingerprint21;
+        @Nullable private LastAuthArgs mLastAuthArgs;
+
+        MockHalResultController(@NonNull Context context, @NonNull Handler handler,
+                @NonNull BiometricScheduler scheduler) {
+            super(context, handler, scheduler);
+        }
+
+        void init(@NonNull RestartAuthRunnable restartAuthRunnable,
+                @NonNull Fingerprint21UdfpsMock fingerprint21) {
+            mRestartAuthRunnable = restartAuthRunnable;
+            mFingerprint21 = fingerprint21;
+        }
+
+        @Nullable AuthenticationConsumer getLastAuthenticatedClient() {
+            return mLastAuthArgs != null ? mLastAuthArgs.lastAuthenticatedClient : null;
+        }
+
+        /**
+         * Intercepts the HAL authentication and holds it until the UDFPS simulation decides
+         * that authentication finished.
+         */
+        @Override
+        public void onAuthenticated(long deviceId, int fingerId, int groupId,
+                ArrayList<Byte> token) {
+            mHandler.post(() -> {
+                final ClientMonitor<?> client = mScheduler.getCurrentClient();
+                if (!(client instanceof AuthenticationConsumer)) {
+                    Slog.e(TAG, "Non authentication consumer: " + client);
+                    return;
+                }
+
+                final boolean accepted = fingerId != 0;
+                if (accepted) {
+                    mFingerprint21.setDebugMessage("Finger accepted");
+                } else {
+                    mFingerprint21.setDebugMessage("Finger rejected");
+                }
+
+                final AuthenticationConsumer authenticationConsumer =
+                        (AuthenticationConsumer) client;
+                mLastAuthArgs = new LastAuthArgs(authenticationConsumer, deviceId, fingerId,
+                        groupId, token);
+
+                // Remove any existing restart runnbables since auth just started, and put a new
+                // one on the queue.
+                mHandler.removeCallbacks(mRestartAuthRunnable);
+                mRestartAuthRunnable.setLastAuthReference(authenticationConsumer);
+                mHandler.postDelayed(mRestartAuthRunnable, AUTH_VALIDITY_MS);
+            });
+        }
+
+        /**
+         * Calls through to the real interface and notifies clients of accept/reject.
+         */
+        void sendAuthenticated(long deviceId, int fingerId, int groupId,
+                ArrayList<Byte> token) {
+            Slog.d(TAG, "sendAuthenticated: " + (fingerId != 0));
+            mFingerprint21.setDebugMessage("Udfps match: " + (fingerId != 0));
+            super.onAuthenticated(deviceId, fingerId, groupId, token);
+        }
+    }
+
+    static Fingerprint21UdfpsMock newInstance(@NonNull Context context, int sensorId,
+            @NonNull LockoutResetDispatcher lockoutResetDispatcher,
+            @NonNull GestureAvailabilityDispatcher gestureAvailabilityDispatcher) {
+        Slog.d(TAG, "Creating Fingerprint23Mock!");
+
+        final Handler handler = new Handler(Looper.getMainLooper());
+        final TestableBiometricScheduler scheduler =
+                new TestableBiometricScheduler(TAG, gestureAvailabilityDispatcher);
+        final MockHalResultController controller =
+                new MockHalResultController(context, handler, scheduler);
+        return new Fingerprint21UdfpsMock(context, scheduler, handler, sensorId,
+                lockoutResetDispatcher, controller);
+    }
+
+    private static abstract class FakeFingerRunnable implements Runnable {
+        private long mFingerDownTime;
+        private int mCaptureDuration;
+
+        /**
+         * @param fingerDownTime System time when onFingerDown occurred
+         * @param captureDuration Duration that the finger needs to be down for
+         */
+        void setSimulationTime(long fingerDownTime, int captureDuration) {
+            mFingerDownTime = fingerDownTime;
+            mCaptureDuration = captureDuration;
+        }
+
+        @SuppressWarnings("BooleanMethodIsAlwaysInverted")
+        boolean isImageCaptureComplete() {
+            return System.currentTimeMillis() - mFingerDownTime > mCaptureDuration;
+        }
+    }
+
+    private final class FakeRejectRunnable extends FakeFingerRunnable {
+        @Override
+        public void run() {
+            mMockHalResultController.sendAuthenticated(0, 0, 0, null);
+        }
+    }
+
+    private final class FakeAcceptRunnable extends FakeFingerRunnable {
+        @Override
+        public void run() {
+            if (mMockHalResultController.mLastAuthArgs == null) {
+                // This can happen if the user has trust agents enabled, which make lockscreen
+                // dismissable. Send a fake non-zero (accept) finger.
+                Slog.d(TAG, "Sending fake finger");
+                mMockHalResultController.sendAuthenticated(1 /* deviceId */,
+                        1 /* fingerId */, 1 /* groupId */, null /* token */);
+            } else {
+                mMockHalResultController.sendAuthenticated(
+                        mMockHalResultController.mLastAuthArgs.deviceId,
+                        mMockHalResultController.mLastAuthArgs.fingerId,
+                        mMockHalResultController.mLastAuthArgs.groupId,
+                        mMockHalResultController.mLastAuthArgs.token);
+            }
+        }
+    }
+
+    /**
+     * The fingerprint HAL allows multiple (5) fingerprint attempts per HIDL invocation of the
+     * authenticate method. However, valid fingerprint authentications are invalidated after
+     * {@link MockHalResultController#AUTH_VALIDITY_MS}, meaning UDFPS touches will be reported as
+     * rejects if touched after that duration. However, since a valid fingerprint was detected, the
+     * HAL and FingerprintService will not look for subsequent fingerprints.
+     *
+     * In order to keep the FingerprintManager API consistent (that multiple fingerprint attempts
+     * are allowed per auth lifecycle), we internally cancel and restart authentication so that the
+     * sensor is responsive again.
+     */
+    private static final class RestartAuthRunnable implements Runnable {
+        @NonNull private final Fingerprint21UdfpsMock mFingerprint21;
+        @NonNull private final TestableBiometricScheduler mScheduler;
+
+        // Store a reference to the auth consumer that should be invalidated.
+        private AuthenticationConsumer mLastAuthConsumer;
+
+        RestartAuthRunnable(@NonNull Fingerprint21UdfpsMock fingerprint21,
+                @NonNull TestableBiometricScheduler scheduler) {
+            mFingerprint21 = fingerprint21;
+            mScheduler = scheduler;
+        }
+
+        void setLastAuthReference(AuthenticationConsumer lastAuthConsumer) {
+            mLastAuthConsumer = lastAuthConsumer;
+        }
+
+        @Override
+        public void run() {
+            final ClientMonitor<?> client = mScheduler.getCurrentClient();
+
+            // We don't care about FingerprintDetectClient, since accept/rejects are both OK. UDFPS
+            // rejects will just simulate the path where non-enrolled fingers are presented.
+            if (!(client instanceof FingerprintAuthenticationClient)) {
+                Slog.e(TAG, "Non-FingerprintAuthenticationClient client: " + client);
+                return;
+            }
+
+            // Perhaps the runnable is stale, or the user stopped/started auth manually. Do not
+            // restart auth in this case.
+            if (client != mLastAuthConsumer) {
+                Slog.e(TAG, "Current client: " + client
+                        + " does not match mLastAuthConsumer: " + mLastAuthConsumer);
+                return;
+            }
+
+            Slog.d(TAG, "Restarting auth, current: " + client);
+            mFingerprint21.setDebugMessage("Auth timed out");
+
+            final FingerprintAuthenticationClient authClient =
+                    (FingerprintAuthenticationClient) client;
+            // Store the authClient parameters so it can be rescheduled
+            final IBinder token = client.getToken();
+            final long operationId = authClient.getOperationId();
+            final int user = client.getTargetUserId();
+            final int cookie = client.getCookie();
+            final ClientMonitorCallbackConverter listener = client.getListener();
+            final String opPackageName = client.getOwnerString();
+            final boolean restricted = authClient.isRestricted();
+            final int statsClient = client.getStatsClient();
+            final boolean isKeyguard = authClient.isKeyguard();
+
+            // Don't actually send cancel() to the HAL, since successful auth already finishes
+            // HAL authenticate() lifecycle. Just
+            mScheduler.getInternalCallback().onClientFinished(client, true /* success */);
+
+            // Schedule this only after we invoke onClientFinished for the previous client, so that
+            // internal preemption logic is not run.
+            mFingerprint21.scheduleAuthenticate(token, operationId, user, cookie,
+                    listener, opPackageName, restricted, statsClient, isKeyguard);
+        }
+    }
+
+    private Fingerprint21UdfpsMock(@NonNull Context context,
+            @NonNull TestableBiometricScheduler scheduler,
+            @NonNull Handler handler, int sensorId,
+            @NonNull LockoutResetDispatcher lockoutResetDispatcher,
+            @NonNull MockHalResultController controller) {
+        super(context, scheduler, handler, sensorId, lockoutResetDispatcher, controller);
+        mScheduler = scheduler;
+        mScheduler.init(this);
+        mHandler = handler;
+        // resetLockout is controlled by the framework, so hardwareAuthToken is not required
+        final boolean resetLockoutRequiresHardwareAuthToken = false;
+        mSensorProperties = new FingerprintSensorProperties(sensorId,
+                FingerprintSensorProperties.TYPE_UDFPS, resetLockoutRequiresHardwareAuthToken);
+        mMockHalResultController = controller;
+        mUserHasTrust = new SparseBooleanArray();
+        mTrustManager = context.getSystemService(TrustManager.class);
+        mTrustManager.registerTrustListener(this);
+        mRandom = new Random();
+        mFakeRejectRunnable = new FakeRejectRunnable();
+        mFakeAcceptRunnable = new FakeAcceptRunnable();
+        mRestartAuthRunnable = new RestartAuthRunnable(this, mScheduler);
+
+        // We can't initialize this during MockHalresultController's constructor due to a circular
+        // dependency.
+        mMockHalResultController.init(mRestartAuthRunnable, this);
+    }
+
+    @Override
+    public void onTrustChanged(boolean enabled, int userId, int flags) {
+        mUserHasTrust.put(userId, enabled);
+    }
+
+    @Override
+    public void onTrustManagedChanged(boolean enabled, int userId) {
+
+    }
+
+    @Override
+    public void onTrustError(CharSequence message) {
+
+    }
+
+    @Override
+    @NonNull
+    FingerprintSensorProperties getFingerprintSensorProperties() {
+        return mSensorProperties;
+    }
+
+    @Override
+    void onFingerDown(int x, int y, float minor, float major) {
+        mHandler.post(() -> {
+            Slog.d(TAG, "onFingerDown");
+            final AuthenticationConsumer lastAuthenticatedConsumer =
+                    mMockHalResultController.getLastAuthenticatedClient();
+            final ClientMonitor<?> currentScheduledClient = mScheduler.getCurrentClient();
+
+            if (currentScheduledClient == null) {
+                Slog.d(TAG, "Not authenticating");
+                return;
+            }
+
+            mHandler.removeCallbacks(mFakeRejectRunnable);
+            mHandler.removeCallbacks(mFakeAcceptRunnable);
+
+            // The sensor was authenticated, is still the currently scheduled client, and the
+            // user touched the UDFPS affordance. Pretend that auth succeeded.
+            final boolean authenticatedClientIsCurrent = lastAuthenticatedConsumer != null
+                    && lastAuthenticatedConsumer == currentScheduledClient;
+            // User is unlocked on keyguard via Trust Agent
+            final boolean keyguardAndTrusted;
+            if (currentScheduledClient instanceof FingerprintAuthenticationClient) {
+                keyguardAndTrusted = ((FingerprintAuthenticationClient) currentScheduledClient)
+                        .isKeyguard()
+                        && mUserHasTrust.get(currentScheduledClient.getTargetUserId(), false);
+            } else {
+                keyguardAndTrusted = false;
+            }
+
+            final int captureDuration = getNewCaptureDuration();
+            final int matchingDuration = getMatchingDuration();
+            final int totalDuration = captureDuration + matchingDuration;
+            setDebugMessage("Duration: " + totalDuration
+                    + " (" + captureDuration + " + " + matchingDuration + ")");
+            if (authenticatedClientIsCurrent || keyguardAndTrusted) {
+                mFakeAcceptRunnable.setSimulationTime(System.currentTimeMillis(), captureDuration);
+                mHandler.postDelayed(mFakeAcceptRunnable, totalDuration);
+            } else if (currentScheduledClient instanceof AuthenticationConsumer) {
+                // Something is authenticating but authentication has not succeeded yet. Pretend
+                // that auth rejected.
+                mFakeRejectRunnable.setSimulationTime(System.currentTimeMillis(), captureDuration);
+                mHandler.postDelayed(mFakeRejectRunnable, totalDuration);
+            }
+        });
+    }
+
+    @Override
+    void onFingerUp() {
+        mHandler.post(() -> {
+            Slog.d(TAG, "onFingerUp");
+
+            // Only one of these can be on the handler at any given time (see onFingerDown). If
+            // image capture is not complete, send ACQUIRED_TOO_FAST and remove the runnable from
+            // the handler. Image capture (onFingerDown) needs to happen again.
+            if (mHandler.hasCallbacks(mFakeRejectRunnable)
+                    && !mFakeRejectRunnable.isImageCaptureComplete()) {
+                mHandler.removeCallbacks(mFakeRejectRunnable);
+                mMockHalResultController.onAcquired(0 /* deviceId */,
+                        FingerprintManager.FINGERPRINT_ACQUIRED_TOO_FAST,
+                        0 /* vendorCode */);
+            } else if (mHandler.hasCallbacks(mFakeAcceptRunnable)
+                    && !mFakeAcceptRunnable.isImageCaptureComplete()) {
+                mHandler.removeCallbacks(mFakeAcceptRunnable);
+                mMockHalResultController.onAcquired(0 /* deviceId */,
+                        FingerprintManager.FINGERPRINT_ACQUIRED_TOO_FAST,
+                        0 /* vendorCode */);
+            }
+        });
+    }
+
+    private int getNewCaptureDuration() {
+        final ContentResolver contentResolver = mContext.getContentResolver();
+        final int captureTime = Settings.Secure.getIntForUser(contentResolver,
+                CONFIG_AUTH_DELAY_PT1,
+                DEFAULT_AUTH_DELAY_PT1_MS,
+                UserHandle.USER_CURRENT);
+        final int randomDelayRange = Settings.Secure.getIntForUser(contentResolver,
+                CONFIG_AUTH_DELAY_RANDOMNESS,
+                DEFAULT_AUTH_DELAY_RANDOMNESS_MS,
+                UserHandle.USER_CURRENT);
+        final int randomDelay = mRandom.nextInt(randomDelayRange * 2) - randomDelayRange;
+
+        // Must be at least 0
+        return Math.max(captureTime + randomDelay, 0);
+    }
+
+    private int getMatchingDuration() {
+        final int matchingTime = Settings.Secure.getIntForUser(mContext.getContentResolver(),
+                CONFIG_AUTH_DELAY_PT2,
+                DEFAULT_AUTH_DELAY_PT2_MS,
+                UserHandle.USER_CURRENT);
+
+        // Must be at least 0
+        return Math.max(matchingTime, 0);
+    }
+
+    private void setDebugMessage(String message) {
+        try {
+            final IUdfpsOverlayController controller = getUdfpsOverlayController();
+            // Things can happen before SysUI loads and sets the controller.
+            if (controller != null) {
+                Slog.d(TAG, "setDebugMessage: " + message);
+                controller.setDebugMessage(message);
+            }
+        } catch (RemoteException e) {
+            Slog.e(TAG, "Remote exception when sending message: " + message, e);
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintAuthenticationClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintAuthenticationClient.java
index 1564056..99d348a 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintAuthenticationClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintAuthenticationClient.java
@@ -29,7 +29,6 @@
 import android.os.IBinder;
 import android.os.RemoteException;
 import android.util.Slog;
-import android.view.Surface;
 
 import com.android.server.biometrics.sensors.AuthenticationClient;
 import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter;
@@ -47,6 +46,7 @@
 
     private static final String TAG = "Biometrics/FingerprintAuthClient";
 
+    private final boolean mIsKeyguard;
     private final LockoutFrameworkImpl mLockoutFrameworkImpl;
     @Nullable private final IUdfpsOverlayController mUdfpsOverlayController;
 
@@ -54,16 +54,17 @@
             @NonNull LazyDaemon<IBiometricsFingerprint> lazyDaemon, @NonNull IBinder token,
             @NonNull ClientMonitorCallbackConverter listener, int targetUserId, long operationId,
             boolean restricted, @NonNull String owner, int cookie, boolean requireConfirmation,
-            int sensorId, boolean isStrongBiometric, @Nullable Surface surface, int statsClient,
+            int sensorId, boolean isStrongBiometric, int statsClient,
             @NonNull TaskStackListener taskStackListener,
             @NonNull LockoutFrameworkImpl lockoutTracker,
-            @Nullable IUdfpsOverlayController udfpsOverlayController) {
+            @Nullable IUdfpsOverlayController udfpsOverlayController, boolean isKeyguard) {
         super(context, lazyDaemon, token, listener, targetUserId, operationId, restricted,
                 owner, cookie, requireConfirmation, sensorId, isStrongBiometric,
                 BiometricsProtoEnums.MODALITY_FINGERPRINT, statsClient, taskStackListener,
                 lockoutTracker);
         mLockoutFrameworkImpl = lockoutTracker;
         mUdfpsOverlayController = udfpsOverlayController;
+        mIsKeyguard = isKeyguard;
     }
 
     @Override
@@ -79,7 +80,7 @@
         if (authenticated) {
             resetFailedAttempts(getTargetUserId());
             UdfpsHelper.hideUdfpsOverlay(mUdfpsOverlayController);
-            mFinishCallback.onClientFinished(this, true /* success */);
+            mCallback.onClientFinished(this, true /* success */);
         } else {
             final @LockoutTracker.LockoutMode int lockoutMode =
                     mLockoutFrameworkImpl.getLockoutModeForUser(getTargetUserId());
@@ -119,7 +120,7 @@
             onError(BiometricFingerprintConstants.FINGERPRINT_ERROR_HW_UNAVAILABLE,
                     0 /* vendorCode */);
             UdfpsHelper.hideUdfpsOverlay(mUdfpsOverlayController);
-            mFinishCallback.onClientFinished(this, false /* success */);
+            mCallback.onClientFinished(this, false /* success */);
         }
     }
 
@@ -132,7 +133,7 @@
             Slog.e(TAG, "Remote exception when requesting cancel", e);
             onError(BiometricFingerprintConstants.FINGERPRINT_ERROR_HW_UNAVAILABLE,
                     0 /* vendorCode */);
-            mFinishCallback.onClientFinished(this, false /* success */);
+            mCallback.onClientFinished(this, false /* success */);
         }
     }
 
@@ -145,4 +146,8 @@
     public void onFingerUp() {
         UdfpsHelper.onFingerUp(getFreshDaemon());
     }
+
+    public boolean isKeyguard() {
+        return mIsKeyguard;
+    }
 }
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintAuthenticator.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintAuthenticator.java
index 3418c46..21a46d5 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintAuthenticator.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintAuthenticator.java
@@ -75,11 +75,6 @@
     }
 
     @Override
-    public void resetLockout(int userId, byte[] hardwareAuthToken) throws RemoteException {
-        mFingerprintService.resetLockout(userId, hardwareAuthToken);
-    }
-
-    @Override
     public long getAuthenticatorId(int callingUserId) throws RemoteException {
         return mFingerprintService.getAuthenticatorId(callingUserId);
     }
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintDetectClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintDetectClient.java
index 8b295f8..8652ee4 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintDetectClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintDetectClient.java
@@ -68,13 +68,13 @@
             Slog.e(TAG, "Remote exception when requesting cancel", e);
             onError(BiometricFingerprintConstants.FINGERPRINT_ERROR_HW_UNAVAILABLE,
                     0 /* vendorCode */);
-            mFinishCallback.onClientFinished(this, false /* success */);
+            mCallback.onClientFinished(this, false /* success */);
         }
     }
 
     @Override
-    public void start(@NonNull FinishCallback finishCallback) {
-        super.start(finishCallback);
+    public void start(@NonNull Callback callback) {
+        super.start(callback);
         startHalOperation();
     }
 
@@ -88,7 +88,7 @@
             onError(BiometricFingerprintConstants.FINGERPRINT_ERROR_HW_UNAVAILABLE,
                     0 /* vendorCode */);
             UdfpsHelper.hideUdfpsOverlay(mUdfpsOverlayController);
-            mFinishCallback.onClientFinished(this, false /* success */);
+            mCallback.onClientFinished(this, false /* success */);
         }
     }
 
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintEnrollClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintEnrollClient.java
index 32f8b8f..d5db6e4 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintEnrollClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintEnrollClient.java
@@ -79,7 +79,7 @@
             onError(BiometricFingerprintConstants.FINGERPRINT_ERROR_HW_UNAVAILABLE,
                     0 /* vendorCode */);
             UdfpsHelper.hideUdfpsOverlay(mUdfpsOverlayController);
-            mFinishCallback.onClientFinished(this, false /* success */);
+            mCallback.onClientFinished(this, false /* success */);
         }
     }
 
@@ -92,7 +92,7 @@
             Slog.e(TAG, "Remote exception when requesting cancel", e);
             onError(BiometricFingerprintConstants.FINGERPRINT_ERROR_HW_UNAVAILABLE,
                     0 /* vendorCode */);
-            mFinishCallback.onClientFinished(this, false /* success */);
+            mCallback.onClientFinished(this, false /* success */);
         }
     }
 
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintInternalEnumerateClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintInternalEnumerateClient.java
index 240c3c5..834bf42 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintInternalEnumerateClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintInternalEnumerateClient.java
@@ -52,7 +52,7 @@
             getFreshDaemon().enumerate();
         } catch (RemoteException e) {
             Slog.e(TAG, "Remote exception when requesting enumerate", e);
-            mFinishCallback.onClientFinished(this, false /* success */);
+            mCallback.onClientFinished(this, false /* success */);
         }
     }
 }
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintRemovalClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintRemovalClient.java
index a9336ef..9f54563 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintRemovalClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintRemovalClient.java
@@ -54,7 +54,7 @@
             getFreshDaemon().remove(getTargetUserId(), mBiometricId);
         } catch (RemoteException e) {
             Slog.e(TAG, "Remote exception when requesting remove", e);
-            mFinishCallback.onClientFinished(this, false /* success */);
+            mCallback.onClientFinished(this, false /* success */);
         }
     }
 }
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java
index e4387c9..7c7da11 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java
@@ -25,6 +25,7 @@
 import static android.Manifest.permission.USE_FINGERPRINT;
 
 import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.app.AppOpsManager;
 import android.content.Context;
 import android.content.pm.PackageManager;
@@ -38,14 +39,17 @@
 import android.hardware.fingerprint.IFingerprintServiceReceiver;
 import android.hardware.fingerprint.IUdfpsOverlayController;
 import android.os.Binder;
+import android.os.Build;
 import android.os.IBinder;
 import android.os.NativeHandle;
 import android.os.Process;
 import android.os.UserHandle;
+import android.provider.Settings;
 import android.util.EventLog;
 import android.util.Slog;
 import android.view.Surface;
 
+import com.android.internal.R;
 import com.android.internal.util.DumpUtils;
 import com.android.internal.widget.LockPatternUtils;
 import com.android.server.SystemService;
@@ -94,10 +98,16 @@
         }
 
         @Override // Binder call
-        public void generateChallenge(IBinder token, IFingerprintServiceReceiver receiver,
-                String opPackageName) {
+        public void generateChallenge(IBinder token, int sensorId,
+                IFingerprintServiceReceiver receiver, String opPackageName) {
             Utils.checkPermission(getContext(), MANAGE_FINGERPRINT);
-            mFingerprint21.scheduleGenerateChallenge(token, receiver, opPackageName);
+
+            if (sensorId == mFingerprint21.getFingerprintSensorProperties().sensorId) {
+                mFingerprint21.scheduleGenerateChallenge(token, receiver, opPackageName);
+                return;
+            }
+
+            Slog.w(TAG, "No matching sensor for generateChallenge, sensorId: " + sensorId);
         }
 
         @Override // Binder call
@@ -158,8 +168,8 @@
             final int statsClient = isKeyguard ? BiometricsProtoEnums.CLIENT_KEYGUARD
                     : BiometricsProtoEnums.CLIENT_FINGERPRINT_MANAGER;
             mFingerprint21.scheduleAuthenticate(token, operationId, userId, 0 /* cookie */,
-                    new ClientMonitorCallbackConverter(receiver), opPackageName, surface,
-                    restricted, statsClient);
+                    new ClientMonitorCallbackConverter(receiver), opPackageName,
+                    restricted, statsClient, isKeyguard);
         }
 
         @Override
@@ -193,8 +203,8 @@
 
             final boolean restricted = true; // BiometricPrompt is always restricted
             mFingerprint21.scheduleAuthenticate(token, operationId, userId, cookie,
-                    new ClientMonitorCallbackConverter(sensorReceiver), opPackageName, surface,
-                    restricted, BiometricsProtoEnums.CLIENT_BIOMETRIC_PROMPT);
+                    new ClientMonitorCallbackConverter(sensorReceiver), opPackageName, restricted,
+                    BiometricsProtoEnums.CLIENT_BIOMETRIC_PROMPT, false /* isKeyguard */);
         }
 
         @Override // Binder call
@@ -343,9 +353,16 @@
         }
 
         @Override // Binder call
-        public void resetLockout(int userId, byte [] hardwareAuthToken) {
+        public void resetLockout(IBinder token, int sensorId, int userId,
+                @Nullable byte [] hardwareAuthToken, String opPackageName) {
             Utils.checkPermission(getContext(), RESET_FINGERPRINT_LOCKOUT);
-            mFingerprint21.scheduleResetLockout(userId, hardwareAuthToken);
+
+            if (sensorId == mFingerprint21.getFingerprintSensorProperties().sensorId) {
+                mFingerprint21.scheduleResetLockout(userId);
+                return;
+            }
+
+            Slog.w(TAG, "No matching sensor for resetLockout, sensorId: " + sensorId);
         }
 
         @Override
@@ -369,8 +386,18 @@
         @Override // Binder call
         public void initializeConfiguration(int sensorId) {
             Utils.checkPermission(getContext(), USE_BIOMETRIC_INTERNAL);
-            mFingerprint21 = new Fingerprint21(getContext(), sensorId, mLockoutResetDispatcher,
-                    mGestureAvailabilityDispatcher);
+
+            if ((Build.IS_USERDEBUG || Build.IS_ENG)
+                    && getContext().getResources().getBoolean(R.bool.allow_test_udfps)
+                    && Settings.Secure.getIntForUser(getContext().getContentResolver(),
+                    Fingerprint21UdfpsMock.CONFIG_ENABLE_TEST_UDFPS, 0 /* default */,
+                    UserHandle.USER_CURRENT) != 0) {
+                mFingerprint21 = Fingerprint21UdfpsMock.newInstance(getContext(), sensorId,
+                        mLockoutResetDispatcher, mGestureAvailabilityDispatcher);
+            } else {
+                mFingerprint21 = Fingerprint21.newInstance(getContext(), sensorId,
+                        mLockoutResetDispatcher, mGestureAvailabilityDispatcher);
+            }
         }
 
         @Override
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintUpdateActiveUserClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintUpdateActiveUserClient.java
index e1082ae..c1c3593 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintUpdateActiveUserClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintUpdateActiveUserClient.java
@@ -22,7 +22,6 @@
 import android.hardware.biometrics.fingerprint.V2_1.IBiometricsFingerprint;
 import android.os.Build;
 import android.os.Environment;
-import android.os.IBinder;
 import android.os.RemoteException;
 import android.os.SELinux;
 import android.util.Slog;
@@ -58,8 +57,8 @@
     }
 
     @Override
-    public void start(@NonNull FinishCallback finishCallback) {
-        super.start(finishCallback);
+    public void start(@NonNull Callback callback) {
+        super.start(callback);
 
         if (mCurrentUserId == getTargetUserId()) {
             Slog.d(TAG, "Already user: " + mCurrentUserId + ", refreshing authenticatorId");
@@ -69,7 +68,7 @@
             } catch (RemoteException e) {
                 Slog.e(TAG, "Unable to refresh authenticatorId", e);
             }
-            finishCallback.onClientFinished(this, true /* success */);
+            callback.onClientFinished(this, true /* success */);
             return;
         }
 
@@ -89,7 +88,7 @@
         if (!mDirectory.exists()) {
             if (!mDirectory.mkdir()) {
                 Slog.e(TAG, "Cannot make directory: " + mDirectory.getAbsolutePath());
-                finishCallback.onClientFinished(this, false /* success */);
+                callback.onClientFinished(this, false /* success */);
                 return;
             }
             // Calling mkdir() from this process will create a directory with our
@@ -97,7 +96,7 @@
             // the label.
             if (!SELinux.restorecon(mDirectory)) {
                 Slog.e(TAG, "Restorecons failed. Directory will have wrong label.");
-                finishCallback.onClientFinished(this, false /* success */);
+                callback.onClientFinished(this, false /* success */);
                 return;
             }
         }
@@ -116,10 +115,10 @@
             getFreshDaemon().setActiveGroup(getTargetUserId(), mDirectory.getAbsolutePath());
             mAuthenticatorIds.put(getTargetUserId(), mHasEnrolledBiometrics
                     ? getFreshDaemon().getAuthenticatorId() : 0L);
-            mFinishCallback.onClientFinished(this, true /* success */);
+            mCallback.onClientFinished(this, true /* success */);
         } catch (RemoteException e) {
             Slog.e(TAG, "Failed to setActiveGroup: " + e);
-            mFinishCallback.onClientFinished(this, false /* success */);
+            mCallback.onClientFinished(this, false /* success */);
         }
     }
 }
diff --git a/services/core/java/com/android/server/biometrics/sensors/iris/IrisAuthenticator.java b/services/core/java/com/android/server/biometrics/sensors/iris/IrisAuthenticator.java
index 9e04057..0400ef5 100644
--- a/services/core/java/com/android/server/biometrics/sensors/iris/IrisAuthenticator.java
+++ b/services/core/java/com/android/server/biometrics/sensors/iris/IrisAuthenticator.java
@@ -70,10 +70,6 @@
     }
 
     @Override
-    public void resetLockout(int userId, byte[] hardwareAuthToken) throws RemoteException {
-    }
-
-    @Override
     public long getAuthenticatorId(int callingUserId) throws RemoteException {
         return 0;
     }
diff --git a/services/core/java/com/android/server/camera/CameraServiceProxy.java b/services/core/java/com/android/server/camera/CameraServiceProxy.java
index 61c99b8..88867fc 100644
--- a/services/core/java/com/android/server/camera/CameraServiceProxy.java
+++ b/services/core/java/com/android/server/camera/CameraServiceProxy.java
@@ -15,6 +15,8 @@
  */
 package com.android.server.camera;
 
+import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.Intent;
@@ -43,6 +45,7 @@
 import com.android.server.LocalServices;
 import com.android.server.ServiceThread;
 import com.android.server.SystemService;
+import com.android.server.SystemService.TargetUser;
 import com.android.server.wm.WindowManagerInternal;
 
 import java.util.ArrayList;
@@ -252,20 +255,20 @@
     }
 
     @Override
-    public void onStartUser(int userHandle) {
+    public void onUserStarting(@NonNull TargetUser user) {
         synchronized(mLock) {
             if (mEnabledCameraUsers == null) {
                 // Initialize cameraserver, or update cameraserver if we are recovering
                 // from a crash.
-                switchUserLocked(userHandle);
+                switchUserLocked(user.getUserIdentifier());
             }
         }
     }
 
     @Override
-    public void onSwitchUser(int userHandle) {
+    public void onUserSwitching(@Nullable TargetUser from, @NonNull TargetUser to) {
         synchronized(mLock) {
-            switchUserLocked(userHandle);
+            switchUserLocked(to.getUserIdentifier());
         }
     }
 
diff --git a/services/core/java/com/android/server/clipboard/ClipboardService.java b/services/core/java/com/android/server/clipboard/ClipboardService.java
index ed3a223..a0bc7d8 100644
--- a/services/core/java/com/android/server/clipboard/ClipboardService.java
+++ b/services/core/java/com/android/server/clipboard/ClipboardService.java
@@ -19,6 +19,7 @@
 import static android.app.ActivityManagerInternal.ALLOW_FULL_ONLY;
 
 import android.Manifest;
+import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.UserIdInt;
 import android.app.ActivityManagerInternal;
@@ -59,6 +60,7 @@
 
 import com.android.server.LocalServices;
 import com.android.server.SystemService;
+import com.android.server.SystemService.TargetUser;
 import com.android.server.contentcapture.ContentCaptureManagerInternal;
 import com.android.server.uri.UriGrantsManagerInternal;
 import com.android.server.wm.WindowManagerInternal;
@@ -218,9 +220,9 @@
     }
 
     @Override
-    public void onCleanupUser(int userId) {
+    public void onUserStopped(@NonNull TargetUser user) {
         synchronized (mClipboards) {
-            mClipboards.remove(userId);
+            mClipboards.remove(user.getUserIdentifier());
         }
     }
 
diff --git a/services/core/java/com/android/server/connectivity/PermissionMonitor.java b/services/core/java/com/android/server/connectivity/PermissionMonitor.java
index f8774b1..a75a80a 100644
--- a/services/core/java/com/android/server/connectivity/PermissionMonitor.java
+++ b/services/core/java/com/android/server/connectivity/PermissionMonitor.java
@@ -21,23 +21,14 @@
 import static android.Manifest.permission.INTERNET;
 import static android.Manifest.permission.NETWORK_STACK;
 import static android.Manifest.permission.UPDATE_DEVICE_STATS;
+import static android.content.pm.PackageInfo.REQUESTED_PERMISSION_GRANTED;
 import static android.content.pm.PackageManager.GET_PERMISSIONS;
 import static android.content.pm.PackageManager.MATCH_ANY_USER;
-import static android.net.INetd.PERMISSION_INTERNET;
-import static android.net.INetd.PERMISSION_NETWORK;
-import static android.net.INetd.PERMISSION_NONE;
-import static android.net.INetd.PERMISSION_SYSTEM;
-import static android.net.INetd.PERMISSION_UNINSTALLED;
-import static android.net.INetd.PERMISSION_UPDATE_DEVICE_STATS;
 import static android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK;
 import static android.os.Process.INVALID_UID;
 import static android.os.Process.SYSTEM_UID;
 
-import static com.android.internal.util.ArrayUtils.convertToIntArray;
-
 import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.app.ActivityManager;
 import android.content.Context;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageInfo;
@@ -60,6 +51,7 @@
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.ArrayUtils;
 import com.android.internal.util.IndentingPrintWriter;
 import com.android.server.LocalServices;
 import com.android.server.SystemConfig;
@@ -73,6 +65,7 @@
 import java.util.Map.Entry;
 import java.util.Set;
 
+
 /**
  * A utility class to inform Netd of UID permisisons.
  * Does a mass update at boot and then monitors for app install/remove.
@@ -121,13 +114,6 @@
         public int getDeviceFirstSdkInt() {
             return Build.VERSION.FIRST_SDK_INT;
         }
-
-        /**
-         * Check whether given uid has specific permission.
-         */
-        public int uidPermission(@NonNull final String permission, final int uid) {
-            return ActivityManager.checkUidPermission(permission, uid);
-        }
     }
 
     public PermissionMonitor(@NonNull final Context context, @NonNull final INetd netd) {
@@ -170,9 +156,8 @@
             }
             mAllApps.add(UserHandle.getAppId(uid));
 
-            final boolean isNetwork = hasPermission(CHANGE_NETWORK_STATE, uid);
-            final boolean hasRestrictedPermission =
-                    hasRestrictedNetworkPermission(app.applicationInfo);
+            boolean isNetwork = hasNetworkPermission(app);
+            boolean hasRestrictedPermission = hasRestrictedNetworkPermission(app);
 
             if (isNetwork || hasRestrictedPermission) {
                 Boolean permission = mApps.get(uid);
@@ -184,7 +169,8 @@
             }
 
             //TODO: unify the management of the permissions into one codepath.
-            final int otherNetdPerms = getNetdPermissionMask(uid);
+            int otherNetdPerms = getNetdPermissionMask(app.requestedPermissions,
+                    app.requestedPermissionsFlags);
             netdPermsUids.put(uid, netdPermsUids.get(uid) | otherNetdPerms);
         }
 
@@ -204,8 +190,9 @@
             // Get the uids of native services that have UPDATE_DEVICE_STATS or INTERNET permission.
             if (perms != null) {
                 netdPermission |= perms.contains(UPDATE_DEVICE_STATS)
-                        ? PERMISSION_UPDATE_DEVICE_STATS : 0;
-                netdPermission |= perms.contains(INTERNET) ? PERMISSION_INTERNET : 0;
+                        ? INetd.PERMISSION_UPDATE_DEVICE_STATS : 0;
+                netdPermission |= perms.contains(INTERNET)
+                        ? INetd.PERMISSION_INTERNET : 0;
             }
             netdPermsUids.put(uid, netdPermsUids.get(uid) | netdPermission);
         }
@@ -220,33 +207,48 @@
     }
 
     @VisibleForTesting
-    boolean hasPermission(@NonNull final String permission, final int uid) {
-        return mDeps.uidPermission(permission, uid) == PackageManager.PERMISSION_GRANTED;
+    boolean hasPermission(@NonNull final PackageInfo app, @NonNull final String permission) {
+        if (app.requestedPermissions == null || app.requestedPermissionsFlags == null) {
+            return false;
+        }
+        final int index = ArrayUtils.indexOf(app.requestedPermissions, permission);
+        if (index < 0 || index >= app.requestedPermissionsFlags.length) return false;
+        return (app.requestedPermissionsFlags[index] & REQUESTED_PERMISSION_GRANTED) != 0;
     }
 
     @VisibleForTesting
-    boolean hasRestrictedNetworkPermission(@Nullable final ApplicationInfo appInfo) {
-        if (appInfo == null)  return false;
-        // TODO : remove this check in the future(b/162295056). All apps should just
+    boolean hasNetworkPermission(@NonNull final PackageInfo app) {
+        return hasPermission(app, CHANGE_NETWORK_STATE);
+    }
+
+    @VisibleForTesting
+    boolean hasRestrictedNetworkPermission(@NonNull final PackageInfo app) {
+        // TODO : remove this check in the future(b/31479477). All apps should just
         // request the appropriate permission for their use case since android Q.
-        if ((appInfo.targetSdkVersion < VERSION_Q && isVendorApp(appInfo))
-                // Backward compatibility for b/114245686, on devices that launched before Q daemons
-                // and apps running as the system UID are exempted from this check.
-                || (appInfo.uid == SYSTEM_UID && mDeps.getDeviceFirstSdkInt() < VERSION_Q)) {
-            return true;
+        if (app.applicationInfo != null) {
+            // Backward compatibility for b/114245686, on devices that launched before Q daemons
+            // and apps running as the system UID are exempted from this check.
+            if (app.applicationInfo.uid == SYSTEM_UID && mDeps.getDeviceFirstSdkInt() < VERSION_Q) {
+                return true;
+            }
+
+            if (app.applicationInfo.targetSdkVersion < VERSION_Q
+                    && isVendorApp(app.applicationInfo)) {
+                return true;
+            }
         }
 
-        return hasPermission(PERMISSION_MAINLINE_NETWORK_STACK, appInfo.uid)
-                || hasPermission(NETWORK_STACK, appInfo.uid)
-                || hasPermission(CONNECTIVITY_USE_RESTRICTED_NETWORKS, appInfo.uid);
+        return hasPermission(app, PERMISSION_MAINLINE_NETWORK_STACK)
+                || hasPermission(app, NETWORK_STACK)
+                || hasPermission(app, CONNECTIVITY_USE_RESTRICTED_NETWORKS);
     }
 
     /** Returns whether the given uid has using background network permission. */
     public synchronized boolean hasUseBackgroundNetworksPermission(final int uid) {
         // Apps with any of the CHANGE_NETWORK_STATE, NETWORK_STACK, CONNECTIVITY_INTERNAL or
         // CONNECTIVITY_USE_RESTRICTED_NETWORKS permission has the permission to use background
-        // networks. mApps contains the result of checks for both CHANGE_NETWORK_STATE permission
-        // and hasRestrictedNetworkPermission. If uid is in the mApps list that means uid has one of
+        // networks. mApps contains the result of checks for both hasNetworkPermission and
+        // hasRestrictedNetworkPermission. If uid is in the mApps list that means uid has one of
         // permissions at least.
         return mApps.containsKey(uid);
     }
@@ -271,11 +273,11 @@
         }
         try {
             if (add) {
-                mNetd.networkSetPermissionForUser(PERMISSION_NETWORK, convertToIntArray(network));
-                mNetd.networkSetPermissionForUser(PERMISSION_SYSTEM, convertToIntArray(system));
+                mNetd.networkSetPermissionForUser(INetd.PERMISSION_NETWORK, toIntArray(network));
+                mNetd.networkSetPermissionForUser(INetd.PERMISSION_SYSTEM, toIntArray(system));
             } else {
-                mNetd.networkClearPermissionForUser(convertToIntArray(network));
-                mNetd.networkClearPermissionForUser(convertToIntArray(system));
+                mNetd.networkClearPermissionForUser(toIntArray(network));
+                mNetd.networkClearPermissionForUser(toIntArray(system));
             }
         } catch (RemoteException e) {
             loge("Exception when updating permissions: " + e);
@@ -321,15 +323,14 @@
     }
 
     @VisibleForTesting
-    protected Boolean highestPermissionForUid(Boolean currentPermission, String name, int uid) {
+    protected Boolean highestPermissionForUid(Boolean currentPermission, String name) {
         if (currentPermission == SYSTEM) {
             return currentPermission;
         }
         try {
             final PackageInfo app = mPackageManager.getPackageInfo(name, GET_PERMISSIONS);
-            final boolean isNetwork = hasPermission(CHANGE_NETWORK_STATE, uid);
-            final boolean hasRestrictedPermission =
-                    hasRestrictedNetworkPermission(app.applicationInfo);
+            final boolean isNetwork = hasNetworkPermission(app);
+            final boolean hasRestrictedPermission = hasRestrictedNetworkPermission(app);
             if (isNetwork || hasRestrictedPermission) {
                 currentPermission = hasRestrictedPermission;
             }
@@ -341,14 +342,23 @@
     }
 
     private int getPermissionForUid(final int uid) {
+        int permission = INetd.PERMISSION_NONE;
         // Check all the packages for this UID. The UID has the permission if any of the
         // packages in it has the permission.
         final String[] packages = mPackageManager.getPackagesForUid(uid);
-        if (packages == null || packages.length <= 0) {
+        if (packages != null && packages.length > 0) {
+            for (String name : packages) {
+                final PackageInfo app = getPackageInfo(name);
+                if (app != null && app.requestedPermissions != null) {
+                    permission |= getNetdPermissionMask(app.requestedPermissions,
+                            app.requestedPermissionsFlags);
+                }
+            }
+        } else {
             // The last package of this uid is removed from device. Clean the package up.
-            return PERMISSION_UNINSTALLED;
+            permission = INetd.PERMISSION_UNINSTALLED;
         }
-        return getNetdPermissionMask(uid);
+        return permission;
     }
 
     /**
@@ -365,7 +375,7 @@
 
         // If multiple packages share a UID (cf: android:sharedUserId) and ask for different
         // permissions, don't downgrade (i.e., if it's already SYSTEM, leave it as is).
-        final Boolean permission = highestPermissionForUid(mApps.get(uid), packageName, uid);
+        final Boolean permission = highestPermissionForUid(mApps.get(uid), packageName);
         if (permission != mApps.get(uid)) {
             mApps.put(uid, permission);
 
@@ -421,7 +431,7 @@
         String[] packages = mPackageManager.getPackagesForUid(uid);
         if (packages != null && packages.length > 0) {
             for (String name : packages) {
-                permission = highestPermissionForUid(permission, name, uid);
+                permission = highestPermissionForUid(permission, name);
                 if (permission == SYSTEM) {
                     // An app with this UID still has the SYSTEM permission.
                     // Therefore, this UID must already have the SYSTEM permission.
@@ -457,13 +467,19 @@
         sendPackagePermissionsForUid(uid, getPermissionForUid(uid));
     }
 
-    private int getNetdPermissionMask(final int uid) {
-        int permissions = PERMISSION_NONE;
-        if (hasPermission(INTERNET, uid)) {
-            permissions |= PERMISSION_INTERNET;
-        }
-        if (hasPermission(UPDATE_DEVICE_STATS, uid)) {
-            permissions |= PERMISSION_UPDATE_DEVICE_STATS;
+    private static int getNetdPermissionMask(String[] requestedPermissions,
+                                             int[] requestedPermissionsFlags) {
+        int permissions = 0;
+        if (requestedPermissions == null || requestedPermissionsFlags == null) return permissions;
+        for (int i = 0; i < requestedPermissions.length; i++) {
+            if (requestedPermissions[i].equals(INTERNET)
+                    && ((requestedPermissionsFlags[i] & REQUESTED_PERMISSION_GRANTED) != 0)) {
+                permissions |= INetd.PERMISSION_INTERNET;
+            }
+            if (requestedPermissions[i].equals(UPDATE_DEVICE_STATS)
+                    && ((requestedPermissionsFlags[i] & REQUESTED_PERMISSION_GRANTED) != 0)) {
+                permissions |= INetd.PERMISSION_UPDATE_DEVICE_STATS;
+            }
         }
         return permissions;
     }
@@ -632,19 +648,19 @@
         for (int i = 0; i < netdPermissionsAppIds.size(); i++) {
             int permissions = netdPermissionsAppIds.valueAt(i);
             switch(permissions) {
-                case (PERMISSION_INTERNET | PERMISSION_UPDATE_DEVICE_STATS):
+                case (INetd.PERMISSION_INTERNET | INetd.PERMISSION_UPDATE_DEVICE_STATS):
                     allPermissionAppIds.add(netdPermissionsAppIds.keyAt(i));
                     break;
-                case PERMISSION_INTERNET:
+                case INetd.PERMISSION_INTERNET:
                     internetPermissionAppIds.add(netdPermissionsAppIds.keyAt(i));
                     break;
-                case PERMISSION_UPDATE_DEVICE_STATS:
+                case INetd.PERMISSION_UPDATE_DEVICE_STATS:
                     updateStatsPermissionAppIds.add(netdPermissionsAppIds.keyAt(i));
                     break;
-                case PERMISSION_NONE:
+                case INetd.PERMISSION_NONE:
                     noPermissionAppIds.add(netdPermissionsAppIds.keyAt(i));
                     break;
-                case PERMISSION_UNINSTALLED:
+                case INetd.PERMISSION_UNINSTALLED:
                     uninstalledAppIds.add(netdPermissionsAppIds.keyAt(i));
                 default:
                     Log.e(TAG, "unknown permission type: " + permissions + "for uid: "
@@ -655,24 +671,24 @@
             // TODO: add a lock inside netd to protect IPC trafficSetNetPermForUids()
             if (allPermissionAppIds.size() != 0) {
                 mNetd.trafficSetNetPermForUids(
-                        PERMISSION_INTERNET | PERMISSION_UPDATE_DEVICE_STATS,
-                        convertToIntArray(allPermissionAppIds));
+                        INetd.PERMISSION_INTERNET | INetd.PERMISSION_UPDATE_DEVICE_STATS,
+                        ArrayUtils.convertToIntArray(allPermissionAppIds));
             }
             if (internetPermissionAppIds.size() != 0) {
-                mNetd.trafficSetNetPermForUids(PERMISSION_INTERNET,
-                        convertToIntArray(internetPermissionAppIds));
+                mNetd.trafficSetNetPermForUids(INetd.PERMISSION_INTERNET,
+                        ArrayUtils.convertToIntArray(internetPermissionAppIds));
             }
             if (updateStatsPermissionAppIds.size() != 0) {
-                mNetd.trafficSetNetPermForUids(PERMISSION_UPDATE_DEVICE_STATS,
-                        convertToIntArray(updateStatsPermissionAppIds));
+                mNetd.trafficSetNetPermForUids(INetd.PERMISSION_UPDATE_DEVICE_STATS,
+                        ArrayUtils.convertToIntArray(updateStatsPermissionAppIds));
             }
             if (noPermissionAppIds.size() != 0) {
-                mNetd.trafficSetNetPermForUids(PERMISSION_NONE,
-                        convertToIntArray(noPermissionAppIds));
+                mNetd.trafficSetNetPermForUids(INetd.PERMISSION_NONE,
+                        ArrayUtils.convertToIntArray(noPermissionAppIds));
             }
             if (uninstalledAppIds.size() != 0) {
-                mNetd.trafficSetNetPermForUids(PERMISSION_UNINSTALLED,
-                        convertToIntArray(uninstalledAppIds));
+                mNetd.trafficSetNetPermForUids(INetd.PERMISSION_UNINSTALLED,
+                        ArrayUtils.convertToIntArray(uninstalledAppIds));
             }
         } catch (RemoteException e) {
             Log.e(TAG, "Pass appId list of special permission failed." + e);
diff --git a/services/core/java/com/android/server/connectivity/Vpn.java b/services/core/java/com/android/server/connectivity/Vpn.java
index 1f85d10..5484bfc 100644
--- a/services/core/java/com/android/server/connectivity/Vpn.java
+++ b/services/core/java/com/android/server/connectivity/Vpn.java
@@ -48,6 +48,7 @@
 import android.content.pm.ResolveInfo;
 import android.content.pm.UserInfo;
 import android.net.ConnectivityManager;
+import android.net.DnsResolver;
 import android.net.INetworkManagementEventObserver;
 import android.net.Ikev2VpnProfile;
 import android.net.IpPrefix;
@@ -79,6 +80,7 @@
 import android.os.Binder;
 import android.os.Build.VERSION_CODES;
 import android.os.Bundle;
+import android.os.CancellationSignal;
 import android.os.FileUtils;
 import android.os.IBinder;
 import android.os.INetworkManagementService;
@@ -123,6 +125,7 @@
 import java.net.Inet4Address;
 import java.net.Inet6Address;
 import java.net.InetAddress;
+import java.net.UnknownHostException;
 import java.nio.charset.StandardCharsets;
 import java.security.GeneralSecurityException;
 import java.util.ArrayList;
@@ -134,6 +137,8 @@
 import java.util.Set;
 import java.util.SortedSet;
 import java.util.TreeSet;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ExecutionException;
 import java.util.concurrent.Executor;
 import java.util.concurrent.ExecutorService;
 import java.util.concurrent.Executors;
@@ -148,8 +153,8 @@
     private static final boolean LOGD = true;
 
     // Length of time (in milliseconds) that an app hosting an always-on VPN is placed on
-    // the device idle whitelist during service launch and VPN bootstrap.
-    private static final long VPN_LAUNCH_IDLE_WHITELIST_DURATION_MS = 60 * 1000;
+    // the device idle allowlist during service launch and VPN bootstrap.
+    private static final long VPN_LAUNCH_IDLE_ALLOWLIST_DURATION_MS = 60 * 1000;
 
     // Settings for how much of the address space should be routed so that Vpn considers
     // "most" of the address space is routed. This is used to determine whether this Vpn
@@ -175,7 +180,8 @@
     // This is taken as a total of IPv4 + IPV6 routes for simplicity, but the algorithm
     // is actually O(n²)+O(n²).
     private static final int MAX_ROUTES_TO_EVALUATE = 150;
-
+    private static final String LOCKDOWN_ALLOWLIST_SETTING_NAME =
+            Settings.Secure.ALWAYS_ON_VPN_LOCKDOWN_WHITELIST;
     /**
      * Largest profile size allowable for Platform VPNs.
      *
@@ -190,6 +196,7 @@
     // automated reconnection
 
     private final Context mContext;
+    @VisibleForTesting final Dependencies mDeps;
     private final NetworkInfo mNetworkInfo;
     @VisibleForTesting protected String mPackage;
     private int mOwnerUID;
@@ -230,7 +237,7 @@
      * Set of packages in addition to the VPN app itself that can access the network directly when
      * VPN is not connected even if {@code mLockdown} is set.
      */
-    private @NonNull List<String> mLockdownWhitelist = Collections.emptyList();
+    private @NonNull List<String> mLockdownAllowlist = Collections.emptyList();
 
      /**
      * A memory of what UIDs this class told netd to block for the lockdown feature.
@@ -252,17 +259,143 @@
     // Handle of the user initiating VPN.
     private final int mUserHandle;
 
+    interface RetryScheduler {
+        void checkInterruptAndDelay(boolean sleepLonger) throws InterruptedException;
+    }
+
+    static class Dependencies {
+        public void startService(final String serviceName) {
+            SystemService.start(serviceName);
+        }
+
+        public void stopService(final String serviceName) {
+            SystemService.stop(serviceName);
+        }
+
+        public boolean isServiceRunning(final String serviceName) {
+            return SystemService.isRunning(serviceName);
+        }
+
+        public boolean isServiceStopped(final String serviceName) {
+            return SystemService.isStopped(serviceName);
+        }
+
+        public File getStateFile() {
+            return new File("/data/misc/vpn/state");
+        }
+
+        public void sendArgumentsToDaemon(
+                final String daemon, final LocalSocket socket, final String[] arguments,
+                final RetryScheduler retryScheduler) throws IOException, InterruptedException {
+            final LocalSocketAddress address = new LocalSocketAddress(
+                    daemon, LocalSocketAddress.Namespace.RESERVED);
+
+            // Wait for the socket to connect.
+            while (true) {
+                try {
+                    socket.connect(address);
+                    break;
+                } catch (Exception e) {
+                    // ignore
+                }
+                retryScheduler.checkInterruptAndDelay(true /* sleepLonger */);
+            }
+            socket.setSoTimeout(500);
+
+            final OutputStream out = socket.getOutputStream();
+            for (String argument : arguments) {
+                byte[] bytes = argument.getBytes(StandardCharsets.UTF_8);
+                if (bytes.length >= 0xFFFF) {
+                    throw new IllegalArgumentException("Argument is too large");
+                }
+                out.write(bytes.length >> 8);
+                out.write(bytes.length);
+                out.write(bytes);
+                retryScheduler.checkInterruptAndDelay(false /* sleepLonger */);
+            }
+            out.write(0xFF);
+            out.write(0xFF);
+
+            // Wait for End-of-File.
+            final InputStream in = socket.getInputStream();
+            while (true) {
+                try {
+                    if (in.read() == -1) {
+                        break;
+                    }
+                } catch (Exception e) {
+                    // ignore
+                }
+                retryScheduler.checkInterruptAndDelay(true /* sleepLonger */);
+            }
+        }
+
+        @NonNull
+        public InetAddress resolve(final String endpoint)
+                throws ExecutionException, InterruptedException {
+            try {
+                return InetAddress.parseNumericAddress(endpoint);
+            } catch (IllegalArgumentException e) {
+                // Endpoint is not numeric : fall through and resolve
+            }
+
+            final CancellationSignal cancellationSignal = new CancellationSignal();
+            try {
+                final DnsResolver resolver = DnsResolver.getInstance();
+                final CompletableFuture<InetAddress> result = new CompletableFuture();
+                final DnsResolver.Callback<List<InetAddress>> cb =
+                        new DnsResolver.Callback<List<InetAddress>>() {
+                            @Override
+                            public void onAnswer(@NonNull final List<InetAddress> answer,
+                                    final int rcode) {
+                                if (answer.size() > 0) {
+                                    result.complete(answer.get(0));
+                                } else {
+                                    result.completeExceptionally(
+                                            new UnknownHostException(endpoint));
+                                }
+                            }
+
+                            @Override
+                            public void onError(@Nullable final DnsResolver.DnsException error) {
+                                // Unfortunately UnknownHostException doesn't accept a cause, so
+                                // print a message here instead. Only show the summary, not the
+                                // full stack trace.
+                                Log.e(TAG, "Async dns resolver error : " + error);
+                                result.completeExceptionally(new UnknownHostException(endpoint));
+                            }
+                        };
+                resolver.query(null /* network, null for default */, endpoint,
+                        DnsResolver.FLAG_EMPTY, r -> r.run(), cancellationSignal, cb);
+                return result.get();
+            } catch (final ExecutionException e) {
+                Log.e(TAG, "Cannot resolve VPN endpoint : " + endpoint + ".", e);
+                throw e;
+            } catch (final InterruptedException e) {
+                Log.e(TAG, "Legacy VPN was interrupted while resolving the endpoint", e);
+                cancellationSignal.cancel();
+                throw e;
+            }
+        }
+
+        public boolean checkInterfacePresent(final Vpn vpn, final String iface) {
+            return vpn.jniCheck(iface) == 0;
+        }
+    }
+
     public Vpn(Looper looper, Context context, INetworkManagementService netService,
             @UserIdInt int userHandle, @NonNull KeyStore keyStore) {
-        this(looper, context, netService, userHandle, keyStore,
+        this(looper, context, new Dependencies(), netService, userHandle, keyStore,
                 new SystemServices(context), new Ikev2SessionCreator());
     }
 
     @VisibleForTesting
-    protected Vpn(Looper looper, Context context, INetworkManagementService netService,
+    protected Vpn(Looper looper, Context context, Dependencies deps,
+            INetworkManagementService netService,
             int userHandle, @NonNull KeyStore keyStore, SystemServices systemServices,
             Ikev2SessionCreator ikev2SessionCreator) {
         mContext = context;
+        mDeps = deps;
         mNetd = netService;
         mUserHandle = userHandle;
         mLooper = looper;
@@ -388,7 +521,7 @@
             }
         }
         if (!hadUnderlyingNetworks) {
-            // No idea what the underlying networks are; assume sane defaults
+            // No idea what the underlying networks are; assume the safer defaults
             metered = true;
             roaming = false;
             congested = false;
@@ -521,18 +654,18 @@
      *
      * @param packageName the package to designate as always-on VPN supplier.
      * @param lockdown whether to prevent traffic outside of a VPN, for example while connecting.
-     * @param lockdownWhitelist packages to be whitelisted from lockdown.
+     * @param lockdownAllowlist packages to be allowed from lockdown.
      * @param keyStore the Keystore instance to use for checking of PlatformVpnProfile(s)
      * @return {@code true} if the package has been set as always-on, {@code false} otherwise.
      */
     public synchronized boolean setAlwaysOnPackage(
             @Nullable String packageName,
             boolean lockdown,
-            @Nullable List<String> lockdownWhitelist,
+            @Nullable List<String> lockdownAllowlist,
             @NonNull KeyStore keyStore) {
         enforceControlPermissionOrInternalCaller();
 
-        if (setAlwaysOnPackageInternal(packageName, lockdown, lockdownWhitelist, keyStore)) {
+        if (setAlwaysOnPackageInternal(packageName, lockdown, lockdownAllowlist, keyStore)) {
             saveAlwaysOnPackage();
             return true;
         }
@@ -547,7 +680,7 @@
      *
      * @param packageName the package to designate as always-on VPN supplier.
      * @param lockdown whether to prevent traffic outside of a VPN, for example while connecting.
-     * @param lockdownWhitelist packages to be whitelisted from lockdown. This is only used if
+     * @param lockdownAllowlist packages to be allowed to bypass lockdown. This is only used if
      *     {@code lockdown} is {@code true}. Packages must not contain commas.
      * @param keyStore the system keystore instance to check for profiles
      * @return {@code true} if the package has been set as always-on, {@code false} otherwise.
@@ -555,16 +688,16 @@
     @GuardedBy("this")
     private boolean setAlwaysOnPackageInternal(
             @Nullable String packageName, boolean lockdown,
-            @Nullable List<String> lockdownWhitelist, @NonNull KeyStore keyStore) {
+            @Nullable List<String> lockdownAllowlist, @NonNull KeyStore keyStore) {
         if (VpnConfig.LEGACY_VPN.equals(packageName)) {
             Log.w(TAG, "Not setting legacy VPN \"" + packageName + "\" as always-on.");
             return false;
         }
 
-        if (lockdownWhitelist != null) {
-            for (String pkg : lockdownWhitelist) {
+        if (lockdownAllowlist != null) {
+            for (String pkg : lockdownAllowlist) {
                 if (pkg.contains(",")) {
-                    Log.w(TAG, "Not setting always-on vpn, invalid whitelisted package: " + pkg);
+                    Log.w(TAG, "Not setting always-on vpn, invalid allowed package: " + pkg);
                     return false;
                 }
             }
@@ -592,8 +725,8 @@
         }
 
         mLockdown = (mAlwaysOn && lockdown);
-        mLockdownWhitelist = (mLockdown && lockdownWhitelist != null)
-                ? Collections.unmodifiableList(new ArrayList<>(lockdownWhitelist))
+        mLockdownAllowlist = (mLockdown && lockdownAllowlist != null)
+                ? Collections.unmodifiableList(new ArrayList<>(lockdownAllowlist))
                 : Collections.emptyList();
 
         if (isCurrentPreparedPackage(packageName)) {
@@ -622,10 +755,10 @@
     }
 
     /**
-     * @return an immutable list of packages whitelisted from always-on VPN lockdown.
+     * @return an immutable list of packages allowed to bypass always-on VPN lockdown.
      */
-    public synchronized List<String> getLockdownWhitelist() {
-        return mLockdown ? mLockdownWhitelist : null;
+    public synchronized List<String> getLockdownAllowlist() {
+        return mLockdown ? mLockdownAllowlist : null;
     }
 
     /**
@@ -640,8 +773,8 @@
             mSystemServices.settingsSecurePutIntForUser(Settings.Secure.ALWAYS_ON_VPN_LOCKDOWN,
                     (mAlwaysOn && mLockdown ? 1 : 0), mUserHandle);
             mSystemServices.settingsSecurePutStringForUser(
-                    Settings.Secure.ALWAYS_ON_VPN_LOCKDOWN_WHITELIST,
-                    String.join(",", mLockdownWhitelist), mUserHandle);
+                    LOCKDOWN_ALLOWLIST_SETTING_NAME,
+                    String.join(",", mLockdownAllowlist), mUserHandle);
         } finally {
             Binder.restoreCallingIdentity(token);
         }
@@ -656,12 +789,12 @@
                     Settings.Secure.ALWAYS_ON_VPN_APP, mUserHandle);
             final boolean alwaysOnLockdown = mSystemServices.settingsSecureGetIntForUser(
                     Settings.Secure.ALWAYS_ON_VPN_LOCKDOWN, 0 /*default*/, mUserHandle) != 0;
-            final String whitelistString = mSystemServices.settingsSecureGetStringForUser(
-                    Settings.Secure.ALWAYS_ON_VPN_LOCKDOWN_WHITELIST, mUserHandle);
-            final List<String> whitelistedPackages = TextUtils.isEmpty(whitelistString)
-                    ? Collections.emptyList() : Arrays.asList(whitelistString.split(","));
+            final String allowlistString = mSystemServices.settingsSecureGetStringForUser(
+                    LOCKDOWN_ALLOWLIST_SETTING_NAME, mUserHandle);
+            final List<String> allowedPackages = TextUtils.isEmpty(allowlistString)
+                    ? Collections.emptyList() : Arrays.asList(allowlistString.split(","));
             setAlwaysOnPackageInternal(
-                    alwaysOnPackage, alwaysOnLockdown, whitelistedPackages, keyStore);
+                    alwaysOnPackage, alwaysOnLockdown, allowedPackages, keyStore);
         } finally {
             Binder.restoreCallingIdentity(token);
         }
@@ -717,7 +850,7 @@
             DeviceIdleInternal idleController =
                     LocalServices.getService(DeviceIdleInternal.class);
             idleController.addPowerSaveTempWhitelistApp(Process.myUid(), alwaysOnPackage,
-                    VPN_LAUNCH_IDLE_WHITELIST_DURATION_MS, mUserHandle, false, "vpn");
+                    VPN_LAUNCH_IDLE_ALLOWLIST_DURATION_MS, mUserHandle, false, "vpn");
 
             // Start the VPN service declared in the app's manifest.
             Intent serviceIntent = new Intent(VpnConfig.SERVICE_INTERFACE);
@@ -1080,7 +1213,7 @@
         // applications have changed. Consider diffing UID ranges and only applying the delta.
         if (!Objects.equals(oldConfig.allowedApplications, mConfig.allowedApplications) ||
                 !Objects.equals(oldConfig.disallowedApplications, mConfig.disallowedApplications)) {
-            Log.i(TAG, "Handover not possible due to changes to whitelisted/blacklisted apps");
+            Log.i(TAG, "Handover not possible due to changes to allowed/denied apps");
             return false;
         }
 
@@ -1308,13 +1441,13 @@
      * associated with one user, and any restricted profiles attached to that user.
      *
      * <p>If one of {@param allowedApplications} or {@param disallowedApplications} is provided,
-     * the UID ranges will match the app whitelist or blacklist specified there. Otherwise, all UIDs
+     * the UID ranges will match the app list specified there. Otherwise, all UIDs
      * in each user and profile will be included.
      *
      * @param userHandle The userId to create UID ranges for along with any of its restricted
      *                   profiles.
-     * @param allowedApplications (optional) whitelist of applications to include.
-     * @param disallowedApplications (optional) blacklist of applications to exclude.
+     * @param allowedApplications (optional) List of applications to allow.
+     * @param disallowedApplications (optional) List of applications to deny.
      */
     @VisibleForTesting
     Set<UidRange> createUserAndRestrictedProfilesRanges(@UserIdInt int userHandle,
@@ -1348,13 +1481,13 @@
      * associated with one user.
      *
      * <p>If one of {@param allowedApplications} or {@param disallowedApplications} is provided,
-     * the UID ranges will match the app whitelist or blacklist specified there. Otherwise, all UIDs
+     * the UID ranges will match the app allowlist or denylist specified there. Otherwise, all UIDs
      * in the user will be included.
      *
      * @param ranges {@link Set} of {@link UidRange}s to which to add.
      * @param userHandle The userId to add to {@param ranges}.
-     * @param allowedApplications (optional) whitelist of applications to include.
-     * @param disallowedApplications (optional) blacklist of applications to exclude.
+     * @param allowedApplications (optional) allowlist of applications to include.
+     * @param disallowedApplications (optional) denylist of applications to exclude.
      */
     @VisibleForTesting
     void addUserToRanges(@NonNull Set<UidRange> ranges, @UserIdInt int userHandle,
@@ -1476,7 +1609,7 @@
 
     /**
      * Restricts network access from all UIDs affected by this {@link Vpn}, apart from the VPN
-     * service app itself and whitelisted packages, to only sockets that have had {@code protect()}
+     * service app itself and allowed packages, to only sockets that have had {@code protect()}
      * called on them. All non-VPN traffic is blocked via a {@code PROHIBIT} response from the
      * kernel.
      *
@@ -1498,7 +1631,7 @@
         if (isNullOrLegacyVpn(mPackage)) {
             exemptedPackages = null;
         } else {
-            exemptedPackages = new ArrayList<>(mLockdownWhitelist);
+            exemptedPackages = new ArrayList<>(mLockdownAllowlist);
             exemptedPackages.add(mPackage);
         }
         final Set<UidRange> rangesToTellNetdToRemove = new ArraySet<>(mBlockedUidsAsToldToNetd);
@@ -1543,7 +1676,7 @@
      * Tell netd to add or remove a list of {@link UidRange}s to the list of UIDs that are only
      * allowed to make connections through sockets that have had {@code protect()} called on them.
      *
-     * @param enforce {@code true} to add to the blacklist, {@code false} to remove.
+     * @param enforce {@code true} to add to the denylist, {@code false} to remove.
      * @param ranges {@link Collection} of {@link UidRange}s to add (if {@param enforce} is
      *               {@code true}) or to remove.
      * @return {@code true} if all of the UIDs were added/removed. {@code false} otherwise,
@@ -2129,7 +2262,8 @@
     }
 
     /** This class represents the common interface for all VPN runners. */
-    private abstract class VpnRunner extends Thread {
+    @VisibleForTesting
+    abstract class VpnRunner extends Thread {
 
         protected VpnRunner(String name) {
             super(name);
@@ -2638,7 +2772,7 @@
                     } catch (InterruptedException e) {
                     }
                     for (String daemon : mDaemons) {
-                        SystemService.stop(daemon);
+                        mDeps.stopService(daemon);
                     }
                 }
                 agentDisconnect();
@@ -2655,21 +2789,55 @@
             }
         }
 
+        private void checkAndFixupArguments(@NonNull final InetAddress endpointAddress) {
+            final String endpointAddressString = endpointAddress.getHostAddress();
+            // Perform some safety checks before inserting the address in place.
+            // Position 0 in mDaemons and mArguments must be racoon, and position 1 must be mtpd.
+            if (!"racoon".equals(mDaemons[0]) || !"mtpd".equals(mDaemons[1])) {
+                throw new IllegalStateException("Unexpected daemons order");
+            }
+
+            // Respectively, the positions at which racoon and mtpd take the server address
+            // argument are 1 and 2. Not all types of VPN require both daemons however, and
+            // in that case the corresponding argument array is null.
+            if (mArguments[0] != null) {
+                if (!mProfile.server.equals(mArguments[0][1])) {
+                    throw new IllegalStateException("Invalid server argument for racoon");
+                }
+                mArguments[0][1] = endpointAddressString;
+            }
+
+            if (mArguments[1] != null) {
+                if (!mProfile.server.equals(mArguments[1][2])) {
+                    throw new IllegalStateException("Invalid server argument for mtpd");
+                }
+                mArguments[1][2] = endpointAddressString;
+            }
+        }
+
         private void bringup() {
             // Catch all exceptions so we can clean up a few things.
             try {
+                // resolve never returns null. If it does because of some bug, it will be
+                // caught by the catch() block below and cleanup gracefully.
+                final InetAddress endpointAddress = mDeps.resolve(mProfile.server);
+
+                // Big hack : dynamically replace the address of the server in the arguments
+                // with the resolved address.
+                checkAndFixupArguments(endpointAddress);
+
                 // Initialize the timer.
                 mBringupStartTime = SystemClock.elapsedRealtime();
 
                 // Wait for the daemons to stop.
                 for (String daemon : mDaemons) {
-                    while (!SystemService.isStopped(daemon)) {
+                    while (!mDeps.isServiceStopped(daemon)) {
                         checkInterruptAndDelay(true);
                     }
                 }
 
                 // Clear the previous state.
-                File state = new File("/data/misc/vpn/state");
+                final File state = mDeps.getStateFile();
                 state.delete();
                 if (state.exists()) {
                     throw new IllegalStateException("Cannot delete the state");
@@ -2696,57 +2864,19 @@
 
                     // Start the daemon.
                     String daemon = mDaemons[i];
-                    SystemService.start(daemon);
+                    mDeps.startService(daemon);
 
                     // Wait for the daemon to start.
-                    while (!SystemService.isRunning(daemon)) {
+                    while (!mDeps.isServiceRunning(daemon)) {
                         checkInterruptAndDelay(true);
                     }
 
                     // Create the control socket.
                     mSockets[i] = new LocalSocket();
-                    LocalSocketAddress address = new LocalSocketAddress(
-                            daemon, LocalSocketAddress.Namespace.RESERVED);
 
-                    // Wait for the socket to connect.
-                    while (true) {
-                        try {
-                            mSockets[i].connect(address);
-                            break;
-                        } catch (Exception e) {
-                            // ignore
-                        }
-                        checkInterruptAndDelay(true);
-                    }
-                    mSockets[i].setSoTimeout(500);
-
-                    // Send over the arguments.
-                    OutputStream out = mSockets[i].getOutputStream();
-                    for (String argument : arguments) {
-                        byte[] bytes = argument.getBytes(StandardCharsets.UTF_8);
-                        if (bytes.length >= 0xFFFF) {
-                            throw new IllegalArgumentException("Argument is too large");
-                        }
-                        out.write(bytes.length >> 8);
-                        out.write(bytes.length);
-                        out.write(bytes);
-                        checkInterruptAndDelay(false);
-                    }
-                    out.write(0xFF);
-                    out.write(0xFF);
-
-                    // Wait for End-of-File.
-                    InputStream in = mSockets[i].getInputStream();
-                    while (true) {
-                        try {
-                            if (in.read() == -1) {
-                                break;
-                            }
-                        } catch (Exception e) {
-                            // ignore
-                        }
-                        checkInterruptAndDelay(true);
-                    }
+                    // Wait for the socket to connect and send over the arguments.
+                    mDeps.sendArgumentsToDaemon(daemon, mSockets[i], arguments,
+                            this::checkInterruptAndDelay);
                 }
 
                 // Wait for the daemons to create the new state.
@@ -2754,7 +2884,7 @@
                     // Check if a running daemon is dead.
                     for (int i = 0; i < mDaemons.length; ++i) {
                         String daemon = mDaemons[i];
-                        if (mArguments[i] != null && !SystemService.isRunning(daemon)) {
+                        if (mArguments[i] != null && !mDeps.isServiceRunning(daemon)) {
                             throw new IllegalStateException(daemon + " is dead");
                         }
                     }
@@ -2764,7 +2894,8 @@
                 // Now we are connected. Read and parse the new state.
                 String[] parameters = FileUtils.readTextFile(state, 0, null).split("\n", -1);
                 if (parameters.length != 7) {
-                    throw new IllegalStateException("Cannot parse the state");
+                    throw new IllegalStateException("Cannot parse the state: '"
+                            + String.join("', '", parameters) + "'");
                 }
 
                 // Set the interface and the addresses in the config.
@@ -2793,20 +2924,15 @@
                 }
 
                 // Add a throw route for the VPN server endpoint, if one was specified.
-                String endpoint = parameters[5].isEmpty() ? mProfile.server : parameters[5];
-                if (!endpoint.isEmpty()) {
-                    try {
-                        InetAddress addr = InetAddress.parseNumericAddress(endpoint);
-                        if (addr instanceof Inet4Address) {
-                            mConfig.routes.add(new RouteInfo(new IpPrefix(addr, 32), RTN_THROW));
-                        } else if (addr instanceof Inet6Address) {
-                            mConfig.routes.add(new RouteInfo(new IpPrefix(addr, 128), RTN_THROW));
-                        } else {
-                            Log.e(TAG, "Unknown IP address family for VPN endpoint: " + endpoint);
-                        }
-                    } catch (IllegalArgumentException e) {
-                        Log.e(TAG, "Exception constructing throw route to " + endpoint + ": " + e);
-                    }
+                if (endpointAddress instanceof Inet4Address) {
+                    mConfig.routes.add(new RouteInfo(
+                            new IpPrefix(endpointAddress, 32), RTN_THROW));
+                } else if (endpointAddress instanceof Inet6Address) {
+                    mConfig.routes.add(new RouteInfo(
+                            new IpPrefix(endpointAddress, 128), RTN_THROW));
+                } else {
+                    Log.e(TAG, "Unknown IP address family for VPN endpoint: "
+                            + endpointAddress);
                 }
 
                 // Here is the last step and it must be done synchronously.
@@ -2818,7 +2944,7 @@
                     checkInterruptAndDelay(false);
 
                     // Check if the interface is gone while we are waiting.
-                    if (jniCheck(mConfig.interfaze) == 0) {
+                    if (mDeps.checkInterfacePresent(Vpn.this, mConfig.interfaze)) {
                         throw new IllegalStateException(mConfig.interfaze + " is gone");
                     }
 
@@ -2849,7 +2975,7 @@
             while (true) {
                 Thread.sleep(2000);
                 for (int i = 0; i < mDaemons.length; i++) {
-                    if (mArguments[i] != null && SystemService.isStopped(mDaemons[i])) {
+                    if (mArguments[i] != null && mDeps.isServiceStopped(mDaemons[i])) {
                         return;
                     }
                 }
diff --git a/services/core/java/com/android/server/content/ContentService.java b/services/core/java/com/android/server/content/ContentService.java
index 9a910bf..1294e90 100644
--- a/services/core/java/com/android/server/content/ContentService.java
+++ b/services/core/java/com/android/server/content/ContentService.java
@@ -20,6 +20,7 @@
 
 import android.Manifest;
 import android.accounts.Account;
+import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.RequiresPermission;
 import android.app.ActivityManager;
@@ -75,6 +76,7 @@
 import com.android.internal.util.IndentingPrintWriter;
 import com.android.server.LocalServices;
 import com.android.server.SystemService;
+import com.android.server.SystemService.TargetUser;
 import com.android.server.pm.permission.PermissionManagerServiceInternal;
 
 import java.io.FileDescriptor;
@@ -124,26 +126,25 @@
             mService.onBootPhase(phase);
         }
 
-
         @Override
-        public void onStartUser(int userHandle) {
-            mService.onStartUser(userHandle);
+        public void onUserStarting(@NonNull TargetUser user) {
+            mService.onStartUser(user.getUserIdentifier());
         }
 
         @Override
-        public void onUnlockUser(int userHandle) {
-            mService.onUnlockUser(userHandle);
+        public void onUserUnlocking(@NonNull TargetUser user) {
+            mService.onUnlockUser(user.getUserIdentifier());
         }
 
         @Override
-        public void onStopUser(int userHandle) {
-            mService.onStopUser(userHandle);
+        public void onUserStopping(@NonNull TargetUser user) {
+            mService.onStopUser(user.getUserIdentifier());
         }
 
         @Override
-        public void onCleanupUser(int userHandle) {
+        public void onUserStopped(@NonNull TargetUser user) {
             synchronized (mService.mCache) {
-                mService.mCache.remove(userHandle);
+                mService.mCache.remove(user.getUserIdentifier());
             }
         }
     }
diff --git a/services/core/java/com/android/server/content/SyncManager.java b/services/core/java/com/android/server/content/SyncManager.java
index 041bedc..ec12a97 100644
--- a/services/core/java/com/android/server/content/SyncManager.java
+++ b/services/core/java/com/android/server/content/SyncManager.java
@@ -210,6 +210,7 @@
     private static final String HANDLE_SYNC_ALARM_WAKE_LOCK = "SyncManagerHandleSyncAlarm";
     private static final String SYNC_LOOP_WAKE_LOCK = "SyncLoopWakeLock";
 
+    private static final boolean USE_WTF_FOR_ACCOUNT_ERROR = false;
 
     private static final int SYNC_OP_STATE_VALID = 0;
     // "1" used to include errors 3, 4 and 5 but now it's split up.
@@ -3446,7 +3447,7 @@
                 if (isLoggable) {
                     Slog.v(TAG, "    Dropping sync operation: account doesn't exist.");
                 }
-                Slog.wtf(TAG, "SYNC_OP_STATE_INVALID: account doesn't exist.");
+                logAccountError("SYNC_OP_STATE_INVALID: account doesn't exist.");
                 return SYNC_OP_STATE_INVALID_NO_ACCOUNT;
             }
             // Drop this sync request if it isn't syncable.
@@ -3456,14 +3457,14 @@
                     Slog.v(TAG, "    Dropping sync operation: "
                             + "isSyncable == SYNCABLE_NO_ACCOUNT_ACCESS");
                 }
-                Slog.wtf(TAG, "SYNC_OP_STATE_INVALID_NO_ACCOUNT_ACCESS");
+                logAccountError("SYNC_OP_STATE_INVALID_NO_ACCOUNT_ACCESS");
                 return SYNC_OP_STATE_INVALID_NO_ACCOUNT_ACCESS;
             }
             if (state == AuthorityInfo.NOT_SYNCABLE) {
                 if (isLoggable) {
                     Slog.v(TAG, "    Dropping sync operation: isSyncable == NOT_SYNCABLE");
                 }
-                Slog.wtf(TAG, "SYNC_OP_STATE_INVALID: NOT_SYNCABLE");
+                logAccountError("SYNC_OP_STATE_INVALID: NOT_SYNCABLE");
                 return SYNC_OP_STATE_INVALID_NOT_SYNCABLE;
             }
 
@@ -3482,12 +3483,20 @@
                 if (isLoggable) {
                     Slog.v(TAG, "    Dropping sync operation: disallowed by settings/network.");
                 }
-                Slog.wtf(TAG, "SYNC_OP_STATE_INVALID: disallowed by settings/network");
+                logAccountError("SYNC_OP_STATE_INVALID: disallowed by settings/network");
                 return SYNC_OP_STATE_INVALID_SYNC_DISABLED;
             }
             return SYNC_OP_STATE_VALID;
         }
 
+        private void logAccountError(String message) {
+            if (USE_WTF_FOR_ACCOUNT_ERROR) {
+                Slog.wtf(TAG, message);
+            } else {
+                Slog.e(TAG, message);
+            }
+        }
+
         private boolean dispatchSyncOperation(SyncOperation op) {
             if (Log.isLoggable(TAG, Log.VERBOSE)) {
                 Slog.v(TAG, "dispatchSyncOperation: we are going to sync " + op);
diff --git a/services/core/java/com/android/server/display/BrightnessMappingStrategy.java b/services/core/java/com/android/server/display/BrightnessMappingStrategy.java
index 6f12155..b8e579d 100644
--- a/services/core/java/com/android/server/display/BrightnessMappingStrategy.java
+++ b/services/core/java/com/android/server/display/BrightnessMappingStrategy.java
@@ -349,9 +349,7 @@
 
     // Normalize entire brightness range to 0 - 1.
     protected static float normalizeAbsoluteBrightness(int brightness) {
-        return BrightnessSynchronizer.brightnessIntToFloat(brightness,
-                PowerManager.BRIGHTNESS_OFF + 1, PowerManager.BRIGHTNESS_ON,
-                PowerManager.BRIGHTNESS_MIN, PowerManager.BRIGHTNESS_MAX);
+        return BrightnessSynchronizer.brightnessIntToFloat(brightness);
     }
 
     private Pair<float[], float[]> insertControlPoint(
diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java
index a00c22a..2c632d9 100644
--- a/services/core/java/com/android/server/display/DisplayManagerService.java
+++ b/services/core/java/com/android/server/display/DisplayManagerService.java
@@ -30,8 +30,6 @@
 import static android.hardware.display.DisplayViewport.VIEWPORT_EXTERNAL;
 import static android.hardware.display.DisplayViewport.VIEWPORT_INTERNAL;
 import static android.hardware.display.DisplayViewport.VIEWPORT_VIRTUAL;
-import static android.view.Surface.ROTATION_270;
-import static android.view.Surface.ROTATION_90;
 
 import android.Manifest;
 import android.annotation.NonNull;
@@ -46,7 +44,6 @@
 import android.database.ContentObserver;
 import android.graphics.ColorSpace;
 import android.graphics.Point;
-import android.graphics.Rect;
 import android.hardware.SensorManager;
 import android.hardware.display.AmbientBrightnessDayStats;
 import android.hardware.display.BrightnessChangeEvent;
@@ -417,7 +414,8 @@
     }
 
     @Override
-    public void onSwitchUser(@UserIdInt int newUserId) {
+    public void onUserSwitching(@Nullable TargetUser from, @NonNull TargetUser to) {
+        final int newUserId = to.getUserIdentifier();
         final int userSerial = getUserManager().getUserSerialNumber(newUserId);
         synchronized (mSyncRoot) {
             if (mCurrentUserId != newUserId) {
@@ -1392,9 +1390,13 @@
             }
 
             final DisplayInfo displayInfo = logicalDisplay.getDisplayInfoLocked();
-            return SurfaceControl.screenshotToBufferWithSecureLayersUnsafe(token, new Rect(),
-                    displayInfo.getNaturalWidth(), displayInfo.getNaturalHeight(),
-                    false /* useIdentityTransform */, 0 /* rotation */);
+            final SurfaceControl.DisplayCaptureArgs captureArgs =
+                    new SurfaceControl.DisplayCaptureArgs.Builder(token)
+                            .setSize(displayInfo.getNaturalWidth(), displayInfo.getNaturalHeight())
+                            .setUseIdentityTransform(true)
+                            .setCaptureSecureLayers(true)
+                            .build();
+            return SurfaceControl.captureDisplay(captureArgs);
         }
     }
 
@@ -1404,30 +1406,11 @@
             if (token == null) {
                 return null;
             }
-            final LogicalDisplay logicalDisplay = mLogicalDisplays.get(displayId);
-            if (logicalDisplay == null) {
-                return null;
-            }
 
-            final DisplayInfo displayInfo = logicalDisplay.getDisplayInfoLocked();
-            // Takes screenshot based on current device orientation.
-            final Display display = DisplayManagerGlobal.getInstance()
-                    .getRealDisplay(displayId);
-            if (display == null) {
-                return null;
-            }
-            final Point displaySize = new Point();
-            display.getRealSize(displaySize);
-
-            int rotation = displayInfo.rotation;
-            // TODO (b/153382624) : This workaround solution would be removed after
-            // SurfaceFlinger fixes the inconsistency with rotation direction issue.
-            if (rotation == ROTATION_90 || rotation == ROTATION_270) {
-                rotation = (rotation == ROTATION_90) ? ROTATION_270 : ROTATION_90;
-            }
-
-            return SurfaceControl.screenshotToBuffer(token, new Rect(), displaySize.x,
-                    displaySize.y, false /* useIdentityTransform */, rotation /* rotation */);
+            final SurfaceControl.DisplayCaptureArgs captureArgs =
+                    new SurfaceControl.DisplayCaptureArgs.Builder(token)
+                            .build();
+            return SurfaceControl.captureDisplay(captureArgs);
         }
     }
 
@@ -2191,10 +2174,14 @@
                 }
             }
 
-            if (callingUid == Process.SYSTEM_UID
-                    || checkCallingPermission(ADD_TRUSTED_DISPLAY, "createVirtualDisplay()")) {
-                flags |= VIRTUAL_DISPLAY_FLAG_TRUSTED;
-            } else {
+            if (callingUid != Process.SYSTEM_UID && (flags & VIRTUAL_DISPLAY_FLAG_TRUSTED) != 0) {
+                if (!checkCallingPermission(ADD_TRUSTED_DISPLAY, "createVirtualDisplay()")) {
+                    throw new SecurityException("Requires ADD_TRUSTED_DISPLAY permission to "
+                            + "create a trusted virtual display.");
+                }
+            }
+
+            if ((flags & VIRTUAL_DISPLAY_FLAG_TRUSTED) == 0) {
                 flags &= ~VIRTUAL_DISPLAY_FLAG_SHOULD_SHOW_SYSTEM_DECORATIONS;
             }
 
@@ -2548,8 +2535,7 @@
         public boolean requestPowerState(DisplayPowerRequest request,
                 boolean waitForNegativeProximity) {
             synchronized (mSyncRoot) {
-                return mDisplayPowerController.requestPowerState(request,
-                        waitForNegativeProximity);
+                return mDisplayPowerController.requestPowerState(request, waitForNegativeProximity);
             }
         }
 
@@ -2677,6 +2663,10 @@
             return getDisplayedContentSampleInternal(displayId, maxFrames, timestamp);
         }
 
+        @Override
+        public void ignoreProximitySensorUntilChanged() {
+            mDisplayPowerController.ignoreProximitySensorUntilChanged();
+        }
     }
 
     class DesiredDisplayModeSpecsObserver
diff --git a/services/core/java/com/android/server/display/DisplayPowerController.java b/services/core/java/com/android/server/display/DisplayPowerController.java
index 9411c562..58ef9d1 100644
--- a/services/core/java/com/android/server/display/DisplayPowerController.java
+++ b/services/core/java/com/android/server/display/DisplayPowerController.java
@@ -117,6 +117,7 @@
     private static final int MSG_CONFIGURE_BRIGHTNESS = 5;
     private static final int MSG_SET_TEMPORARY_BRIGHTNESS = 6;
     private static final int MSG_SET_TEMPORARY_AUTO_BRIGHTNESS_ADJUSTMENT = 7;
+    private static final int MSG_IGNORE_PROXIMITY = 8;
 
     private static final int PROXIMITY_UNKNOWN = -1;
     private static final int PROXIMITY_NEGATIVE = 0;
@@ -263,6 +264,11 @@
     // go to sleep by the user.  While true, the screen remains off.
     private boolean mWaitingForNegativeProximity;
 
+    // True if the device should not take into account the proximity sensor
+    // until either the proximity sensor state changes, or there is no longer a
+    // request to listen to proximity sensor.
+    private boolean mIgnoreProximityUntilChanged;
+
     // The actual proximity sensor threshold value.
     private float mProximityThreshold;
 
@@ -699,13 +705,13 @@
         // Initialize screen state for battery stats.
         try {
             mBatteryStats.noteScreenState(mPowerState.getScreenState());
-            mBatteryStats.noteScreenBrightness(BrightnessSynchronizer.brightnessFloatToInt(mContext,
+            mBatteryStats.noteScreenBrightness(BrightnessSynchronizer.brightnessFloatToInt(
                     mPowerState.getScreenBrightness()));
         } catch (RemoteException ex) {
             // same process
         }
         // Initialize all of the brightness tracking state
-        final float brightness = convertToNits(BrightnessSynchronizer.brightnessFloatToInt(mContext,
+        final float brightness = convertToNits(BrightnessSynchronizer.brightnessFloatToInt(
                 mPowerState.getScreenBrightness()));
         if (brightness >= 0.0f) {
             mBrightnessTracker.start(brightness);
@@ -760,8 +766,7 @@
 
             if (mPowerRequest == null) {
                 mPowerRequest = new DisplayPowerRequest(mPendingRequestLocked);
-                mWaitingForNegativeProximity = mPendingWaitForNegativeProximityLocked;
-                mPendingWaitForNegativeProximityLocked = false;
+                updatePendingProximityRequestsLocked();
                 mPendingRequestChangedLocked = false;
                 mustInitialize = true;
                 // Assume we're on and bright until told otherwise, since that's the state we turn
@@ -770,8 +775,7 @@
             } else if (mPendingRequestChangedLocked) {
                 previousPolicy = mPowerRequest.policy;
                 mPowerRequest.copyFrom(mPendingRequestLocked);
-                mWaitingForNegativeProximity |= mPendingWaitForNegativeProximityLocked;
-                mPendingWaitForNegativeProximityLocked = false;
+                updatePendingProximityRequestsLocked();
                 mPendingRequestChangedLocked = false;
                 mDisplayReadyLocked = false;
             } else {
@@ -822,9 +826,16 @@
         // Apply the proximity sensor.
         if (mProximitySensor != null) {
             if (mPowerRequest.useProximitySensor && state != Display.STATE_OFF) {
+                // At this point the policy says that the screen should be on, but we've been
+                // asked to listen to the prox sensor to adjust the display state, so lets make
+                // sure the sensor is on.
                 setProximitySensorEnabled(true);
                 if (!mScreenOffBecauseOfProximity
-                        && mProximity == PROXIMITY_POSITIVE) {
+                        && mProximity == PROXIMITY_POSITIVE
+                        && !mIgnoreProximityUntilChanged) {
+                    // Prox sensor already reporting "near" so we should turn off the screen.
+                    // Also checked that we aren't currently set to ignore the proximity sensor
+                    // temporarily.
                     mScreenOffBecauseOfProximity = true;
                     sendOnProximityPositiveWithWakelock();
                 }
@@ -832,18 +843,28 @@
                     && mScreenOffBecauseOfProximity
                     && mProximity == PROXIMITY_POSITIVE
                     && state != Display.STATE_OFF) {
+                // The policy says that we should have the screen on, but it's off due to the prox
+                // and we've been asked to wait until the screen is far from the user to turn it
+                // back on. Let keep the prox sensor on so we can tell when it's far again.
                 setProximitySensorEnabled(true);
             } else {
+                // We haven't been asked to use the prox sensor and we're not waiting on the screen
+                // to turn back on...so lets shut down the prox sensor.
                 setProximitySensorEnabled(false);
                 mWaitingForNegativeProximity = false;
             }
+
             if (mScreenOffBecauseOfProximity
-                    && mProximity != PROXIMITY_POSITIVE) {
+                    && (mProximity != PROXIMITY_POSITIVE || mIgnoreProximityUntilChanged)) {
+                // The screen *was* off due to prox being near, but now it's "far" so lets turn
+                // the screen back on.  Also turn it back on if we've been asked to ignore the
+                // prox sensor temporarily.
                 mScreenOffBecauseOfProximity = false;
                 sendOnProximityNegativeWithWakelock();
             }
         } else {
             mWaitingForNegativeProximity = false;
+            mIgnoreProximityUntilChanged = false;
         }
         if (mScreenOffBecauseOfProximity) {
             state = Display.STATE_OFF;
@@ -1093,7 +1114,7 @@
                     userInitiatedChange = false;
                 }
                 notifyBrightnessChanged(
-                        BrightnessSynchronizer.brightnessFloatToInt(mContext, brightnessState),
+                        BrightnessSynchronizer.brightnessFloatToInt(brightnessState),
                         userInitiatedChange, hadUserBrightnessPoint);
             }
 
@@ -1181,6 +1202,14 @@
         sendUpdatePowerState();
     }
 
+    /**
+     * Ignores the proximity sensor until the sensor state changes, but only if the sensor is
+     * currently enabled and forcing the screen to be dark.
+     */
+    public void ignoreProximitySensorUntilChanged() {
+        mHandler.sendEmptyMessage(MSG_IGNORE_PROXIMITY);
+    }
+
     public void setBrightnessConfiguration(BrightnessConfiguration c) {
         Message msg = mHandler.obtainMessage(MSG_CONFIGURE_BRIGHTNESS, c);
         msg.sendToTarget();
@@ -1341,8 +1370,7 @@
             try {
                 // TODO(brightnessfloat): change BatteryStats to use float
                 mBatteryStats.noteScreenBrightness(
-                        BrightnessSynchronizer.brightnessFloatToInt(
-                        mContext, target));
+                        BrightnessSynchronizer.brightnessFloatToInt(target));
             } catch (RemoteException ex) {
                 // same process
             }
@@ -1529,6 +1557,7 @@
                 // Register the listener.
                 // Proximity sensor state already cleared initially.
                 mProximitySensorEnabled = true;
+                mIgnoreProximityUntilChanged = false;
                 mSensorManager.registerListener(mProximitySensorListener, mProximitySensor,
                         SensorManager.SENSOR_DELAY_NORMAL, mHandler);
             }
@@ -1538,6 +1567,7 @@
                 // Clear the proximity sensor state for next time.
                 mProximitySensorEnabled = false;
                 mProximity = PROXIMITY_UNKNOWN;
+                mIgnoreProximityUntilChanged = false;
                 mPendingProximity = PROXIMITY_UNKNOWN;
                 mHandler.removeMessages(MSG_PROXIMITY_SENSOR_DEBOUNCED);
                 mSensorManager.unregisterListener(mProximitySensorListener);
@@ -1580,6 +1610,11 @@
                 && mPendingProximityDebounceTime >= 0) {
             final long now = SystemClock.uptimeMillis();
             if (mPendingProximityDebounceTime <= now) {
+                if (mProximity != mPendingProximity) {
+                    // if the status of the sensor changed, stop ignoring.
+                    mIgnoreProximityUntilChanged = false;
+                    Slog.i(TAG, "No longer ignoring proximity [" + mPendingProximity + "]");
+                }
                 // Sensor reading accepted.  Apply the change then release the wake lock.
                 mProximity = mPendingProximity;
                 updatePowerState();
@@ -1723,6 +1758,27 @@
         }
     }
 
+    private void updatePendingProximityRequestsLocked() {
+        mWaitingForNegativeProximity |= mPendingWaitForNegativeProximityLocked;
+        mPendingWaitForNegativeProximityLocked = false;
+
+        if (mIgnoreProximityUntilChanged) {
+            // Also, lets stop waiting for negative proximity if we're ignoring it.
+            mWaitingForNegativeProximity = false;
+        }
+    }
+
+    private void ignoreProximitySensorUntilChangedInternal() {
+        if (!mIgnoreProximityUntilChanged
+                && mPowerRequest.useProximitySensor
+                && mProximity == PROXIMITY_POSITIVE) {
+            // Only ignore if it is still reporting positive (near)
+            mIgnoreProximityUntilChanged = true;
+            Slog.i(TAG, "Ignoring proximity");
+            updatePowerState();
+        }
+    }
+
     private final Runnable mOnStateChangedRunnable = new Runnable() {
         @Override
         public void run() {
@@ -1961,6 +2017,10 @@
                     mTemporaryAutoBrightnessAdjustment = Float.intBitsToFloat(msg.arg1);
                     updatePowerState();
                     break;
+
+                case MSG_IGNORE_PROXIMITY:
+                    ignoreProximitySensorUntilChangedInternal();
+                    break;
             }
         }
     }
diff --git a/services/core/java/com/android/server/display/LocalDisplayAdapter.java b/services/core/java/com/android/server/display/LocalDisplayAdapter.java
index 48fa1bf..0be428b 100644
--- a/services/core/java/com/android/server/display/LocalDisplayAdapter.java
+++ b/services/core/java/com/android/server/display/LocalDisplayAdapter.java
@@ -728,16 +728,14 @@
                         try {
                             if (isHalBrightnessRangeSpecified()) {
                                 brightness = displayBrightnessToHalBrightness(
-                                        BrightnessSynchronizer.brightnessFloatToIntRange(
-                                                getContext(), brightness));
+                                        BrightnessSynchronizer.brightnessFloatToIntRange(brightness));
                             }
                             if (mBacklight != null) {
                                 mBacklight.setBrightness(brightness);
                             }
                             Trace.traceCounter(Trace.TRACE_TAG_POWER,
                                     "ScreenBrightness",
-                                    BrightnessSynchronizer.brightnessFloatToInt(
-                                            getContext(), brightness));
+                                    BrightnessSynchronizer.brightnessFloatToInt(brightness));
                         } finally {
                             Trace.traceEnd(Trace.TRACE_TAG_POWER);
                         }
diff --git a/services/core/java/com/android/server/display/color/ColorDisplayService.java b/services/core/java/com/android/server/display/color/ColorDisplayService.java
index fa4ba38..92a3ccf 100644
--- a/services/core/java/com/android/server/display/color/ColorDisplayService.java
+++ b/services/core/java/com/android/server/display/color/ColorDisplayService.java
@@ -77,6 +77,7 @@
 import com.android.server.DisplayThread;
 import com.android.server.LocalServices;
 import com.android.server.SystemService;
+import com.android.server.SystemService.TargetUser;
 import com.android.server.twilight.TwilightListener;
 import com.android.server.twilight.TwilightManager;
 import com.android.server.twilight.TwilightState;
@@ -206,30 +207,24 @@
     }
 
     @Override
-    public void onStartUser(int userHandle) {
-        super.onStartUser(userHandle);
-
+    public void onUserStarting(@NonNull TargetUser user) {
         if (mCurrentUser == UserHandle.USER_NULL) {
             final Message message = mHandler.obtainMessage(MSG_USER_CHANGED);
-            message.arg1 = userHandle;
+            message.arg1 = user.getUserIdentifier();
             mHandler.sendMessage(message);
         }
     }
 
     @Override
-    public void onSwitchUser(int userHandle) {
-        super.onSwitchUser(userHandle);
-
+    public void onUserSwitching(@Nullable TargetUser from, @NonNull TargetUser to) {
         final Message message = mHandler.obtainMessage(MSG_USER_CHANGED);
-        message.arg1 = userHandle;
+        message.arg1 = to.getUserIdentifier();
         mHandler.sendMessage(message);
     }
 
     @Override
-    public void onStopUser(int userHandle) {
-        super.onStopUser(userHandle);
-
-        if (mCurrentUser == userHandle) {
+    public void onUserStopping(@NonNull TargetUser user) {
+        if (mCurrentUser == user.getUserIdentifier()) {
             final Message message = mHandler.obtainMessage(MSG_USER_CHANGED);
             message.arg1 = UserHandle.USER_NULL;
             mHandler.sendMessage(message);
diff --git a/services/core/java/com/android/server/gpu/GpuService.java b/services/core/java/com/android/server/gpu/GpuService.java
index c0617ca..54794fe 100644
--- a/services/core/java/com/android/server/gpu/GpuService.java
+++ b/services/core/java/com/android/server/gpu/GpuService.java
@@ -65,7 +65,7 @@
 
     private static final String PROD_DRIVER_PROPERTY = "ro.gfx.driver.0";
     private static final String DEV_DRIVER_PROPERTY = "ro.gfx.driver.1";
-    private static final String GAME_DRIVER_ALLOWLIST_FILENAME = "allowlist.txt";
+    private static final String UPDATABLE_DRIVER_PRODUCTION_ALLOWLIST_FILENAME = "allowlist.txt";
     private static final int BASE64_FLAGS = Base64.NO_PADDING | Base64.NO_WRAP;
 
     private final Context mContext;
@@ -77,7 +77,7 @@
     private final boolean mHasProdDriver;
     private final boolean mHasDevDriver;
     private ContentResolver mContentResolver;
-    private long mGameDriverVersionCode;
+    private long mProdDriverVersionCode;
     private SettingsObserver mSettingsObserver;
     private DeviceConfigListener mDeviceConfigListener;
     @GuardedBy("mLock")
@@ -88,7 +88,7 @@
 
         mContext = context;
         mProdDriverPackageName = SystemProperties.get(PROD_DRIVER_PROPERTY);
-        mGameDriverVersionCode = -1;
+        mProdDriverVersionCode = -1;
         mDevDriverPackageName = SystemProperties.get(DEV_DRIVER_PROPERTY);
         mPackageManager = context.getPackageManager();
         mHasProdDriver = !TextUtils.isEmpty(mProdDriverPackageName);
@@ -117,20 +117,20 @@
             }
             mSettingsObserver = new SettingsObserver();
             mDeviceConfigListener = new DeviceConfigListener();
-            fetchGameDriverPackageProperties();
+            fetchProductionDriverPackageProperties();
             processDenylists();
             setDenylist();
-            fetchDeveloperDriverPackageProperties();
+            fetchPrereleaseDriverPackageProperties();
         }
     }
 
     private final class SettingsObserver extends ContentObserver {
-        private final Uri mGameDriverDenylistsUri =
-                Settings.Global.getUriFor(Settings.Global.GAME_DRIVER_DENYLISTS);
+        private final Uri mProdDriverDenylistsUri =
+                Settings.Global.getUriFor(Settings.Global.UPDATABLE_DRIVER_PRODUCTION_DENYLISTS);
 
         SettingsObserver() {
             super(new Handler());
-            mContentResolver.registerContentObserver(mGameDriverDenylistsUri, false, this,
+            mContentResolver.registerContentObserver(mProdDriverDenylistsUri, false, this,
                     UserHandle.USER_ALL);
         }
 
@@ -140,7 +140,7 @@
                 return;
             }
 
-            if (mGameDriverDenylistsUri.equals(uri)) {
+            if (mProdDriverDenylistsUri.equals(uri)) {
                 processDenylists();
                 setDenylist();
             }
@@ -157,9 +157,11 @@
         @Override
         public void onPropertiesChanged(Properties properties) {
             synchronized (mDeviceConfigLock) {
-                if (properties.getKeyset().contains(Settings.Global.GAME_DRIVER_DENYLISTS)) {
+                if (properties.getKeyset().contains(
+                            Settings.Global.UPDATABLE_DRIVER_PRODUCTION_DENYLISTS)) {
                     parseDenylists(
-                            properties.getString(Settings.Global.GAME_DRIVER_DENYLISTS, ""));
+                            properties.getString(
+                                    Settings.Global.UPDATABLE_DRIVER_PRODUCTION_DENYLISTS, ""));
                     setDenylist();
                 }
             }
@@ -186,10 +188,10 @@
                 case ACTION_PACKAGE_CHANGED:
                 case ACTION_PACKAGE_REMOVED:
                     if (isProdDriver) {
-                        fetchGameDriverPackageProperties();
+                        fetchProductionDriverPackageProperties();
                         setDenylist();
                     } else if (isDevDriver) {
-                        fetchDeveloperDriverPackageProperties();
+                        fetchPrereleaseDriverPackageProperties();
                     }
                     break;
                 default:
@@ -218,7 +220,7 @@
         }
     }
 
-    private void fetchGameDriverPackageProperties() {
+    private void fetchProductionDriverPackageProperties() {
         final ApplicationInfo driverInfo;
         try {
             driverInfo = mPackageManager.getApplicationInfo(mProdDriverPackageName,
@@ -241,15 +243,16 @@
 
         // Reset the allowlist.
         Settings.Global.putString(mContentResolver,
-                                  Settings.Global.GAME_DRIVER_ALLOWLIST, "");
-        mGameDriverVersionCode = driverInfo.longVersionCode;
+                                  Settings.Global.UPDATABLE_DRIVER_PRODUCTION_ALLOWLIST, "");
+        mProdDriverVersionCode = driverInfo.longVersionCode;
 
         try {
             final Context driverContext = mContext.createPackageContext(mProdDriverPackageName,
                                                                         Context.CONTEXT_RESTRICTED);
 
-            assetToSettingsGlobal(mContext, driverContext, GAME_DRIVER_ALLOWLIST_FILENAME,
-                    Settings.Global.GAME_DRIVER_ALLOWLIST, ",");
+            assetToSettingsGlobal(mContext, driverContext,
+                    UPDATABLE_DRIVER_PRODUCTION_ALLOWLIST_FILENAME,
+                    Settings.Global.UPDATABLE_DRIVER_PRODUCTION_ALLOWLIST, ",");
         } catch (PackageManager.NameNotFoundException e) {
             if (DEBUG) {
                 Slog.w(TAG, "driver package '" + mProdDriverPackageName + "' not installed");
@@ -259,11 +262,11 @@
 
     private void processDenylists() {
         String base64String = DeviceConfig.getProperty(DeviceConfig.NAMESPACE_GAME_DRIVER,
-                Settings.Global.GAME_DRIVER_DENYLISTS);
+                Settings.Global.UPDATABLE_DRIVER_PRODUCTION_DENYLISTS);
         if (base64String == null) {
             base64String =
                     Settings.Global.getString(mContentResolver,
-                                              Settings.Global.GAME_DRIVER_DENYLISTS);
+                            Settings.Global.UPDATABLE_DRIVER_PRODUCTION_DENYLISTS);
         }
         parseDenylists(base64String != null ? base64String : "");
     }
@@ -288,16 +291,16 @@
 
     private void setDenylist() {
         Settings.Global.putString(mContentResolver,
-                                  Settings.Global.GAME_DRIVER_DENYLIST, "");
+                                  Settings.Global.UPDATABLE_DRIVER_PRODUCTION_DENYLIST, "");
         synchronized (mLock) {
             if (mDenylists == null) {
                 return;
             }
             List<Denylist> denylists = mDenylists.getDenylistsList();
             for (Denylist denylist : denylists) {
-                if (denylist.getVersionCode() == mGameDriverVersionCode) {
+                if (denylist.getVersionCode() == mProdDriverVersionCode) {
                     Settings.Global.putString(mContentResolver,
-                            Settings.Global.GAME_DRIVER_DENYLIST,
+                            Settings.Global.UPDATABLE_DRIVER_PRODUCTION_DENYLIST,
                             String.join(",", denylist.getPackageNamesList()));
                     return;
                 }
@@ -305,7 +308,7 @@
         }
     }
 
-    private void fetchDeveloperDriverPackageProperties() {
+    private void fetchPrereleaseDriverPackageProperties() {
         final ApplicationInfo driverInfo;
         try {
             driverInfo = mPackageManager.getApplicationInfo(mDevDriverPackageName,
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDevicePlayback.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDevicePlayback.java
index 596c1ec..d675b81 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDevicePlayback.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDevicePlayback.java
@@ -232,11 +232,12 @@
         mAutoTvOff = enabled;
     }
 
+    @Override
     @ServiceThreadOnly
     @VisibleForTesting
     void setIsActiveSource(boolean on) {
         assertRunOnServiceThread();
-        mIsActiveSource = on;
+        super.setIsActiveSource(on);
         if (on) {
             getWakeLock().acquire();
         } else {
@@ -274,19 +275,15 @@
 
     @Override
     @ServiceThreadOnly
-    protected boolean handleActiveSource(HdmiCecMessage message) {
-        super.handleActiveSource(message);
-        if (mIsActiveSource) {
-            return true;
-        }
+    protected void onActiveSourceLost() {
+        assertRunOnServiceThread();
         switch (mPowerStateChangeOnActiveSourceLost) {
             case STANDBY_NOW:
                 mService.standby();
-                return true;
+                return;
             case NONE:
-                return true;
+                return;
         }
-        return true;
     }
 
     @ServiceThreadOnly
@@ -398,9 +395,12 @@
     }
 
     @Override
+    @ServiceThreadOnly
     protected void handleRoutingChangeAndInformation(int physicalAddress, HdmiCecMessage message) {
+        assertRunOnServiceThread();
         if (physicalAddress != mService.getPhysicalAddress()) {
-            return; // Do nothing.
+            setActiveSource(physicalAddress);
+            return;
         }
         switch (mPlaybackDeviceActionOnRoutingControl) {
             case WAKE_UP_AND_SEND_ACTIVE_SOURCE:
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceSource.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceSource.java
index 1c67718..44ad8ee 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceSource.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceSource.java
@@ -114,6 +114,19 @@
     }
 
     @ServiceThreadOnly
+    protected void onActiveSourceLost() {
+        // Nothing to do.
+    }
+
+    @ServiceThreadOnly
+    protected void setActiveSource(int physicalAddress) {
+        assertRunOnServiceThread();
+        // Invalidate the internal active source record. This will also update mIsActiveSource.
+        ActiveSource activeSource = ActiveSource.of(Constants.ADDR_INVALID, physicalAddress);
+        setActiveSource(activeSource);
+    }
+
+    @ServiceThreadOnly
     protected boolean handleActiveSource(HdmiCecMessage message) {
         assertRunOnServiceThread();
         int logicalAddress = message.getSource();
@@ -148,6 +161,9 @@
         if (physicalAddress == mService.getPhysicalAddress() && mService.isPlaybackDevice()) {
             setAndBroadcastActiveSource(message, physicalAddress);
         }
+        if (physicalAddress != mService.getPhysicalAddress()) {
+            setActiveSource(physicalAddress);
+        }
         switchInputOnReceivingNewActivePath(physicalAddress);
         return true;
     }
@@ -156,18 +172,21 @@
     @ServiceThreadOnly
     protected boolean handleRoutingChange(HdmiCecMessage message) {
         assertRunOnServiceThread();
+        int physicalAddress = HdmiUtils.twoBytesToInt(message.getParams(), 2);
+        if (physicalAddress != mService.getPhysicalAddress()) {
+            setActiveSource(physicalAddress);
+        }
         if (!isRoutingControlFeatureEnabled()) {
             mService.maySendFeatureAbortCommand(message, Constants.ABORT_REFUSED);
             return true;
         }
-        int newPath = HdmiUtils.twoBytesToInt(message.getParams(), 2);
         // if the current device is a pure playback device
         if (!mIsSwitchDevice
-                && newPath == mService.getPhysicalAddress()
+                && physicalAddress == mService.getPhysicalAddress()
                 && mService.isPlaybackDevice()) {
-            setAndBroadcastActiveSource(message, newPath);
+            setAndBroadcastActiveSource(message, physicalAddress);
         }
-        handleRoutingChangeAndInformation(newPath, message);
+        handleRoutingChangeAndInformation(physicalAddress, message);
         return true;
     }
 
@@ -175,11 +194,14 @@
     @ServiceThreadOnly
     protected boolean handleRoutingInformation(HdmiCecMessage message) {
         assertRunOnServiceThread();
+        int physicalAddress = HdmiUtils.twoBytesToInt(message.getParams());
+        if (physicalAddress != mService.getPhysicalAddress()) {
+            setActiveSource(physicalAddress);
+        }
         if (!isRoutingControlFeatureEnabled()) {
             mService.maySendFeatureAbortCommand(message, Constants.ABORT_REFUSED);
             return true;
         }
-        int physicalAddress = HdmiUtils.twoBytesToInt(message.getParams());
         // if the current device is a pure playback device
         if (!mIsSwitchDevice
                 && physicalAddress == mService.getPhysicalAddress()
@@ -222,7 +244,11 @@
     @ServiceThreadOnly
     void setIsActiveSource(boolean on) {
         assertRunOnServiceThread();
+        boolean wasActiveSource = mIsActiveSource;
         mIsActiveSource = on;
+        if (wasActiveSource && !mIsActiveSource) {
+            onActiveSourceLost();
+        }
     }
 
     protected void wakeUpIfActiveSource() {
diff --git a/services/core/java/com/android/server/infra/AbstractMasterSystemService.java b/services/core/java/com/android/server/infra/AbstractMasterSystemService.java
index 2672f84..6672daa 100644
--- a/services/core/java/com/android/server/infra/AbstractMasterSystemService.java
+++ b/services/core/java/com/android/server/infra/AbstractMasterSystemService.java
@@ -43,6 +43,7 @@
 import com.android.internal.os.BackgroundThread;
 import com.android.server.LocalServices;
 import com.android.server.SystemService;
+import com.android.server.SystemService.TargetUser;
 
 import java.io.PrintWriter;
 import java.lang.annotation.Retention;
@@ -299,16 +300,16 @@
     }
 
     @Override // from SystemService
-    public void onUnlockUser(int userId) {
+    public void onUserUnlocking(@NonNull TargetUser user) {
         synchronized (mLock) {
-            updateCachedServiceLocked(userId);
+            updateCachedServiceLocked(user.getUserIdentifier());
         }
     }
 
     @Override // from SystemService
-    public void onCleanupUser(int userId) {
+    public void onUserStopped(@NonNull TargetUser user) {
         synchronized (mLock) {
-            removeCachedServiceLocked(userId);
+            removeCachedServiceLocked(user.getUserIdentifier());
         }
     }
 
@@ -948,7 +949,7 @@
                         if (debug) {
                             Slog.d(mTag, "Eagerly recreating service for user " + userId);
                         }
-                        getServiceForUserLocked(userId);
+                        updateCachedServiceLocked(userId);
                     }
                 }
                 onServicePackageRestartedLocked(userId);
diff --git a/services/core/java/com/android/server/input/InputManagerService.java b/services/core/java/com/android/server/input/InputManagerService.java
index 74ed815..813def4 100644
--- a/services/core/java/com/android/server/input/InputManagerService.java
+++ b/services/core/java/com/android/server/input/InputManagerService.java
@@ -17,6 +17,7 @@
 package com.android.server.input;
 
 import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.app.Notification;
 import android.app.NotificationManager;
 import android.app.PendingIntent;
@@ -62,10 +63,12 @@
 import android.os.ResultReceiver;
 import android.os.ShellCallback;
 import android.os.UserHandle;
+import android.os.VibrationEffect;
 import android.provider.DeviceConfig;
 import android.provider.Settings;
 import android.provider.Settings.SettingNotFoundException;
 import android.text.TextUtils;
+import android.util.ArrayMap;
 import android.util.Log;
 import android.util.Slog;
 import android.util.SparseArray;
@@ -139,6 +142,8 @@
     private static final int MSG_RELOAD_DEVICE_ALIASES = 5;
     private static final int MSG_DELIVER_TABLET_MODE_CHANGED = 6;
 
+    private static final int DEFAULT_VIBRATION_MAGNITUDE = 192;
+
     // Pointer to native input manager service object.
     private final long mPtr;
 
@@ -181,8 +186,7 @@
 
     // State for vibrator tokens.
     private Object mVibratorLock = new Object();
-    private HashMap<IBinder, VibratorToken> mVibratorTokens =
-            new HashMap<IBinder, VibratorToken>();
+    private Map<IBinder, VibratorToken> mVibratorTokens = new ArrayMap<IBinder, VibratorToken>();
     private int mNextVibratorTokenValue;
 
     // State for the currently installed input filter.
@@ -190,12 +194,16 @@
     IInputFilter mInputFilter; // guarded by mInputFilterLock
     InputFilterHost mInputFilterHost; // guarded by mInputFilterLock
 
+    private final Object mGestureMonitorPidsLock = new Object();
+    @GuardedBy("mGestureMonitorPidsLock")
+    private final ArrayMap<IBinder, Integer> mGestureMonitorPidsByToken = new ArrayMap<>();
+
     // The associations of input devices to displays by port. Maps from input device port (String)
     // to display id (int). Currently only accessed by InputReader.
     private final Map<String, Integer> mStaticAssociations;
     private final Object mAssociationsLock = new Object();
     @GuardedBy("mAssociationLock")
-    private final Map<String, Integer> mRuntimeAssociations = new HashMap<String, Integer>();
+    private final Map<String, Integer> mRuntimeAssociations = new ArrayMap<String, Integer>();
 
     private static native long nativeInit(InputManagerService service,
             Context context, MessageQueue messageQueue);
@@ -540,13 +548,17 @@
         if (displayId < Display.DEFAULT_DISPLAY) {
             throw new IllegalArgumentException("displayId must >= 0.");
         }
+        final int pid = Binder.getCallingPid();
 
         final long ident = Binder.clearCallingIdentity();
         try {
             InputChannel[] inputChannels = InputChannel.openInputChannelPair(inputChannelName);
             InputMonitorHost host = new InputMonitorHost(inputChannels[0]);
-            nativeRegisterInputMonitor(mPtr, inputChannels[0], displayId,
-                    true /*isGestureMonitor*/);
+            nativeRegisterInputMonitor(
+                    mPtr, inputChannels[0], displayId, true /*isGestureMonitor*/);
+            synchronized (mGestureMonitorPidsLock) {
+                mGestureMonitorPidsByToken.put(inputChannels[1].getToken(), pid);
+            }
             return new InputMonitor(inputChannels[1], host);
         } finally {
             Binder.restoreCallingIdentity(ident);
@@ -575,6 +587,9 @@
         if (inputChannel == null) {
             throw new IllegalArgumentException("inputChannel must not be null.");
         }
+        synchronized (mGestureMonitorPidsLock) {
+            mGestureMonitorPidsByToken.remove(inputChannel.getToken());
+        }
 
         nativeUnregisterInputChannel(mPtr, inputChannel);
     }
@@ -1716,7 +1731,37 @@
 
     // Binder call
     @Override
-    public void vibrate(int deviceId, long[] pattern, int[] amplitudes, int repeat, IBinder token) {
+    public void vibrate(int deviceId, VibrationEffect effect, IBinder token) {
+        long[] pattern;
+        int[] amplitudes;
+        int repeat;
+        if (effect instanceof VibrationEffect.OneShot) {
+            VibrationEffect.OneShot oneShot = (VibrationEffect.OneShot) effect;
+            pattern = new long[] { 0, oneShot.getDuration() };
+            int amplitude = oneShot.getAmplitude();
+            // android framework uses DEFAULT_AMPLITUDE to signal that the vibration
+            // should use some built-in default value, denoted here as DEFAULT_VIBRATION_MAGNITUDE
+            if (amplitude == VibrationEffect.DEFAULT_AMPLITUDE) {
+                amplitude = DEFAULT_VIBRATION_MAGNITUDE;
+            }
+            amplitudes = new int[] { 0, amplitude };
+            repeat = -1;
+        } else if (effect instanceof VibrationEffect.Waveform) {
+            VibrationEffect.Waveform waveform = (VibrationEffect.Waveform) effect;
+            pattern = waveform.getTimings();
+            amplitudes = waveform.getAmplitudes();
+            for (int i = 0; i < amplitudes.length; i++) {
+                if (amplitudes[i] == VibrationEffect.DEFAULT_AMPLITUDE) {
+                    amplitudes[i] = DEFAULT_VIBRATION_MAGNITUDE;
+                }
+            }
+            repeat = waveform.getRepeatIndex();
+        } else {
+            // TODO: Add support for prebaked effects
+            Log.w(TAG, "Pre-baked effects aren't supported on input devices");
+            return;
+        }
+
         if (repeat >= pattern.length) {
             throw new ArrayIndexOutOfBoundsException();
         }
@@ -1735,7 +1780,6 @@
                 mVibratorTokens.put(token, v);
             }
         }
-
         synchronized (v) {
             v.mVibrating = true;
             nativeVibrate(mPtr, deviceId, pattern, amplitudes, repeat, v.mTokenValue);
@@ -1838,6 +1882,7 @@
         if (dumpStr != null) {
             pw.println(dumpStr);
             dumpAssociations(pw);
+            dumpGestureMonitorPidsByToken(pw);
         }
     }
 
@@ -1861,6 +1906,19 @@
         }
     }
 
+    private void dumpGestureMonitorPidsByToken(PrintWriter pw) {
+        synchronized (mGestureMonitorPidsLock) {
+            if (!mGestureMonitorPidsByToken.isEmpty()) {
+                pw.println("Gesture monitor pids by token:");
+                for (int i = 0; i < mGestureMonitorPidsByToken.size(); i++) {
+                    pw.print("  " + i + ": ");
+                    pw.print(" token: " + mGestureMonitorPidsByToken.keyAt(i));
+                    pw.println(" pid: " + mGestureMonitorPidsByToken.valueAt(i));
+                }
+            }
+        }
+    }
+
     private boolean checkCallingPermission(String permission, String func) {
         // Quick check: if the calling permission is me, it's all okay.
         if (Binder.getCallingPid() == Process.myPid()) {
@@ -1883,6 +1941,7 @@
     public void monitor() {
         synchronized (mInputFilterLock) { }
         synchronized (mAssociationsLock) { /* Test if blocked by associations lock. */}
+        synchronized (mGestureMonitorPidsLock) { /* Test if blocked by gesture monitor pids lock */}
         nativeMonitor(mPtr);
     }
 
@@ -1944,6 +2003,9 @@
 
     // Native callback.
     private void notifyInputChannelBroken(IBinder token) {
+        synchronized (mGestureMonitorPidsLock) {
+            mGestureMonitorPidsByToken.remove(token);
+        }
         mWindowManagerCallbacks.notifyInputChannelBroken(token);
     }
 
@@ -1959,8 +2021,12 @@
     // Native callback.
     private long notifyANR(InputApplicationHandle inputApplicationHandle, IBinder token,
             String reason) {
-        return mWindowManagerCallbacks.notifyANR(inputApplicationHandle,
-                token, reason);
+        Integer gestureMonitorPid;
+        synchronized (mGestureMonitorPidsLock) {
+            gestureMonitorPid = mGestureMonitorPidsByToken.get(token);
+        }
+        return mWindowManagerCallbacks.notifyANR(inputApplicationHandle, token, gestureMonitorPid,
+                reason);
     }
 
     // Native callback.
@@ -2206,22 +2272,48 @@
      * Callback interface implemented by the Window Manager.
      */
     public interface WindowManagerCallbacks {
+        /**
+         * This callback is invoked when the confuguration changes.
+         */
         public void notifyConfigurationChanged();
 
+        /**
+         * This callback is invoked when the lid switch changes state.
+         * @param whenNanos the time when the change occurred
+         * @param lidOpen true if the lid is open
+         */
         public void notifyLidSwitchChanged(long whenNanos, boolean lidOpen);
 
+        /**
+         * This callback is invoked when the camera lens cover switch changes state.
+         * @param whenNanos the time when the change occurred
+         * @param lensCovered true is the lens is covered
+         */
         public void notifyCameraLensCoverSwitchChanged(long whenNanos, boolean lensCovered);
 
+        /**
+         * This callback is invoked when an input channel is closed unexpectedly.
+         * @param token the connection token of the broken channel
+         */
         public void notifyInputChannelBroken(IBinder token);
 
         /**
-         * Notifies the window manager about an application that is not responding.
-         * Returns a new timeout to continue waiting in nanoseconds, or 0 to abort dispatch.
+         * Notify the window manager about an application that is not responding.
+         * Return a new timeout to continue waiting in nanoseconds, or 0 to abort dispatch.
          */
         long notifyANR(InputApplicationHandle inputApplicationHandle, IBinder token,
-                String reason);
+                @Nullable Integer pid, String reason);
 
-        public int interceptKeyBeforeQueueing(KeyEvent event, int policyFlags);
+        /**
+         * This callback is invoked when an event first arrives to InputDispatcher and before it is
+         * placed onto InputDispatcher's queue. If this event is intercepted, it will never be
+         * processed by InputDispacher.
+         * @param event The key event that's arriving to InputDispatcher
+         * @param policyFlags The policy flags
+         * @return the flags that tell InputDispatcher how to handle the event (for example, whether
+         * to pass it to the user)
+         */
+        int interceptKeyBeforeQueueing(KeyEvent event, int policyFlags);
 
         /**
          * Provides an opportunity for the window manager policy to intercept early motion event
@@ -2231,11 +2323,23 @@
         int interceptMotionBeforeQueueingNonInteractive(int displayId, long whenNanos,
                 int policyFlags);
 
-        public long interceptKeyBeforeDispatching(IBinder token,
-                KeyEvent event, int policyFlags);
+        /**
+         * This callback is invoked just before the key is about to be sent to an application.
+         * This allows the policy to make some last minute decisions on whether to intercept this
+         * key.
+         * @param token the window token that's about to receive this event
+         * @param event the key event that's being dispatched
+         * @param policyFlags the policy flags
+         * @return negative value if the key should be skipped (not sent to the app). 0 if the key
+         * should proceed getting dispatched to the app. positive value to indicate the additional
+         * time delay, in nanoseconds, to wait before sending this key to the app.
+         */
+        long interceptKeyBeforeDispatching(IBinder token, KeyEvent event, int policyFlags);
 
-        public KeyEvent dispatchUnhandledKey(IBinder token,
-                KeyEvent event, int policyFlags);
+        /**
+         * Dispatch unhandled key
+         */
+        KeyEvent dispatchUnhandledKey(IBinder token, KeyEvent event, int policyFlags);
 
         public int getPointerLayer();
 
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerInternal.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerInternal.java
index 05cf40a..3ac95d7 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerInternal.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerInternal.java
@@ -178,7 +178,7 @@
             };
 
     /**
-     * @return Global instance if exists.  Otherwise, a dummy no-op instance.
+     * @return Global instance if exists.  Otherwise, a fallback no-op instance.
      */
     @NonNull
     public static InputMethodManagerInternal get() {
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
index 254285d..9ab410d 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
@@ -159,6 +159,7 @@
 import com.android.server.EventLogTags;
 import com.android.server.LocalServices;
 import com.android.server.SystemService;
+import com.android.server.SystemService.TargetUser;
 import com.android.server.inputmethod.InputMethodManagerInternal.InputMethodListListener;
 import com.android.server.inputmethod.InputMethodSubtypeSwitchingController.ImeSubtypeListItem;
 import com.android.server.inputmethod.InputMethodUtils.InputMethodSettings;
@@ -1596,10 +1597,11 @@
         }
 
         @Override
-        public void onSwitchUser(@UserIdInt int userHandle) {
+        public void onUserSwitching(@Nullable TargetUser from, @NonNull TargetUser to) {
             // Called on ActivityManager thread.
             synchronized (mService.mMethodMap) {
-                mService.scheduleSwitchUserTaskLocked(userHandle, null /* clientToBeReset */);
+                mService.scheduleSwitchUserTaskLocked(to.getUserIdentifier(),
+                        /* clientToBeReset= */ null);
             }
         }
 
@@ -1615,10 +1617,10 @@
         }
 
         @Override
-        public void onUnlockUser(final @UserIdInt int userHandle) {
+        public void onUserUnlocking(@NonNull TargetUser user) {
             // Called on ActivityManager thread.
             mService.mHandler.sendMessage(mService.mHandler.obtainMessage(MSG_SYSTEM_UNLOCK_USER,
-                    userHandle /* arg1 */, 0 /* arg2 */));
+                    /* arg1= */ user.getUserIdentifier(), /* arg2= */ 0));
         }
     }
 
@@ -2211,8 +2213,7 @@
      * @param client {@link android.os.Binder} proxy that is associated with the singleton instance
      *               of {@link android.view.inputmethod.InputMethodManager} that runs on the client
      *               process
-     * @param inputContext communication channel for the dummy
-     *                     {@link android.view.inputmethod.InputConnection}
+     * @param inputContext communication channel for the fallback {@link InputConnection}
      * @param selfReportedDisplayId self-reported display ID to which the client is associated.
      *                              Whether the client is still allowed to access to this display
      *                              or not needs to be evaluated every time the client interacts
@@ -3449,9 +3450,9 @@
             return InputBindResult.USER_SWITCHING;
         }
 
-        // Master feature flag that overrides other conditions and forces IME preRendering.
+        // Main feature flag that overrides other conditions and forces IME preRendering.
         if (DEBUG) {
-            Slog.v(TAG, "IME PreRendering MASTER flag: "
+            Slog.v(TAG, "IME PreRendering main flag: "
                     + DebugFlags.FLAG_PRE_RENDER_IME_VIEWS.value() + ", LowRam: " + mIsLowRam);
         }
         // pre-rendering not supported on low-ram devices.
diff --git a/services/core/java/com/android/server/inputmethod/MultiClientInputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/MultiClientInputMethodManagerService.java
index 2516e28..b518eb1 100644
--- a/services/core/java/com/android/server/inputmethod/MultiClientInputMethodManagerService.java
+++ b/services/core/java/com/android/server/inputmethod/MultiClientInputMethodManagerService.java
@@ -94,6 +94,7 @@
 import com.android.internal.view.InputBindResult;
 import com.android.server.LocalServices;
 import com.android.server.SystemService;
+import com.android.server.SystemService.TargetUser;
 import com.android.server.wm.WindowManagerInternal;
 
 import java.io.FileDescriptor;
@@ -249,23 +250,26 @@
 
         @MainThread
         @Override
-        public void onStartUser(@UserIdInt int userId) {
+        public void onUserStarting(@NonNull TargetUser user) {
             mOnWorkerThreadCallback.getHandler().sendMessage(PooledLambda.obtainMessage(
-                    OnWorkerThreadCallback::onStartUser, mOnWorkerThreadCallback, userId));
+                    OnWorkerThreadCallback::onStartUser, mOnWorkerThreadCallback,
+                    user.getUserIdentifier()));
         }
 
         @MainThread
         @Override
-        public void onUnlockUser(@UserIdInt int userId) {
+        public void onUserUnlocking(@NonNull TargetUser user) {
             mOnWorkerThreadCallback.getHandler().sendMessage(PooledLambda.obtainMessage(
-                    OnWorkerThreadCallback::onUnlockUser, mOnWorkerThreadCallback, userId));
+                    OnWorkerThreadCallback::onUnlockUser, mOnWorkerThreadCallback,
+                    user.getUserIdentifier()));
         }
 
         @MainThread
         @Override
-        public void onStopUser(@UserIdInt int userId) {
+        public void onUserStopping(@NonNull TargetUser user) {
             mOnWorkerThreadCallback.getHandler().sendMessage(PooledLambda.obtainMessage(
-                    OnWorkerThreadCallback::onStopUser, mOnWorkerThreadCallback, userId));
+                    OnWorkerThreadCallback::onStopUser, mOnWorkerThreadCallback,
+                    user.getUserIdentifier()));
         }
     }
 
@@ -1659,7 +1663,7 @@
                 }
 
                 if (editorInfo == null) {
-                    // So-called dummy InputConnection scenario.  For app compatibility, we still
+                    // So-called fallback InputConnection scenario.  For app compatibility, we still
                     // notify this to the IME.
                     switch (clientInfo.mState) {
                         case InputMethodClientState.READY_TO_SEND_FIRST_BIND_RESULT:
diff --git a/services/core/java/com/android/server/lights/LightsService.java b/services/core/java/com/android/server/lights/LightsService.java
index c4f8441..ac9b7ea 100644
--- a/services/core/java/com/android/server/lights/LightsService.java
+++ b/services/core/java/com/android/server/lights/LightsService.java
@@ -318,8 +318,7 @@
                     SurfaceControl.setDisplayBrightness(mDisplayToken, brightness);
                 } else {
                     // Old system
-                    int brightnessInt = BrightnessSynchronizer.brightnessFloatToInt(
-                            getContext(), brightness);
+                    int brightnessInt = BrightnessSynchronizer.brightnessFloatToInt(brightness);
                     int color = brightnessInt & 0x000000ff;
                     color = 0xff000000 | (color << 16) | (color << 8) | color;
                     setLightLocked(color, LIGHT_FLASH_NONE, 0, 0, brightnessMode);
diff --git a/services/core/java/com/android/server/location/LocationFudger.java b/services/core/java/com/android/server/location/LocationFudger.java
index 1f458ed..6f35c8b 100644
--- a/services/core/java/com/android/server/location/LocationFudger.java
+++ b/services/core/java/com/android/server/location/LocationFudger.java
@@ -112,7 +112,7 @@
     public Location createCoarse(Location fine) {
         synchronized (this) {
             if (fine == mCachedFineLocation) {
-                return new Location(mCachedCoarseLocation);
+                return mCachedCoarseLocation;
             }
         }
 
@@ -154,7 +154,7 @@
             mCachedCoarseLocation = coarse;
         }
 
-        return new Location(mCachedCoarseLocation);
+        return mCachedCoarseLocation;
     }
 
     /**
diff --git a/services/core/java/com/android/server/location/LocationManagerService.java b/services/core/java/com/android/server/location/LocationManagerService.java
index d3558f1..71f1833 100644
--- a/services/core/java/com/android/server/location/LocationManagerService.java
+++ b/services/core/java/com/android/server/location/LocationManagerService.java
@@ -17,43 +17,27 @@
 package com.android.server.location;
 
 import static android.Manifest.permission.ACCESS_FINE_LOCATION;
-import static android.app.AppOpsManager.OP_MOCK_LOCATION;
-import static android.app.AppOpsManager.OP_MONITOR_HIGH_POWER_LOCATION;
-import static android.app.AppOpsManager.OP_MONITOR_LOCATION;
 import static android.content.pm.PackageManager.MATCH_DIRECT_BOOT_AWARE;
 import static android.content.pm.PackageManager.MATCH_SYSTEM_ONLY;
 import static android.content.pm.PackageManager.PERMISSION_GRANTED;
-import static android.location.LocationManager.EXTRA_LOCATION_ENABLED;
-import static android.location.LocationManager.EXTRA_PROVIDER_ENABLED;
-import static android.location.LocationManager.EXTRA_PROVIDER_NAME;
 import static android.location.LocationManager.FUSED_PROVIDER;
 import static android.location.LocationManager.GPS_PROVIDER;
-import static android.location.LocationManager.KEY_LOCATION_CHANGED;
-import static android.location.LocationManager.KEY_PROVIDER_ENABLED;
-import static android.location.LocationManager.MODE_CHANGED_ACTION;
 import static android.location.LocationManager.NETWORK_PROVIDER;
-import static android.location.LocationManager.PASSIVE_PROVIDER;
-import static android.location.LocationManager.PROVIDERS_CHANGED_ACTION;
-import static android.location.LocationManager.invalidateLocalLocationEnabledCaches;
-import static android.os.PowerManager.locationPowerSaveModeToString;
 
 import static com.android.server.location.LocationPermissions.PERMISSION_COARSE;
 import static com.android.server.location.LocationPermissions.PERMISSION_FINE;
-import static com.android.server.location.LocationPermissions.PERMISSION_NONE;
+import static com.android.server.location.LocationProviderManager.FASTEST_COARSE_INTERVAL_MS;
 
 import static java.util.concurrent.TimeUnit.NANOSECONDS;
 
 import android.Manifest.permission;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
-import android.annotation.UserIdInt;
 import android.app.ActivityManager;
 import android.app.AppOpsManager;
 import android.app.PendingIntent;
-import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.Intent;
-import android.content.IntentFilter;
 import android.location.Criteria;
 import android.location.GeocoderParams;
 import android.location.Geofence;
@@ -78,17 +62,9 @@
 import android.location.LocationTime;
 import android.location.util.identity.CallerIdentity;
 import android.os.Binder;
-import android.os.Build;
 import android.os.Bundle;
-import android.os.CancellationSignal;
-import android.os.Handler;
-import android.os.IBinder;
 import android.os.ICancellationSignal;
-import android.os.IRemoteCallback;
 import android.os.ParcelFileDescriptor;
-import android.os.PowerManager;
-import android.os.PowerManager.ServiceType;
-import android.os.PowerManagerInternal;
 import android.os.Process;
 import android.os.RemoteException;
 import android.os.SystemClock;
@@ -96,24 +72,16 @@
 import android.os.WorkSource;
 import android.os.WorkSource.WorkChain;
 import android.stats.location.LocationStatsEnums;
-import android.text.TextUtils;
-import android.util.EventLog;
 import android.util.IndentingPrintWriter;
 import android.util.Log;
-import android.util.SparseArray;
 import android.util.TimeUtils;
 
 import com.android.internal.annotations.GuardedBy;
-import com.android.internal.content.PackageMonitor;
 import com.android.internal.location.ProviderProperties;
-import com.android.internal.location.ProviderRequest;
 import com.android.internal.util.DumpUtils;
 import com.android.internal.util.Preconditions;
-import com.android.server.FgThread;
 import com.android.server.LocalServices;
-import com.android.server.PendingIntentUtils;
 import com.android.server.SystemService;
-import com.android.server.location.AbstractLocationProvider.State;
 import com.android.server.location.LocationPermissions.PermissionLevel;
 import com.android.server.location.LocationRequestStatistics.PackageProviderKey;
 import com.android.server.location.LocationRequestStatistics.PackageStatistics;
@@ -137,24 +105,17 @@
 import com.android.server.location.util.SystemSettingsHelper;
 import com.android.server.location.util.SystemUserInfoHelper;
 import com.android.server.location.util.UserInfoHelper;
-import com.android.server.location.util.UserInfoHelper.UserListener;
 import com.android.server.pm.permission.PermissionManagerServiceInternal;
 
-import java.io.ByteArrayOutputStream;
 import java.io.FileDescriptor;
-import java.io.PrintStream;
 import java.io.PrintWriter;
 import java.util.ArrayList;
 import java.util.Collections;
-import java.util.HashMap;
-import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
-import java.util.Map.Entry;
 import java.util.Objects;
 import java.util.TreeMap;
 import java.util.concurrent.CopyOnWriteArrayList;
-import java.util.concurrent.Executor;
 
 /**
  * The service class that manages LocationProviders and issues location
@@ -241,54 +202,21 @@
     public static final String TAG = "LocationManagerService";
     public static final boolean D = Log.isLoggable(TAG, Log.DEBUG);
 
-    private static final String WAKELOCK_KEY = "*location*";
-
     private static final String NETWORK_LOCATION_SERVICE_ACTION =
             "com.android.location.service.v3.NetworkLocationProvider";
     private static final String FUSED_LOCATION_SERVICE_ACTION =
             "com.android.location.service.FusedLocationProvider";
 
-    // The maximum interval a location request can have and still be considered "high power".
-    private static final long HIGH_POWER_INTERVAL_MS = 5 * 60 * 1000;
-
-    // The fastest interval that applications can receive coarse locations
-    private static final long FASTEST_COARSE_INTERVAL_MS = 10 * 60 * 1000;
-
-    // maximum age of a location before it is no longer considered "current"
-    private static final long MAX_CURRENT_LOCATION_AGE_MS = 10 * 1000;
-
-    // Location Providers may sometimes deliver location updates
-    // slightly faster that requested - provide grace period so
-    // we don't unnecessarily filter events that are otherwise on
-    // time
-    private static final int MAX_PROVIDER_SCHEDULING_JITTER_MS = 100;
-
-    private static final long GET_CURRENT_LOCATION_MAX_TIMEOUT_MS = 30000;
-
     private static final String ATTRIBUTION_TAG = "LocationService";
 
     private final Object mLock = new Object();
 
-    private final Handler mHandler;
+    private final Context mContext;
+    private final Injector mInjector;
     private final LocalService mLocalService;
 
-    private final Injector mInjector;
-
-    private final Context mContext;
-    private final AppOpsHelper mAppOpsHelper;
-    private final UserInfoHelper mUserInfoHelper;
-    private final SettingsHelper mSettingsHelper;
-    private final AppForegroundHelper mAppForegroundHelper;
-    private final LocationUsageLogger mLocationUsageLogger;
-
     private final GeofenceManager mGeofenceManager;
-
     @Nullable private volatile GnssManagerService mGnssManagerService = null;
-
-    private final PassiveLocationProviderManager mPassiveManager;
-
-    private PowerManager mPowerManager;
-
     private GeocoderProxy mGeocodeProvider;
 
     @GuardedBy("mLock")
@@ -296,46 +224,30 @@
     @GuardedBy("mLock")
     private boolean mExtraLocationControllerPackageEnabled;
 
-    // @GuardedBy("mLock")
-    // hold lock for write or to prevent write, no lock for read
-    final CopyOnWriteArrayList<LocationProviderManager> mProviderManagers =
+    // location provider managers
+
+    private final PassiveLocationProviderManager mPassiveManager;
+
+    // @GuardedBy("mProviderManagers")
+    // hold lock for writes, no lock necessary for simple reads
+    private final CopyOnWriteArrayList<LocationProviderManager> mProviderManagers =
             new CopyOnWriteArrayList<>();
 
-    @GuardedBy("mLock")
-    private final HashMap<Object, Receiver> mReceivers = new HashMap<>();
-    private final HashMap<String, ArrayList<UpdateRecord>> mRecordsByProvider =
-            new HashMap<>();
-
-    private final LocationRequestStatistics mRequestStatistics = new LocationRequestStatistics();
-
-    @GuardedBy("mLock")
-    @PowerManager.LocationPowerSaveMode
-    private int mBatterySaverMode;
-
     LocationManagerService(Context context, Injector injector) {
-        mHandler = FgThread.getHandler();
-        mLocalService = new LocalService();
-
-        LocalServices.addService(LocationManagerInternal.class, mLocalService);
-
+        mContext = context.createAttributionContext(ATTRIBUTION_TAG);
         mInjector = injector;
 
-        mContext = context.createAttributionContext(ATTRIBUTION_TAG);
-        mUserInfoHelper = injector.getUserInfoHelper();
-        mAppOpsHelper = injector.getAppOpsHelper();
-        mSettingsHelper = injector.getSettingsHelper();
-        mAppForegroundHelper = injector.getAppForegroundHelper();
-        mLocationUsageLogger = injector.getLocationUsageLogger();
+        mLocalService = new LocalService();
+        LocalServices.addService(LocationManagerInternal.class, mLocalService);
 
         mGeofenceManager = new GeofenceManager(mContext, injector);
 
-        // set up passive provider - we do this early because it has no dependencies on system
-        // services or external code that isn't ready yet, and because this allows the variable to
-        // be final. other more complex providers are initialized later, when system services are
-        // ready
-        mPassiveManager = new PassiveLocationProviderManager();
-        mProviderManagers.add(mPassiveManager);
-        mPassiveManager.setRealProvider(new PassiveProvider(mContext));
+        // set up passive provider first since it will be required for all other location providers,
+        // which are loaded later once the system is ready.
+        mPassiveManager = new PassiveLocationProviderManager(mContext, injector);
+        addLocationProviderManager(mPassiveManager, new PassiveProvider(mContext));
+
+        // TODO: load the gps provider here as well, which will require refactoring
 
         // Let the package manager query which are the default location
         // providers as they get certain permissions granted by default.
@@ -347,253 +259,77 @@
         permissionManagerInternal.setLocationExtraPackagesProvider(
                 userId -> mContext.getResources().getStringArray(
                         com.android.internal.R.array.config_locationExtraPackageNames));
+    }
 
-        // most startup is deferred until systemReady()
+    @Nullable
+    private LocationProviderManager getLocationProviderManager(String providerName) {
+        if (providerName == null) {
+            return null;
+        }
+
+        for (LocationProviderManager manager : mProviderManagers) {
+            if (providerName.equals(manager.getName())) {
+                return manager;
+            }
+        }
+
+        return null;
+    }
+
+    private LocationProviderManager getOrAddLocationProviderManager(String providerName) {
+        synchronized (mProviderManagers) {
+            for (LocationProviderManager manager : mProviderManagers) {
+                if (providerName.equals(manager.getName())) {
+                    return manager;
+                }
+            }
+
+            LocationProviderManager manager = new LocationProviderManager(mContext, mInjector,
+                    providerName, mPassiveManager);
+            addLocationProviderManager(manager, null);
+            return manager;
+        }
+    }
+
+    private void addLocationProviderManager(LocationProviderManager manager,
+            @Nullable AbstractLocationProvider realProvider) {
+        synchronized (mProviderManagers) {
+            Preconditions.checkState(getLocationProviderManager(manager.getName()) == null);
+
+            manager.startManager();
+            if (realProvider != null) {
+                manager.setRealProvider(realProvider);
+            }
+            mProviderManagers.add(manager);
+        }
+    }
+
+    private void removeLocationProviderManager(LocationProviderManager manager) {
+        synchronized (mProviderManagers) {
+            Preconditions.checkState(getLocationProviderManager(manager.getName()) == manager);
+
+            mProviderManagers.remove(manager);
+            manager.setMockProvider(null);
+            manager.setRealProvider(null);
+            manager.stopManager();
+        }
     }
 
     void onSystemReady() {
-        synchronized (mLock) {
-            mPowerManager = mContext.getSystemService(PowerManager.class);
-
-            // add listeners
-            mContext.getPackageManager().addOnPermissionsChangeListener(
-                    uid -> {
-                        // listener invoked on ui thread, move to our thread to reduce risk of
-                        // blocking ui thread
-                        mHandler.post(() -> {
-                            synchronized (mLock) {
-                                onPermissionsChangedLocked();
-                            }
-                        });
-                    });
-
-            LocalServices.getService(PowerManagerInternal.class).registerLowPowerModeObserver(
-                    ServiceType.LOCATION,
-                    state -> {
-                        // listener invoked on ui thread, move to our thread to reduce risk of
-                        // blocking ui thread
-                        mHandler.post(() -> {
-                            synchronized (mLock) {
-                                onBatterySaverModeChangedLocked(state.locationMode);
-                            }
-                        });
-                    });
-            mBatterySaverMode = mPowerManager.getLocationPowerSaveMode();
-
-            mAppOpsHelper.addListener(this::onAppOpChanged);
-
-            mSettingsHelper.addOnLocationEnabledChangedListener(this::onLocationModeChanged);
-            mSettingsHelper.addOnBackgroundThrottleIntervalChangedListener(
-                    this::onBackgroundThrottleIntervalChanged);
-            mSettingsHelper.addOnBackgroundThrottlePackageWhitelistChangedListener(
-                    this::onBackgroundThrottleWhitelistChanged);
-            mSettingsHelper.addOnIgnoreSettingsPackageWhitelistChangedListener(
-                    this::onIgnoreSettingsWhitelistChanged);
-
-            PackageMonitor packageMonitor = new PackageMonitor() {
-                @Override
-                public void onPackageDisappeared(String packageName, int reason) {
-                    synchronized (mLock) {
-                        LocationManagerService.this.onPackageDisappeared(packageName);
-                    }
-                }
-            };
-            packageMonitor.register(mContext, null, true, mHandler);
-
-            mUserInfoHelper.addListener(this::onUserChanged);
-
-            mAppForegroundHelper.addListener(this::onAppForegroundChanged);
-
-            IntentFilter screenIntentFilter = new IntentFilter();
-            screenIntentFilter.addAction(Intent.ACTION_SCREEN_OFF);
-            screenIntentFilter.addAction(Intent.ACTION_SCREEN_ON);
-            mContext.registerReceiverAsUser(new BroadcastReceiver() {
-                @Override
-                public void onReceive(Context context, Intent intent) {
-                    if (Intent.ACTION_SCREEN_ON.equals(intent.getAction())
-                            || Intent.ACTION_SCREEN_OFF.equals(intent.getAction())) {
-                        onScreenStateChanged();
-                    }
-                }
-            }, UserHandle.ALL, screenIntentFilter, null, mHandler);
-
-            // initialize the current users. we would get the user started notifications for these
-            // users eventually anyways, but this takes care of it as early as possible.
-            onUserChanged(UserHandle.USER_ALL, UserListener.USER_STARTED);
-        }
+        mInjector.getSettingsHelper().addOnLocationEnabledChangedListener(
+                this::onLocationModeChanged);
     }
 
     void onSystemThirdPartyAppsCanStart() {
-        synchronized (mLock) {
-            // prepare providers
-            initializeProvidersLocked();
-        }
-
-        // initialize gnss last because it has no awareness of boot phases and blindly assumes that
-        // all other location providers are loaded at initialization
-        initializeGnss();
-    }
-
-    private void onAppOpChanged(String packageName) {
-        synchronized (mLock) {
-            for (Receiver receiver : mReceivers.values()) {
-                if (receiver.mCallerIdentity.getPackageName().equals(packageName)) {
-                    receiver.updateMonitoring(true);
-                }
-            }
-
-            HashSet<String> affectedProviders = new HashSet<>(mRecordsByProvider.size());
-            for (Entry<String, ArrayList<UpdateRecord>> entry : mRecordsByProvider.entrySet()) {
-                String provider = entry.getKey();
-                for (UpdateRecord record : entry.getValue()) {
-                    if (record.mReceiver.mCallerIdentity.getPackageName().equals(packageName)) {
-                        affectedProviders.add(provider);
-                    }
-                }
-            }
-            for (String provider : affectedProviders) {
-                applyRequirementsLocked(provider);
-            }
-        }
-    }
-
-    @GuardedBy("mLock")
-    private void onPermissionsChangedLocked() {
-        for (LocationProviderManager manager : mProviderManagers) {
-            applyRequirementsLocked(manager);
-        }
-    }
-
-    @GuardedBy("mLock")
-    private void onBatterySaverModeChangedLocked(int newLocationMode) {
-        if (mBatterySaverMode == newLocationMode) {
-            return;
-        }
-
-        if (D) {
-            Log.d(TAG,
-                    "Battery Saver location mode changed from "
-                            + locationPowerSaveModeToString(mBatterySaverMode) + " to "
-                            + locationPowerSaveModeToString(newLocationMode));
-        }
-
-        mBatterySaverMode = newLocationMode;
-
-        for (LocationProviderManager manager : mProviderManagers) {
-            applyRequirementsLocked(manager);
-        }
-    }
-
-    private void onScreenStateChanged() {
-        synchronized (mLock) {
-            if (mBatterySaverMode == PowerManager.LOCATION_MODE_THROTTLE_REQUESTS_WHEN_SCREEN_OFF) {
-                for (LocationProviderManager manager : mProviderManagers) {
-                    applyRequirementsLocked(manager);
-                }
-            }
-        }
-    }
-
-    private void onLocationModeChanged(int userId) {
-        boolean enabled = mSettingsHelper.isLocationEnabled(userId);
-        LocationManager.invalidateLocalLocationEnabledCaches();
-
-        if (D) {
-            Log.d(TAG, "[u" + userId + "] location enabled = " + enabled);
-        }
-
-        Intent intent = new Intent(MODE_CHANGED_ACTION)
-                .putExtra(EXTRA_LOCATION_ENABLED, enabled)
-                .addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY)
-                .addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
-        mContext.sendBroadcastAsUser(intent, UserHandle.of(userId));
-
-        synchronized (mLock) {
-            for (LocationProviderManager manager : mProviderManagers) {
-                manager.onEnabledChangedLocked(userId);
-            }
-        }
-    }
-
-    private void onPackageDisappeared(String packageName) {
-        synchronized (mLock) {
-            ArrayList<Receiver> deadReceivers = null;
-
-            for (Receiver receiver : mReceivers.values()) {
-                if (receiver.mCallerIdentity.getPackageName().equals(packageName)) {
-                    if (deadReceivers == null) {
-                        deadReceivers = new ArrayList<>();
-                    }
-                    deadReceivers.add(receiver);
-                }
-            }
-
-            // perform removal outside of mReceivers loop
-            if (deadReceivers != null) {
-                for (Receiver receiver : deadReceivers) {
-                    removeUpdatesLocked(receiver);
-                }
-            }
-        }
-    }
-
-    private void onAppForegroundChanged(int uid, boolean foreground) {
-        synchronized (mLock) {
-            HashSet<String> affectedProviders = new HashSet<>(mRecordsByProvider.size());
-            for (Entry<String, ArrayList<UpdateRecord>> entry : mRecordsByProvider.entrySet()) {
-                String provider = entry.getKey();
-                for (UpdateRecord record : entry.getValue()) {
-                    if (record.mReceiver.mCallerIdentity.getUid() == uid
-                            && record.mIsForegroundUid != foreground) {
-                        record.updateForeground(foreground);
-
-                        if (!isThrottlingExempt(record.mReceiver.mCallerIdentity)) {
-                            affectedProviders.add(provider);
-                        }
-                    }
-                }
-            }
-            for (String provider : affectedProviders) {
-                applyRequirementsLocked(provider);
-            }
-        }
-    }
-
-    private void onBackgroundThrottleIntervalChanged() {
-        synchronized (mLock) {
-            for (LocationProviderManager manager : mProviderManagers) {
-                applyRequirementsLocked(manager);
-            }
-        }
-    }
-
-    private void onBackgroundThrottleWhitelistChanged() {
-        synchronized (mLock) {
-            for (LocationProviderManager manager : mProviderManagers) {
-                applyRequirementsLocked(manager);
-            }
-        }
-    }
-
-    private void onIgnoreSettingsWhitelistChanged() {
-        synchronized (mLock) {
-            for (LocationProviderManager manager : mProviderManagers) {
-                applyRequirementsLocked(manager);
-            }
-        }
-    }
-
-    @GuardedBy("mLock")
-    private void initializeProvidersLocked() {
         LocationProviderProxy networkProvider = LocationProviderProxy.createAndRegister(
                 mContext,
                 NETWORK_LOCATION_SERVICE_ACTION,
                 com.android.internal.R.bool.config_enableNetworkLocationOverlay,
                 com.android.internal.R.string.config_networkLocationProviderPackageName);
         if (networkProvider != null) {
-            LocationProviderManager networkManager = new LocationProviderManager(NETWORK_PROVIDER);
-            mProviderManagers.add(networkManager);
-            networkManager.setRealProvider(networkProvider);
+            LocationProviderManager networkManager = new LocationProviderManager(mContext,
+                    mInjector, NETWORK_PROVIDER, mPassiveManager);
+            addLocationProviderManager(networkManager, networkProvider);
         } else {
             Log.w(TAG, "no network location provider found");
         }
@@ -604,18 +340,28 @@
                 MATCH_DIRECT_BOOT_AWARE | MATCH_SYSTEM_ONLY, UserHandle.USER_SYSTEM).isEmpty(),
                 "Unable to find a direct boot aware fused location provider");
 
-        // bind to fused provider
         LocationProviderProxy fusedProvider = LocationProviderProxy.createAndRegister(
                 mContext,
                 FUSED_LOCATION_SERVICE_ACTION,
                 com.android.internal.R.bool.config_enableFusedLocationOverlay,
                 com.android.internal.R.string.config_fusedLocationProviderPackageName);
         if (fusedProvider != null) {
-            LocationProviderManager fusedManager = new LocationProviderManager(FUSED_PROVIDER);
-            mProviderManagers.add(fusedManager);
-            fusedManager.setRealProvider(fusedProvider);
+            LocationProviderManager fusedManager = new LocationProviderManager(mContext, mInjector,
+                    FUSED_PROVIDER, mPassiveManager);
+            addLocationProviderManager(fusedManager, fusedProvider);
         } else {
-            Log.e(TAG, "no fused location provider found");
+            Log.wtf(TAG, "no fused location provider found");
+        }
+
+        // initialize gnss last because it has no awareness of boot phases and blindly assumes that
+        // all other location providers are loaded at initialization
+        if (GnssManagerService.isGnssSupported()) {
+            mGnssManagerService = new GnssManagerService(mContext, mInjector);
+            mGnssManagerService.onSystemReady();
+
+            LocationProviderManager gnssManager = new LocationProviderManager(mContext, mInjector,
+                    GPS_PROVIDER, mPassiveManager);
+            addLocationProviderManager(gnssManager, mGnssManagerService.getGnssLocationProvider());
         }
 
         // bind to geocoder provider
@@ -631,6 +377,18 @@
             Log.e(TAG, "unable to bind ActivityRecognitionProxy");
         }
 
+        // bind to gnss geofence proxy
+        if (GnssManagerService.isGnssSupported()) {
+            IGpsGeofenceHardware gpsGeofenceHardware = mGnssManagerService.getGpsGeofenceProxy();
+            if (gpsGeofenceHardware != null) {
+                GeofenceProxy provider = GeofenceProxy.createAndBind(mContext, gpsGeofenceHardware);
+                if (provider == null) {
+                    Log.e(TAG, "unable to bind to GeofenceProxy");
+                }
+            }
+        }
+
+        // create any predefined test providers
         String[] testProviderStrings = mContext.getResources().getStringArray(
                 com.android.internal.R.array.config_testLocationProviders);
         for (String testProviderString : testProviderStrings) {
@@ -646,763 +404,24 @@
                     Boolean.parseBoolean(fragments[7]) /* supportsBearing */,
                     Integer.parseInt(fragments[8]) /* powerRequirement */,
                     Integer.parseInt(fragments[9]) /* accuracy */);
-            LocationProviderManager manager = getLocationProviderManager(name);
-            if (manager == null) {
-                manager = new LocationProviderManager(name);
-                mProviderManagers.add(manager);
-            }
-            manager.setMockProvider(
+            getOrAddLocationProviderManager(name).setMockProvider(
                     new MockProvider(properties, CallerIdentity.fromContext(mContext)));
         }
     }
 
-    private void initializeGnss() {
-        // Do not hold mLock when calling GnssManagerService#isGnssSupported() which calls into HAL.
-        if (GnssManagerService.isGnssSupported()) {
-            mGnssManagerService = new GnssManagerService(mContext, mInjector);
-            mGnssManagerService.onSystemReady();
+    private void onLocationModeChanged(int userId) {
+        boolean enabled = mInjector.getSettingsHelper().isLocationEnabled(userId);
+        LocationManager.invalidateLocalLocationEnabledCaches();
 
-            LocationProviderManager gnssManager = new LocationProviderManager(GPS_PROVIDER);
-            synchronized (mLock) {
-                mProviderManagers.add(gnssManager);
-            }
-            gnssManager.setRealProvider(mGnssManagerService.getGnssLocationProvider());
-
-            // bind to geofence proxy
-            IGpsGeofenceHardware gpsGeofenceHardware = mGnssManagerService.getGpsGeofenceProxy();
-            if (gpsGeofenceHardware != null) {
-                GeofenceProxy provider = GeofenceProxy.createAndBind(mContext, gpsGeofenceHardware);
-                if (provider == null) {
-                    Log.e(TAG, "unable to bind to GeofenceProxy");
-                }
-            }
-        }
-    }
-
-    private void onUserChanged(@UserIdInt int userId, @UserListener.UserChange int change) {
-        switch (change) {
-            case UserListener.CURRENT_USER_CHANGED:
-                synchronized (mLock) {
-                    for (LocationProviderManager manager : mProviderManagers) {
-                        manager.onEnabledChangedLocked(userId);
-                    }
-                }
-                break;
-            case UserListener.USER_STARTED:
-                synchronized (mLock) {
-                    for (LocationProviderManager manager : mProviderManagers) {
-                        manager.onUserStarted(userId);
-                    }
-                }
-                break;
-            case UserListener.USER_STOPPED:
-                synchronized (mLock) {
-                    for (LocationProviderManager manager : mProviderManagers) {
-                        manager.onUserStopped(userId);
-                    }
-                }
-                break;
-        }
-    }
-
-    /**
-     * Location provider manager, manages a LocationProvider.
-     */
-    class LocationProviderManager implements MockableLocationProvider.Listener {
-
-        private final String mName;
-
-        private final LocationFudger mLocationFudger;
-
-        // if the provider is enabled for a given user id - null or not present means unknown
-        @GuardedBy("mLock")
-        private final SparseArray<Boolean> mEnabled;
-
-        // last location for a given user
-        @GuardedBy("mLock")
-        private final SparseArray<Location> mLastLocation;
-
-        // last coarse location for a given user
-        @GuardedBy("mLock")
-        private final SparseArray<Location> mLastCoarseLocation;
-
-        // acquiring mLock makes operations on mProvider atomic, but is otherwise unnecessary
-        protected final MockableLocationProvider mProvider;
-
-        LocationProviderManager(String name) {
-            mName = name;
-            mLocationFudger = new LocationFudger(mSettingsHelper.getCoarseLocationAccuracyM());
-            mEnabled = new SparseArray<>(2);
-            mLastLocation = new SparseArray<>(2);
-            mLastCoarseLocation = new SparseArray<>(2);
-
-            // initialize last since this lets our reference escape
-            mProvider = new MockableLocationProvider(mLock, this);
+        if (D) {
+            Log.d(TAG, "[u" + userId + "] location enabled = " + enabled);
         }
 
-        public String getName() {
-            return mName;
-        }
-
-        public boolean hasProvider() {
-            return mProvider.getProvider() != null;
-        }
-
-        public void setRealProvider(AbstractLocationProvider provider) {
-            mProvider.setRealProvider(provider);
-        }
-
-        public void setMockProvider(@Nullable MockProvider provider) {
-            synchronized (mLock) {
-                mProvider.setMockProvider(provider);
-
-                // when removing a mock provider, also clear any mock last locations and reset the
-                // location fudger. the mock provider could have been used to infer the current
-                // location fudger offsets.
-                if (provider == null) {
-                    for (int i = 0; i < mLastLocation.size(); i++) {
-                        Location lastLocation = mLastLocation.valueAt(i);
-                        if (lastLocation != null && lastLocation.isFromMockProvider()) {
-                            mLastLocation.setValueAt(i, null);
-                        }
-                    }
-
-                    for (int i = 0; i < mLastCoarseLocation.size(); i++) {
-                        Location lastCoarseLocation = mLastCoarseLocation.valueAt(i);
-                        if (lastCoarseLocation != null && lastCoarseLocation.isFromMockProvider()) {
-                            mLastCoarseLocation.setValueAt(i, null);
-                        }
-                    }
-
-                    mLocationFudger.resetOffsets();
-                }
-            }
-        }
-
-        @Nullable
-        public CallerIdentity getProviderIdentity() {
-            return mProvider.getState().identity;
-        }
-
-        @Nullable
-        public ProviderProperties getProperties() {
-            return mProvider.getState().properties;
-        }
-
-        @Nullable
-        public Location getLastLocation(int userId, @PermissionLevel int permissionlevel) {
-            synchronized (mLock) {
-                switch (permissionlevel) {
-                    case PERMISSION_COARSE:
-                        return mLastCoarseLocation.get(userId);
-                    case PERMISSION_FINE:
-                        return mLastLocation.get(userId);
-                    default:
-                        throw new AssertionError();
-                }
-            }
-        }
-
-        public void injectLastLocation(Location location, int userId) {
-            synchronized (mLock) {
-                if (mLastLocation.get(userId) == null) {
-                    setLastLocation(location, userId);
-                }
-            }
-        }
-
-        private void setLastLocation(Location location, int userId) {
-            synchronized (mLock) {
-                mLastLocation.put(userId, location);
-
-                // update last coarse interval only if enough time has passed
-                long timeDeltaMs = Long.MAX_VALUE;
-                Location coarseLocation = mLastCoarseLocation.get(userId);
-                if (coarseLocation != null) {
-                    timeDeltaMs = NANOSECONDS.toMillis(location.getElapsedRealtimeNanos())
-                            - NANOSECONDS.toMillis(coarseLocation.getElapsedRealtimeNanos());
-                }
-                if (timeDeltaMs > FASTEST_COARSE_INTERVAL_MS) {
-                    mLastCoarseLocation.put(userId, mLocationFudger.createCoarse(location));
-                }
-            }
-        }
-
-        public void setMockProviderAllowed(boolean enabled) {
-            synchronized (mLock) {
-                if (!mProvider.isMock()) {
-                    throw new IllegalArgumentException(mName + " provider is not a test provider");
-                }
-
-                mProvider.setMockProviderAllowed(enabled);
-            }
-        }
-
-        public void setMockProviderLocation(Location location) {
-            synchronized (mLock) {
-                if (!mProvider.isMock()) {
-                    throw new IllegalArgumentException(mName + " provider is not a test provider");
-                }
-
-                String locationProvider = location.getProvider();
-                if (!TextUtils.isEmpty(locationProvider) && !mName.equals(locationProvider)) {
-                    // The location has an explicit provider that is different from the mock
-                    // provider name. The caller may be trying to fool us via b/33091107.
-                    EventLog.writeEvent(0x534e4554, "33091107", Binder.getCallingUid(),
-                            mName + "!=" + locationProvider);
-                }
-
-                mProvider.setMockProviderLocation(location);
-            }
-        }
-
-        public List<LocationRequest> getMockProviderRequests() {
-            synchronized (mLock) {
-                if (!mProvider.isMock()) {
-                    throw new IllegalArgumentException(mName + " provider is not a test provider");
-                }
-
-                return mProvider.getCurrentRequest().locationRequests;
-            }
-        }
-
-        public void setRequest(ProviderRequest request) {
-            mProvider.setRequest(request);
-        }
-
-        public void sendExtraCommand(int uid, int pid, String command, Bundle extras) {
-            mProvider.sendExtraCommand(uid, pid, command, extras);
-        }
-
-        @GuardedBy("mLock")
-        @Override
-        public void onReportLocation(Location location) {
-            // don't validate mock locations
-            if (!location.isFromMockProvider()) {
-                if (location.getLatitude() == 0 && location.getLongitude() == 0) {
-                    Log.w(TAG, "blocking 0,0 location from " + mName + " provider");
-                    return;
-                }
-            }
-
-            if (!location.isComplete()) {
-                Log.w(TAG, "blocking incomplete location from " + mName + " provider");
-                return;
-            }
-
-            // update last location if the provider is enabled or if servicing a bypass request
-            boolean locationSettingsIgnored = mProvider.getCurrentRequest().locationSettingsIgnored;
-            for (int userId : mUserInfoHelper.getRunningUserIds()) {
-                if (locationSettingsIgnored || isEnabled(userId)) {
-                    setLastLocation(location, userId);
-                }
-            }
-
-            handleLocationChangedLocked(this, location, mLocationFudger.createCoarse(location));
-        }
-
-        @GuardedBy("mLock")
-        @Override
-        public void onReportLocation(List<Location> locations) {
-            if (mGnssManagerService == null || !GPS_PROVIDER.equals(mName)) {
-                return;
-            }
-
-            mGnssManagerService.onReportLocation(locations);
-        }
-
-        @GuardedBy("mLock")
-        @Override
-        public void onStateChanged(State oldState, State newState) {
-            if (oldState.allowed != newState.allowed) {
-                if (D) {
-                    Log.d(TAG, mName + " provider allowed = " + newState.allowed);
-                }
-
-                onEnabledChangedLocked(UserHandle.USER_ALL);
-            }
-        }
-
-        public void onUserStarted(int userId) {
-            if (userId == UserHandle.USER_NULL) {
-                return;
-            } else if (userId == UserHandle.USER_ALL) {
-                for (int runningUserId : mUserInfoHelper.getRunningUserIds()) {
-                    onUserStarted(runningUserId);
-                }
-                return;
-            }
-
-            Preconditions.checkArgument(userId >= 0);
-
-            synchronized (mLock) {
-                // clear the user's prior enabled state to prevent broadcast of enabled state
-                // change. user starts should never result in a broadcast since the state has
-                // technically not changed.
-                mEnabled.put(userId, null);
-                onEnabledChangedLocked(userId);
-            }
-        }
-
-        public void onUserStopped(int userId) {
-            if (userId == UserHandle.USER_NULL) {
-                return;
-            } else if (userId == UserHandle.USER_ALL) {
-                mEnabled.clear();
-                mLastLocation.clear();
-                mLastCoarseLocation.clear();
-                return;
-            }
-
-            Preconditions.checkArgument(userId >= 0);
-
-            synchronized (mLock) {
-                mEnabled.remove(userId);
-                mLastLocation.remove(userId);
-                mLastCoarseLocation.remove(userId);
-            }
-        }
-
-        public boolean isEnabled(int userId) {
-            if (userId == UserHandle.USER_NULL) {
-                // used during initialization - ignore since many lower level operations (checking
-                // settings for instance) do not support the null user
-                return false;
-            }
-
-            Preconditions.checkArgument(userId >= 0);
-
-            synchronized (mLock) {
-                Boolean enabled = mEnabled.get(userId);
-                if (enabled == null) {
-                    // this generally shouldn't occur, but might be possible due to race conditions
-                    // on when we are notified of new users
-                    Log.w(TAG, mName + " provider saw user " + userId + " unexpectedly");
-                    onEnabledChangedLocked(userId);
-                    enabled = Objects.requireNonNull(mEnabled.get(userId));
-                }
-
-                return enabled;
-            }
-        }
-
-        @GuardedBy("mLock")
-        public void onEnabledChangedLocked(int userId) {
-            if (userId == UserHandle.USER_NULL) {
-                // used during initialization - ignore since many lower level operations (checking
-                // settings for instance) do not support the null user
-                return;
-            } else if (userId == UserHandle.USER_ALL) {
-                for (int runningUserId : mUserInfoHelper.getRunningUserIds()) {
-                    onEnabledChangedLocked(runningUserId);
-                }
-                return;
-            }
-
-            Preconditions.checkArgument(userId >= 0);
-
-            // if any property that contributes to "enabled" here changes state, it MUST result
-            // in a direct or indrect call to onEnabledChangedLocked. this allows the provider to
-            // guarantee that it will always eventually reach the correct state.
-            boolean enabled = mProvider.getState().allowed
-                    && mUserInfoHelper.isCurrentUserId(userId)
-                    && mSettingsHelper.isLocationEnabled(userId);
-
-            Boolean wasEnabled = mEnabled.get(userId);
-            if (wasEnabled != null && wasEnabled == enabled) {
-                return;
-            }
-
-            mEnabled.put(userId, enabled);
-
-            if (D) {
-                Log.d(TAG, "[u" + userId + "] " + mName + " provider enabled = " + enabled);
-            }
-
-            // clear last locations if we become disabled and if not servicing a bypass request
-            if (!enabled && !mProvider.getCurrentRequest().locationSettingsIgnored) {
-                mLastLocation.put(userId, null);
-                mLastCoarseLocation.put(userId, null);
-            }
-
-            // do not send change notifications if we just saw this user for the first time
-            if (wasEnabled != null) {
-                // fused and passive provider never get public updates for legacy reasons
-                if (!FUSED_PROVIDER.equals(mName) && !PASSIVE_PROVIDER.equals(mName)) {
-                    Intent intent = new Intent(PROVIDERS_CHANGED_ACTION)
-                            .putExtra(EXTRA_PROVIDER_NAME, mName)
-                            .putExtra(EXTRA_PROVIDER_ENABLED, enabled)
-                            .addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY)
-                            .addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
-                    mContext.sendBroadcastAsUser(intent, UserHandle.of(userId));
-                }
-            }
-
-            updateProviderEnabledLocked(this, enabled);
-        }
-
-        public void dump(FileDescriptor fd, IndentingPrintWriter pw, String[] args) {
-            synchronized (mLock) {
-                pw.print(mName + " provider");
-                if (mProvider.isMock()) {
-                    pw.print(" [mock]");
-                }
-                pw.println(":");
-
-                pw.increaseIndent();
-
-                int[] userIds = mUserInfoHelper.getRunningUserIds();
-                for (int userId : userIds) {
-                    if (userIds.length != 1) {
-                        pw.println("user " + userId + ":");
-                        pw.increaseIndent();
-                    }
-                    pw.println("last location=" + mLastLocation.get(userId));
-                    pw.println("last coarse location=" + mLastCoarseLocation.get(userId));
-                    pw.println("enabled=" + isEnabled(userId));
-                    if (userIds.length != 1) {
-                        pw.decreaseIndent();
-                    }
-                }
-            }
-
-            mProvider.dump(fd, pw, args);
-
-            pw.decreaseIndent();
-        }
-    }
-
-    class PassiveLocationProviderManager extends LocationProviderManager {
-
-        private PassiveLocationProviderManager() {
-            super(PASSIVE_PROVIDER);
-        }
-
-        @Override
-        public void setRealProvider(AbstractLocationProvider provider) {
-            Preconditions.checkArgument(provider instanceof PassiveProvider);
-            super.setRealProvider(provider);
-        }
-
-        @Override
-        public void setMockProvider(@Nullable MockProvider provider) {
-            if (provider != null) {
-                throw new IllegalArgumentException("Cannot mock the passive provider");
-            }
-        }
-
-        public void updateLocation(Location location) {
-            synchronized (mLock) {
-                PassiveProvider passiveProvider = (PassiveProvider) mProvider.getProvider();
-                Preconditions.checkState(passiveProvider != null);
-
-                long identity = Binder.clearCallingIdentity();
-                try {
-                    passiveProvider.updateLocation(location);
-                } finally {
-                    Binder.restoreCallingIdentity(identity);
-                }
-            }
-        }
-    }
-
-    /**
-     * A wrapper class holding either an ILocationListener or a PendingIntent to receive
-     * location updates.
-     */
-    private final class Receiver extends LocationManagerServiceUtils.LinkedListenerBase implements
-            PendingIntent.OnFinished {
-        private static final long WAKELOCK_TIMEOUT_MILLIS = 60 * 1000;
-
-        private final ILocationListener mListener;
-        final PendingIntent mPendingIntent;
-        final WorkSource mWorkSource; // WorkSource for battery blame, or null to assign to caller.
-        private final boolean mHideFromAppOps; // True if AppOps should not monitor this receiver.
-        private final Object mKey;
-
-        final HashMap<String, UpdateRecord> mUpdateRecords = new HashMap<>();
-
-        // True if app ops has started monitoring this receiver for locations.
-        private boolean mOpMonitoring;
-        // True if app ops has started monitoring this receiver for high power (gps) locations.
-        private boolean mOpHighPowerMonitoring;
-        private int mPendingBroadcasts;
-        PowerManager.WakeLock mWakeLock;
-
-        private Receiver(ILocationListener listener, PendingIntent intent, CallerIdentity identity,
-                WorkSource workSource, boolean hideFromAppOps) {
-            super(identity);
-            mListener = listener;
-            mPendingIntent = intent;
-            if (listener != null) {
-                mKey = listener.asBinder();
-            } else {
-                mKey = intent;
-            }
-            if (workSource != null && workSource.isEmpty()) {
-                workSource = null;
-            }
-            mWorkSource = workSource;
-            mHideFromAppOps = hideFromAppOps;
-
-            updateMonitoring(true);
-
-            // construct/configure wakelock
-            mWakeLock = mPowerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, WAKELOCK_KEY);
-            if (workSource == null) {
-                workSource = mCallerIdentity.addToWorkSource(null);
-            }
-            mWakeLock.setWorkSource(workSource);
-
-            // For a non-reference counted wakelock, each acquire will reset the timeout, and we
-            // only need to release it once.
-            mWakeLock.setReferenceCounted(false);
-        }
-
-        @Override
-        public boolean equals(Object otherObj) {
-            return (otherObj instanceof Receiver) && mKey.equals(((Receiver) otherObj).mKey);
-        }
-
-        @Override
-        public int hashCode() {
-            return mKey.hashCode();
-        }
-
-        @Override
-        public String toString() {
-            StringBuilder s = new StringBuilder();
-            s.append("Reciever[");
-            s.append(Integer.toHexString(System.identityHashCode(this)));
-            if (mListener != null) {
-                s.append(" listener");
-            } else {
-                s.append(" intent");
-            }
-            for (String p : mUpdateRecords.keySet()) {
-                s.append(" ").append(mUpdateRecords.get(p).toString());
-            }
-            s.append(" monitoring location: ").append(mOpMonitoring);
-            s.append("]");
-            return s.toString();
-        }
-
-        /**
-         * Update AppOp monitoring for this receiver.
-         *
-         * @param allow If true receiver is currently active, if false it's been removed.
-         */
-        public void updateMonitoring(boolean allow) {
-            if (mHideFromAppOps) {
-                return;
-            }
-
-            boolean requestingLocation = false;
-            boolean requestingHighPowerLocation = false;
-            if (allow) {
-                // See if receiver has any enabled update records.  Also note if any update records
-                // are high power (has a high power provider with an interval under a threshold).
-                for (UpdateRecord updateRecord : mUpdateRecords.values()) {
-                    LocationProviderManager manager = getLocationProviderManager(
-                            updateRecord.mProvider);
-                    if (manager == null) {
-                        continue;
-                    }
-                    if (!manager.isEnabled(UserHandle.getUserId(mCallerIdentity.getUid()))
-                            && !isSettingsExempt(updateRecord)) {
-                        continue;
-                    }
-
-                    requestingLocation = true;
-                    ProviderProperties properties = manager.getProperties();
-                    if (properties != null
-                            && properties.mPowerRequirement == Criteria.POWER_HIGH
-                            && updateRecord.mRequest.getInterval() < HIGH_POWER_INTERVAL_MS) {
-                        requestingHighPowerLocation = true;
-                        break;
-                    }
-                }
-            }
-
-            // First update monitoring of any location request (including high power).
-            mOpMonitoring = updateMonitoring(
-                    requestingLocation,
-                    mOpMonitoring,
-                    false);
-
-            // Now update monitoring of high power requests only.
-            mOpHighPowerMonitoring = updateMonitoring(
-                    requestingHighPowerLocation,
-                    mOpHighPowerMonitoring,
-                    true);
-        }
-
-        private boolean updateMonitoring(boolean allowMonitoring, boolean currentlyMonitoring,
-                boolean highPower) {
-            if (!currentlyMonitoring) {
-                if (allowMonitoring) {
-                    if (!highPower) {
-                        return mAppOpsHelper.startOpNoThrow(OP_MONITOR_LOCATION, mCallerIdentity);
-                    } else {
-                        return mAppOpsHelper.startOpNoThrow(OP_MONITOR_HIGH_POWER_LOCATION,
-                                mCallerIdentity);
-                    }
-                }
-            } else {
-                int permissionLevel = LocationPermissions.getPermissionLevel(mContext,
-                        mCallerIdentity.getUid(), mCallerIdentity.getPid());
-                if (!allowMonitoring || permissionLevel == PERMISSION_NONE
-                        || !mAppOpsHelper.checkOpNoThrow(
-                        LocationPermissions.asAppOp(permissionLevel), mCallerIdentity)) {
-                    if (!highPower) {
-                        mAppOpsHelper.finishOp(OP_MONITOR_LOCATION, mCallerIdentity);
-                    } else {
-                        mAppOpsHelper.finishOp(OP_MONITOR_HIGH_POWER_LOCATION, mCallerIdentity);
-                    }
-                    return false;
-                }
-            }
-
-            return currentlyMonitoring;
-        }
-
-        public boolean isListener() {
-            return mListener != null;
-        }
-
-        public boolean isPendingIntent() {
-            return mPendingIntent != null;
-        }
-
-        public ILocationListener getListener() {
-            if (mListener != null) {
-                return mListener;
-            }
-            throw new IllegalStateException("Request for non-existent listener");
-        }
-
-        public boolean callLocationChangedLocked(Location location,
-                LocationRequest locationRequest) {
-            if (mListener != null) {
-                try {
-                    mListener.onLocationChanged(new Location(location), new IRemoteCallback.Stub() {
-                        @Override
-                        public void sendResult(Bundle data) {
-                            synchronized (mLock) {
-                                decrementPendingBroadcastsLocked();
-                            }
-                        }
-                    });
-                    // call this after broadcasting so we do not increment
-                    // if we throw an exception.
-                    incrementPendingBroadcastsLocked();
-                } catch (RemoteException e) {
-                    return false;
-                }
-            } else {
-                Intent locationChanged = new Intent();
-                locationChanged.putExtra(KEY_LOCATION_CHANGED, new Location(location));
-                try {
-                    mPendingIntent.send(mContext, 0, locationChanged, this, mHandler,
-                            LocationPermissions.asPermission(
-                                    locationRequest.isCoarse() ? PERMISSION_COARSE
-                                            : PERMISSION_FINE),
-                            PendingIntentUtils.createDontSendToRestrictedAppsBundle(null));
-                    // call this after broadcasting so we do not increment
-                    // if we throw an exception.
-                    incrementPendingBroadcastsLocked();
-                } catch (PendingIntent.CanceledException e) {
-                    return false;
-                }
-            }
-            return true;
-        }
-
-        private boolean callProviderEnabledLocked(String provider, boolean enabled,
-                LocationRequest locationRequest) {
-            // First update AppOp monitoring.
-            // An app may get/lose location access as providers are enabled/disabled.
-            updateMonitoring(true);
-
-            if (mListener != null) {
-                try {
-                    mListener.onProviderEnabledChanged(provider, enabled);
-                } catch (RemoteException e) {
-                    return false;
-                }
-            } else {
-                Intent providerIntent = new Intent();
-                providerIntent.putExtra(KEY_PROVIDER_ENABLED, enabled);
-                try {
-                    mPendingIntent.send(mContext, 0, providerIntent, null, mHandler,
-                            LocationPermissions.asPermission(
-                                    locationRequest.isCoarse() ? PERMISSION_COARSE
-                                            : PERMISSION_FINE),
-                            PendingIntentUtils.createDontSendToRestrictedAppsBundle(null));
-                } catch (PendingIntent.CanceledException e) {
-                    return false;
-                }
-            }
-            return true;
-        }
-
-        @Override
-        public void binderDied() {
-            synchronized (mLock) {
-                removeUpdatesLocked(this);
-                clearPendingBroadcastsLocked();
-            }
-        }
-
-        @Override
-        public void onSendFinished(PendingIntent pendingIntent, Intent intent,
-                int resultCode, String resultData, Bundle resultExtras) {
-            synchronized (mLock) {
-                decrementPendingBroadcastsLocked();
-            }
-        }
-
-        // this must be called while synchronized by caller in a synchronized block
-        // containing the sending of the broadcaset
-        private void incrementPendingBroadcastsLocked() {
-            mPendingBroadcasts++;
-            // so wakelock calls will succeed
-            long identity = Binder.clearCallingIdentity();
-            try {
-                mWakeLock.acquire(WAKELOCK_TIMEOUT_MILLIS);
-            } finally {
-                Binder.restoreCallingIdentity(identity);
-            }
-        }
-
-        private void decrementPendingBroadcastsLocked() {
-            if (--mPendingBroadcasts == 0) {
-                // so wakelock calls will succeed
-                long identity = Binder.clearCallingIdentity();
-                try {
-                    if (mWakeLock.isHeld()) {
-                        mWakeLock.release();
-                    }
-                } finally {
-                    Binder.restoreCallingIdentity(identity);
-                }
-            }
-        }
-
-        public void clearPendingBroadcastsLocked() {
-            if (mPendingBroadcasts > 0) {
-                mPendingBroadcasts = 0;
-                // so wakelock calls will succeed
-                long identity = Binder.clearCallingIdentity();
-                try {
-                    if (mWakeLock.isHeld()) {
-                        mWakeLock.release();
-                    }
-                } finally {
-                    Binder.restoreCallingIdentity(identity);
-                }
-            }
-        }
+        Intent intent = new Intent(LocationManager.MODE_CHANGED_ACTION)
+                .putExtra(LocationManager.EXTRA_LOCATION_ENABLED, enabled)
+                .addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY)
+                .addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
+        mContext.sendBroadcastAsUser(intent, UserHandle.of(userId));
     }
 
     @Override
@@ -1459,17 +478,6 @@
         }
     }
 
-    @Nullable
-    LocationProviderManager getLocationProviderManager(String providerName) {
-        for (LocationProviderManager manager : mProviderManagers) {
-            if (providerName.equals(manager.getName())) {
-                return manager;
-            }
-        }
-
-        return null;
-    }
-
     @Override
     public List<String> getAllProviders() {
         ArrayList<String> providers = new ArrayList<>(mProviderManagers.size());
@@ -1532,397 +540,16 @@
         return null;
     }
 
-    @GuardedBy("mLock")
-    private void updateProviderEnabledLocked(LocationProviderManager manager, boolean enabled) {
-        ArrayList<Receiver> deadReceivers = null;
-        ArrayList<UpdateRecord> records = mRecordsByProvider.get(manager.getName());
-        if (records != null) {
-            for (UpdateRecord record : records) {
-                if (!mUserInfoHelper.isCurrentUserId(
-                        UserHandle.getUserId(record.mReceiver.mCallerIdentity.getUid()))) {
-                    continue;
-                }
-
-                // requests that ignore location settings will never provide notifications
-                if (isSettingsExempt(record)) {
-                    continue;
-                }
-
-                // Sends a notification message to the receiver
-                if (!record.mReceiver.callProviderEnabledLocked(manager.getName(), enabled,
-                        record.mRequest)) {
-                    if (deadReceivers == null) {
-                        deadReceivers = new ArrayList<>();
-                    }
-                    deadReceivers.add(record.mReceiver);
-                }
-            }
-        }
-
-        if (deadReceivers != null) {
-            for (int i = deadReceivers.size() - 1; i >= 0; i--) {
-                removeUpdatesLocked(deadReceivers.get(i));
-            }
-        }
-
-        applyRequirementsLocked(manager);
-    }
-
-    @GuardedBy("mLock")
-    private void applyRequirementsLocked(String providerName) {
-        LocationProviderManager manager = getLocationProviderManager(providerName);
-        if (manager != null) {
-            applyRequirementsLocked(manager);
-        }
-    }
-
-    @GuardedBy("mLock")
-    private void applyRequirementsLocked(LocationProviderManager manager) {
-        ArrayList<UpdateRecord> records = mRecordsByProvider.get(manager.getName());
-        ProviderRequest.Builder providerRequest = new ProviderRequest.Builder();
-
-        // if provider is not active, it should not respond to requests
-
-        if (mProviderManagers.contains(manager) && records != null && !records.isEmpty()) {
-            long backgroundThrottleInterval = mSettingsHelper.getBackgroundThrottleIntervalMs();
-
-            ArrayList<LocationRequest> requests = new ArrayList<>(records.size());
-
-            final boolean isForegroundOnlyMode =
-                    mBatterySaverMode == PowerManager.LOCATION_MODE_FOREGROUND_ONLY;
-            final boolean shouldThrottleRequests =
-                    mBatterySaverMode
-                            == PowerManager.LOCATION_MODE_THROTTLE_REQUESTS_WHEN_SCREEN_OFF
-                            && !mPowerManager.isInteractive();
-            // initialize the low power mode to true and set to false if any of the records requires
-            providerRequest.setLowPowerMode(true);
-            for (UpdateRecord record : records) {
-                CallerIdentity identity = record.mReceiver.mCallerIdentity;
-                if (!mUserInfoHelper.isCurrentUserId(identity.getUserId())) {
-                    continue;
-                }
-
-                if (!mAppOpsHelper.checkOpNoThrow(LocationPermissions.asAppOp(
-                        record.mRequest.isCoarse() ? PERMISSION_COARSE : PERMISSION_FINE),
-                        identity)) {
-                    continue;
-                }
-                final boolean isBatterySaverDisablingLocation = shouldThrottleRequests
-                        || (isForegroundOnlyMode && !record.mIsForegroundUid);
-                if (!manager.isEnabled(identity.getUserId()) || isBatterySaverDisablingLocation) {
-                    if (isSettingsExempt(record)) {
-                        providerRequest.setLocationSettingsIgnored(true);
-                        providerRequest.setLowPowerMode(false);
-                    } else {
-                        continue;
-                    }
-                }
-
-                LocationRequest locationRequest = record.mRealRequest;
-                long interval = locationRequest.getInterval();
-
-
-                // if we're forcing location, don't apply any throttling
-                if (!providerRequest.isLocationSettingsIgnored() && !isThrottlingExempt(
-                        record.mReceiver.mCallerIdentity)) {
-                    if (!record.mIsForegroundUid) {
-                        interval = Math.max(interval, backgroundThrottleInterval);
-                    }
-                    if (interval != locationRequest.getInterval()) {
-                        locationRequest = new LocationRequest(locationRequest);
-                        locationRequest.setInterval(interval);
-                    }
-                }
-
-                record.mRequest = locationRequest;
-                requests.add(locationRequest);
-                if (!locationRequest.isLowPowerMode()) {
-                    providerRequest.setLowPowerMode(false);
-                }
-                if (interval < providerRequest.getInterval()) {
-                    providerRequest.setInterval(interval);
-                }
-            }
-
-            providerRequest.setLocationRequests(requests);
-
-            if (providerRequest.getInterval() < Long.MAX_VALUE) {
-                // calculate who to blame for power
-                // This is somewhat arbitrary. We pick a threshold interval
-                // that is slightly higher that the minimum interval, and
-                // spread the blame across all applications with a request
-                // under that threshold.
-                // TODO: overflow
-                long thresholdInterval = (providerRequest.getInterval() + 1000) * 3 / 2;
-                for (UpdateRecord record : records) {
-                    if (mUserInfoHelper.isCurrentUserId(
-                            UserHandle.getUserId(record.mReceiver.mCallerIdentity.getUid()))) {
-                        LocationRequest locationRequest = record.mRequest;
-
-                        // Don't assign battery blame for update records whose
-                        // client has no permission to receive location data.
-                        if (!providerRequest.getLocationRequests().contains(locationRequest)) {
-                            continue;
-                        }
-
-                        if (locationRequest.getInterval() <= thresholdInterval) {
-                            if (record.mReceiver.mWorkSource != null
-                                    && isValidWorkSource(record.mReceiver.mWorkSource)) {
-                                providerRequest.getWorkSource().add(record.mReceiver.mWorkSource);
-                            } else {
-                                // Assign blame to caller if there's no WorkSource associated with
-                                // the request or if it's invalid.
-                                providerRequest.getWorkSource().add(
-                                        record.mReceiver.mCallerIdentity.getUid(),
-                                        record.mReceiver.mCallerIdentity.getPackageName());
-                            }
-                        }
-                    }
-                }
-            }
-        }
-
-        manager.setRequest(providerRequest.build());
-    }
-
-    /**
-     * Whether a given {@code WorkSource} associated with a Location request is valid.
-     */
-    private static boolean isValidWorkSource(WorkSource workSource) {
-        if (workSource.size() > 0) {
-            // If the WorkSource has one or more non-chained UIDs, make sure they're accompanied
-            // by tags.
-            return workSource.getPackageName(0) != null;
-        } else {
-            // For now, make sure callers have supplied an attribution tag for use with
-            // AppOpsManager. This might be relaxed in the future.
-            final List<WorkChain> workChains = workSource.getWorkChains();
-            return workChains != null && !workChains.isEmpty()
-                    && workChains.get(0).getAttributionTag() != null;
-        }
-    }
-
     @Override
     public String[] getBackgroundThrottlingWhitelist() {
-        return mSettingsHelper.getBackgroundThrottlePackageWhitelist().toArray(new String[0]);
+        return mInjector.getSettingsHelper().getBackgroundThrottlePackageWhitelist().toArray(
+                new String[0]);
     }
 
     @Override
     public String[] getIgnoreSettingsWhitelist() {
-        return mSettingsHelper.getIgnoreSettingsPackageWhitelist().toArray(new String[0]);
-    }
-
-    private boolean isThrottlingExempt(CallerIdentity callerIdentity) {
-        if (callerIdentity.getUid() == Process.SYSTEM_UID) {
-            return true;
-        }
-
-        if (mSettingsHelper.getBackgroundThrottlePackageWhitelist().contains(
-                callerIdentity.getPackageName())) {
-            return true;
-        }
-
-        return mLocalService.isProvider(null, callerIdentity);
-
-    }
-
-    private boolean isSettingsExempt(UpdateRecord record) {
-        if (!record.mRealRequest.isLocationSettingsIgnored()) {
-            return false;
-        }
-
-        if (mSettingsHelper.getIgnoreSettingsPackageWhitelist().contains(
-                record.mReceiver.mCallerIdentity.getPackageName())) {
-            return true;
-        }
-
-        return mLocalService.isProvider(null, record.mReceiver.mCallerIdentity);
-    }
-
-    private class UpdateRecord {
-        final String mProvider;
-        private final LocationRequest mRealRequest;  // original request from client
-        LocationRequest mRequest;  // possibly throttled version of the request
-        private final Receiver mReceiver;
-        private boolean mIsForegroundUid;
-        private Location mLastFixBroadcast;
-        private Throwable mStackTrace;  // for debugging only
-        private long mExpirationRealtimeMs;
-
-        /**
-         * Note: must be constructed with lock held.
-         */
-        private UpdateRecord(String provider, LocationRequest request, Receiver receiver) {
-            if (Build.IS_DEBUGGABLE) {
-                Preconditions.checkState(Thread.holdsLock(mLock));
-            }
-            mExpirationRealtimeMs = request.getExpirationRealtimeMs(SystemClock.elapsedRealtime());
-            mProvider = provider;
-            mRealRequest = request;
-            mRequest = request;
-            mReceiver = receiver;
-            mIsForegroundUid = mAppForegroundHelper.isAppForeground(
-                    mReceiver.mCallerIdentity.getUid());
-
-            if (D && receiver.mCallerIdentity.getPid() == Process.myPid()) {
-                mStackTrace = new Throwable();
-            }
-
-            ArrayList<UpdateRecord> records = mRecordsByProvider.computeIfAbsent(provider,
-                    k -> new ArrayList<>());
-            if (!records.contains(this)) {
-                records.add(this);
-            }
-
-            // Update statistics for historical location requests by package/provider
-            mRequestStatistics.startRequesting(
-                    mReceiver.mCallerIdentity.getPackageName(),
-                    mReceiver.mCallerIdentity.getAttributionTag(),
-                    provider, request.getInterval(), mIsForegroundUid);
-        }
-
-        /**
-         * Method to be called when record changes foreground/background
-         */
-        private void updateForeground(boolean isForeground) {
-            mIsForegroundUid = isForeground;
-            mRequestStatistics.updateForeground(
-                    mReceiver.mCallerIdentity.getPackageName(),
-                    mReceiver.mCallerIdentity.getAttributionTag(),
-                    mProvider, isForeground);
-        }
-
-        /**
-         * Method to be called when a record will no longer be used.
-         */
-        private void disposeLocked(boolean removeReceiver) {
-            if (Build.IS_DEBUGGABLE) {
-                Preconditions.checkState(Thread.holdsLock(mLock));
-            }
-
-            CallerIdentity identity = mReceiver.mCallerIdentity;
-            mRequestStatistics.stopRequesting(identity.getPackageName(),
-                    identity.getAttributionTag(),
-                    mProvider);
-
-            mLocationUsageLogger.logLocationApiUsage(
-                    LocationStatsEnums.USAGE_ENDED,
-                    LocationStatsEnums.API_REQUEST_LOCATION_UPDATES,
-                    identity.getPackageName(),
-                    mRealRequest,
-                    mReceiver.isListener(),
-                    mReceiver.isPendingIntent(),
-                    /* geofence= */ null,
-                    mAppForegroundHelper.isAppForeground(mReceiver.mCallerIdentity.getUid()));
-
-            // remove from mRecordsByProvider
-            ArrayList<UpdateRecord> globalRecords = mRecordsByProvider.get(this.mProvider);
-            if (globalRecords != null) {
-                globalRecords.remove(this);
-            }
-
-            if (!removeReceiver) return;  // the caller will handle the rest
-
-            // remove from Receiver#mUpdateRecords
-            HashMap<String, UpdateRecord> receiverRecords = mReceiver.mUpdateRecords;
-            receiverRecords.remove(this.mProvider);
-
-            // and also remove the Receiver if it has no more update records
-            if (receiverRecords.size() == 0) {
-                removeUpdatesLocked(mReceiver);
-            }
-        }
-
-        @Override
-        public String toString() {
-            StringBuilder b = new StringBuilder("UpdateRecord[");
-            b.append(mProvider).append(" ");
-            b.append(mReceiver.mCallerIdentity).append(" ");
-            if (!mIsForegroundUid) {
-                b.append("(background) ");
-            }
-            b.append(mRealRequest).append(" ").append(mReceiver.mWorkSource);
-
-            if (mStackTrace != null) {
-                ByteArrayOutputStream tmp = new ByteArrayOutputStream();
-                mStackTrace.printStackTrace(new PrintStream(tmp));
-                b.append("\n\n").append(tmp.toString()).append("\n");
-            }
-
-            b.append("]");
-            return b.toString();
-        }
-    }
-
-    @GuardedBy("mLock")
-    private Receiver getReceiverLocked(ILocationListener listener, CallerIdentity identity,
-            WorkSource workSource, boolean hideFromAppOps) {
-        IBinder binder = listener.asBinder();
-        Receiver receiver = mReceivers.get(binder);
-        if (receiver == null && identity != null) {
-            receiver = new Receiver(listener, null, identity, workSource,
-                    hideFromAppOps);
-            if (!receiver.linkToListenerDeathNotificationLocked(
-                    receiver.getListener().asBinder())) {
-                return null;
-            }
-            mReceivers.put(binder, receiver);
-        }
-        return receiver;
-    }
-
-    @GuardedBy("mLock")
-    private Receiver getReceiverLocked(PendingIntent intent, CallerIdentity identity,
-            WorkSource workSource, boolean hideFromAppOps) {
-        Receiver receiver = mReceivers.get(intent);
-        if (receiver == null && identity != null) {
-            receiver = new Receiver(null, intent, identity, workSource,
-                    hideFromAppOps);
-            mReceivers.put(intent, receiver);
-        }
-        return receiver;
-    }
-
-    /**
-     * Creates a LocationRequest based upon the supplied LocationRequest that to meets resolution
-     * and consistency requirements.
-     *
-     * @param request the LocationRequest from which to create a sanitized version
-     * @return a version of request that meets the given resolution and consistency requirements
-     * @hide
-     */
-    private LocationRequest createSanitizedRequest(LocationRequest request,
-            boolean callerHasLocationHardwarePermission, int permissionLevel) {
-        LocationRequest sanitizedRequest = new LocationRequest(request);
-        if (!callerHasLocationHardwarePermission) {
-            // allow setting low power mode only for callers with location hardware permission
-            sanitizedRequest.setLowPowerMode(false);
-        }
-        if (permissionLevel < PERMISSION_FINE) {
-            sanitizedRequest.setCoarse(true);
-            switch (sanitizedRequest.getQuality()) {
-                case LocationRequest.ACCURACY_FINE:
-                    sanitizedRequest.setQuality(LocationRequest.ACCURACY_BLOCK);
-                    break;
-                case LocationRequest.POWER_HIGH:
-                    sanitizedRequest.setQuality(LocationRequest.POWER_LOW);
-                    break;
-            }
-            // throttle
-            if (sanitizedRequest.getInterval() < FASTEST_COARSE_INTERVAL_MS) {
-                sanitizedRequest.setInterval(FASTEST_COARSE_INTERVAL_MS);
-            }
-            if (sanitizedRequest.getFastestInterval() < FASTEST_COARSE_INTERVAL_MS) {
-                sanitizedRequest.setFastestInterval(FASTEST_COARSE_INTERVAL_MS);
-            }
-        } else {
-            sanitizedRequest.setCoarse(false);
-        }
-        // make getFastestInterval() the minimum of interval and fastest interval
-        if (sanitizedRequest.getFastestInterval() > sanitizedRequest.getInterval()) {
-            sanitizedRequest.setFastestInterval(request.getInterval());
-        }
-        return sanitizedRequest;
+        return mInjector.getSettingsHelper().getIgnoreSettingsPackageWhitelist().toArray(
+                new String[0]);
     }
 
     @Override
@@ -1930,45 +557,24 @@
             String packageName, String attributionTag, String listenerId) {
         CallerIdentity identity = CallerIdentity.fromBinder(mContext, packageName, attributionTag,
                 listenerId);
-        int permissionLevel = LocationPermissions.getCallingOrSelfPermissionLevel(mContext);
-        LocationPermissions.enforceLocationPermission(Binder.getCallingUid(), permissionLevel,
+        int permissionLevel = LocationPermissions.getPermissionLevel(mContext, identity.getUid(),
+                identity.getPid());
+        LocationPermissions.enforceLocationPermission(identity.getUid(), permissionLevel,
                 PERMISSION_COARSE);
 
-        WorkSource workSource = request.getWorkSource();
-        if (workSource != null && !workSource.isEmpty()) {
-            mContext.enforceCallingOrSelfPermission(
-                    permission.UPDATE_DEVICE_STATS, null);
+        // clients in the system process should have an attribution tag set
+        if (identity.getPid() == Process.myPid() && attributionTag == null) {
+            Log.w(TAG, "system location request with no attribution tag",
+                    new IllegalArgumentException());
         }
-        boolean hideFromAppOps = request.getHideFromAppOps();
-        if (hideFromAppOps) {
-            mContext.enforceCallingOrSelfPermission(
-                    permission.UPDATE_APP_OPS_STATS, null);
-        }
-        if (request.isLocationSettingsIgnored()) {
-            mContext.enforceCallingOrSelfPermission(
-                    permission.WRITE_SECURE_SETTINGS, null);
-        }
-        boolean callerHasLocationHardwarePermission =
-                mContext.checkCallingPermission(permission.LOCATION_HARDWARE)
-                        == PERMISSION_GRANTED;
-        LocationRequest sanitizedRequest = createSanitizedRequest(request,
-                callerHasLocationHardwarePermission,
-                permissionLevel);
 
-        mLocationUsageLogger.logLocationApiUsage(
-                LocationStatsEnums.USAGE_STARTED,
-                LocationStatsEnums.API_REQUEST_LOCATION_UPDATES,
-                packageName, request, true, false,
-                /* geofence= */ null,
-                mAppForegroundHelper.isAppForeground(identity.getUid()));
+        request = validateAndSanitizeLocationRequest(request, permissionLevel);
 
-        synchronized (mLock) {
-            Receiver receiver = getReceiverLocked(Objects.requireNonNull(listener), identity,
-                    workSource, hideFromAppOps);
-            if (receiver != null) {
-                requestLocationUpdatesLocked(sanitizedRequest, receiver);
-            }
-        }
+        LocationProviderManager manager = getLocationProviderManager(request.getProvider());
+        Preconditions.checkArgument(manager != null,
+                "provider \"" + request.getProvider() + "\" does not exist");
+
+        manager.registerLocationRequest(request, identity, permissionLevel, listener);
     }
 
     @Override
@@ -1976,248 +582,154 @@
             String packageName, String attributionTag) {
         CallerIdentity identity = CallerIdentity.fromBinder(mContext, packageName, attributionTag,
                 AppOpsManager.toReceiverId(pendingIntent));
-        int permissionLevel = LocationPermissions.getCallingOrSelfPermissionLevel(mContext);
-        LocationPermissions.enforceLocationPermission(Binder.getCallingUid(), permissionLevel,
+        int permissionLevel = LocationPermissions.getPermissionLevel(mContext, identity.getUid(),
+                identity.getPid());
+        LocationPermissions.enforceLocationPermission(identity.getUid(), permissionLevel,
                 PERMISSION_COARSE);
 
+        // clients in the system process must have an attribution tag set
+        Preconditions.checkArgument(identity.getPid() != Process.myPid() || attributionTag != null);
+
+        request = validateAndSanitizeLocationRequest(request, permissionLevel);
+
+        LocationProviderManager manager = getLocationProviderManager(request.getProvider());
+        Preconditions.checkArgument(manager != null,
+                "provider \"" + request.getProvider() + "\" does not exist");
+
+        manager.registerLocationRequest(request, identity, permissionLevel, pendingIntent);
+    }
+
+    private LocationRequest validateAndSanitizeLocationRequest(LocationRequest request,
+            @PermissionLevel int permissionLevel) {
+        Objects.requireNonNull(request.getProvider());
+
         WorkSource workSource = request.getWorkSource();
         if (workSource != null && !workSource.isEmpty()) {
             mContext.enforceCallingOrSelfPermission(
-                    permission.UPDATE_DEVICE_STATS, null);
+                    permission.UPDATE_DEVICE_STATS,
+                    "setting a work source requires " + permission.UPDATE_DEVICE_STATS);
         }
-        boolean hideFromAppOps = request.getHideFromAppOps();
-        if (hideFromAppOps) {
+        if (request.getHideFromAppOps()) {
             mContext.enforceCallingOrSelfPermission(
-                    permission.UPDATE_APP_OPS_STATS, null);
+                    permission.UPDATE_APP_OPS_STATS,
+                    "hiding from app ops requires " + permission.UPDATE_APP_OPS_STATS);
         }
         if (request.isLocationSettingsIgnored()) {
             mContext.enforceCallingOrSelfPermission(
-                    permission.WRITE_SECURE_SETTINGS, null);
-        }
-        boolean callerHasLocationHardwarePermission =
-                mContext.checkCallingPermission(permission.LOCATION_HARDWARE)
-                        == PERMISSION_GRANTED;
-        LocationRequest sanitizedRequest = createSanitizedRequest(request,
-                callerHasLocationHardwarePermission,
-                permissionLevel);
-
-        mLocationUsageLogger.logLocationApiUsage(
-                LocationStatsEnums.USAGE_STARTED,
-                LocationStatsEnums.API_REQUEST_LOCATION_UPDATES,
-                packageName, request, true, false,
-                /* geofence= */ null,
-                mAppForegroundHelper.isAppForeground(identity.getUid()));
-
-        synchronized (mLock) {
-            Receiver receiver = getReceiverLocked(Objects.requireNonNull(pendingIntent), identity,
-                    workSource, hideFromAppOps);
-            if (receiver != null) {
-                requestLocationUpdatesLocked(sanitizedRequest, receiver);
-            }
-        }
-    }
-
-    @GuardedBy("mLock")
-    private void requestLocationUpdatesLocked(LocationRequest request, Receiver receiver) {
-        String provider = request.getProvider();
-
-        LocationProviderManager manager = getLocationProviderManager(provider);
-        if (manager == null) {
-            throw new IllegalArgumentException("provider doesn't exist: " + provider);
+                    permission.WRITE_SECURE_SETTINGS,
+                    "ignoring location settings requires " + permission.WRITE_SECURE_SETTINGS);
         }
 
-        UpdateRecord record = new UpdateRecord(provider, request, receiver);
-
-        UpdateRecord oldRecord = receiver.mUpdateRecords.put(provider, record);
-        if (oldRecord != null) {
-            oldRecord.disposeLocked(false);
+        LocationRequest sanitized = new LocationRequest(request);
+        if (mContext.checkCallingPermission(permission.LOCATION_HARDWARE) != PERMISSION_GRANTED) {
+            sanitized.setLowPowerMode(false);
         }
-
-        long identity = Binder.clearCallingIdentity();
-        try {
-            int userId = UserHandle.getUserId(receiver.mCallerIdentity.getUid());
-            if (!manager.isEnabled(userId) && !isSettingsExempt(record)) {
-                // Notify the listener that updates are currently disabled - but only if the request
-                // does not ignore location settings
-                receiver.callProviderEnabledLocked(provider, false, request);
+        if (permissionLevel < PERMISSION_FINE) {
+            switch (sanitized.getQuality()) {
+                case LocationRequest.ACCURACY_FINE:
+                    sanitized.setQuality(LocationRequest.ACCURACY_BLOCK);
+                    break;
+                case LocationRequest.POWER_HIGH:
+                    sanitized.setQuality(LocationRequest.POWER_LOW);
+                    break;
             }
 
-            applyRequirementsLocked(provider);
-
-            // Update the monitoring here just in case multiple location requests were added to the
-            // same receiver (this request may be high power and the initial might not have been).
-            receiver.updateMonitoring(true);
-        } finally {
-            Binder.restoreCallingIdentity(identity);
+            if (sanitized.getInterval() < FASTEST_COARSE_INTERVAL_MS) {
+                sanitized.setInterval(FASTEST_COARSE_INTERVAL_MS);
+            }
+            if (sanitized.getFastestInterval() < FASTEST_COARSE_INTERVAL_MS) {
+                sanitized.setFastestInterval(FASTEST_COARSE_INTERVAL_MS);
+            }
         }
+        if (sanitized.getFastestInterval() > sanitized.getInterval()) {
+            sanitized.setFastestInterval(request.getInterval());
+        }
+        if (sanitized.getWorkSource() != null) {
+            if (sanitized.getWorkSource().isEmpty()) {
+                sanitized.setWorkSource(null);
+            } else if (sanitized.getWorkSource().getPackageName(0) == null) {
+                Log.w(TAG, "received (and ignoring) illegal worksource with no package name");
+                sanitized.setWorkSource(null);
+            } else {
+                List<WorkChain> workChains = sanitized.getWorkSource().getWorkChains();
+                if (workChains != null && !workChains.isEmpty() && workChains.get(
+                        0).getAttributionTag() == null) {
+                    Log.w(TAG,
+                            "received (and ignoring) illegal worksource with no attribution tag");
+                    sanitized.setWorkSource(null);
+                }
+            }
+        }
+
+        return sanitized;
     }
 
     @Override
     public void unregisterLocationListener(ILocationListener listener) {
-        synchronized (mLock) {
-            Receiver receiver = getReceiverLocked(Objects.requireNonNull(listener), null, null,
-                    false);
-            if (receiver != null) {
-                removeUpdatesLocked(receiver);
-            }
+        for (LocationProviderManager manager : mProviderManagers) {
+            manager.unregisterLocationRequest(listener);
         }
     }
 
     @Override
     public void unregisterLocationPendingIntent(PendingIntent pendingIntent) {
-        synchronized (mLock) {
-            Receiver receiver = getReceiverLocked(Objects.requireNonNull(pendingIntent), null, null,
-                    false);
-            if (receiver != null) {
-                removeUpdatesLocked(receiver);
-            }
-        }
-    }
-
-    @GuardedBy("mLock")
-    private void removeUpdatesLocked(Receiver receiver) {
-        if (D) Log.i(TAG, "remove " + Integer.toHexString(System.identityHashCode(receiver)));
-
-        if (mReceivers.remove(receiver.mKey) != null && receiver.isListener()) {
-            receiver.unlinkFromListenerDeathNotificationLocked(
-                    receiver.getListener().asBinder());
-            receiver.clearPendingBroadcastsLocked();
-        }
-
-        receiver.updateMonitoring(false);
-
-        // Record which providers were associated with this listener
-        HashSet<String> providers = new HashSet<>();
-        HashMap<String, UpdateRecord> oldRecords = receiver.mUpdateRecords;
-        if (oldRecords != null) {
-            // Call dispose() on the obsolete update records.
-            for (UpdateRecord record : oldRecords.values()) {
-                // Update statistics for historical location requests by package/provider
-                record.disposeLocked(false);
-            }
-            // Accumulate providers
-            providers.addAll(oldRecords.keySet());
-        }
-
-        // update provider
-        for (String provider : providers) {
-            applyRequirementsLocked(provider);
+        for (LocationProviderManager manager : mProviderManagers) {
+            manager.unregisterLocationRequest(pendingIntent);
         }
     }
 
     @Override
     public Location getLastLocation(LocationRequest request, String packageName,
             String attributionTag) {
-        // unsafe is ok because app ops will verify the package name
-        CallerIdentity identity = CallerIdentity.fromBinderUnsafe(packageName,   attributionTag);
-        int permissionLevel = LocationPermissions.getCallingOrSelfPermissionLevel(mContext);
-        LocationPermissions.enforceLocationPermission(Binder.getCallingUid(), permissionLevel,
+        CallerIdentity identity = CallerIdentity.fromBinder(mContext, packageName, attributionTag);
+        int permissionLevel = LocationPermissions.getPermissionLevel(mContext, identity.getUid(),
+                identity.getPid());
+        LocationPermissions.enforceLocationPermission(identity.getUid(), permissionLevel,
                 PERMISSION_COARSE);
 
-        if (mSettingsHelper.isLocationPackageBlacklisted(identity.getUserId(),
-                identity.getPackageName())) {
-            return null;
-        }
-        if (!mUserInfoHelper.isCurrentUserId(identity.getUserId())) {
+        // clients in the system process must have an attribution tag set
+        Preconditions.checkArgument(identity.getPid() != Process.myPid() || attributionTag != null);
+
+        request = validateAndSanitizeLocationRequest(request, permissionLevel);
+
+        LocationProviderManager manager = getLocationProviderManager(request.getProvider());
+        if (manager == null) {
             return null;
         }
 
-        synchronized (mLock) {
-            LocationProviderManager manager = getLocationProviderManager(request.getProvider());
-            if (manager == null) {
-                return null;
-            }
-            if (!manager.isEnabled(identity.getUserId()) && !request.isLocationSettingsIgnored()) {
-                return null;
-            }
+        Location location = manager.getLastLocation(request, identity, permissionLevel);
 
-            // appops check should always be right before delivery
-            if (!mAppOpsHelper.noteOpNoThrow(LocationPermissions.asAppOp(permissionLevel),
-                    identity)) {
-                return null;
-            }
-
-            Location location = manager.getLastLocation(identity.getUserId(), permissionLevel);
-
-            // make a defensive copy - the client could be in the same process as us
-            return location != null ? new Location(location) : null;
+        // lastly - note app ops
+        if (!mInjector.getAppOpsHelper().noteOpNoThrow(LocationPermissions.asAppOp(permissionLevel),
+                identity)) {
+            return null;
         }
+
+        return location;
     }
 
     @Override
     public void getCurrentLocation(LocationRequest request,
-            ICancellationSignal remoteCancellationSignal, ILocationCallback callback,
+            ICancellationSignal cancellationTransport, ILocationCallback consumer,
             String packageName, String attributionTag, String listenerId) {
-        // unsafe is ok because app ops will verify the package name
-        CallerIdentity identity = CallerIdentity.fromBinderUnsafe(packageName, attributionTag,
+        CallerIdentity identity = CallerIdentity.fromBinder(mContext, packageName, attributionTag,
                 listenerId);
-        int permissionLevel = LocationPermissions.getCallingOrSelfPermissionLevel(mContext);
-        LocationPermissions.enforceLocationPermission(Binder.getCallingUid(), permissionLevel,
+        int permissionLevel = LocationPermissions.getPermissionLevel(mContext, identity.getUid(),
+                identity.getPid());
+        LocationPermissions.enforceLocationPermission(identity.getUid(), permissionLevel,
                 PERMISSION_COARSE);
 
-        request = createSanitizedRequest(request, false, permissionLevel);
-        request.setNumUpdates(1);
-        if (request.getExpireIn() > GET_CURRENT_LOCATION_MAX_TIMEOUT_MS) {
-            request.setExpireIn(GET_CURRENT_LOCATION_MAX_TIMEOUT_MS);
-        }
+        // clients in the system process must have an attribution tag set
+        Preconditions.checkState(identity.getPid() != Process.myPid() || attributionTag != null);
 
-        GetCurrentLocationTransport transport = new GetCurrentLocationTransport(callback);
+        request = validateAndSanitizeLocationRequest(request, permissionLevel);
 
-        if (mSettingsHelper.isLocationPackageBlacklisted(identity.getUserId(),
-                identity.getPackageName())) {
-            transport.deliverResult(null);
-            return;
-        }
-        if (!mUserInfoHelper.isCurrentUserId(identity.getUserId())) {
-            transport.deliverResult(null);
-            return;
-        }
+        LocationProviderManager manager = getLocationProviderManager(request.getProvider());
+        Preconditions.checkArgument(manager != null,
+                "provider \"" + request.getProvider() + "\" does not exist");
 
-        Location lastLocation;
-        synchronized (mLock) {
-            LocationProviderManager manager = getLocationProviderManager(request.getProvider());
-            if (manager == null) {
-                transport.deliverResult(null);
-                return;
-            }
-            if (!manager.isEnabled(identity.getUserId()) && !request.isLocationSettingsIgnored()) {
-                transport.deliverResult(null);
-                return;
-            }
-
-            lastLocation = manager.getLastLocation(identity.getUserId(), permissionLevel);
-        }
-
-        if (lastLocation != null) {
-            long locationAgeMs = NANOSECONDS.toMillis(
-                    SystemClock.elapsedRealtimeNanos() - lastLocation.getElapsedRealtimeNanos());
-
-            if (locationAgeMs < MAX_CURRENT_LOCATION_AGE_MS) {
-                // appops check should always be right before delivery
-                if (mAppOpsHelper.noteOpNoThrow(LocationPermissions.asAppOp(permissionLevel),
-                        identity)) {
-                    transport.deliverResult(lastLocation);
-                } else {
-                    transport.deliverResult(null);
-                }
-                return;
-            }
-
-            if (!mAppForegroundHelper.isAppForeground(Binder.getCallingUid())) {
-                if (locationAgeMs < mSettingsHelper.getBackgroundThrottleIntervalMs()) {
-                    // not allowed to request new locations, so we can't return anything
-                    transport.deliverResult(null);
-                    return;
-                }
-            }
-        }
-
-        registerLocationListener(request, transport, packageName, attributionTag, listenerId);
-        CancellationSignal cancellationSignal = CancellationSignal.fromTransport(
-                remoteCancellationSignal);
-        if (cancellationSignal != null) {
-            cancellationSignal.setOnCancelListener(() -> unregisterLocationListener(transport));
-        }
+        manager.getCurrentLocation(request, identity, permissionLevel, cancellationTransport,
+                consumer);
     }
 
     @Override
@@ -2228,8 +740,13 @@
                 return null;
             }
 
-            Location location = gpsManager.getLastLocation(UserHandle.getCallingUserId(),
-                    PERMISSION_FINE);
+            // create a location request that works in almost all circumstances
+            LocationRequest request = LocationRequest.createFromDeprecatedProvider(GPS_PROVIDER, 0,
+                    0, true);
+
+            // use our own identity rather than the caller
+            CallerIdentity identity = CallerIdentity.fromContext(mContext);
+            Location location = gpsManager.getLastLocation(request, identity, PERMISSION_FINE);
             if (location == null) {
                 return null;
             }
@@ -2248,11 +765,9 @@
         Preconditions.checkArgument(location.isComplete());
 
         int userId = UserHandle.getCallingUserId();
-        synchronized (mLock) {
-            LocationProviderManager manager = getLocationProviderManager(location.getProvider());
-            if (manager != null && manager.isEnabled(userId)) {
-                manager.injectLastLocation(Objects.requireNonNull(location), userId);
-            }
+        LocationProviderManager manager = getLocationProviderManager(location.getProvider());
+        if (manager != null && manager.isEnabled(userId)) {
+            manager.injectLastLocation(Objects.requireNonNull(location), userId);
         }
     }
 
@@ -2357,12 +872,11 @@
                     Objects.requireNonNull(command), extras);
         }
 
-        mLocationUsageLogger.logLocationApiUsage(
+        mInjector.getLocationUsageLogger().logLocationApiUsage(
                 LocationStatsEnums.USAGE_STARTED,
                 LocationStatsEnums.API_SEND_EXTRA_COMMAND,
                 provider);
-
-        mLocationUsageLogger.logLocationApiUsage(
+        mInjector.getLocationUsageLogger().logLocationApiUsage(
                 LocationStatsEnums.USAGE_ENDED,
                 LocationStatsEnums.API_SEND_EXTRA_COMMAND,
                 provider);
@@ -2385,7 +899,7 @@
             if (provider != null && !provider.equals(manager.getName())) {
                 continue;
             }
-            CallerIdentity identity = manager.getProviderIdentity();
+            CallerIdentity identity = manager.getIdentity();
             if (identity == null) {
                 continue;
             }
@@ -2406,7 +920,7 @@
             return Collections.emptyList();
         }
 
-        CallerIdentity identity = manager.getProviderIdentity();
+        CallerIdentity identity = manager.getIdentity();
         if (identity == null) {
             return Collections.emptyList();
         }
@@ -2454,164 +968,26 @@
 
         mContext.enforceCallingOrSelfPermission(permission.WRITE_SECURE_SETTINGS, null);
 
-        invalidateLocalLocationEnabledCaches();
-        mSettingsHelper.setLocationEnabled(enabled, userId);
+        LocationManager.invalidateLocalLocationEnabledCaches();
+        mInjector.getSettingsHelper().setLocationEnabled(enabled, userId);
     }
 
     @Override
     public boolean isLocationEnabledForUser(int userId) {
         userId = ActivityManager.handleIncomingUser(Binder.getCallingPid(), Binder.getCallingUid(),
                 userId, false, false, "isLocationEnabledForUser", null);
-        return mSettingsHelper.isLocationEnabled(userId);
+        return mInjector.getSettingsHelper().isLocationEnabled(userId);
     }
 
     @Override
     public boolean isProviderEnabledForUser(String provider, int userId) {
-        // Fused provider is accessed indirectly via criteria rather than the provider-based APIs,
+        // fused provider is accessed indirectly via criteria rather than the provider-based APIs,
         // so we discourage its use
         if (FUSED_PROVIDER.equals(provider)) return false;
 
         return mLocalService.isProviderEnabledForUser(provider, userId);
     }
 
-    @GuardedBy("mLock")
-    private static boolean shouldBroadcastSafeLocked(
-            Location loc, Location lastLoc, UpdateRecord record, long now) {
-        // Always broadcast the first update
-        if (lastLoc == null) {
-            return true;
-        }
-
-        // Check whether sufficient time has passed
-        long minTime = record.mRealRequest.getFastestInterval();
-        long deltaMs = NANOSECONDS.toMillis(
-                loc.getElapsedRealtimeNanos() - lastLoc.getElapsedRealtimeNanos());
-        if (deltaMs < minTime - MAX_PROVIDER_SCHEDULING_JITTER_MS) {
-            return false;
-        }
-
-        // Check whether sufficient distance has been traveled
-        double minDistance = record.mRealRequest.getSmallestDisplacement();
-        if (minDistance > 0.0) {
-            if (loc.distanceTo(lastLoc) <= minDistance) {
-                return false;
-            }
-        }
-
-        // Check whether sufficient number of udpates is left
-        if (record.mRealRequest.getNumUpdates() <= 0) {
-            return false;
-        }
-
-        // Check whether the expiry date has passed
-        return record.mExpirationRealtimeMs >= now;
-    }
-
-    @GuardedBy("mLock")
-    private void handleLocationChangedLocked(LocationProviderManager manager, Location fineLocation,
-            Location coarseLocation) {
-        if (!mProviderManagers.contains(manager)) {
-            Log.w(TAG, "received location from unknown provider: " + manager.getName());
-            return;
-        }
-
-        // notify passive provider
-        if (manager != mPassiveManager) {
-            mPassiveManager.updateLocation(fineLocation);
-        }
-
-        long now = SystemClock.elapsedRealtime();
-
-        ArrayList<UpdateRecord> records = mRecordsByProvider.get(manager.getName());
-        if (records == null || records.size() == 0) return;
-
-        ArrayList<Receiver> deadReceivers = null;
-        ArrayList<UpdateRecord> deadUpdateRecords = null;
-
-        // Broadcast location to all listeners
-        for (UpdateRecord r : records) {
-            Receiver receiver = r.mReceiver;
-            CallerIdentity identity = receiver.mCallerIdentity;
-            boolean receiverDead = false;
-
-
-            if (!manager.isEnabled(identity.getUserId()) && !isSettingsExempt(r)) {
-                continue;
-            }
-
-            if (!mUserInfoHelper.isCurrentUserId(identity.getUserId())
-                    && !mLocalService.isProvider(null, identity)) {
-                continue;
-            }
-
-            if (mSettingsHelper.isLocationPackageBlacklisted(identity.getUserId(),
-                    identity.getPackageName())) {
-                continue;
-            }
-
-            int permissionLevel = r.mRequest.isCoarse() ? PERMISSION_COARSE : PERMISSION_FINE;
-
-            Location location;
-            switch (permissionLevel) {
-                case PERMISSION_COARSE:
-                    location = coarseLocation;
-                    break;
-                case PERMISSION_FINE:
-                    location = fineLocation;
-                    break;
-                default:
-                    throw new AssertionError();
-            }
-
-            if (shouldBroadcastSafeLocked(location, r.mLastFixBroadcast, r, now)) {
-                r.mLastFixBroadcast = location;
-
-                // appops check should always be right before delivery
-                if (!mAppOpsHelper.noteOpNoThrow(LocationPermissions.asAppOp(permissionLevel),
-                        receiver.mCallerIdentity)) {
-                    continue;
-                }
-
-                if (!receiver.callLocationChangedLocked(location, r.mRequest)) {
-                    receiverDead = true;
-                }
-                r.mRealRequest.decrementNumUpdates();
-            }
-
-            // track expired records
-            if (r.mRealRequest.getNumUpdates() <= 0 || r.mExpirationRealtimeMs < now) {
-                if (deadUpdateRecords == null) {
-                    deadUpdateRecords = new ArrayList<>();
-                }
-                deadUpdateRecords.add(r);
-            }
-            // track dead receivers
-            if (receiverDead) {
-                if (deadReceivers == null) {
-                    deadReceivers = new ArrayList<>();
-                }
-                if (!deadReceivers.contains(receiver)) {
-                    deadReceivers.add(receiver);
-                }
-            }
-        }
-
-        // remove dead records and receivers outside the loop
-        if (deadReceivers != null) {
-            for (Receiver receiver : deadReceivers) {
-                removeUpdatesLocked(receiver);
-            }
-        }
-        if (deadUpdateRecords != null) {
-            for (UpdateRecord r : deadUpdateRecords) {
-                r.disposeLocked(true);
-            }
-            applyRequirementsLocked(manager);
-        }
-    }
-
-    // Geocoder
-
     @Override
     public boolean geocoderIsPresent() {
         return mGeocodeProvider != null;
@@ -2637,7 +1013,6 @@
             double lowerLeftLatitude, double lowerLeftLongitude,
             double upperRightLatitude, double upperRightLongitude, int maxResults,
             GeocoderParams params, IGeocodeListener listener) {
-
         if (mGeocodeProvider != null) {
             mGeocodeProvider.getFromLocationName(locationName, lowerLeftLatitude,
                     lowerLeftLongitude, upperRightLatitude, upperRightLongitude,
@@ -2651,35 +1026,24 @@
         }
     }
 
-    // Mock Providers
-
     @Override
     public void addTestProvider(String provider, ProviderProperties properties,
             String packageName, String attributionTag) {
         // unsafe is ok because app ops will verify the package name
-        CallerIdentity identity = CallerIdentity.fromBinderUnsafe(packageName,
-                attributionTag);
-        if (!mAppOpsHelper.noteOp(OP_MOCK_LOCATION, identity)) {
+        CallerIdentity identity = CallerIdentity.fromBinderUnsafe(packageName, attributionTag);
+        if (!mInjector.getAppOpsHelper().noteOp(AppOpsManager.OP_MOCK_LOCATION, identity)) {
             return;
         }
 
-        synchronized (mLock) {
-            LocationProviderManager manager = getLocationProviderManager(provider);
-            if (manager == null) {
-                manager = new LocationProviderManager(provider);
-                mProviderManagers.add(manager);
-            }
-
-            manager.setMockProvider(new MockProvider(properties, identity));
-        }
+        getOrAddLocationProviderManager(provider).setMockProvider(
+                new MockProvider(properties, identity));
     }
 
     @Override
     public void removeTestProvider(String provider, String packageName, String attributionTag) {
         // unsafe is ok because app ops will verify the package name
-        CallerIdentity identity = CallerIdentity.fromBinderUnsafe(packageName,
-                attributionTag);
-        if (!mAppOpsHelper.noteOp(OP_MOCK_LOCATION, identity)) {
+        CallerIdentity identity = CallerIdentity.fromBinderUnsafe(packageName, attributionTag);
+        if (!mInjector.getAppOpsHelper().noteOp(AppOpsManager.OP_MOCK_LOCATION, identity)) {
             return;
         }
 
@@ -2691,7 +1055,7 @@
 
             manager.setMockProvider(null);
             if (!manager.hasProvider()) {
-                mProviderManagers.remove(manager);
+                removeLocationProviderManager(manager);
             }
         }
     }
@@ -2702,7 +1066,7 @@
         // unsafe is ok because app ops will verify the package name
         CallerIdentity identity = CallerIdentity.fromBinderUnsafe(packageName,
                 attributionTag);
-        if (!mAppOpsHelper.noteOp(OP_MOCK_LOCATION, identity)) {
+        if (!mInjector.getAppOpsHelper().noteOp(AppOpsManager.OP_MOCK_LOCATION, identity)) {
             return;
         }
 
@@ -2723,7 +1087,7 @@
         // unsafe is ok because app ops will verify the package name
         CallerIdentity identity = CallerIdentity.fromBinderUnsafe(packageName,
                 attributionTag);
-        if (!mAppOpsHelper.noteOp(OP_MOCK_LOCATION, identity)) {
+        if (!mInjector.getAppOpsHelper().noteOp(AppOpsManager.OP_MOCK_LOCATION, identity)) {
             return;
         }
 
@@ -2777,57 +1141,32 @@
 
         ipw.println("User Info:");
         ipw.increaseIndent();
-        mUserInfoHelper.dump(fd, ipw, args);
+        mInjector.getUserInfoHelper().dump(fd, ipw, args);
         ipw.decreaseIndent();
 
         ipw.println("Location Settings:");
         ipw.increaseIndent();
-        mSettingsHelper.dump(fd, ipw, args);
+        mInjector.getSettingsHelper().dump(fd, ipw, args);
         ipw.decreaseIndent();
 
+        ipw.println("Historical Records by Provider:");
+        ipw.increaseIndent();
+        TreeMap<PackageProviderKey, PackageStatistics> sorted = new TreeMap<>(
+                mInjector.getLocationRequestStatistics().statistics);
+        for (Map.Entry<PackageProviderKey, PackageStatistics> entry
+                : sorted.entrySet()) {
+            ipw.println(entry.getKey() + ": " + entry.getValue());
+        }
+        ipw.decreaseIndent();
+
+        mInjector.getLocationRequestStatistics().history.dump(ipw);
+
         synchronized (mLock) {
-            ipw.println("Battery Saver Location Mode: "
-                    + locationPowerSaveModeToString(mBatterySaverMode));
-
-            if (dumpFilter == null) {
-                ipw.println("Location Listeners:");
-                ipw.increaseIndent();
-                for (Receiver receiver : mReceivers.values()) {
-                    ipw.println(receiver);
-                }
-                ipw.decreaseIndent();
-
-                ipw.println("Active Records by Provider:");
-                ipw.increaseIndent();
-                for (Map.Entry<String, ArrayList<UpdateRecord>> entry :
-                        mRecordsByProvider.entrySet()) {
-                    ipw.println(entry.getKey() + ":");
-                    ipw.increaseIndent();
-                    for (UpdateRecord record : entry.getValue()) {
-                        ipw.println(record);
-                    }
-                    ipw.decreaseIndent();
-                }
-                ipw.decreaseIndent();
-
-            ipw.println("Historical Records by Provider:");
-            ipw.increaseIndent();
-            TreeMap<PackageProviderKey, PackageStatistics> sorted = new TreeMap<>(
-                    mRequestStatistics.statistics);
-            for (Map.Entry<PackageProviderKey, PackageStatistics> entry
-                    : sorted.entrySet()) {
-                ipw.println(entry.getKey() + ": " + entry.getValue());
-            }
-            ipw.decreaseIndent();
-
-                mRequestStatistics.history.dump(ipw);
-
-                if (mExtraLocationControllerPackage != null) {
-                    ipw.println(
-                            "Location Controller Extra Package: " + mExtraLocationControllerPackage
-                                    + (mExtraLocationControllerPackageEnabled ? " [enabled]"
-                                    : "[disabled]"));
-                }
+            if (mExtraLocationControllerPackage != null) {
+                ipw.println(
+                        "Location Controller Extra Package: " + mExtraLocationControllerPackage
+                                + (mExtraLocationControllerPackageEnabled ? " [enabled]"
+                                : "[disabled]"));
             }
         }
 
@@ -2857,47 +1196,6 @@
         }
     }
 
-    private class GetCurrentLocationTransport extends ILocationListener.Stub {
-
-        private final Executor mExecutor;
-        private final ILocationCallback mCallback;
-
-        GetCurrentLocationTransport(ILocationCallback callback) {
-            mExecutor = FgThread.getExecutor();
-            mCallback = callback;
-        }
-
-        @Override
-        public void onLocationChanged(Location location, IRemoteCallback onCompleteCallback) {
-            mExecutor.execute(() -> {
-                deliverResult(location);
-                try {
-                    onCompleteCallback.sendResult(null);
-                } catch (RemoteException e) {
-                    e.rethrowFromSystemServer();
-                }
-            });
-            unregisterLocationListener(this);
-        }
-
-        @Override
-        public void onProviderEnabledChanged(String provider, boolean enabled)
-                throws RemoteException {
-            if (!enabled) {
-                deliverResult(null);
-                unregisterLocationListener(this);
-            }
-        }
-
-        public void deliverResult(@Nullable Location location) {
-            try {
-                mCallback.onLocation(location);
-            } catch (RemoteException e) {
-                // do nothing
-            }
-        }
-    }
-
     private class LocalService extends LocationManagerInternal {
 
         LocalService() {}
@@ -2916,17 +1214,28 @@
         }
 
         @Override
-        public boolean isProvider(String provider, CallerIdentity identity) {
-            for (LocationProviderManager manager : mProviderManagers) {
-                if (provider != null && !provider.equals(manager.getName())) {
-                    continue;
-                }
-                if (identity.equals(manager.getProviderIdentity())) {
-                    return true;
-                }
-            }
+        public void addProviderEnabledListener(String provider, ProviderEnabledListener listener) {
+            LocationProviderManager manager = Objects.requireNonNull(
+                    getLocationProviderManager(provider));
+            manager.addEnabledListener(listener);
+        }
 
-            return false;
+        @Override
+        public void removeProviderEnabledListener(String provider,
+                ProviderEnabledListener listener) {
+            LocationProviderManager manager = Objects.requireNonNull(
+                    getLocationProviderManager(provider));
+            manager.removeEnabledListener(listener);
+        }
+
+        @Override
+        public boolean isProvider(String provider, CallerIdentity identity) {
+            LocationProviderManager manager = getLocationProviderManager(provider);
+            if (manager == null) {
+                return false;
+            } else {
+                return identity.equals(manager.getIdentity());
+            }
         }
 
         @Override
@@ -2935,6 +1244,13 @@
                 mGnssManagerService.sendNiResponse(notifId, userResponse);
             }
         }
+
+        @Override
+        public void reportGnssBatchLocations(List<Location> locations) {
+            if (mGnssManagerService != null) {
+                mGnssManagerService.onReportLocation(locations);
+            }
+        }
     }
 
     private static class SystemInjector implements Injector {
diff --git a/services/core/java/com/android/server/location/LocationManagerServiceUtils.java b/services/core/java/com/android/server/location/LocationManagerServiceUtils.java
deleted file mode 100644
index b9d86c8..0000000
--- a/services/core/java/com/android/server/location/LocationManagerServiceUtils.java
+++ /dev/null
@@ -1,73 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.location;
-
-import android.annotation.NonNull;
-import android.location.util.identity.CallerIdentity;
-import android.os.IBinder;
-import android.os.RemoteException;
-
-import java.util.NoSuchElementException;
-
-/**
- * Shared utilities for LocationManagerService and GnssManager.
- */
-public class LocationManagerServiceUtils {
-
-    /**
-     * Skeleton class of listener that can be linked to a binder.
-     */
-    public abstract static class LinkedListenerBase implements IBinder.DeathRecipient {
-        protected final CallerIdentity mCallerIdentity;
-
-        LinkedListenerBase(@NonNull CallerIdentity callerIdentity) {
-            mCallerIdentity = callerIdentity;
-        }
-
-        @Override
-        public String toString() {
-            return mCallerIdentity.toString();
-        }
-
-        public CallerIdentity getCallerIdentity() {
-            return mCallerIdentity;
-        }
-
-        /**
-         * Link listener (i.e. callback) to a binder, so that it will be called upon binder's death.
-         */
-        public boolean linkToListenerDeathNotificationLocked(IBinder binder) {
-            try {
-                binder.linkToDeath(this, 0 /* flags */);
-                return true;
-            } catch (RemoteException e) {
-                return false;
-            }
-        }
-
-        /**
-         * Unlink death listener (i.e. callback) from binder.
-         */
-        public void unlinkFromListenerDeathNotificationLocked(IBinder binder) {
-            try {
-                binder.unlinkToDeath(this, 0 /* flags */);
-            } catch (NoSuchElementException e) {
-                // ignore
-            }
-        }
-    }
-}
diff --git a/services/core/java/com/android/server/location/LocationProviderManager.java b/services/core/java/com/android/server/location/LocationProviderManager.java
new file mode 100644
index 0000000..05aa315
--- /dev/null
+++ b/services/core/java/com/android/server/location/LocationProviderManager.java
@@ -0,0 +1,1953 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.location;
+
+import static android.app.AlarmManager.ELAPSED_REALTIME_WAKEUP;
+import static android.app.AlarmManager.WINDOW_EXACT;
+import static android.location.LocationManager.FUSED_PROVIDER;
+import static android.location.LocationManager.GPS_PROVIDER;
+import static android.location.LocationManager.KEY_LOCATION_CHANGED;
+import static android.location.LocationManager.KEY_PROVIDER_ENABLED;
+import static android.location.LocationManager.PASSIVE_PROVIDER;
+import static android.os.IPowerManager.LOCATION_MODE_NO_CHANGE;
+import static android.os.PowerManager.LOCATION_MODE_ALL_DISABLED_WHEN_SCREEN_OFF;
+import static android.os.PowerManager.LOCATION_MODE_FOREGROUND_ONLY;
+import static android.os.PowerManager.LOCATION_MODE_GPS_DISABLED_WHEN_SCREEN_OFF;
+import static android.os.PowerManager.LOCATION_MODE_THROTTLE_REQUESTS_WHEN_SCREEN_OFF;
+
+import static com.android.server.location.LocationManagerService.D;
+import static com.android.server.location.LocationManagerService.TAG;
+import static com.android.server.location.LocationPermissions.PERMISSION_COARSE;
+import static com.android.server.location.LocationPermissions.PERMISSION_FINE;
+import static com.android.server.location.LocationPermissions.PERMISSION_NONE;
+
+import static java.util.concurrent.TimeUnit.NANOSECONDS;
+
+import android.annotation.Nullable;
+import android.app.AlarmManager;
+import android.app.PendingIntent;
+import android.content.Context;
+import android.content.Intent;
+import android.location.Criteria;
+import android.location.ILocationCallback;
+import android.location.ILocationListener;
+import android.location.Location;
+import android.location.LocationManager;
+import android.location.LocationManagerInternal;
+import android.location.LocationManagerInternal.ProviderEnabledListener;
+import android.location.LocationRequest;
+import android.location.util.identity.CallerIdentity;
+import android.os.Binder;
+import android.os.Build;
+import android.os.Bundle;
+import android.os.CancellationSignal;
+import android.os.IBinder;
+import android.os.ICancellationSignal;
+import android.os.IRemoteCallback;
+import android.os.PowerManager;
+import android.os.PowerManager.LocationPowerSaveMode;
+import android.os.Process;
+import android.os.RemoteException;
+import android.os.SystemClock;
+import android.os.UserHandle;
+import android.os.WorkSource;
+import android.stats.location.LocationStatsEnums;
+import android.text.TextUtils;
+import android.util.ArraySet;
+import android.util.EventLog;
+import android.util.IndentingPrintWriter;
+import android.util.Log;
+import android.util.SparseArray;
+import android.util.SparseBooleanArray;
+import android.util.TimeUtils;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.location.ProviderProperties;
+import com.android.internal.location.ProviderRequest;
+import com.android.internal.util.Preconditions;
+import com.android.server.FgThread;
+import com.android.server.LocalServices;
+import com.android.server.PendingIntentUtils;
+import com.android.server.location.LocationPermissions.PermissionLevel;
+import com.android.server.location.listeners.ListenerMultiplexer;
+import com.android.server.location.listeners.RemovableListenerRegistration;
+import com.android.server.location.util.AppForegroundHelper;
+import com.android.server.location.util.AppForegroundHelper.AppForegroundListener;
+import com.android.server.location.util.AppOpsHelper;
+import com.android.server.location.util.Injector;
+import com.android.server.location.util.LocationAttributionHelper;
+import com.android.server.location.util.LocationPermissionsHelper;
+import com.android.server.location.util.LocationPermissionsHelper.LocationPermissionsListener;
+import com.android.server.location.util.LocationPowerSaveModeHelper;
+import com.android.server.location.util.LocationPowerSaveModeHelper.LocationPowerSaveModeChangedListener;
+import com.android.server.location.util.LocationUsageLogger;
+import com.android.server.location.util.ScreenInteractiveHelper;
+import com.android.server.location.util.ScreenInteractiveHelper.ScreenInteractiveChangedListener;
+import com.android.server.location.util.SettingsHelper;
+import com.android.server.location.util.SettingsHelper.GlobalSettingChangedListener;
+import com.android.server.location.util.SettingsHelper.UserSettingChangedListener;
+import com.android.server.location.util.UserInfoHelper;
+import com.android.server.location.util.UserInfoHelper.UserListener;
+
+import java.io.FileDescriptor;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import java.util.Objects;
+
+class LocationProviderManager extends
+        ListenerMultiplexer<Object, LocationRequest, LocationProviderManager.LocationTransport,
+                LocationProviderManager.Registration, ProviderRequest> implements
+        AbstractLocationProvider.Listener {
+
+    // fastest interval at which clients may receive coarse locations
+    public static final long FASTEST_COARSE_INTERVAL_MS = 10 * 60 * 1000;
+
+    private static final String WAKELOCK_TAG = "*location*";
+    private static final long WAKELOCK_TIMEOUT_MS = 30 * 1000;
+
+    // maximum interval to be considered "high power" request
+    private static final long MAX_HIGH_POWER_INTERVAL_MS = 5 * 60 * 1000;
+
+    // maximum age of a location before it is no longer considered "current"
+    private static final long MAX_CURRENT_LOCATION_AGE_MS = 10 * 1000;
+
+    // max timeout allowed for getting the current location
+    private static final long GET_CURRENT_LOCATION_MAX_TIMEOUT_MS = 30 * 1000;
+
+    // maximum jitter allowed for fastest interval evaluation
+    private static final int MAX_FASTEST_INTERVAL_JITTER_MS = 100;
+
+    protected interface LocationTransport {
+
+        void deliverOnLocationChanged(Location location, @Nullable Runnable onCompleteCallback)
+                throws Exception;
+    }
+
+    protected interface ProviderTransport {
+
+        void deliverOnProviderEnabledChanged(String provider, boolean enabled) throws Exception;
+    }
+
+    protected static final class LocationListenerTransport implements LocationTransport,
+            ProviderTransport {
+
+        private final ILocationListener mListener;
+
+        protected LocationListenerTransport(ILocationListener listener) {
+            mListener = Objects.requireNonNull(listener);
+        }
+
+        @Override
+        public void deliverOnLocationChanged(Location location,
+                @Nullable Runnable onCompleteCallback)
+                throws RemoteException {
+            mListener.onLocationChanged(location,
+                    onCompleteCallback == null ? null : new IRemoteCallback.Stub() {
+                        @Override
+                        public void sendResult(Bundle data) {
+                            onCompleteCallback.run();
+                        }
+                    });
+        }
+
+        @Override
+        public void deliverOnProviderEnabledChanged(String provider, boolean enabled)
+                throws RemoteException {
+            mListener.onProviderEnabledChanged(provider, enabled);
+        }
+    }
+
+    protected static final class LocationPendingIntentTransport implements LocationTransport,
+            ProviderTransport {
+
+        private final Context mContext;
+        private final PendingIntent mPendingIntent;
+
+        public LocationPendingIntentTransport(Context context, PendingIntent pendingIntent) {
+            mContext = context;
+            mPendingIntent = pendingIntent;
+        }
+
+        @Override
+        public void deliverOnLocationChanged(Location location,
+                @Nullable Runnable onCompleteCallback)
+                throws PendingIntent.CanceledException {
+            mPendingIntent.send(mContext, 0, new Intent().putExtra(KEY_LOCATION_CHANGED, location),
+                    onCompleteCallback != null ? (pI, i, rC, rD, rE) -> onCompleteCallback.run()
+                            : null, null, null,
+                    PendingIntentUtils.createDontSendToRestrictedAppsBundle(null));
+        }
+
+        @Override
+        public void deliverOnProviderEnabledChanged(String provider, boolean enabled)
+                throws PendingIntent.CanceledException {
+            mPendingIntent.send(mContext, 0, new Intent().putExtra(KEY_PROVIDER_ENABLED, enabled),
+                    null, null, null,
+                    PendingIntentUtils.createDontSendToRestrictedAppsBundle(null));
+        }
+    }
+
+    protected static final class GetCurrentLocationTransport implements LocationTransport {
+
+        private final ILocationCallback mCallback;
+
+        protected GetCurrentLocationTransport(ILocationCallback callback) {
+            mCallback = Objects.requireNonNull(callback);
+        }
+
+        @Override
+        public void deliverOnLocationChanged(Location location,
+                @Nullable Runnable onCompleteCallback)
+                throws RemoteException {
+            // ILocationCallback doesn't currently support completion callbacks
+            Preconditions.checkState(onCompleteCallback == null);
+            mCallback.onLocation(location);
+        }
+    }
+
+    protected abstract class Registration extends
+            RemovableListenerRegistration<LocationRequest, LocationTransport> {
+
+        @PermissionLevel protected final int mPermissionLevel;
+        private final WorkSource mWorkSource;
+
+        // we cache these values because checking/calculating on the fly is more expensive
+        private boolean mPermitted;
+        private boolean mForeground;
+        private LocationRequest mProviderLocationRequest;
+        private boolean mIsUsingHighPower;
+
+        protected Registration(LocationRequest request, CallerIdentity identity,
+                LocationTransport transport, @PermissionLevel int permissionLevel) {
+            super(TAG, Objects.requireNonNull(request), identity, transport);
+
+            Preconditions.checkArgument(permissionLevel > PERMISSION_NONE);
+            mPermissionLevel = permissionLevel;
+
+            if (request.getWorkSource() != null && !request.getWorkSource().isEmpty()) {
+                mWorkSource = request.getWorkSource();
+            } else {
+                mWorkSource = identity.addToWorkSource(null);
+            }
+
+            mProviderLocationRequest = super.getRequest();
+        }
+
+        @GuardedBy("mLock")
+        @Override
+        protected final void onRemovableListenerRegister() {
+            if (Build.IS_DEBUGGABLE) {
+                Preconditions.checkState(Thread.holdsLock(mLock));
+            }
+
+            if (D) {
+                Log.d(TAG, mName + " provider added registration from " + getIdentity() + " -> "
+                        + getRequest());
+            }
+
+            // initialization order is important as there are ordering dependencies
+            mPermitted = mLocationPermissionsHelper.hasLocationPermissions(mPermissionLevel,
+                    getIdentity());
+            mForeground = mAppForegroundHelper.isAppForeground(getIdentity().getUid());
+            mProviderLocationRequest = calculateProviderLocationRequest();
+            mIsUsingHighPower = isUsingHighPower();
+
+            onProviderListenerRegister();
+        }
+
+        @GuardedBy("mLock")
+        @Override
+        protected final void onRemovableListenerUnregister() {
+            if (Build.IS_DEBUGGABLE) {
+                Preconditions.checkState(Thread.holdsLock(mLock));
+            }
+
+            onProviderListenerUnregister();
+
+            if (D) {
+                Log.d(TAG, mName + " provider removed registration from " + getIdentity());
+            }
+        }
+
+        /**
+         * Subclasses may override this instead of {@link #onRemovableListenerRegister()}.
+         */
+        @GuardedBy("mLock")
+        protected void onProviderListenerRegister() {}
+
+        /**
+         * Subclasses may override this instead of {@link #onRemovableListenerUnregister()}.
+         */
+        @GuardedBy("mLock")
+        protected void onProviderListenerUnregister() {}
+
+        @Override
+        protected final ListenerOperation<LocationTransport> onActive() {
+            if (!getRequest().getHideFromAppOps()) {
+                mLocationAttributionHelper.reportLocationStart(getIdentity(), getName(), getKey());
+            }
+            onHighPowerUsageChanged();
+            return null;
+        }
+
+        @Override
+        protected final void onInactive() {
+            onHighPowerUsageChanged();
+            if (!getRequest().getHideFromAppOps()) {
+                mLocationAttributionHelper.reportLocationStop(getIdentity(), getName(), getKey());
+            }
+        }
+
+        @Override
+        public final LocationRequest getRequest() {
+            return mProviderLocationRequest;
+        }
+
+        public final boolean isForeground() {
+            return mForeground;
+        }
+
+        public final boolean isPermitted() {
+            return mPermitted;
+        }
+
+        @Override
+        protected final LocationProviderManager getOwner() {
+            return LocationProviderManager.this;
+        }
+
+        protected final WorkSource getWorkSource() {
+            return mWorkSource;
+        }
+
+        @GuardedBy("mLock")
+        private void onHighPowerUsageChanged() {
+            boolean isUsingHighPower = isUsingHighPower();
+            if (isUsingHighPower != mIsUsingHighPower) {
+                mIsUsingHighPower = isUsingHighPower;
+
+                if (!getRequest().getHideFromAppOps()) {
+                    if (mIsUsingHighPower) {
+                        mLocationAttributionHelper.reportHighPowerLocationStart(
+                                getIdentity(), getName(), getKey());
+                    } else {
+                        mLocationAttributionHelper.reportHighPowerLocationStop(
+                                getIdentity(), getName(), getKey());
+                    }
+                }
+            }
+        }
+
+        @GuardedBy("mLock")
+        private boolean isUsingHighPower() {
+            if (Build.IS_DEBUGGABLE) {
+                Preconditions.checkState(Thread.holdsLock(mLock));
+            }
+
+            return isActive()
+                    && getRequest().getInterval() < MAX_HIGH_POWER_INTERVAL_MS
+                    && getProperties().mPowerRequirement == Criteria.POWER_HIGH;
+        }
+
+        @GuardedBy("mLock")
+        final boolean onLocationPermissionsChanged(String packageName) {
+            if (getIdentity().getPackageName().equals(packageName)) {
+                return onLocationPermissionsChanged();
+            }
+
+            return false;
+        }
+
+        @GuardedBy("mLock")
+        final boolean onLocationPermissionsChanged(int uid) {
+            if (getIdentity().getUid() == uid) {
+                return onLocationPermissionsChanged();
+            }
+
+            return false;
+        }
+
+        @GuardedBy("mLock")
+        private boolean onLocationPermissionsChanged() {
+            if (Build.IS_DEBUGGABLE) {
+                Preconditions.checkState(Thread.holdsLock(mLock));
+            }
+
+            boolean permitted = mLocationPermissionsHelper.hasLocationPermissions(mPermissionLevel,
+                    getIdentity());
+            if (permitted != mPermitted) {
+                if (D) {
+                    Log.v(TAG, mName + " provider package " + getIdentity().getPackageName()
+                            + " permitted = " + permitted);
+                }
+
+                mPermitted = permitted;
+                return true;
+            }
+
+            return false;
+        }
+
+        @GuardedBy("mLock")
+        final boolean onForegroundChanged(int uid, boolean foreground) {
+            if (Build.IS_DEBUGGABLE) {
+                Preconditions.checkState(Thread.holdsLock(mLock));
+            }
+
+            if (getIdentity().getUid() == uid && foreground != mForeground) {
+                if (D) {
+                    Log.v(TAG, mName + " provider uid " + uid + " foreground = " + foreground);
+                }
+
+                mForeground = foreground;
+
+                // note that onProviderLocationRequestChanged() is always called
+                return onProviderLocationRequestChanged()
+                        || mLocationPowerSaveModeHelper.getLocationPowerSaveMode()
+                        == LOCATION_MODE_FOREGROUND_ONLY;
+            }
+
+            return false;
+        }
+
+        @GuardedBy("mLock")
+        final boolean onProviderLocationRequestChanged() {
+            if (Build.IS_DEBUGGABLE) {
+                Preconditions.checkState(Thread.holdsLock(mLock));
+            }
+
+            LocationRequest newRequest = calculateProviderLocationRequest();
+            if (!mProviderLocationRequest.equals(newRequest)) {
+                LocationRequest oldRequest = mProviderLocationRequest;
+                mProviderLocationRequest = newRequest;
+                onHighPowerUsageChanged();
+                updateService();
+
+                // if location settings ignored has changed then the active state may have changed
+                return oldRequest.isLocationSettingsIgnored()
+                        != newRequest.isLocationSettingsIgnored();
+            }
+
+            return false;
+        }
+
+        private LocationRequest calculateProviderLocationRequest() {
+            LocationRequest newRequest = new LocationRequest(super.getRequest());
+
+            if (newRequest.isLocationSettingsIgnored()) {
+                // if we are not currently allowed use location settings ignored, disable it
+                if (!mSettingsHelper.getIgnoreSettingsPackageWhitelist().contains(
+                        getIdentity().getPackageName()) && !mLocationManagerInternal.isProvider(
+                        null, getIdentity())) {
+                    newRequest.setLocationSettingsIgnored(false);
+                }
+            }
+
+            if (!newRequest.isLocationSettingsIgnored() && !isThrottlingExempt()) {
+                // throttle in the background
+                if (!mForeground) {
+                    newRequest.setInterval(Math.max(newRequest.getInterval(),
+                            mSettingsHelper.getBackgroundThrottleIntervalMs()));
+                }
+            }
+
+            return newRequest;
+        }
+
+        private boolean isThrottlingExempt() {
+            if (mSettingsHelper.getBackgroundThrottlePackageWhitelist().contains(
+                    getIdentity().getPackageName())) {
+                return true;
+            }
+
+            return mLocationManagerInternal.isProvider(null, getIdentity());
+        }
+
+        @Nullable
+        abstract ListenerOperation<LocationTransport> acceptLocationChange(Location fineLocation);
+
+        @Override
+        public String toString() {
+            StringBuilder builder = new StringBuilder();
+            builder.append(getIdentity());
+
+            ArraySet<String> flags = new ArraySet<>(2);
+            if (!isForeground()) {
+                flags.add("bg");
+            }
+            if (!isPermitted()) {
+                flags.add("na");
+            }
+            if (!flags.isEmpty()) {
+                builder.append(" ").append(flags);
+            }
+
+            if (mPermissionLevel == PERMISSION_COARSE) {
+                builder.append(" (COARSE)");
+            }
+
+            builder.append(" ").append(getRequest());
+            return builder.toString();
+        }
+    }
+
+    protected abstract class LocationRegistration extends Registration implements
+            AlarmManager.OnAlarmListener, ProviderEnabledListener {
+
+        private final PowerManager.WakeLock mWakeLock;
+
+        private volatile ProviderTransport mProviderTransport;
+        @Nullable private Location mLastLocation = null;
+        private int mNumLocationsDelivered = 0;
+        private long mExpirationRealtimeMs = Long.MAX_VALUE;
+
+        protected <TTransport extends LocationTransport & ProviderTransport> LocationRegistration(
+                LocationRequest request, CallerIdentity identity, TTransport transport,
+                @PermissionLevel int permissionLevel) {
+            super(request, identity, transport, permissionLevel);
+            mProviderTransport = transport;
+            mWakeLock = Objects.requireNonNull(mContext.getSystemService(PowerManager.class))
+                    .newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, WAKELOCK_TAG);
+            mWakeLock.setReferenceCounted(true);
+            mWakeLock.setWorkSource(getWorkSource());
+        }
+
+        @Override
+        protected void onListenerUnregister() {
+            mProviderTransport = null;
+        }
+
+        @GuardedBy("mLock")
+        @Override
+        protected final void onProviderListenerRegister() {
+            mExpirationRealtimeMs = getRequest().getExpirationRealtimeMs(
+                    SystemClock.elapsedRealtime());
+
+            // add alarm for expiration
+            if (mExpirationRealtimeMs < SystemClock.elapsedRealtime()) {
+                remove();
+            } else if (mExpirationRealtimeMs < Long.MAX_VALUE) {
+                AlarmManager alarmManager = Objects.requireNonNull(
+                        mContext.getSystemService(AlarmManager.class));
+                alarmManager.set(ELAPSED_REALTIME_WAKEUP, mExpirationRealtimeMs, WINDOW_EXACT,
+                        0, this, FgThread.getHandler(), getWorkSource());
+            }
+
+            // start listening for provider enabled/disabled events
+            addEnabledListener(this);
+
+            onLocationListenerRegister();
+
+            // if the provider is currently disabled, let the client know immediately
+            int userId = getIdentity().getUserId();
+            if (!isEnabled(userId)) {
+                onProviderEnabledChanged(mName, userId, false);
+            }
+        }
+
+        @GuardedBy("mLock")
+        @Override
+        protected final void onProviderListenerUnregister() {
+            // stop listening for provider enabled/disabled events
+            removeEnabledListener(this);
+
+            // remove alarm for expiration
+            if (mExpirationRealtimeMs < Long.MAX_VALUE) {
+                AlarmManager alarmManager = Objects.requireNonNull(
+                        mContext.getSystemService(AlarmManager.class));
+                alarmManager.cancel(this);
+            }
+
+            onLocationListenerUnregister();
+        }
+
+        /**
+         * Subclasses may override this instead of {@link #onRemovableListenerRegister()}.
+         */
+        @GuardedBy("mLock")
+        protected void onLocationListenerRegister() {}
+
+        /**
+         * Subclasses may override this instead of {@link #onRemovableListenerUnregister()}.
+         */
+        @GuardedBy("mLock")
+        protected void onLocationListenerUnregister() {}
+
+        @Override
+        public void onAlarm() {
+            if (D) {
+                Log.d(TAG, "removing " + getIdentity() + " from " + mName
+                        + " provider due to expiration at " + TimeUtils.formatRealtime(
+                        mExpirationRealtimeMs));
+            }
+
+            synchronized (mLock) {
+                remove();
+            }
+        }
+
+        @GuardedBy("mLock")
+        @Nullable
+        @Override
+        ListenerOperation<LocationTransport> acceptLocationChange(Location fineLocation) {
+            if (Build.IS_DEBUGGABLE) {
+                Preconditions.checkState(Thread.holdsLock(mLock));
+            }
+
+            // check expiration time - alarm is not guaranteed to go off at the right time,
+            // especially for short intervals
+            if (SystemClock.elapsedRealtime() >= mExpirationRealtimeMs) {
+                remove();
+                return null;
+            }
+
+            Location location;
+            switch (mPermissionLevel) {
+                case PERMISSION_FINE:
+                    location = fineLocation;
+                    break;
+                case PERMISSION_COARSE:
+                    location = mLocationFudger.createCoarse(fineLocation);
+                    break;
+                default:
+                    // shouldn't be possible to have a client added without location permissions
+                    throw new AssertionError();
+            }
+
+            if (mLastLocation != null) {
+                // check fastest interval
+                long deltaMs = NANOSECONDS.toMillis(
+                        location.getElapsedRealtimeNanos()
+                                - mLastLocation.getElapsedRealtimeNanos());
+                if (deltaMs < getRequest().getFastestInterval() - MAX_FASTEST_INTERVAL_JITTER_MS) {
+                    return null;
+                }
+
+                // check smallest displacement
+                double smallestDisplacement = getRequest().getSmallestDisplacement();
+                if (smallestDisplacement > 0.0 && location.distanceTo(mLastLocation)
+                        <= smallestDisplacement) {
+                    return null;
+                }
+            }
+
+            // note app ops
+            if (!mAppOpsHelper.noteOpNoThrow(LocationPermissions.asAppOp(mPermissionLevel),
+                    getIdentity())) {
+                if (D) {
+                    Log.w(TAG, "noteOp denied for " + getIdentity());
+                }
+                return null;
+            }
+
+            // update last location
+            mLastLocation = location;
+
+            return new ListenerOperation<LocationTransport>() {
+                @Override
+                public void onPreExecute() {
+                    // don't acquire a wakelock for mock locations to prevent abuse
+                    if (!location.isFromMockProvider()) {
+                        mWakeLock.acquire(WAKELOCK_TIMEOUT_MS);
+                    }
+                }
+
+                @Override
+                public void operate(LocationTransport listener) throws Exception {
+                    // if delivering to the same process, make a copy of the location first (since
+                    // location is mutable)
+                    Location deliveryLocation;
+                    if (getIdentity().getPid() == Process.myPid()) {
+                        deliveryLocation = new Location(location);
+                    } else {
+                        deliveryLocation = location;
+                    }
+
+                    listener.deliverOnLocationChanged(deliveryLocation,
+                            location.isFromMockProvider() ? null : mWakeLock::release);
+                }
+
+                @Override
+                public void onPostExecute(boolean success) {
+                    if (!success && !location.isFromMockProvider()) {
+                        mWakeLock.release();
+                    }
+
+                    if (success) {
+                        // check num updates - if successful then this function will always be run
+                        // from the same thread, and no additional synchronization is necessary
+                        boolean remove = ++mNumLocationsDelivered >= getRequest().getNumUpdates();
+                        if (remove) {
+                            if (D) {
+                                Log.d(TAG, "removing " + getIdentity() + " from " + mName
+                                        + " provider due to number of updates");
+                            }
+
+                            synchronized (mLock) {
+                                remove();
+                            }
+                        }
+                    }
+                }
+            };
+        }
+
+        @Override
+        public void onProviderEnabledChanged(String provider, int userId, boolean enabled) {
+            Preconditions.checkState(mName.equals(provider));
+
+            if (userId != getIdentity().getUserId()) {
+                return;
+            }
+
+            // we choose not to hold a wakelock for provider enabled changed events
+            executeSafely(getExecutor(), () -> mProviderTransport,
+                    listener -> listener.deliverOnProviderEnabledChanged(mName, enabled));
+        }
+    }
+
+    protected final class LocationListenerRegistration extends LocationRegistration implements
+            IBinder.DeathRecipient {
+
+        protected LocationListenerRegistration(LocationRequest request, CallerIdentity identity,
+                LocationListenerTransport transport, @PermissionLevel int permissionLevel) {
+            super(request, identity, transport, permissionLevel);
+        }
+
+        @GuardedBy("mLock")
+        @Override
+        protected void onLocationListenerRegister() {
+            try {
+                ((IBinder) getKey()).linkToDeath(this, 0);
+            } catch (RemoteException e) {
+                remove();
+            }
+        }
+
+        @GuardedBy("mLock")
+        @Override
+        protected void onLocationListenerUnregister() {
+            ((IBinder) getKey()).unlinkToDeath(this, 0);
+        }
+
+        @Override
+        public void binderDied() {
+            try {
+                if (D) {
+                    Log.d(TAG, mName + " provider client died: " + getIdentity());
+                }
+
+                synchronized (mLock) {
+                    remove();
+                }
+            } catch (RuntimeException e) {
+                // the caller may swallow runtime exceptions, so we rethrow as assertion errors to
+                // ensure the crash is seen
+                throw new AssertionError(e);
+            }
+        }
+    }
+
+    protected final class LocationPendingIntentRegistration extends LocationRegistration implements
+            PendingIntent.CancelListener {
+
+        protected LocationPendingIntentRegistration(LocationRequest request,
+                CallerIdentity identity, LocationPendingIntentTransport transport,
+                @PermissionLevel int permissionLevel) {
+            super(request, identity, transport, permissionLevel);
+        }
+
+        @GuardedBy("mLock")
+        @Override
+        protected void onLocationListenerRegister() {
+            ((PendingIntent) getKey()).registerCancelListener(this);
+        }
+
+        @GuardedBy("mLock")
+        @Override
+        protected void onLocationListenerUnregister() {
+            ((PendingIntent) getKey()).unregisterCancelListener(this);
+        }
+
+        @Override
+        public void onCancelled(PendingIntent intent) {
+            synchronized (mLock) {
+                remove();
+            }
+        }
+    }
+
+    protected final class GetCurrentLocationListenerRegistration extends Registration implements
+            IBinder.DeathRecipient, ProviderEnabledListener, AlarmManager.OnAlarmListener {
+
+        private volatile LocationTransport mTransport;
+
+        private long mExpirationRealtimeMs = Long.MAX_VALUE;
+
+        protected GetCurrentLocationListenerRegistration(LocationRequest request,
+                CallerIdentity identity, LocationTransport transport, int permissionLevel) {
+            super(request, identity, transport, permissionLevel);
+            mTransport = transport;
+        }
+
+        @GuardedBy("mLock")
+        void deliverLocation(@Nullable Location location) {
+            executeSafely(getExecutor(), () -> mTransport, acceptLocationChange(location));
+        }
+
+        @Override
+        protected void onListenerUnregister() {
+            mTransport = null;
+        }
+
+        @GuardedBy("mLock")
+        @Override
+        protected void onProviderListenerRegister() {
+            mExpirationRealtimeMs = getRequest().getExpirationRealtimeMs(
+                    SystemClock.elapsedRealtime());
+
+            // add alarm for expiration
+            if (mExpirationRealtimeMs < Long.MAX_VALUE) {
+                AlarmManager alarmManager = Objects.requireNonNull(
+                        mContext.getSystemService(AlarmManager.class));
+                alarmManager.set(ELAPSED_REALTIME_WAKEUP, mExpirationRealtimeMs, WINDOW_EXACT,
+                        0, this, FgThread.getHandler(), getWorkSource());
+            }
+
+            try {
+                ((IBinder) getKey()).linkToDeath(this, 0);
+            } catch (RemoteException e) {
+                remove();
+            }
+
+            // start listening for provider enabled/disabled events
+            addEnabledListener(this);
+
+            // if the provider is currently disabled fail immediately
+            int userId = getIdentity().getUserId();
+            if (!getRequest().isLocationSettingsIgnored() && !isEnabled(userId)) {
+                deliverLocation(null);
+            }
+        }
+
+        @GuardedBy("mLock")
+        @Override
+        protected void onProviderListenerUnregister() {
+            // stop listening for provider enabled/disabled events
+            removeEnabledListener(this);
+
+            // remove alarm for expiration
+            if (mExpirationRealtimeMs < Long.MAX_VALUE) {
+                AlarmManager alarmManager = Objects.requireNonNull(
+                        mContext.getSystemService(AlarmManager.class));
+                alarmManager.cancel(this);
+            }
+
+            ((IBinder) getKey()).unlinkToDeath(this, 0);
+        }
+
+        @Override
+        public void onAlarm() {
+            if (D) {
+                Log.d(TAG, "removing " + getIdentity() + " from " + mName
+                        + " provider due to expiration at " + TimeUtils.formatRealtime(
+                        mExpirationRealtimeMs));
+            }
+
+            synchronized (mLock) {
+                deliverLocation(null);
+            }
+        }
+
+        @GuardedBy("mLock")
+        @Nullable
+        @Override
+        ListenerOperation<LocationTransport> acceptLocationChange(@Nullable Location fineLocation) {
+            if (Build.IS_DEBUGGABLE) {
+                Preconditions.checkState(Thread.holdsLock(mLock));
+            }
+
+            // lastly - note app ops
+            Location location;
+            if (fineLocation == null) {
+                location = null;
+            } else if (!mAppOpsHelper.noteOpNoThrow(LocationPermissions.asAppOp(mPermissionLevel),
+                    getIdentity())) {
+                if (D) {
+                    Log.w(TAG, "noteOp denied for " + getIdentity());
+                }
+                location = null;
+            } else {
+                switch (mPermissionLevel) {
+                    case PERMISSION_FINE:
+                        location = fineLocation;
+                        break;
+                    case PERMISSION_COARSE:
+                        location = mLocationFudger.createCoarse(fineLocation);
+                        break;
+                    default:
+                        // shouldn't be possible to have a client added without location permissions
+                        throw new AssertionError();
+                }
+            }
+
+            return listener -> {
+                // if delivering to the same process, make a copy of the location first (since
+                // location is mutable)
+                Location deliveryLocation = location;
+                if (getIdentity().getPid() == Process.myPid() && location != null) {
+                    deliveryLocation = new Location(location);
+                }
+
+                // we currently don't hold a wakelock for getCurrentLocation deliveries
+                listener.deliverOnLocationChanged(deliveryLocation, null);
+
+                synchronized (mLock) {
+                    remove();
+                }
+            };
+        }
+
+        @Override
+        public void onProviderEnabledChanged(String provider, int userId, boolean enabled) {
+            Preconditions.checkState(mName.equals(provider));
+
+            if (userId != getIdentity().getUserId()) {
+                return;
+            }
+
+            // if the provider is disabled we give up on current location immediately
+            if (!getRequest().isLocationSettingsIgnored() && !enabled) {
+                synchronized (mLock) {
+                    deliverLocation(null);
+                }
+            }
+        }
+
+        @Override
+        public void binderDied() {
+            try {
+                if (D) {
+                    Log.d(TAG, mName + " provider client died: " + getIdentity());
+                }
+
+                synchronized (mLock) {
+                    remove();
+                }
+            } catch (RuntimeException e) {
+                // the caller may swallow runtime exceptions, so we rethrow as assertion errors to
+                // ensure the crash is seen
+                throw new AssertionError(e);
+            }
+        }
+    }
+
+    protected final Object mLock = new Object();
+
+    protected final String mName;
+    @Nullable private final PassiveLocationProviderManager mPassiveManager;
+
+    protected final Context mContext;
+
+    @GuardedBy("mLock")
+    private boolean mStarted;
+
+    // maps of user id to value
+    @GuardedBy("mLock")
+    private final SparseBooleanArray mEnabled; // null or not present means unknown
+    @GuardedBy("mLock")
+    private final SparseArray<LastLocation> mLastLocations;
+
+    @GuardedBy("mLock")
+    private final ArrayList<ProviderEnabledListener> mEnabledListeners;
+
+    protected final LocationManagerInternal mLocationManagerInternal;
+    protected final SettingsHelper mSettingsHelper;
+    protected final UserInfoHelper mUserInfoHelper;
+    protected final AppOpsHelper mAppOpsHelper;
+    protected final LocationPermissionsHelper mLocationPermissionsHelper;
+    protected final AppForegroundHelper mAppForegroundHelper;
+    protected final LocationPowerSaveModeHelper mLocationPowerSaveModeHelper;
+    protected final ScreenInteractiveHelper mScreenInteractiveHelper;
+    protected final LocationAttributionHelper mLocationAttributionHelper;
+    protected final LocationUsageLogger mLocationUsageLogger;
+    protected final LocationFudger mLocationFudger;
+    protected final LocationRequestStatistics mLocationRequestStatistics;
+
+    private final UserListener mUserChangedListener = this::onUserChanged;
+    private final UserSettingChangedListener mLocationEnabledChangedListener =
+            this::onLocationEnabledChanged;
+    private final GlobalSettingChangedListener mBackgroundThrottlePackageWhitelistChangedListener =
+            this::onBackgroundThrottlePackageWhitelistChanged;
+    private final UserSettingChangedListener mLocationPackageBlacklistChangedListener =
+            this::onLocationPackageBlacklistChanged;
+    private final LocationPermissionsListener mLocationPermissionsListener =
+            new LocationPermissionsListener() {
+                @Override
+                public void onLocationPermissionsChanged(String packageName) {
+                    LocationProviderManager.this.onLocationPermissionsChanged(packageName);
+                }
+
+                @Override
+                public void onLocationPermissionsChanged(int uid) {
+                    LocationProviderManager.this.onLocationPermissionsChanged(uid);
+                }
+            };
+    private final AppForegroundListener mAppForegroundChangedListener =
+            this::onAppForegroundChanged;
+    private final GlobalSettingChangedListener mBackgroundThrottleIntervalChangedListener =
+            this::onBackgroundThrottleIntervalChanged;
+    private final GlobalSettingChangedListener mIgnoreSettingsPackageWhitelistChangedListener =
+            this::onIgnoreSettingsWhitelistChanged;
+    private final LocationPowerSaveModeChangedListener mLocationPowerSaveModeChangedListener =
+            this::onLocationPowerSaveModeChanged;
+    private final ScreenInteractiveChangedListener mScreenInteractiveChangedListener =
+            this::onScreenInteractiveChanged;
+
+    // acquiring mLock makes operations on mProvider atomic, but is otherwise unnecessary
+    protected final MockableLocationProvider mProvider;
+
+    LocationProviderManager(Context context, Injector injector, String name,
+            @Nullable PassiveLocationProviderManager passiveManager) {
+        mContext = context;
+        mName = Objects.requireNonNull(name);
+        mPassiveManager = passiveManager;
+        mStarted = false;
+        mEnabled = new SparseBooleanArray(2);
+        mLastLocations = new SparseArray<>(2);
+
+        mEnabledListeners = new ArrayList<>();
+
+        mLocationManagerInternal = Objects.requireNonNull(
+                LocalServices.getService(LocationManagerInternal.class));
+        mSettingsHelper = injector.getSettingsHelper();
+        mUserInfoHelper = injector.getUserInfoHelper();
+        mAppOpsHelper = injector.getAppOpsHelper();
+        mLocationPermissionsHelper = injector.getLocationPermissionsHelper();
+        mAppForegroundHelper = injector.getAppForegroundHelper();
+        mLocationPowerSaveModeHelper = injector.getLocationPowerSaveModeHelper();
+        mScreenInteractiveHelper = injector.getScreenInteractiveHelper();
+        mLocationAttributionHelper = injector.getLocationAttributionHelper();
+        mLocationUsageLogger = injector.getLocationUsageLogger();
+        mLocationRequestStatistics = injector.getLocationRequestStatistics();
+        mLocationFudger = new LocationFudger(mSettingsHelper.getCoarseLocationAccuracyM());
+
+        // initialize last since this lets our reference escape
+        mProvider = new MockableLocationProvider(mLock, this);
+    }
+
+    public void startManager() {
+        synchronized (mLock) {
+            mStarted = true;
+
+            mUserInfoHelper.addListener(mUserChangedListener);
+            mSettingsHelper.addOnLocationEnabledChangedListener(mLocationEnabledChangedListener);
+
+            // initialize enabled state
+            onUserStarted(UserHandle.USER_ALL);
+        }
+    }
+
+    public void stopManager() {
+        synchronized (mLock) {
+            mUserInfoHelper.removeListener(mUserChangedListener);
+            mSettingsHelper.removeOnLocationEnabledChangedListener(mLocationEnabledChangedListener);
+
+            // notify and remove all listeners
+            onUserStopped(UserHandle.USER_ALL);
+            removeRegistrationIf(key -> true);
+            mEnabledListeners.clear();
+
+            mStarted = false;
+        }
+    }
+
+    public String getName() {
+        return mName;
+    }
+
+    @Nullable
+    public CallerIdentity getIdentity() {
+        return mProvider.getState().identity;
+    }
+
+    @Nullable
+    public ProviderProperties getProperties() {
+        return mProvider.getState().properties;
+    }
+
+    public boolean hasProvider() {
+        return mProvider.getProvider() != null;
+    }
+
+    public boolean isEnabled(int userId) {
+        if (userId == UserHandle.USER_NULL) {
+            return false;
+        }
+
+        Preconditions.checkArgument(userId >= 0);
+
+        synchronized (mLock) {
+            int index = mEnabled.indexOfKey(userId);
+            if (index < 0) {
+                // this generally shouldn't occur, but might be possible due to race conditions
+                // on when we are notified of new users
+                Log.w(TAG, mName + " provider saw user " + userId + " unexpectedly");
+                onEnabledChanged(userId);
+                index = mEnabled.indexOfKey(userId);
+            }
+
+            return mEnabled.valueAt(index);
+        }
+    }
+
+    public void addEnabledListener(ProviderEnabledListener listener) {
+        synchronized (mLock) {
+            Preconditions.checkState(mStarted);
+            mEnabledListeners.add(listener);
+        }
+    }
+
+    public void removeEnabledListener(ProviderEnabledListener listener) {
+        synchronized (mLock) {
+            Preconditions.checkState(mStarted);
+            mEnabledListeners.remove(listener);
+        }
+    }
+
+    public void setRealProvider(AbstractLocationProvider provider) {
+        synchronized (mLock) {
+            Preconditions.checkState(mStarted);
+            mProvider.setRealProvider(provider);
+        }
+    }
+
+    public void setMockProvider(@Nullable MockProvider provider) {
+        synchronized (mLock) {
+            Preconditions.checkState(mStarted);
+            mProvider.setMockProvider(provider);
+
+            // when removing a mock provider, also clear any mock last locations and reset the
+            // location fudger. the mock provider could have been used to infer the current
+            // location fudger offsets.
+            if (provider == null) {
+                final int lastLocationSize = mLastLocations.size();
+                for (int i = 0; i < lastLocationSize; i++) {
+                    mLastLocations.valueAt(i).clearMock();
+                }
+
+                mLocationFudger.resetOffsets();
+            }
+        }
+    }
+
+    public void setMockProviderAllowed(boolean enabled) {
+        synchronized (mLock) {
+            if (!mProvider.isMock()) {
+                throw new IllegalArgumentException(mName + " provider is not a test provider");
+            }
+
+            mProvider.setMockProviderAllowed(enabled);
+        }
+    }
+
+    public void setMockProviderLocation(Location location) {
+        synchronized (mLock) {
+            if (!mProvider.isMock()) {
+                throw new IllegalArgumentException(mName + " provider is not a test provider");
+            }
+
+            String locationProvider = location.getProvider();
+            if (!TextUtils.isEmpty(locationProvider) && !mName.equals(locationProvider)) {
+                // The location has an explicit provider that is different from the mock
+                // provider name. The caller may be trying to fool us via b/33091107.
+                EventLog.writeEvent(0x534e4554, "33091107", Binder.getCallingUid(),
+                        mName + "!=" + locationProvider);
+            }
+
+            mProvider.setMockProviderLocation(location);
+        }
+    }
+
+    public List<LocationRequest> getMockProviderRequests() {
+        synchronized (mLock) {
+            if (!mProvider.isMock()) {
+                throw new IllegalArgumentException(mName + " provider is not a test provider");
+            }
+
+            return mProvider.getCurrentRequest().locationRequests;
+        }
+    }
+
+    @Nullable
+    public Location getLastLocation(LocationRequest request, CallerIdentity identity,
+            @PermissionLevel int permissionLevel) {
+        Preconditions.checkArgument(mName.equals(request.getProvider()));
+
+        if (mSettingsHelper.isLocationPackageBlacklisted(identity.getUserId(),
+                identity.getPackageName())) {
+            return null;
+        }
+        if (!mUserInfoHelper.isCurrentUserId(identity.getUserId())) {
+            return null;
+        }
+        if (!request.isLocationSettingsIgnored() && !isEnabled(identity.getUserId())) {
+            return null;
+        }
+
+        Location location = getLastLocation(identity.getUserId(), permissionLevel,
+                request.isLocationSettingsIgnored());
+
+        // we don't note op here because we don't know what the client intends to do with the
+        // location, the client is responsible for noting if necessary
+
+        if (identity.getPid() == Process.myPid() && location != null) {
+            // if delivering to the same process, make a copy of the location first (since
+            // location is mutable)
+            return new Location(location);
+        } else {
+            return location;
+        }
+    }
+
+    @Nullable
+    private Location getLastLocation(int userId, @PermissionLevel int permissionLevel,
+            boolean ignoreLocationSettings) {
+        synchronized (mLock) {
+            LastLocation lastLocation = mLastLocations.get(userId);
+            if (lastLocation == null) {
+                return null;
+            }
+            return lastLocation.get(permissionLevel, ignoreLocationSettings);
+        }
+    }
+
+    public void injectLastLocation(Location location, int userId) {
+        synchronized (mLock) {
+            if (getLastLocation(userId, PERMISSION_FINE, false) == null) {
+                setLastLocation(location, userId);
+            }
+        }
+    }
+
+    private void setLastLocation(Location location, int userId) {
+        if (userId == UserHandle.USER_ALL) {
+            final int[] runningUserIds = mUserInfoHelper.getRunningUserIds();
+            for (int i = 0; i < runningUserIds.length; i++) {
+                setLastLocation(location, runningUserIds[i]);
+            }
+            return;
+        }
+
+        Preconditions.checkArgument(userId >= 0);
+
+        synchronized (mLock) {
+            LastLocation lastLocation = mLastLocations.get(userId);
+            if (lastLocation == null) {
+                lastLocation = new LastLocation();
+                mLastLocations.put(userId, lastLocation);
+            }
+
+            Location coarseLocation = mLocationFudger.createCoarse(location);
+            if (isEnabled(userId)) {
+                lastLocation.set(location, coarseLocation);
+            }
+            lastLocation.setBypass(location, coarseLocation);
+        }
+    }
+
+    public void getCurrentLocation(LocationRequest request, CallerIdentity identity,
+            int permissionLevel, ICancellationSignal cancellationTransport,
+            ILocationCallback callback) {
+        Preconditions.checkArgument(mName.equals(request.getProvider()));
+
+        if (request.getExpireIn() > GET_CURRENT_LOCATION_MAX_TIMEOUT_MS) {
+            request.setExpireIn(GET_CURRENT_LOCATION_MAX_TIMEOUT_MS);
+        }
+
+        GetCurrentLocationListenerRegistration registration =
+                new GetCurrentLocationListenerRegistration(
+                        request,
+                        identity,
+                        new GetCurrentLocationTransport(callback),
+                        permissionLevel);
+
+        synchronized (mLock) {
+            Location lastLocation = getLastLocation(request, identity, permissionLevel);
+            if (lastLocation != null) {
+                long locationAgeMs = NANOSECONDS.toMillis(
+                        SystemClock.elapsedRealtimeNanos()
+                                - lastLocation.getElapsedRealtimeNanos());
+                if (locationAgeMs < MAX_CURRENT_LOCATION_AGE_MS) {
+                    registration.deliverLocation(lastLocation);
+                    return;
+                }
+
+                if (!mAppForegroundHelper.isAppForeground(Binder.getCallingUid())
+                        && locationAgeMs < mSettingsHelper.getBackgroundThrottleIntervalMs()) {
+                    registration.deliverLocation(null);
+                    return;
+                }
+            }
+
+            // if last location isn't good enough then we add a location request
+            addRegistration(callback.asBinder(), registration);
+            CancellationSignal cancellationSignal = CancellationSignal.fromTransport(
+                    cancellationTransport);
+            if (cancellationSignal != null) {
+                cancellationSignal.setOnCancelListener(
+                        () -> {
+                            synchronized (mLock) {
+                                removeRegistration(callback.asBinder(), registration);
+                            }
+                        });
+            }
+        }
+    }
+
+    public void sendExtraCommand(int uid, int pid, String command, Bundle extras) {
+        mProvider.sendExtraCommand(uid, pid, command, extras);
+    }
+
+    public void registerLocationRequest(LocationRequest request, CallerIdentity identity,
+            @PermissionLevel int permissionLevel, ILocationListener listener) {
+        Preconditions.checkArgument(mName.equals(request.getProvider()));
+
+        synchronized (mLock) {
+            addRegistration(
+                    listener.asBinder(),
+                    new LocationListenerRegistration(
+                            request,
+                            identity,
+                            new LocationListenerTransport(listener),
+                            permissionLevel));
+        }
+    }
+
+    public void registerLocationRequest(LocationRequest request, CallerIdentity identity,
+            @PermissionLevel int permissionLevel, PendingIntent pendingIntent) {
+        Preconditions.checkArgument(mName.equals(request.getProvider()));
+
+        synchronized (mLock) {
+            addRegistration(
+                    pendingIntent,
+                    new LocationPendingIntentRegistration(
+                            request,
+                            identity,
+                            new LocationPendingIntentTransport(mContext, pendingIntent),
+                            permissionLevel));
+        }
+    }
+
+    public void unregisterLocationRequest(ILocationListener listener) {
+        synchronized (mLock) {
+            removeRegistration(listener.asBinder());
+        }
+    }
+
+    public void unregisterLocationRequest(PendingIntent pendingIntent) {
+        synchronized (mLock) {
+            removeRegistration(pendingIntent);
+        }
+    }
+
+    @GuardedBy("mLock")
+    @Override
+    protected void onRegister() {
+        if (Build.IS_DEBUGGABLE) {
+            Preconditions.checkState(Thread.holdsLock(mLock));
+        }
+
+        mSettingsHelper.addOnBackgroundThrottleIntervalChangedListener(
+                mBackgroundThrottleIntervalChangedListener);
+        mSettingsHelper.addOnBackgroundThrottlePackageWhitelistChangedListener(
+                mBackgroundThrottlePackageWhitelistChangedListener);
+        mSettingsHelper.addOnLocationPackageBlacklistChangedListener(
+                mLocationPackageBlacklistChangedListener);
+        mSettingsHelper.addOnIgnoreSettingsPackageWhitelistChangedListener(
+                mIgnoreSettingsPackageWhitelistChangedListener);
+        mLocationPermissionsHelper.addListener(mLocationPermissionsListener);
+        mAppForegroundHelper.addListener(mAppForegroundChangedListener);
+        mLocationPowerSaveModeHelper.addListener(mLocationPowerSaveModeChangedListener);
+        mScreenInteractiveHelper.addListener(mScreenInteractiveChangedListener);
+    }
+
+    @GuardedBy("mLock")
+    @Override
+    protected void onUnregister() {
+        if (Build.IS_DEBUGGABLE) {
+            Preconditions.checkState(Thread.holdsLock(mLock));
+        }
+
+        mSettingsHelper.removeOnBackgroundThrottleIntervalChangedListener(
+                mBackgroundThrottleIntervalChangedListener);
+        mSettingsHelper.removeOnBackgroundThrottlePackageWhitelistChangedListener(
+                mBackgroundThrottlePackageWhitelistChangedListener);
+        mSettingsHelper.removeOnLocationPackageBlacklistChangedListener(
+                mLocationPackageBlacklistChangedListener);
+        mSettingsHelper.removeOnIgnoreSettingsPackageWhitelistChangedListener(
+                mIgnoreSettingsPackageWhitelistChangedListener);
+        mLocationPermissionsHelper.removeListener(mLocationPermissionsListener);
+        mAppForegroundHelper.removeListener(mAppForegroundChangedListener);
+        mLocationPowerSaveModeHelper.removeListener(mLocationPowerSaveModeChangedListener);
+        mScreenInteractiveHelper.removeListener(mScreenInteractiveChangedListener);
+    }
+
+    @GuardedBy("mLock")
+    @Override
+    protected void onRegistrationAdded(Object key, Registration registration) {
+        if (Build.IS_DEBUGGABLE) {
+            Preconditions.checkState(Thread.holdsLock(mLock));
+        }
+
+        mLocationUsageLogger.logLocationApiUsage(
+                LocationStatsEnums.USAGE_STARTED,
+                LocationStatsEnums.API_REQUEST_LOCATION_UPDATES,
+                registration.getIdentity().getPackageName(),
+                registration.getRequest(),
+                key instanceof PendingIntent,
+                key instanceof IBinder,
+                /* geofence= */ null,
+                registration.isForeground());
+
+        mLocationRequestStatistics.startRequesting(
+                registration.getIdentity().getPackageName(),
+                registration.getIdentity().getAttributionTag(),
+                mName,
+                registration.getRequest().getInterval(),
+                registration.isForeground());
+    }
+
+    @GuardedBy("mLock")
+    @Override
+    protected void onRegistrationRemoved(Object key, Registration registration) {
+        if (Build.IS_DEBUGGABLE) {
+            Preconditions.checkState(Thread.holdsLock(mLock));
+        }
+
+        mLocationRequestStatistics.stopRequesting(
+                registration.getIdentity().getPackageName(),
+                registration.getIdentity().getAttributionTag(),
+                mName);
+
+        mLocationUsageLogger.logLocationApiUsage(
+                LocationStatsEnums.USAGE_ENDED,
+                LocationStatsEnums.API_REQUEST_LOCATION_UPDATES,
+                registration.getIdentity().getPackageName(),
+                registration.getRequest(),
+                key instanceof PendingIntent,
+                key instanceof IBinder,
+                /* geofence= */ null,
+                registration.isForeground());
+    }
+
+    @GuardedBy("mLock")
+    @Override
+    protected boolean registerWithService(ProviderRequest mergedRequest) {
+        if (Build.IS_DEBUGGABLE) {
+            Preconditions.checkState(Thread.holdsLock(mLock));
+        }
+
+        mProvider.setRequest(mergedRequest);
+        return true;
+    }
+
+    @GuardedBy("mLock")
+    @Override
+    protected boolean reregisterWithService(ProviderRequest oldRequest,
+            ProviderRequest newRequest) {
+        return registerWithService(newRequest);
+    }
+
+    @GuardedBy("mLock")
+    @Override
+    protected void unregisterWithService() {
+        if (Build.IS_DEBUGGABLE) {
+            Preconditions.checkState(Thread.holdsLock(mLock));
+        }
+        mProvider.setRequest(ProviderRequest.EMPTY_REQUEST);
+    }
+
+    @GuardedBy("mLock")
+    @Override
+    protected boolean isActive(Registration registration) {
+        if (Build.IS_DEBUGGABLE) {
+            Preconditions.checkState(Thread.holdsLock(mLock));
+        }
+
+        CallerIdentity identity = registration.getIdentity();
+
+        if (!registration.isPermitted()) {
+            return false;
+        }
+
+        if (!registration.getRequest().isLocationSettingsIgnored()) {
+            if (!isEnabled(identity.getUserId())) {
+                return false;
+            }
+
+            switch (mLocationPowerSaveModeHelper.getLocationPowerSaveMode()) {
+                case LOCATION_MODE_FOREGROUND_ONLY:
+                    if (!registration.isForeground()) {
+                        return false;
+                    }
+                    break;
+                case LOCATION_MODE_GPS_DISABLED_WHEN_SCREEN_OFF:
+                    if (!GPS_PROVIDER.equals(mName)) {
+                        break;
+                    }
+                    // fall through
+                case LOCATION_MODE_THROTTLE_REQUESTS_WHEN_SCREEN_OFF:
+                    // fall through
+                case LOCATION_MODE_ALL_DISABLED_WHEN_SCREEN_OFF:
+                    if (!mScreenInteractiveHelper.isInteractive()) {
+                        return false;
+                    }
+                    break;
+                case LOCATION_MODE_NO_CHANGE:
+                    // fall through
+                default:
+                    break;
+            }
+        }
+
+        return !mSettingsHelper.isLocationPackageBlacklisted(identity.getUserId(),
+                identity.getPackageName());
+    }
+
+    @GuardedBy("mLock")
+    @Override
+    protected ProviderRequest mergeRequests(Collection<Registration> registrations) {
+        if (Build.IS_DEBUGGABLE) {
+            Preconditions.checkState(Thread.holdsLock(mLock));
+        }
+
+        ProviderRequest.Builder providerRequest = new ProviderRequest.Builder();
+        // initialize the low power mode to true and set to false if any of the records requires
+        providerRequest.setLowPowerMode(true);
+
+        ArrayList<Registration> providerRegistrations = new ArrayList<>(registrations.size());
+        for (Registration registration : registrations) {
+            LocationRequest locationRequest = registration.getRequest();
+
+            if (locationRequest.isLocationSettingsIgnored()) {
+                providerRequest.setLocationSettingsIgnored(true);
+            }
+            if (!locationRequest.isLowPowerMode()) {
+                providerRequest.setLowPowerMode(false);
+            }
+
+            providerRequest.setInterval(
+                    Math.min(locationRequest.getInterval(), providerRequest.getInterval()));
+            providerRegistrations.add(registration);
+        }
+
+        // collect contributing location requests
+        ArrayList<LocationRequest> providerRequests = new ArrayList<>(providerRegistrations.size());
+        final int registrationsSize = providerRegistrations.size();
+        for (int i = 0; i < registrationsSize; i++) {
+            providerRequests.add(providerRegistrations.get(i).getRequest());
+        }
+
+        providerRequest.setLocationRequests(providerRequests);
+
+        // calculate who to blame for power in a somewhat arbitrary fashion. we pick a threshold
+        // interval slightly higher that the minimum interval, and spread the blame across all
+        // contributing registrations under that threshold.
+        long thresholdIntervalMs = (providerRequest.getInterval() + 1000) * 3 / 2;
+        if (thresholdIntervalMs < 0) {
+            // handle overflow
+            thresholdIntervalMs = Long.MAX_VALUE;
+        }
+        for (int i = 0; i < registrationsSize; i++) {
+            LocationRequest request = providerRegistrations.get(i).getRequest();
+            if (request.getInterval() <= thresholdIntervalMs) {
+                providerRequest.getWorkSource().add(providerRegistrations.get(i).getWorkSource());
+            }
+        }
+
+        return providerRequest.build();
+    }
+
+    private void onUserChanged(int userId, int change) {
+        synchronized (mLock) {
+            switch (change) {
+                case UserListener.CURRENT_USER_CHANGED:
+                    onEnabledChanged(userId);
+                    break;
+                case UserListener.USER_STARTED:
+                    onUserStarted(userId);
+                    break;
+                case UserListener.USER_STOPPED:
+                    onUserStopped(userId);
+                    break;
+            }
+        }
+    }
+
+    private void onLocationEnabledChanged(int userId) {
+        synchronized (mLock) {
+            onEnabledChanged(userId);
+        }
+    }
+
+    private void onScreenInteractiveChanged(boolean screenInteractive) {
+        synchronized (mLock) {
+            switch (mLocationPowerSaveModeHelper.getLocationPowerSaveMode()) {
+                case LOCATION_MODE_GPS_DISABLED_WHEN_SCREEN_OFF:
+                    if (!GPS_PROVIDER.equals(mName)) {
+                        break;
+                    }
+                    // fall through
+                case LOCATION_MODE_THROTTLE_REQUESTS_WHEN_SCREEN_OFF:
+                    // fall through
+                case LOCATION_MODE_ALL_DISABLED_WHEN_SCREEN_OFF:
+                    updateRegistrations(registration -> true);
+                    break;
+                default:
+                    break;
+            }
+        }
+    }
+
+    private void onBackgroundThrottlePackageWhitelistChanged() {
+        synchronized (mLock) {
+            updateRegistrations(Registration::onProviderLocationRequestChanged);
+        }
+    }
+
+    private void onBackgroundThrottleIntervalChanged() {
+        synchronized (mLock) {
+            updateRegistrations(Registration::onProviderLocationRequestChanged);
+        }
+    }
+
+    private void onLocationPowerSaveModeChanged(@LocationPowerSaveMode int locationPowerSaveMode) {
+        synchronized (mLock) {
+            // this is rare, just assume everything has changed to keep it simple
+            updateRegistrations(registration -> true);
+        }
+    }
+
+    private void onAppForegroundChanged(int uid, boolean foreground) {
+        synchronized (mLock) {
+            updateRegistrations(registration -> registration.onForegroundChanged(uid, foreground));
+        }
+    }
+
+    private void onIgnoreSettingsWhitelistChanged() {
+        synchronized (mLock) {
+            updateRegistrations(Registration::onProviderLocationRequestChanged);
+        }
+    }
+
+    private void onLocationPackageBlacklistChanged(int userId) {
+        synchronized (mLock) {
+            updateRegistrations(registration -> registration.getIdentity().getUserId() == userId);
+        }
+    }
+
+    private void onLocationPermissionsChanged(String packageName) {
+        synchronized (mLock) {
+            updateRegistrations(
+                    registration -> registration.onLocationPermissionsChanged(packageName));
+        }
+    }
+
+    private void onLocationPermissionsChanged(int uid) {
+        synchronized (mLock) {
+            updateRegistrations(registration -> registration.onLocationPermissionsChanged(uid));
+        }
+    }
+
+    @GuardedBy("mLock")
+    @Override
+    public void onStateChanged(
+            AbstractLocationProvider.State oldState, AbstractLocationProvider.State newState) {
+        if (Build.IS_DEBUGGABLE) {
+            Preconditions.checkState(Thread.holdsLock(mLock));
+        }
+
+        if (oldState.allowed != newState.allowed) {
+            onEnabledChanged(UserHandle.USER_ALL);
+        }
+    }
+
+    @GuardedBy("mLock")
+    @Override
+    public void onReportLocation(Location location) {
+        if (Build.IS_DEBUGGABLE) {
+            Preconditions.checkState(Thread.holdsLock(mLock));
+        }
+
+        // don't validate mock locations
+        if (!location.isFromMockProvider()) {
+            if (location.getLatitude() == 0 && location.getLongitude() == 0) {
+                Log.w(TAG, "blocking 0,0 location from " + mName + " provider");
+                return;
+            }
+        }
+
+        if (!location.isComplete()) {
+            Log.w(TAG, "blocking incomplete location from " + mName + " provider");
+            return;
+        }
+
+        // update last location
+        setLastLocation(location, UserHandle.USER_ALL);
+
+        // notify passive provider
+        if (mPassiveManager != null) {
+            mPassiveManager.updateLocation(location);
+        }
+
+        // attempt listener delivery
+        deliverToListeners(registration -> {
+            return registration.acceptLocationChange(location);
+        });
+    }
+
+    @GuardedBy("mLock")
+    @Override
+    public void onReportLocation(List<Location> locations) {
+        if (!GPS_PROVIDER.equals(mName)) {
+            return;
+        }
+
+        mLocationManagerInternal.reportGnssBatchLocations(locations);
+    }
+
+    @GuardedBy("mLock")
+    private void onUserStarted(int userId) {
+        if (Build.IS_DEBUGGABLE) {
+            Preconditions.checkState(Thread.holdsLock(mLock));
+        }
+
+        if (userId == UserHandle.USER_NULL) {
+            return;
+        }
+
+        if (userId == UserHandle.USER_ALL) {
+            // clear the user's prior enabled state to prevent broadcast of enabled state change
+            mEnabled.clear();
+            onEnabledChanged(UserHandle.USER_ALL);
+        } else {
+            Preconditions.checkArgument(userId >= 0);
+
+            // clear the user's prior enabled state to prevent broadcast of enabled state change
+            mEnabled.delete(userId);
+            onEnabledChanged(userId);
+        }
+    }
+
+    @GuardedBy("mLock")
+    private void onUserStopped(int userId) {
+        if (Build.IS_DEBUGGABLE) {
+            Preconditions.checkState(Thread.holdsLock(mLock));
+        }
+
+        if (userId == UserHandle.USER_NULL) {
+            return;
+        }
+
+        if (userId == UserHandle.USER_ALL) {
+            onEnabledChanged(UserHandle.USER_ALL);
+            mEnabled.clear();
+            mLastLocations.clear();
+        } else {
+            Preconditions.checkArgument(userId >= 0);
+
+            onEnabledChanged(userId);
+            mEnabled.delete(userId);
+            mLastLocations.remove(userId);
+        }
+    }
+
+    @GuardedBy("mLock")
+    private void onEnabledChanged(int userId) {
+        if (Build.IS_DEBUGGABLE) {
+            Preconditions.checkState(Thread.holdsLock(mLock));
+        }
+
+        if (userId == UserHandle.USER_NULL) {
+            // used during initialization - ignore since many lower level operations (checking
+            // settings for instance) do not support the null user
+            return;
+        } else if (userId == UserHandle.USER_ALL) {
+            final int[] runningUserIds = mUserInfoHelper.getRunningUserIds();
+            for (int i = 0; i < runningUserIds.length; i++) {
+                onEnabledChanged(runningUserIds[i]);
+            }
+            return;
+        }
+
+        Preconditions.checkArgument(userId >= 0);
+
+        boolean enabled = mStarted
+                && mProvider.getState().allowed
+                && mUserInfoHelper.isCurrentUserId(userId)
+                && mSettingsHelper.isLocationEnabled(userId);
+
+        int index = mEnabled.indexOfKey(userId);
+        Boolean wasEnabled = index < 0 ? null : mEnabled.valueAt(index);
+        if (wasEnabled != null && wasEnabled == enabled) {
+            return;
+        }
+
+        mEnabled.put(userId, enabled);
+
+        if (D) {
+            Log.d(TAG, "[u" + userId + "] " + mName + " provider enabled = " + enabled);
+        }
+
+        // clear last locations if we become disabled
+        if (!enabled) {
+            LastLocation lastLocation = mLastLocations.get(userId);
+            if (lastLocation != null) {
+                lastLocation.clearLocations();
+            }
+        }
+
+        // do not send change notifications if we just saw this user for the first time
+        if (wasEnabled != null) {
+            // fused and passive provider never get public updates for legacy reasons
+            if (!FUSED_PROVIDER.equals(mName) && !PASSIVE_PROVIDER.equals(mName)) {
+                Intent intent = new Intent(LocationManager.PROVIDERS_CHANGED_ACTION)
+                        .putExtra(LocationManager.EXTRA_PROVIDER_NAME, mName)
+                        .putExtra(LocationManager.EXTRA_PROVIDER_ENABLED, enabled)
+                        .addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY)
+                        .addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
+                mContext.sendBroadcastAsUser(intent, UserHandle.of(userId));
+            }
+
+            // send updates to internal listeners - since we expect listener changes to be more
+            // frequent than enabled changes, we use copy-on-read instead of copy-on-write
+            if (!mEnabledListeners.isEmpty()) {
+                ProviderEnabledListener[] listeners = mEnabledListeners.toArray(
+                        new ProviderEnabledListener[0]);
+                FgThread.getHandler().post(() -> {
+                    for (int i = 0; i < listeners.length; i++) {
+                        listeners[i].onProviderEnabledChanged(mName, userId, enabled);
+                    }
+                });
+            }
+        }
+
+        // update active state of affected registrations
+        updateRegistrations(registration -> registration.getIdentity().getUserId() == userId);
+    }
+
+    public void dump(FileDescriptor fd, IndentingPrintWriter ipw, String[] args) {
+        synchronized (mLock) {
+            ipw.print(mName + " provider");
+            if (mProvider.isMock()) {
+                ipw.print(" [mock]");
+            }
+            ipw.println(":");
+            ipw.increaseIndent();
+
+            super.dump(fd, ipw, args);
+
+            int[] userIds = mUserInfoHelper.getRunningUserIds();
+            for (int userId : userIds) {
+                if (userIds.length != 1) {
+                    ipw.println("user " + userId + ":");
+                    ipw.increaseIndent();
+                }
+                ipw.println("last location=" + getLastLocation(userId, PERMISSION_FINE, false));
+                ipw.println("enabled=" + isEnabled(userId));
+                if (userIds.length != 1) {
+                    ipw.decreaseIndent();
+                }
+            }
+        }
+
+        mProvider.dump(fd, ipw, args);
+
+        ipw.decreaseIndent();
+    }
+
+    private static class LastLocation {
+
+        @Nullable private Location mFineLocation;
+        @Nullable private Location mCoarseLocation;
+        @Nullable private Location mFineBypassLocation;
+        @Nullable private Location mCoarseBypassLocation;
+
+        public void clearMock() {
+            if (mFineLocation != null && mFineLocation.isFromMockProvider()) {
+                mFineLocation = null;
+                mCoarseLocation = null;
+            }
+            if (mFineBypassLocation != null && mFineBypassLocation.isFromMockProvider()) {
+                mFineBypassLocation = null;
+                mCoarseBypassLocation = null;
+            }
+        }
+
+        public void clearLocations() {
+            mFineLocation = null;
+            mCoarseLocation = null;
+        }
+
+        @Nullable
+        public Location get(@PermissionLevel int permissionLevel, boolean ignoreLocationSettings) {
+            switch (permissionLevel) {
+                case PERMISSION_FINE:
+                    if (ignoreLocationSettings) {
+                        return mFineBypassLocation;
+                    } else {
+                        return mFineLocation;
+                    }
+                case PERMISSION_COARSE:
+                    if (ignoreLocationSettings) {
+                        return mCoarseBypassLocation;
+                    } else {
+                        return mCoarseLocation;
+                    }
+                default:
+                    // shouldn't be possible to have a client added without location permissions
+                    throw new AssertionError();
+            }
+        }
+
+        public void set(Location location, Location coarseLocation) {
+            mFineLocation = location;
+            mCoarseLocation = calculateNextCoarse(mCoarseLocation, coarseLocation);
+        }
+
+        public void setBypass(Location location, Location coarseLocation) {
+            mFineBypassLocation = location;
+            mCoarseBypassLocation = calculateNextCoarse(mCoarseBypassLocation, coarseLocation);
+        }
+
+        private Location calculateNextCoarse(@Nullable Location oldCoarse, Location newCoarse) {
+            if (oldCoarse == null) {
+                return newCoarse;
+            }
+            // update last coarse interval only if enough time has passed
+            long timeDeltaMs = NANOSECONDS.toMillis(newCoarse.getElapsedRealtimeNanos())
+                        - NANOSECONDS.toMillis(oldCoarse.getElapsedRealtimeNanos());
+            if (timeDeltaMs > FASTEST_COARSE_INTERVAL_MS) {
+                return newCoarse;
+            } else {
+                return oldCoarse;
+            }
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/location/MockProvider.java b/services/core/java/com/android/server/location/MockProvider.java
index c5bd5e6..fc88f14 100644
--- a/services/core/java/com/android/server/location/MockProvider.java
+++ b/services/core/java/com/android/server/location/MockProvider.java
@@ -40,9 +40,8 @@
 
     public MockProvider(ProviderProperties properties, CallerIdentity identity) {
         // using a direct executor is ok because this class has no locks that could deadlock
-        super(DIRECT_EXECUTOR);
+        super(DIRECT_EXECUTOR, identity);
         setProperties(properties);
-        setIdentity(identity);
     }
 
     /** Sets the allowed state of this mock provider. */
diff --git a/services/core/java/com/android/server/location/MockableLocationProvider.java b/services/core/java/com/android/server/location/MockableLocationProvider.java
index 54af1c8..d8d435a 100644
--- a/services/core/java/com/android/server/location/MockableLocationProvider.java
+++ b/services/core/java/com/android/server/location/MockableLocationProvider.java
@@ -241,7 +241,6 @@
             if (getState().properties != null) {
                 pw.println("properties=" + getState().properties);
             }
-            pw.println("request=" + mRequest);
         }
 
         if (provider != null) {
diff --git a/services/core/java/com/android/server/location/PassiveLocationProviderManager.java b/services/core/java/com/android/server/location/PassiveLocationProviderManager.java
new file mode 100644
index 0000000..d4999ab
--- /dev/null
+++ b/services/core/java/com/android/server/location/PassiveLocationProviderManager.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.location;
+
+import android.annotation.Nullable;
+import android.content.Context;
+import android.location.Location;
+import android.location.LocationManager;
+import android.os.Binder;
+
+import com.android.internal.util.Preconditions;
+import com.android.server.location.util.Injector;
+
+class PassiveLocationProviderManager extends LocationProviderManager {
+
+    PassiveLocationProviderManager(Context context, Injector injector) {
+        super(context, injector, LocationManager.PASSIVE_PROVIDER, null);
+    }
+
+    @Override
+    public void setRealProvider(AbstractLocationProvider provider) {
+        Preconditions.checkArgument(provider instanceof PassiveProvider);
+        super.setRealProvider(provider);
+    }
+
+    @Override
+    public void setMockProvider(@Nullable MockProvider provider) {
+        if (provider != null) {
+            throw new IllegalArgumentException("Cannot mock the passive provider");
+        }
+    }
+
+    public void updateLocation(Location location) {
+        synchronized (mLock) {
+            PassiveProvider passiveProvider = (PassiveProvider) mProvider.getProvider();
+            Preconditions.checkState(passiveProvider != null);
+
+            long identity = Binder.clearCallingIdentity();
+            try {
+                passiveProvider.updateLocation(location);
+            } finally {
+                Binder.restoreCallingIdentity(identity);
+            }
+        }
+    }
+}
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 0b7968b..1b599b0 100644
--- a/services/core/java/com/android/server/location/gnss/GnssListenerMultiplexer.java
+++ b/services/core/java/com/android/server/location/gnss/GnssListenerMultiplexer.java
@@ -16,17 +16,21 @@
 
 package com.android.server.location.gnss;
 
+import static android.location.LocationManager.GPS_PROVIDER;
+
 import static com.android.server.location.LocationPermissions.PERMISSION_FINE;
 import static com.android.server.location.gnss.GnssManagerService.TAG;
 
 import android.annotation.Nullable;
 import android.location.LocationManagerInternal;
+import android.location.LocationManagerInternal.ProviderEnabledListener;
 import android.location.util.identity.CallerIdentity;
 import android.os.IBinder;
 import android.os.IInterface;
 import android.os.Process;
 import android.util.ArraySet;
 
+import com.android.internal.util.Preconditions;
 import com.android.server.LocalServices;
 import com.android.server.location.listeners.BinderListenerRegistration;
 import com.android.server.location.listeners.ListenerMultiplexer;
@@ -161,8 +165,8 @@
     protected final LocationManagerInternal mLocationManagerInternal;
 
     private final UserListener mUserChangedListener = this::onUserChanged;
-    private final SettingsHelper.UserSettingChangedListener mLocationEnabledChangedListener =
-            this::onLocationEnabledChanged;
+    private final ProviderEnabledListener mProviderEnabledChangedListener =
+            this::onProviderEnabledChanged;
     private final SettingsHelper.GlobalSettingChangedListener
             mBackgroundThrottlePackageWhitelistChangedListener =
             this::onBackgroundThrottlePackageWhitelistChanged;
@@ -233,12 +237,11 @@
         }
 
         CallerIdentity identity = registration.getIdentity();
-        // TODO: this should be checking if the gps provider is enabled, not if location is enabled,
-        //  but this is the same for now.
         return registration.isPermitted()
                 && (registration.isForeground() || isBackgroundRestrictionExempt(identity))
                 && mUserInfoHelper.isCurrentUserId(identity.getUserId())
-                && mSettingsHelper.isLocationEnabled(identity.getUserId())
+                && mLocationManagerInternal.isProviderEnabledForUser(GPS_PROVIDER,
+                identity.getUserId())
                 && !mSettingsHelper.isLocationPackageBlacklisted(identity.getUserId(),
                 identity.getPackageName());
     }
@@ -263,7 +266,8 @@
         }
 
         mUserInfoHelper.addListener(mUserChangedListener);
-        mSettingsHelper.addOnLocationEnabledChangedListener(mLocationEnabledChangedListener);
+        mLocationManagerInternal.addProviderEnabledListener(GPS_PROVIDER,
+                mProviderEnabledChangedListener);
         mSettingsHelper.addOnBackgroundThrottlePackageWhitelistChangedListener(
                 mBackgroundThrottlePackageWhitelistChangedListener);
         mSettingsHelper.addOnLocationPackageBlacklistChangedListener(
@@ -279,7 +283,8 @@
         }
 
         mUserInfoHelper.removeListener(mUserChangedListener);
-        mSettingsHelper.removeOnLocationEnabledChangedListener(mLocationEnabledChangedListener);
+        mLocationManagerInternal.removeProviderEnabledListener(GPS_PROVIDER,
+                mProviderEnabledChangedListener);
         mSettingsHelper.removeOnBackgroundThrottlePackageWhitelistChangedListener(
                 mBackgroundThrottlePackageWhitelistChangedListener);
         mSettingsHelper.removeOnLocationPackageBlacklistChangedListener(
@@ -294,7 +299,8 @@
         }
     }
 
-    private void onLocationEnabledChanged(int userId) {
+    private void onProviderEnabledChanged(String provider, int userId, boolean enabled) {
+        Preconditions.checkState(GPS_PROVIDER.equals(provider));
         updateRegistrations(registration -> registration.getIdentity().getUserId() == userId);
     }
 
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 8004ec7..850cf7f 100644
--- a/services/core/java/com/android/server/location/gnss/GnssLocationProvider.java
+++ b/services/core/java/com/android/server/location/gnss/GnssLocationProvider.java
@@ -49,8 +49,6 @@
 import android.os.Message;
 import android.os.PersistableBundle;
 import android.os.PowerManager;
-import android.os.PowerManager.ServiceType;
-import android.os.PowerSaveState;
 import android.os.RemoteException;
 import android.os.ServiceManager;
 import android.os.SystemClock;
@@ -486,10 +484,6 @@
                         deviceIdleService.unregisterStationaryListener(
                                 mDeviceIdleStationaryListener);
                     }
-                    // Intentional fall-through.
-                case PowerManager.ACTION_POWER_SAVE_MODE_CHANGED:
-                case Intent.ACTION_SCREEN_OFF:
-                case Intent.ACTION_SCREEN_ON:
                     // Call updateLowPowerMode on handler thread so it's always called from the
                     // same thread.
                     mHandler.sendEmptyMessage(UPDATE_LOW_POWER_MODE);
@@ -554,15 +548,6 @@
     private void updateLowPowerMode() {
         // Disable GPS if we are in device idle mode and the device is stationary.
         boolean disableGpsForPowerManager = mPowerManager.isDeviceIdleMode() && mIsDeviceStationary;
-        final PowerSaveState result = mPowerManager.getPowerSaveState(ServiceType.LOCATION);
-        switch (result.locationMode) {
-            case PowerManager.LOCATION_MODE_GPS_DISABLED_WHEN_SCREEN_OFF:
-            case PowerManager.LOCATION_MODE_ALL_DISABLED_WHEN_SCREEN_OFF:
-                // If we are in battery saver mode and the screen is off, disable GPS.
-                disableGpsForPowerManager |=
-                        result.batterySaverEnabled && !mPowerManager.isInteractive();
-                break;
-        }
         if (disableGpsForPowerManager != mDisableGpsForPowerManager) {
             mDisableGpsForPowerManager = disableGpsForPowerManager;
             updateEnabled();
@@ -1959,10 +1944,7 @@
             IntentFilter intentFilter = new IntentFilter();
             intentFilter.addAction(ALARM_WAKEUP);
             intentFilter.addAction(ALARM_TIMEOUT);
-            intentFilter.addAction(PowerManager.ACTION_POWER_SAVE_MODE_CHANGED);
             intentFilter.addAction(PowerManager.ACTION_DEVICE_IDLE_MODE_CHANGED);
-            intentFilter.addAction(Intent.ACTION_SCREEN_OFF);
-            intentFilter.addAction(Intent.ACTION_SCREEN_ON);
             intentFilter.addAction(CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED);
             intentFilter.addAction(TelephonyManager.ACTION_DEFAULT_DATA_SUBSCRIPTION_CHANGED);
             mContext.registerReceiver(mBroadcastReceiver, intentFilter, null, this);
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 e17cca4..cea5a69 100644
--- a/services/core/java/com/android/server/location/gnss/GnssNetworkConnectivityHandler.java
+++ b/services/core/java/com/android/server/location/gnss/GnssNetworkConnectivityHandler.java
@@ -88,7 +88,7 @@
 
     // Default time limit in milliseconds for the ConnectivityManager to find a suitable
     // network with SUPL connectivity or report an error.
-    private static final int SUPL_NETWORK_REQUEST_TIMEOUT_MILLIS = 10 * 1000;
+    private static final int SUPL_NETWORK_REQUEST_TIMEOUT_MILLIS = 20 * 1000;
 
     private static final int HASH_MAP_INITIAL_CAPACITY_TO_TRACK_CONNECTED_NETWORKS = 5;
 
diff --git a/services/core/java/com/android/server/location/listeners/ListenerMultiplexer.java b/services/core/java/com/android/server/location/listeners/ListenerMultiplexer.java
index 528cf8a..f94de9b 100644
--- a/services/core/java/com/android/server/location/listeners/ListenerMultiplexer.java
+++ b/services/core/java/com/android/server/location/listeners/ListenerMultiplexer.java
@@ -104,18 +104,18 @@
      * should return true if a matching call to {@link #unregisterWithService()} is required to
      * unregister (ie, if registration succeeds).
      *
-     * @see #reregisterWithService(Object)
+     * @see #reregisterWithService(Object, Object)
      */
-    protected abstract boolean registerWithService(TMergedRequest mergedRequest);
+    protected abstract boolean registerWithService(TMergedRequest newRequest);
 
     /**
      * Invoked when the service already has a request, and it is being replaced with a new request.
      * The default implementation unregisters first, then registers with the new merged request, but
      * this may be overridden by subclasses in order to reregister more efficiently.
      */
-    protected boolean reregisterWithService(TMergedRequest mergedRequest) {
+    protected boolean reregisterWithService(TMergedRequest oldRequest, TMergedRequest newRequest) {
         unregisterWithService();
-        return registerWithService(mergedRequest);
+        return registerWithService(newRequest);
     }
 
     /**
@@ -368,6 +368,7 @@
                     mCurrentRequest = null;
                     if (mServiceRegistered) {
                         mServiceRegistered = false;
+                        mCurrentRequest = null;
                         unregisterWithService();
                     }
                     return;
@@ -376,11 +377,15 @@
                 TMergedRequest merged = mergeRequests(actives);
                 if (!mServiceRegistered || !Objects.equals(merged, mCurrentRequest)) {
                     if (mServiceRegistered) {
-                        mServiceRegistered = reregisterWithService(merged);
+                        mServiceRegistered = reregisterWithService(mCurrentRequest, merged);
                     } else {
                         mServiceRegistered = registerWithService(merged);
                     }
-                    mCurrentRequest = merged;
+                    if (mServiceRegistered) {
+                        mCurrentRequest = merged;
+                    } else {
+                        mCurrentRequest = null;
+                    }
                 }
             } finally {
                 Binder.restoreCallingIdentity(identity);
@@ -389,29 +394,6 @@
     }
 
     /**
-     * Evaluates the given predicate for all registrations, and forces an {@link #updateService()}
-     * if any predicate returns true for an active registration. The predicate will always be
-     * evaluated for all registrations, even inactive registrations, or if it has already returned
-     * true for a prior registration.
-     */
-    protected final void updateService(Predicate<TRegistration> predicate) {
-        synchronized (mRegistrations) {
-            boolean updateService = false;
-            final int size = mRegistrations.size();
-            for (int i = 0; i < size; i++) {
-                TRegistration registration = mRegistrations.valueAt(i);
-                if (predicate.test(registration) && registration.isActive()) {
-                    updateService = true;
-                }
-            }
-
-            if (updateService) {
-                updateService();
-            }
-        }
-    }
-
-    /**
      * Begins buffering calls to {@link #updateService()} until {@link UpdateServiceLock#close()}
      * is called. This is useful to prevent extra work when combining multiple calls (for example,
      * buffering {@code updateService()} until after multiple adds/removes/updates occur.
diff --git a/services/core/java/com/android/server/location/listeners/ListenerRegistration.java b/services/core/java/com/android/server/location/listeners/ListenerRegistration.java
index 0bdd131..ac56c51 100644
--- a/services/core/java/com/android/server/location/listeners/ListenerRegistration.java
+++ b/services/core/java/com/android/server/location/listeners/ListenerRegistration.java
@@ -24,6 +24,7 @@
 import android.location.util.identity.CallerIdentity;
 import android.os.Process;
 
+import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.listeners.ListenerExecutor;
 import com.android.server.FgThread;
 
@@ -39,6 +40,9 @@
  */
 public class ListenerRegistration<TRequest, TListener> implements ListenerExecutor {
 
+    @VisibleForTesting
+    public static final Executor IN_PROCESS_EXECUTOR = FgThread.getExecutor();
+
     private final Executor mExecutor;
     private final @Nullable TRequest mRequest;
     private final CallerIdentity mIdentity;
@@ -55,9 +59,9 @@
             // there's a slight loophole here for pending intents - pending intent callbacks can
             // always be run on the direct executor since they're always asynchronous, but honestly
             // you shouldn't be using pending intent callbacks within the same process anyways
-            mExecutor =  FgThread.getExecutor();
+            mExecutor = IN_PROCESS_EXECUTOR;
         } else {
-            mExecutor =  DIRECT_EXECUTOR;
+            mExecutor = DIRECT_EXECUTOR;
         }
 
         mRequest = request;
@@ -73,7 +77,7 @@
     /**
      * Returns the request associated with this listener, or null if one wasn't supplied.
      */
-    public final @Nullable TRequest getRequest() {
+    public @Nullable TRequest getRequest() {
         return mRequest;
     }
 
@@ -107,7 +111,7 @@
      */
     protected void onInactive() {}
 
-    final boolean isActive() {
+    public final boolean isActive() {
         return mActive;
     }
 
@@ -120,7 +124,7 @@
         return false;
     }
 
-    final boolean isRegistered() {
+    public final boolean isRegistered() {
         return mListener != null;
     }
 
diff --git a/services/core/java/com/android/server/location/listeners/RemovableListenerRegistration.java b/services/core/java/com/android/server/location/listeners/RemovableListenerRegistration.java
index 6a815ea..0698cca 100644
--- a/services/core/java/com/android/server/location/listeners/RemovableListenerRegistration.java
+++ b/services/core/java/com/android/server/location/listeners/RemovableListenerRegistration.java
@@ -39,7 +39,7 @@
     protected RemovableListenerRegistration(String tag, @Nullable TRequest request,
             CallerIdentity callerIdentity, TListener listener) {
         super(request, callerIdentity, listener);
-        mTag = tag;
+        mTag = Objects.requireNonNull(tag);
     }
 
     /**
diff --git a/services/core/java/com/android/server/locksettings/BiometricDeferredQueue.java b/services/core/java/com/android/server/locksettings/BiometricDeferredQueue.java
new file mode 100644
index 0000000..ddd56c8
--- /dev/null
+++ b/services/core/java/com/android/server/locksettings/BiometricDeferredQueue.java
@@ -0,0 +1,257 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.locksettings;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.Context;
+import android.hardware.face.FaceManager;
+import android.hardware.face.FaceSensorProperties;
+import android.hardware.fingerprint.FingerprintManager;
+import android.hardware.fingerprint.FingerprintSensorProperties;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.ServiceManager;
+import android.service.gatekeeper.IGateKeeperService;
+import android.util.ArraySet;
+import android.util.Slog;
+
+import com.android.internal.widget.VerifyCredentialResponse;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Class that handles biometric-related work in the {@link LockSettingsService} area, for example
+ * resetLockout.
+ */
+@SuppressWarnings("deprecation")
+public class BiometricDeferredQueue {
+    private static final String TAG = "BiometricDeferredQueue";
+
+    @NonNull private final Context mContext;
+    @NonNull private final SyntheticPasswordManager mSpManager;
+    @NonNull private final Handler mHandler;
+    @Nullable private FingerprintManager mFingerprintManager;
+    @Nullable private FaceManager mFaceManager;
+
+    // Entries added by LockSettingsService once a user's synthetic password is known. At this point
+    // things are still keyed by userId.
+    @NonNull private final ArrayList<UserAuthInfo> mPendingResetLockouts;
+
+    /**
+     * Authentication info for a successful user unlock via Synthetic Password. This can be used to
+     * perform multiple operations (e.g. resetLockout for multiple HALs/Sensors) by sending the
+     * Gatekeeper Password to Gatekeer multiple times, each with a sensor-specific challenge.
+     */
+    private static class UserAuthInfo {
+        final int userId;
+        @NonNull final byte[] gatekeeperPassword;
+
+        UserAuthInfo(int userId, @NonNull byte[] gatekeeperPassword) {
+            this.userId = userId;
+            this.gatekeeperPassword = gatekeeperPassword;
+        }
+    }
+
+    /**
+     * Per-authentication callback.
+     */
+    private static class FaceResetLockoutTask implements FaceManager.GenerateChallengeCallback {
+        interface FinishCallback {
+            void onFinished();
+        }
+
+        @NonNull FinishCallback finishCallback;
+        @NonNull FaceManager faceManager;
+        @NonNull SyntheticPasswordManager spManager;
+        @NonNull Set<Integer> sensorIds; // IDs of sensors waiting for challenge
+        @NonNull List<UserAuthInfo> pendingResetLockuts;
+
+        FaceResetLockoutTask(
+                @NonNull FinishCallback finishCallback,
+                @NonNull FaceManager faceManager,
+                @NonNull SyntheticPasswordManager spManager,
+                @NonNull Set<Integer> sensorIds,
+                @NonNull List<UserAuthInfo> pendingResetLockouts) {
+            this.finishCallback = finishCallback;
+            this.faceManager = faceManager;
+            this.spManager = spManager;
+            this.sensorIds = sensorIds;
+            this.pendingResetLockuts = pendingResetLockouts;
+        }
+
+        @Override
+        public void onChallengeInterrupted(int sensorId) {
+            Slog.w(TAG, "Challenge interrupted, sensor: " + sensorId);
+            // Consider re-attempting generateChallenge/resetLockout/revokeChallenge
+            // when onChallengeInterruptFinished is invoked
+        }
+
+        @Override
+        public void onChallengeInterruptFinished(int sensorId) {
+            Slog.w(TAG, "Challenge interrupt finished, sensor: " + sensorId);
+        }
+
+        @Override
+        public void onGenerateChallengeResult(int sensorId, long challenge) {
+            if (!sensorIds.contains(sensorId)) {
+                Slog.e(TAG, "Unknown sensorId received: " + sensorId);
+                return;
+            }
+
+            // Challenge received for a sensor. For each sensor, reset lockout for all users.
+            for (UserAuthInfo userAuthInfo : pendingResetLockuts) {
+                Slog.d(TAG, "Resetting face lockout for sensor: " + sensorId
+                        + ", user: " + userAuthInfo.userId);
+                final VerifyCredentialResponse response = spManager.verifyChallengeInternal(
+                        getGatekeeperService(), userAuthInfo.gatekeeperPassword, challenge,
+                        userAuthInfo.userId);
+                if (response == null) {
+                    Slog.wtf(TAG, "VerifyChallenge failed, null response");
+                    continue;
+                }
+                if (response.getResponseCode() != VerifyCredentialResponse.RESPONSE_OK) {
+                    Slog.wtf(TAG, "VerifyChallenge failed, response: "
+                            + response.getResponseCode());
+                }
+                faceManager.resetLockout(sensorId, userAuthInfo.userId,
+                        response.getGatekeeperHAT());
+            }
+
+            sensorIds.remove(sensorId);
+            faceManager.revokeChallenge(sensorId);
+
+            if (sensorIds.isEmpty()) {
+                Slog.d(TAG, "Done requesting resetLockout for all face sensors");
+                finishCallback.onFinished();
+            }
+        }
+
+        synchronized IGateKeeperService getGatekeeperService() {
+            final IBinder service = ServiceManager.getService(Context.GATEKEEPER_SERVICE);
+            if (service == null) {
+                Slog.e(TAG, "Unable to acquire GateKeeperService");
+                return null;
+            }
+            return IGateKeeperService.Stub.asInterface(service);
+        }
+    }
+
+    @Nullable private FaceResetLockoutTask mFaceResetLockoutTask;
+
+    private final FaceResetLockoutTask.FinishCallback mFaceFinishCallback = () -> {
+        mFaceResetLockoutTask = null;
+    };
+
+    BiometricDeferredQueue(@NonNull Context context, @NonNull SyntheticPasswordManager spManager,
+            @NonNull Handler handler) {
+        mContext = context;
+        mSpManager = spManager;
+        mHandler = handler;
+        mPendingResetLockouts = new ArrayList<>();
+    }
+
+    public void systemReady(@Nullable FingerprintManager fingerprintManager,
+            @Nullable FaceManager faceManager) {
+        mFingerprintManager = fingerprintManager;
+        mFaceManager = faceManager;
+    }
+
+    /**
+     * Adds a request for resetLockout on all biometric sensors for the user specified. The queue
+     * owner must invoke {@link #processPendingLockoutResets()} at some point to kick off the
+     * operations.
+     *
+     * Note that this should only ever be invoked for successful authentications, otherwise it will
+     * consume a Gatekeeper authentication attempt and potentially wipe the user/device.
+     *
+     * @param userId The user that the operation will apply for.
+     * @param gatekeeperPassword The Gatekeeper Password
+     */
+    void addPendingLockoutResetForUser(int userId, @NonNull byte[] gatekeeperPassword) {
+        mHandler.post(() -> {
+            Slog.d(TAG, "addPendingLockoutResetForUser: " + userId);
+            mPendingResetLockouts.add(new UserAuthInfo(userId, gatekeeperPassword));
+        });
+    }
+
+    void processPendingLockoutResets() {
+        mHandler.post(() -> {
+            Slog.d(TAG, "processPendingLockoutResets: " + mPendingResetLockouts.size());
+            processPendingLockoutsForFingerprint(new ArrayList<>(mPendingResetLockouts));
+            processPendingLockoutsForFace(new ArrayList<>(mPendingResetLockouts));
+            mPendingResetLockouts.clear();
+        });
+    }
+
+    private void processPendingLockoutsForFingerprint(List<UserAuthInfo> pendingResetLockouts) {
+        if (mFingerprintManager != null) {
+            final List<FingerprintSensorProperties> fingerprintSensorProperties =
+                    mFingerprintManager.getSensorProperties();
+            for (FingerprintSensorProperties prop : fingerprintSensorProperties) {
+                if (!prop.resetLockoutRequiresHardwareAuthToken) {
+                    for (UserAuthInfo user : pendingResetLockouts) {
+                        mFingerprintManager.resetLockout(prop.sensorId, user.userId,
+                                null /* hardwareAuthToken */);
+                    }
+                } else {
+                    Slog.e(TAG, "Fingerprint resetLockout with HAT not supported yet");
+                    // TODO(b/152414803): Implement this when resetLockout is implemented below
+                    //  the framework.
+                }
+            }
+        }
+    }
+
+    /**
+     * For devices on {@link android.hardware.biometrics.face.V1_0} which only support a single
+     * in-flight challenge, we generate a single challenge to reset lockout for all profiles. This
+     * hopefully reduces/eliminates issues such as overwritten challenge, incorrectly revoked
+     * challenge, or other race conditions.
+     *
+     * TODO(b/162965646) This logic can be avoided if multiple in-flight challenges are supported.
+     *  Though it will need to continue to exist to support existing HIDLs, each profile that
+     *  requires resetLockout could have its own challenge, and the `mPendingResetLockouts` queue
+     *  can be avoided.
+     */
+    private void processPendingLockoutsForFace(List<UserAuthInfo> pendingResetLockouts) {
+        if (mFaceManager != null) {
+            if (mFaceResetLockoutTask != null) {
+                // This code will need to be updated if this problem ever occurs.
+                Slog.w(TAG, "mFaceGenerateChallengeCallback not null, previous operation may be"
+                        + " stuck");
+            }
+            final List<FaceSensorProperties> faceSensorProperties =
+                    mFaceManager.getSensorProperties();
+            final Set<Integer> sensorIds = new ArraySet<>();
+            for (FaceSensorProperties prop : faceSensorProperties) {
+                sensorIds.add(prop.sensorId);
+            }
+
+            mFaceResetLockoutTask = new FaceResetLockoutTask(mFaceFinishCallback, mFaceManager,
+                    mSpManager, sensorIds, pendingResetLockouts);
+            for (final FaceSensorProperties prop : faceSensorProperties) {
+                // Generate a challenge for each sensor. The challenge does not need to be
+                // per-user, since the HAT returned by gatekeeper contains userId.
+                mFaceManager.generateChallenge(prop.sensorId, mFaceResetLockoutTask);
+            }
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/locksettings/LockSettingsService.java b/services/core/java/com/android/server/locksettings/LockSettingsService.java
index e568848..93352dc 100644
--- a/services/core/java/com/android/server/locksettings/LockSettingsService.java
+++ b/services/core/java/com/android/server/locksettings/LockSettingsService.java
@@ -33,10 +33,10 @@
 import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_LOCKOUT;
 import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_FOR_UNATTENDED_UPDATE;
 import static com.android.internal.widget.LockPatternUtils.USER_FRP;
+import static com.android.internal.widget.LockPatternUtils.VERIFY_FLAG_REQUEST_GK_PW_HANDLE;
 import static com.android.internal.widget.LockPatternUtils.frpCredentialEnabled;
 import static com.android.internal.widget.LockPatternUtils.userOwnsFrpCredential;
 
-import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.UserIdInt;
@@ -104,6 +104,7 @@
 import android.util.ArrayMap;
 import android.util.ArraySet;
 import android.util.EventLog;
+import android.util.LongSparseArray;
 import android.util.Slog;
 import android.util.SparseArray;
 
@@ -154,6 +155,7 @@
 import java.util.Map;
 import java.util.NoSuchElementException;
 import java.util.Objects;
+import java.util.Random;
 import java.util.Set;
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.TimeUnit;
@@ -185,19 +187,13 @@
     private static final String SYNTHETIC_PASSWORD_UPDATE_TIME_KEY = "sp-handle-ts";
     private static final String USER_SERIAL_NUMBER_KEY = "serial-number";
 
-    // No challenge provided
-    private static final int CHALLENGE_NONE = 0;
-    // Challenge was provided from the external caller (non-LockSettingsService)
-    private static final int CHALLENGE_FROM_CALLER = 1;
-    // Challenge was generated from within LockSettingsService, for resetLockout. When challenge
-    // type is set to internal, LSS will revokeChallenge after all profiles for that user are
-    // unlocked.
-    private static final int CHALLENGE_INTERNAL = 2;
-
-    @IntDef({CHALLENGE_NONE,
-            CHALLENGE_FROM_CALLER,
-            CHALLENGE_INTERNAL})
-    @interface ChallengeType {}
+    // Duration that LockSettingsService will store the gatekeeper password for. This allows
+    // multiple biometric enrollments without prompting the user to enter their password via
+    // ConfirmLockPassword/ConfirmLockPattern multiple times. This needs to be at least the duration
+    // from the start of the first biometric sensor's enrollment to the start of the last biometric
+    // sensor's enrollment. If biometric enrollment requests a password handle that has expired, the
+    // user's credential must be presented again, e.g. via ConfirmLockPattern/ConfirmLockPassword.
+    private static final int GK_PW_HANDLE_STORE_DURATION_MS = 10 * 60 * 1000; // 10 minutes
 
     // Order of holding lock: mSeparateChallengeLock -> mSpManager -> this
     // Do not call into ActivityManager while holding mSpManager lock.
@@ -214,6 +210,9 @@
     protected final LockSettingsStorage mStorage;
     private final LockSettingsStrongAuth mStrongAuth;
     private final SynchronizedStrongAuthTracker mStrongAuthTracker;
+    private final BiometricDeferredQueue mBiometricDeferredQueue;
+    private final LongSparseArray<byte[]> mGatekeeperPasswords;
+    private final Random mRandom;
 
     private final NotificationManager mNotificationManager;
     private final UserManager mUserManager;
@@ -275,18 +274,18 @@
         }
 
         @Override
-        public void onStartUser(int userHandle) {
-            mLockSettingsService.onStartUser(userHandle);
+        public void onUserStarting(@NonNull TargetUser user) {
+            mLockSettingsService.onStartUser(user.getUserIdentifier());
         }
 
         @Override
-        public void onUnlockUser(int userHandle) {
-            mLockSettingsService.onUnlockUser(userHandle);
+        public void onUserUnlocking(@NonNull TargetUser user) {
+            mLockSettingsService.onUnlockUser(user.getUserIdentifier());
         }
 
         @Override
-        public void onCleanupUser(int userHandle) {
-            mLockSettingsService.onCleanupUser(userHandle);
+        public void onUserStopped(@NonNull TargetUser user) {
+            mLockSettingsService.onCleanupUser(user.getUserIdentifier());
         }
     }
 
@@ -316,15 +315,6 @@
         }
     }
 
-    private class PendingResetLockout {
-        final int mUserId;
-        final byte[] mHAT;
-        PendingResetLockout(int userId, byte[] hat) {
-            mUserId = userId;
-            mHAT = hat;
-        }
-    }
-
     private LockscreenCredential generateRandomProfilePassword() {
         byte[] randomLockSeed = new byte[] {};
         try {
@@ -580,9 +570,12 @@
         mStorageManager = injector.getStorageManager();
         mStrongAuthTracker = injector.getStrongAuthTracker();
         mStrongAuthTracker.register(mStrongAuth);
+        mGatekeeperPasswords = new LongSparseArray<>();
+        mRandom = new SecureRandom();
 
         mSpManager = injector.getSyntheticPasswordManager(mStorage);
         mManagedProfilePasswordCache = injector.getManagedProfilePasswordCache();
+        mBiometricDeferredQueue = new BiometricDeferredQueue(mContext, mSpManager, mHandler);
 
         mRebootEscrowManager = injector.getRebootEscrowManager(new RebootEscrowCallbacks(),
                 mStorage);
@@ -735,8 +728,7 @@
             // If boot took too long and the password in vold got expired, parent keystore will
             // be still locked, we ignore this case since the user will be prompted to unlock
             // the device after boot.
-            unlockChildProfile(userId, true /* ignoreUserNotAuthenticated */,
-                    CHALLENGE_NONE, 0 /* challenge */, null /* resetLockouts */);
+            unlockChildProfile(userId, true /* ignoreUserNotAuthenticated */);
         }
     }
 
@@ -825,6 +817,8 @@
         mRebootEscrowManager.loadRebootEscrowDataIfAvailable();
         // TODO: maybe skip this for split system user mode.
         mStorage.prefetchUser(UserHandle.USER_SYSTEM);
+        mBiometricDeferredQueue.systemReady(mInjector.getFingerprintManager(),
+                mInjector.getFaceManager());
     }
 
     private void getAuthSecretHal() {
@@ -1036,7 +1030,7 @@
         mContext.enforceCallingOrSelfPermission(PERMISSION, "LockSettingsWrite");
     }
 
-    private final void checkPasswordReadPermission(int userId) {
+    private final void checkPasswordReadPermission() {
         mContext.enforceCallingOrSelfPermission(PERMISSION, "LockSettingsRead");
     }
 
@@ -1301,13 +1295,10 @@
         return credential;
     }
 
-    private void unlockChildProfile(int profileHandle, boolean ignoreUserNotAuthenticated,
-            @ChallengeType int challengeType, long challenge,
-            @Nullable ArrayList<PendingResetLockout> resetLockouts) {
+    private void unlockChildProfile(int profileHandle, boolean ignoreUserNotAuthenticated) {
         try {
             doVerifyCredential(getDecryptedPasswordForTiedProfile(profileHandle),
-                    challengeType, challenge, profileHandle, null /* progressCallback */,
-                    resetLockouts);
+                    profileHandle, null /* progressCallback */, 0 /* flags */);
         } catch (UnrecoverableKeyException | InvalidKeyException | KeyStoreException
                 | NoSuchAlgorithmException | NoSuchPaddingException
                 | InvalidAlgorithmParameterException | IllegalBlockSizeException
@@ -1322,10 +1313,6 @@
         }
     }
 
-    private void unlockUser(int userId, byte[] token, byte[] secret) {
-        unlockUser(userId, token, secret, CHALLENGE_NONE, 0 /* challenge */, null);
-    }
-
     /**
      * Unlock the user (both storage and user state) and its associated managed profiles
      * synchronously.
@@ -1334,9 +1321,7 @@
      * can end up calling into other system services to process user unlock request (via
      * {@link com.android.server.SystemServiceManager#unlockUser} </em>
      */
-    private void unlockUser(int userId, byte[] token, byte[] secret,
-            @ChallengeType int challengeType, long challenge,
-            @Nullable ArrayList<PendingResetLockout> resetLockouts) {
+    private void unlockUser(int userId, byte[] token, byte[] secret) {
         Slog.i(TAG, "Unlocking user " + userId + " with secret only, length "
                 + (secret != null ? secret.length : 0));
         // TODO: make this method fully async so we can update UI with progress strings
@@ -1373,6 +1358,9 @@
         }
 
         if (mUserManager.getUserInfo(userId).isManagedProfile()) {
+            if (!hasUnifiedChallenge(userId)) {
+                mBiometricDeferredQueue.processPendingLockoutResets();
+            }
             return;
         }
 
@@ -1383,10 +1371,7 @@
             if (hasUnifiedChallenge(profile.id)) {
                 if (mUserManager.isUserRunning(profile.id)) {
                     // Unlock managed profile with unified lock
-                    // Must pass the challenge on for resetLockout, so it's not over-written, which
-                    // causes LockSettingsService to revokeChallenge inappropriately.
-                    unlockChildProfile(profile.id, false /* ignoreUserNotAuthenticated */,
-                            challengeType, challenge, resetLockouts);
+                    unlockChildProfile(profile.id, false /* ignoreUserNotAuthenticated */);
                 } else {
                     try {
                         // Profile not ready for unlock yet, but decrypt the unified challenge now
@@ -1407,22 +1392,9 @@
                     restoreCallingIdentity(ident);
                 }
             }
-
         }
 
-        if (resetLockouts != null && !resetLockouts.isEmpty()) {
-            mHandler.post(() -> {
-                final BiometricManager bm = mContext.getSystemService(BiometricManager.class);
-                final PackageManager pm = mContext.getPackageManager();
-                for (int i = 0; i < resetLockouts.size(); i++) {
-                    bm.resetLockout(resetLockouts.get(i).mUserId, resetLockouts.get(i).mHAT);
-                }
-                if (challengeType == CHALLENGE_INTERNAL
-                        && pm.hasSystemFeature(PackageManager.FEATURE_FACE)) {
-                    mContext.getSystemService(FaceManager.class).revokeChallenge();
-                }
-            });
-        }
+        mBiometricDeferredQueue.processPendingLockoutResets();
     }
 
     private boolean hasUnifiedChallenge(int userId) {
@@ -1607,8 +1579,8 @@
             // Verify the parent credential again, to make sure we have a fresh enough
             // auth token such that getDecryptedPasswordForTiedProfile() inside
             // setLockCredentialInternal() can function correctly.
-            verifyCredential(savedCredential, /* challenge */ 0,
-                    mUserManager.getProfileParent(userId).id);
+            verifyCredential(savedCredential, mUserManager.getProfileParent(userId).id,
+                    0 /* flags */);
             savedCredential.zeroize();
             savedCredential = LockscreenCredential.createNone();
         }
@@ -1722,8 +1694,7 @@
         setUserKeyProtection(userId, credential, convertResponse(gkResponse));
         fixateNewestUserKeyAuth(userId);
         // Refresh the auth token
-        doVerifyCredential(credential, CHALLENGE_FROM_CALLER, 0, userId,
-                null /* progressCallback */);
+        doVerifyCredential(credential, userId, null /* progressCallback */, 0 /* flags */);
         synchronizeUnifiedWorkChallengeForProfiles(userId, null);
         sendCredentialsOnChangeIfRequired(credential, userId, isLockTiedToParent);
         return true;
@@ -1834,7 +1805,7 @@
             throw new IllegalArgumentException("Non-OK response verifying a credential we just set "
                     + vcr.getResponseCode());
         }
-        byte[] token = vcr.getPayload();
+        byte[] token = vcr.getGatekeeperHAT();
         if (token == null) {
             throw new IllegalArgumentException("Empty payload verifying a credential we just set");
         }
@@ -1965,47 +1936,69 @@
     @Override
     public VerifyCredentialResponse checkCredential(LockscreenCredential credential, int userId,
             ICheckCredentialProgressCallback progressCallback) {
-        checkPasswordReadPermission(userId);
+        checkPasswordReadPermission();
         try {
-            return doVerifyCredential(credential, CHALLENGE_NONE, 0, userId, progressCallback);
+            return doVerifyCredential(credential, userId, progressCallback, 0 /* flags */);
         } finally {
             scheduleGc();
         }
     }
 
     @Override
+    @Nullable
     public VerifyCredentialResponse verifyCredential(LockscreenCredential credential,
-            long challenge, int userId) {
-        checkPasswordReadPermission(userId);
-        @ChallengeType int challengeType = CHALLENGE_FROM_CALLER;
-        if (challenge == 0) {
-            Slog.w(TAG, "VerifyCredential called with challenge=0");
-            challengeType = CHALLENGE_NONE;
-
-        }
+            int userId, int flags) {
+        checkPasswordReadPermission();
         try {
-            return doVerifyCredential(credential, challengeType, challenge, userId,
-                    null /* progressCallback */);
+            return doVerifyCredential(credential, userId, null /* progressCallback */, flags);
         } finally {
             scheduleGc();
         }
     }
 
-    private VerifyCredentialResponse doVerifyCredential(LockscreenCredential credential,
-            @ChallengeType int challengeType, long challenge, int userId,
-            ICheckCredentialProgressCallback progressCallback) {
-        return doVerifyCredential(credential, challengeType, challenge, userId,
-                progressCallback, null /* resetLockouts */);
+    @Override
+    public VerifyCredentialResponse verifyGatekeeperPasswordHandle(long gatekeeperPasswordHandle,
+            long challenge, int userId) {
+        checkPasswordReadPermission();
+
+        final VerifyCredentialResponse response;
+        final byte[] gatekeeperPassword;
+
+        synchronized (mGatekeeperPasswords) {
+            gatekeeperPassword = mGatekeeperPasswords.get(gatekeeperPasswordHandle);
+        }
+
+        synchronized (mSpManager) {
+            if (gatekeeperPassword == null) {
+                response = VerifyCredentialResponse.ERROR;
+            } else {
+                response = mSpManager.verifyChallengeInternal(getGateKeeperService(),
+                        gatekeeperPassword, challenge, userId);
+            }
+        }
+        return response;
     }
 
-    /**
+    @Override
+    public void removeGatekeeperPasswordHandle(long gatekeeperPasswordHandle) {
+        checkPasswordReadPermission();
+        synchronized (mGatekeeperPasswords) {
+            mGatekeeperPasswords.remove(gatekeeperPasswordHandle);
+        }
+    }
+
+    /*
      * Verify user credential and unlock the user. Fix pattern bug by deprecating the old base zero
      * format.
+     * @param credential User's lockscreen credential
+     * @param userId User to verify the credential for
+     * @param progressCallback Receive progress callbacks
+     * @param flags See {@link LockPatternUtils.VerifyFlag}
+     * @return See {@link VerifyCredentialResponse}
      */
     private VerifyCredentialResponse doVerifyCredential(LockscreenCredential credential,
-            @ChallengeType int challengeType, long challenge, int userId,
-            ICheckCredentialProgressCallback progressCallback,
-            @Nullable ArrayList<PendingResetLockout> resetLockouts) {
+            int userId, ICheckCredentialProgressCallback progressCallback,
+            @LockPatternUtils.VerifyFlag int flags) {
         if (credential == null || credential.isNone()) {
             throw new IllegalArgumentException("Credential can't be null or empty");
         }
@@ -2014,9 +2007,10 @@
             Slog.e(TAG, "FRP credential can only be verified prior to provisioning.");
             return VerifyCredentialResponse.ERROR;
         }
-        VerifyCredentialResponse response = null;
-        response = spBasedDoVerifyCredential(credential, challengeType, challenge,
-                userId, progressCallback, resetLockouts);
+
+        VerifyCredentialResponse response = spBasedDoVerifyCredential(credential, userId,
+                progressCallback, flags);
+
         // The user employs synthetic password based credential.
         if (response != null) {
             if (response.getResponseCode() == VerifyCredentialResponse.RESPONSE_OK) {
@@ -2037,8 +2031,7 @@
             return VerifyCredentialResponse.ERROR;
         }
 
-        response = verifyCredential(userId, storedHash, credential,
-                challengeType, challenge, progressCallback);
+        response = verifyCredential(userId, storedHash, credential, progressCallback);
 
         if (response.getResponseCode() == VerifyCredentialResponse.RESPONSE_OK) {
             mStrongAuth.reportSuccessfulStrongAuthUnlock(userId);
@@ -2049,8 +2042,8 @@
 
     @Override
     public VerifyCredentialResponse verifyTiedProfileChallenge(LockscreenCredential credential,
-            long challenge, int userId) {
-        checkPasswordReadPermission(userId);
+            int userId, @LockPatternUtils.VerifyFlag int flags) {
+        checkPasswordReadPermission();
         if (!isManagedProfileWithUnifiedLock(userId)) {
             throw new IllegalArgumentException("User id must be managed profile with unified lock");
         }
@@ -2058,10 +2051,9 @@
         // Unlock parent by using parent's challenge
         final VerifyCredentialResponse parentResponse = doVerifyCredential(
                 credential,
-                CHALLENGE_FROM_CALLER,
-                challenge,
                 parentProfileId,
-                null /* progressCallback */);
+                null /* progressCallback */,
+                flags);
         if (parentResponse.getResponseCode() != VerifyCredentialResponse.RESPONSE_OK) {
             // Failed, just return parent's response
             return parentResponse;
@@ -2070,9 +2062,7 @@
         try {
             // Unlock work profile, and work profile with unified lock must use password only
             return doVerifyCredential(getDecryptedPasswordForTiedProfile(userId),
-                    CHALLENGE_FROM_CALLER,
-                    challenge,
-                    userId, null /* progressCallback */);
+                    userId, null /* progressCallback */, flags);
         } catch (UnrecoverableKeyException | InvalidKeyException | KeyStoreException
                 | NoSuchAlgorithmException | NoSuchPaddingException
                 | InvalidAlgorithmParameterException | IllegalBlockSizeException
@@ -2090,8 +2080,7 @@
      * hash to GK.
      */
     private VerifyCredentialResponse verifyCredential(int userId, CredentialHash storedHash,
-            LockscreenCredential credential, @ChallengeType int challengeType, long challenge,
-            ICheckCredentialProgressCallback progressCallback) {
+            LockscreenCredential credential, ICheckCredentialProgressCallback progressCallback) {
         if ((storedHash == null || storedHash.hash.length == 0) && credential.isNone()) {
             // don't need to pass empty credentials to GateKeeper
             return VerifyCredentialResponse.OK;
@@ -2108,7 +2097,7 @@
         GateKeeperResponse gateKeeperResponse;
         try {
             gateKeeperResponse = getGateKeeperService().verifyChallenge(
-                    userId, challenge, storedHash.hash, credential.getCredential());
+                    userId, 0L /* challenge */, storedHash.hash, credential.getCredential());
         } catch (RemoteException e) {
             Slog.e(TAG, "gatekeeper verify failed", e);
             gateKeeperResponse = GateKeeperResponse.ERROR;
@@ -2131,8 +2120,8 @@
             unlockKeystore(credential.getCredential(), userId);
 
             Slog.i(TAG, "Unlocking user " + userId + " with token length "
-                    + response.getPayload().length);
-            unlockUser(userId, response.getPayload(), secretFromCredential(credential));
+                    + response.getGatekeeperHAT().length);
+            unlockUser(userId, response.getGatekeeperHAT(), secretFromCredential(credential));
 
             if (isManagedProfileWithSeparatedLock(userId)) {
                 setDeviceUnlockedForUser(userId);
@@ -2221,7 +2210,7 @@
         }
         mFirstCallToVold = false;
 
-        checkPasswordReadPermission(userId);
+        checkPasswordReadPermission();
 
         // There's no guarantee that this will safely connect, but if it fails
         // we will simply show the lock screen when we shouldn't, so relatively
@@ -2311,13 +2300,13 @@
 
     @Override
     public void registerStrongAuthTracker(IStrongAuthTracker tracker) {
-        checkPasswordReadPermission(UserHandle.USER_ALL);
+        checkPasswordReadPermission();
         mStrongAuth.registerStrongAuthTracker(tracker);
     }
 
     @Override
     public void unregisterStrongAuthTracker(IStrongAuthTracker tracker) {
-        checkPasswordReadPermission(UserHandle.USER_ALL);
+        checkPasswordReadPermission();
         mStrongAuth.unregisterStrongAuthTracker(tracker);
     }
 
@@ -2347,7 +2336,7 @@
 
     @Override
     public int getStrongAuthForUser(int userId) {
-        checkPasswordReadPermission(userId);
+        checkPasswordReadPermission();
         return mStrongAuthTracker.getStrongAuthForUser(userId);
     }
 
@@ -2681,29 +2670,17 @@
     }
 
     private VerifyCredentialResponse spBasedDoVerifyCredential(LockscreenCredential userCredential,
-            @ChallengeType int challengeType, long challenge,
             int userId, ICheckCredentialProgressCallback progressCallback,
-            @Nullable ArrayList<PendingResetLockout> resetLockouts) {
-
+            @LockPatternUtils.VerifyFlag int flags) {
         final boolean hasEnrolledBiometrics = mInjector.hasEnrolledBiometrics(userId);
 
-        Slog.d(TAG, "spBasedDoVerifyCredential: user=" + userId + " challengeType=" + challengeType
+        Slog.d(TAG, "spBasedDoVerifyCredential: user=" + userId
                 + " hasEnrolledBiometrics=" + hasEnrolledBiometrics);
 
-        final PackageManager pm = mContext.getPackageManager();
-        // TODO: When lockout is handled under the HAL for all biometrics (fingerprint),
-        // we need to generate challenge for each one, have it signed by GK and reset lockout
-        // for each modality.
-        if (challengeType == CHALLENGE_NONE && pm.hasSystemFeature(PackageManager.FEATURE_FACE)
-                && hasEnrolledBiometrics) {
-            // If there are multiple profiles in the same account, ensure we only generate the
-            // challenge once.
-            challengeType = CHALLENGE_INTERNAL;
-            challenge = mContext.getSystemService(FaceManager.class).generateChallengeBlocking();
-        }
-
         final AuthenticationResult authResult;
         VerifyCredentialResponse response;
+        final boolean requestGkPw = (flags & VERIFY_FLAG_REQUEST_GK_PW_HANDLE) != 0;
+
         synchronized (mSpManager) {
             if (!isSyntheticPasswordBasedCredentialLocked(userId)) {
                 return null;
@@ -2716,14 +2693,17 @@
             long handle = getSyntheticPasswordHandleLocked(userId);
             authResult = mSpManager.unwrapPasswordBasedSyntheticPassword(
                     getGateKeeperService(), handle, userCredential, userId, progressCallback);
-
             response = authResult.gkResponse;
+
             // credential has matched
             if (response.getResponseCode() == VerifyCredentialResponse.RESPONSE_OK) {
+                mBiometricDeferredQueue.addPendingLockoutResetForUser(userId,
+                        authResult.authToken.deriveGkPassword());
+
                 // perform verifyChallenge with synthetic password which generates the real GK auth
                 // token and response for the current user
                 response = mSpManager.verifyChallenge(getGateKeeperService(), authResult.authToken,
-                        challenge, userId);
+                        0L /* challenge */, userId);
                 if (response.getResponseCode() != VerifyCredentialResponse.RESPONSE_OK) {
                     // This shouldn't really happen: the unwrapping of SP succeeds, but SP doesn't
                     // match the recorded GK password handle.
@@ -2733,15 +2713,7 @@
             }
         }
         if (response.getResponseCode() == VerifyCredentialResponse.RESPONSE_OK) {
-            // Do resetLockout / revokeChallenge when all profiles are unlocked
-            if (hasEnrolledBiometrics) {
-                if (resetLockouts == null) {
-                    resetLockouts = new ArrayList<>();
-                }
-                resetLockouts.add(new PendingResetLockout(userId, response.getPayload()));
-            }
-
-            onCredentialVerified(authResult.authToken, challengeType, challenge, resetLockouts,
+            onCredentialVerified(authResult.authToken,
                     PasswordMetrics.computeForCredential(userCredential), userId);
         } else if (response.getResponseCode() == VerifyCredentialResponse.RESPONSE_RETRY) {
             if (response.getTimeout() > 0) {
@@ -2749,12 +2721,44 @@
             }
         }
 
-        return response;
+        if (response.isMatched() && requestGkPw) {
+            final long handle = storeGatekeeperPasswordTemporarily(
+                    authResult.authToken.deriveGkPassword());
+            return new VerifyCredentialResponse.Builder().setGatekeeperPasswordHandle(handle)
+                    .build();
+        } else {
+            return response;
+        }
     }
 
-    private void onCredentialVerified(AuthenticationToken authToken,
-            @ChallengeType int challengeType, long challenge,
-            @Nullable ArrayList<PendingResetLockout> resetLockouts, PasswordMetrics metrics,
+    /**
+     * Stores the gatekeeper password temporarily.
+     * @param gatekeeperPassword unlocked upon successful Synthetic Password
+     * @return non-zero handle to the gatekeeper password, which can be used for a set amount of
+     *         time.
+     */
+    private long storeGatekeeperPasswordTemporarily(byte[] gatekeeperPassword) {
+        long handle = 0L;
+
+        synchronized (mGatekeeperPasswords) {
+            while (handle == 0L || mGatekeeperPasswords.get(handle) != null) {
+                handle = mRandom.nextLong();
+            }
+            mGatekeeperPasswords.put(handle, gatekeeperPassword);
+        }
+
+        final long finalHandle = handle;
+        mHandler.postDelayed(() -> {
+            synchronized (mGatekeeperPasswords) {
+                Slog.d(TAG, "Removing handle: " + finalHandle);
+                mGatekeeperPasswords.remove(finalHandle);
+            }
+        }, GK_PW_HANDLE_STORE_DURATION_MS);
+
+        return handle;
+    }
+
+    private void onCredentialVerified(AuthenticationToken authToken, PasswordMetrics metrics,
             int userId) {
 
         if (metrics != null) {
@@ -2769,7 +2773,7 @@
 
         {
             final byte[] secret = authToken.deriveDiskEncryptionKey();
-            unlockUser(userId, null, secret, challengeType, challenge, resetLockouts);
+            unlockUser(userId, null, secret);
             Arrays.fill(secret, (byte) 0);
         }
         activateEscrowTokens(authToken, userId);
@@ -2991,7 +2995,7 @@
      */
     @Override
     public byte[] getHashFactor(LockscreenCredential currentCredential, int userId) {
-        checkPasswordReadPermission(userId);
+        checkPasswordReadPermission();
         try {
             if (isManagedProfileWithUnifiedLock(userId)) {
                 try {
@@ -3069,7 +3073,7 @@
 
     @Override
     public boolean hasPendingEscrowToken(int userId) {
-        checkPasswordReadPermission(userId);
+        checkPasswordReadPermission();
         synchronized (mSpManager) {
             return !mSpManager.getPendingTokensForUser(userId).isEmpty();
         }
@@ -3156,11 +3160,8 @@
                 return false;
             }
         }
-        // TODO: Reset biometrics lockout here. Ideally that should be self-contained inside
-        // onCredentialVerified(), which will require some refactoring on the current lockout
-        // reset logic.
 
-        onCredentialVerified(authResult.authToken, CHALLENGE_NONE, 0, null,
+        onCredentialVerified(authResult.authToken,
                 loadPasswordMetrics(authResult.authToken, userId), userId);
         return true;
     }
@@ -3171,7 +3172,7 @@
             if (cred == null) {
                 return false;
             }
-            return doVerifyCredential(cred, CHALLENGE_NONE, 0, userId, null /* progressCallback */)
+            return doVerifyCredential(cred, userId, null /* progressCallback */, 0 /* flags */)
                     .getResponseCode() == VerifyCredentialResponse.RESPONSE_OK;
         }
     }
@@ -3258,6 +3259,8 @@
         mRebootEscrowManager.dump(pw);
         pw.println();
         pw.decreaseIndent();
+
+        pw.println("PasswordHandleCount: " + mGatekeeperPasswords.size());
     }
 
     /**
@@ -3494,8 +3497,7 @@
             SyntheticPasswordManager.AuthenticationToken
                     authToken = new SyntheticPasswordManager.AuthenticationToken(spVersion);
             authToken.recreateDirectly(syntheticPassword);
-            onCredentialVerified(authToken, CHALLENGE_NONE, 0, null,
-                    loadPasswordMetrics(authToken, userId), userId);
+            onCredentialVerified(authToken, loadPasswordMetrics(authToken, userId), userId);
         }
     }
 }
diff --git a/services/core/java/com/android/server/locksettings/LockSettingsStorage.java b/services/core/java/com/android/server/locksettings/LockSettingsStorage.java
index 81d07cc..e9a05a8 100644
--- a/services/core/java/com/android/server/locksettings/LockSettingsStorage.java
+++ b/services/core/java/com/android/server/locksettings/LockSettingsStorage.java
@@ -484,7 +484,7 @@
     public Map<Integer, List<Long>> listSyntheticPasswordHandlesForAllUsers(String stateName) {
         Map<Integer, List<Long>> result = new ArrayMap<>();
         final UserManager um = UserManager.get(mContext);
-        for (UserInfo user : um.getUsers(false)) {
+        for (UserInfo user : um.getUsers()) {
             result.put(user.id, listSyntheticPasswordHandlesForUser(stateName, user.id));
         }
         return result;
diff --git a/services/core/java/com/android/server/locksettings/SyntheticPasswordManager.java b/services/core/java/com/android/server/locksettings/SyntheticPasswordManager.java
index d644b1d..e31bf3d 100644
--- a/services/core/java/com/android/server/locksettings/SyntheticPasswordManager.java
+++ b/services/core/java/com/android/server/locksettings/SyntheticPasswordManager.java
@@ -482,11 +482,12 @@
                     (int status, WeaverReadResponse readResponse) -> {
                     switch (status) {
                         case WeaverReadStatus.OK:
-                            response[0] = new VerifyCredentialResponse(
-                                    fromByteArrayList(readResponse.value));
+                            response[0] = new VerifyCredentialResponse.Builder().setGatekeeperHAT(
+                                    fromByteArrayList(readResponse.value)).build();
                             break;
                         case WeaverReadStatus.THROTTLE:
-                            response[0] = new VerifyCredentialResponse(readResponse.timeout);
+                            response[0] = VerifyCredentialResponse
+                                    .fromTimeout(readResponse.timeout);
                             Slog.e(TAG, "weaver read failed (THROTTLE), slot: " + slot);
                             break;
                         case WeaverReadStatus.INCORRECT_KEY:
@@ -494,7 +495,8 @@
                                 response[0] = VerifyCredentialResponse.ERROR;
                                 Slog.e(TAG, "weaver read failed (INCORRECT_KEY), slot: " + slot);
                             } else {
-                                response[0] = new VerifyCredentialResponse(readResponse.timeout);
+                                response[0] = VerifyCredentialResponse
+                                        .fromTimeout(readResponse.timeout);
                                 Slog.e(TAG, "weaver read failed (INCORRECT_KEY/THROTTLE), slot: "
                                         + slot);
                             }
@@ -1007,7 +1009,8 @@
                 return result;
             }
             sid = GateKeeper.INVALID_SECURE_USER_ID;
-            applicationId = transformUnderWeaverSecret(pwdToken, result.gkResponse.getPayload());
+            applicationId = transformUnderWeaverSecret(pwdToken,
+                    result.gkResponse.getGatekeeperHAT());
         } else {
             byte[] gkPwdToken = passwordTokenToGkInput(pwdToken);
             GateKeeperResponse response;
@@ -1045,7 +1048,7 @@
                     }
                 }
             } else if (responseCode == GateKeeperResponse.RESPONSE_RETRY) {
-                result.gkResponse = new VerifyCredentialResponse(response.getTimeout());
+                result.gkResponse = VerifyCredentialResponse.fromTimeout(response.getTimeout());
                 return result;
             } else  {
                 result.gkResponse = VerifyCredentialResponse.ERROR;
@@ -1096,12 +1099,12 @@
             }
             VerifyCredentialResponse response = weaverVerify(slotId, null);
             if (response.getResponseCode() != VerifyCredentialResponse.RESPONSE_OK ||
-                    response.getPayload() == null) {
+                    response.getGatekeeperHAT() == null) {
                 Slog.e(TAG, "Failed to retrieve weaver secret when unwrapping token");
                 result.gkResponse = VerifyCredentialResponse.ERROR;
                 return result;
             }
-            secdiscardable = SyntheticPasswordCrypto.decrypt(response.getPayload(),
+            secdiscardable = SyntheticPasswordCrypto.decrypt(response.getGatekeeperHAT(),
                     PERSONALISATION_WEAVER_TOKEN, secdiscardable);
         }
         byte[] applicationId = transformUnderSecdiscardable(token, secdiscardable);
@@ -1174,6 +1177,12 @@
      */
     public @Nullable VerifyCredentialResponse verifyChallenge(IGateKeeperService gatekeeper,
             @NonNull AuthenticationToken auth, long challenge, int userId) {
+        return verifyChallengeInternal(gatekeeper, auth.deriveGkPassword(), challenge, userId);
+    }
+
+    protected @Nullable VerifyCredentialResponse verifyChallengeInternal(
+            IGateKeeperService gatekeeper, @NonNull byte[] gatekeeperPassword, long challenge,
+            int userId) {
         byte[] spHandle = loadSyntheticPasswordHandle(userId);
         if (spHandle == null) {
             // There is no password handle associated with the given user, i.e. the user is not
@@ -1183,18 +1192,19 @@
         GateKeeperResponse response;
         try {
             response = gatekeeper.verifyChallenge(userId, challenge,
-                    spHandle, auth.deriveGkPassword());
+                    spHandle, gatekeeperPassword);
         } catch (RemoteException e) {
             Slog.e(TAG, "Fail to verify with gatekeeper " + userId, e);
             return VerifyCredentialResponse.ERROR;
         }
         int responseCode = response.getResponseCode();
         if (responseCode == GateKeeperResponse.RESPONSE_OK) {
-            VerifyCredentialResponse result = new VerifyCredentialResponse(response.getPayload());
+            VerifyCredentialResponse result = new VerifyCredentialResponse.Builder()
+                    .setGatekeeperHAT(response.getPayload()).build();
             if (response.getShouldReEnroll()) {
                 try {
                     response = gatekeeper.enroll(userId, spHandle, spHandle,
-                            auth.deriveGkPassword());
+                            gatekeeperPassword);
                 } catch (RemoteException e) {
                     Slog.e(TAG, "Failed to invoke gatekeeper.enroll", e);
                     response = GateKeeperResponse.ERROR;
@@ -1203,7 +1213,8 @@
                     spHandle = response.getPayload();
                     saveSyntheticPasswordHandle(spHandle, userId);
                     // Call self again to re-verify with updated handle
-                    return verifyChallenge(gatekeeper, auth, challenge, userId);
+                    return verifyChallengeInternal(gatekeeper, gatekeeperPassword, challenge,
+                            userId);
                 } else {
                     // Fall through, return result from the previous verification attempt.
                     Slog.w(TAG, "Fail to re-enroll SP handle for user " + userId);
@@ -1211,7 +1222,7 @@
             }
             return result;
         } else if (responseCode == GateKeeperResponse.RESPONSE_RETRY) {
-            return new VerifyCredentialResponse(response.getTimeout());
+            return VerifyCredentialResponse.fromTimeout(response.getTimeout());
         } else {
             return VerifyCredentialResponse.ERROR;
         }
diff --git a/services/core/java/com/android/server/media/BluetoothRouteProvider.java b/services/core/java/com/android/server/media/BluetoothRouteProvider.java
index 3a4dfaf..0b3cdae 100644
--- a/services/core/java/com/android/server/media/BluetoothRouteProvider.java
+++ b/services/core/java/com/android/server/media/BluetoothRouteProvider.java
@@ -34,6 +34,7 @@
 import android.media.AudioManager;
 import android.media.AudioSystem;
 import android.media.MediaRoute2Info;
+import android.os.UserHandle;
 import android.text.TextUtils;
 import android.util.Log;
 import android.util.Slog;
@@ -55,7 +56,6 @@
     private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
 
     private static final String HEARING_AID_ROUTE_ID_PREFIX = "HEARING_AID_";
-    private static BluetoothRouteProvider sInstance;
 
     @SuppressWarnings("WeakerAccess") /* synthetic access */
     // Maps hardware address to BluetoothRouteInfo
@@ -79,19 +79,21 @@
     private final BroadcastReceiver mBroadcastReceiver = new BluetoothBroadcastReceiver();
     private final BluetoothProfileListener mProfileListener = new BluetoothProfileListener();
 
-    static synchronized BluetoothRouteProvider getInstance(@NonNull Context context,
+    /**
+     * Create an instance of {@link BluetoothRouteProvider}.
+     * It may return {@code null} if Bluetooth is not supported on this hardware platform.
+     */
+    @Nullable
+    static BluetoothRouteProvider createInstance(@NonNull Context context,
             @NonNull BluetoothRoutesUpdatedListener listener) {
         Objects.requireNonNull(context);
         Objects.requireNonNull(listener);
 
-        if (sInstance == null) {
-            BluetoothAdapter btAdapter = BluetoothAdapter.getDefaultAdapter();
-            if (btAdapter == null) {
-                return null;
-            }
-            sInstance = new BluetoothRouteProvider(context, btAdapter, listener);
+        BluetoothAdapter btAdapter = BluetoothAdapter.getDefaultAdapter();
+        if (btAdapter == null) {
+            return null;
         }
-        return sInstance;
+        return new BluetoothRouteProvider(context, btAdapter, listener);
     }
 
     private BluetoothRouteProvider(Context context, BluetoothAdapter btAdapter,
@@ -103,7 +105,7 @@
         buildBluetoothRoutes();
     }
 
-    public void start() {
+    public void start(UserHandle user) {
         mBluetoothAdapter.getProfileProxy(mContext, mProfileListener, BluetoothProfile.A2DP);
         mBluetoothAdapter.getProfileProxy(mContext, mProfileListener, BluetoothProfile.HEARING_AID);
 
@@ -118,7 +120,8 @@
         addEventReceiver(BluetoothHearingAid.ACTION_CONNECTION_STATE_CHANGED,
                 deviceStateChangedReceiver);
 
-        mContext.registerReceiver(mBroadcastReceiver, mIntentFilter, null, null);
+        mContext.registerReceiverAsUser(mBroadcastReceiver, user,
+                mIntentFilter, null, null);
     }
 
     /**
diff --git a/services/core/java/com/android/server/media/HandlerExecutor.java b/services/core/java/com/android/server/media/HandlerExecutor.java
new file mode 100644
index 0000000..7c9e72b
--- /dev/null
+++ b/services/core/java/com/android/server/media/HandlerExecutor.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.media;
+
+import android.annotation.NonNull;
+import android.os.Handler;
+
+import java.util.Objects;
+import java.util.concurrent.Executor;
+import java.util.concurrent.RejectedExecutionException;
+
+/**
+ * An adapter {@link Executor} that posts all executed tasks onto the given
+ * {@link Handler}.
+ *
+ * @hide
+ */
+public class HandlerExecutor implements Executor {
+    private final Handler mHandler;
+
+    public HandlerExecutor(@NonNull Handler handler) {
+        mHandler = Objects.requireNonNull(handler);
+    }
+
+    @Override
+    public void execute(Runnable command) {
+        if (!mHandler.post(command)) {
+            throw new RejectedExecutionException(mHandler + " is shutting down");
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java b/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java
index 875bfdf..1114fe0 100644
--- a/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java
+++ b/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java
@@ -1176,7 +1176,8 @@
             super(Looper.getMainLooper(), null, true);
             mServiceRef = new WeakReference<>(service);
             mUserRecord = userRecord;
-            mSystemProvider = new SystemMediaRoute2Provider(service.mContext);
+            mSystemProvider = new SystemMediaRoute2Provider(service.mContext,
+                    UserHandle.of(userRecord.mUserId));
             mRouteProviders.add(mSystemProvider);
             mWatcher = new MediaRoute2ProviderWatcher(service.mContext, this,
                     this, mUserRecord.mUserId);
diff --git a/services/core/java/com/android/server/media/MediaSession2Record.java b/services/core/java/com/android/server/media/MediaSession2Record.java
index 5d1b7491..162c388 100644
--- a/services/core/java/com/android/server/media/MediaSession2Record.java
+++ b/services/core/java/com/android/server/media/MediaSession2Record.java
@@ -20,7 +20,6 @@
 import android.media.Session2CommandGroup;
 import android.media.Session2Token;
 import android.os.Handler;
-import android.os.HandlerExecutor;
 import android.os.Looper;
 import android.os.ResultReceiver;
 import android.os.UserHandle;
diff --git a/services/core/java/com/android/server/media/MediaSessionRecord.java b/services/core/java/com/android/server/media/MediaSessionRecord.java
index 0f67483..1e02f49 100644
--- a/services/core/java/com/android/server/media/MediaSessionRecord.java
+++ b/services/core/java/com/android/server/media/MediaSessionRecord.java
@@ -36,6 +36,7 @@
 import android.media.session.MediaController.PlaybackInfo;
 import android.media.session.MediaSession;
 import android.media.session.MediaSession.QueueItem;
+import android.media.session.ParcelableListBinder;
 import android.media.session.PlaybackState;
 import android.net.Uri;
 import android.os.Binder;
@@ -589,11 +590,19 @@
             if (mDestroyed) {
                 return;
             }
+            ParceledListSlice<QueueItem> parcelableQueue;
+            if (mQueue == null) {
+                parcelableQueue = null;
+            } else {
+                parcelableQueue = new ParceledListSlice<>(mQueue);
+                // Limit the size of initial Parcel to prevent binder buffer overflow
+                // as onQueueChanged is an async binder call.
+                parcelableQueue.setInlineCountLimit(1);
+            }
             for (int i = mControllerCallbackHolders.size() - 1; i >= 0; i--) {
                 ISessionControllerCallbackHolder holder = mControllerCallbackHolders.get(i);
                 try {
-                    holder.mCallback.onQueueChanged(mQueue == null ? null :
-                            new ParceledListSlice<>(mQueue));
+                    holder.mCallback.onQueueChanged(parcelableQueue);
                 } catch (DeadObjectException e) {
                     mControllerCallbackHolders.remove(i);
                     logCallbackException("Removing dead callback in pushQueueUpdate", holder, e);
@@ -897,14 +906,24 @@
         }
 
         @Override
-        public void setQueue(ParceledListSlice queue) throws RemoteException {
+        public void resetQueue() throws RemoteException {
             synchronized (mLock) {
-                mQueue = queue == null ? null : (List<QueueItem>) queue.getList();
+                mQueue = null;
             }
             mHandler.post(MessageHandler.MSG_UPDATE_QUEUE);
         }
 
         @Override
+        public IBinder getBinderForSetQueue() throws RemoteException {
+            return new ParcelableListBinder<QueueItem>((list) -> {
+                synchronized (mLock) {
+                    mQueue = list;
+                }
+                mHandler.post(MessageHandler.MSG_UPDATE_QUEUE);
+            });
+        }
+
+        @Override
         public void setQueueTitle(CharSequence title) throws RemoteException {
             mQueueTitle = title;
             mHandler.post(MessageHandler.MSG_UPDATE_QUEUE_TITLE);
diff --git a/services/core/java/com/android/server/media/MediaSessionService.java b/services/core/java/com/android/server/media/MediaSessionService.java
index 9f6c18d..9521611 100644
--- a/services/core/java/com/android/server/media/MediaSessionService.java
+++ b/services/core/java/com/android/server/media/MediaSessionService.java
@@ -25,19 +25,22 @@
 import static com.android.server.media.MediaKeyDispatcher.isSingleTapOverridden;
 import static com.android.server.media.MediaKeyDispatcher.isTripleTapOverridden;
 
+import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.app.ActivityManager;
 import android.app.KeyguardManager;
 import android.app.NotificationManager;
 import android.app.PendingIntent;
 import android.content.ActivityNotFoundException;
+import android.content.BroadcastReceiver;
 import android.content.ComponentName;
 import android.content.ContentResolver;
 import android.content.Context;
 import android.content.Intent;
+import android.content.IntentFilter;
 import android.content.pm.PackageManager;
 import android.content.pm.ParceledListSlice;
 import android.content.pm.UserInfo;
-import android.database.ContentObserver;
 import android.media.AudioManager;
 import android.media.AudioManagerInternal;
 import android.media.AudioPlaybackConfiguration;
@@ -56,7 +59,6 @@
 import android.media.session.MediaController;
 import android.media.session.MediaSession;
 import android.media.session.MediaSessionManager;
-import android.net.Uri;
 import android.os.Binder;
 import android.os.Bundle;
 import android.os.Handler;
@@ -85,6 +87,7 @@
 import com.android.internal.util.DumpUtils;
 import com.android.server.LocalServices;
 import com.android.server.SystemService;
+import com.android.server.SystemService.TargetUser;
 import com.android.server.Watchdog;
 import com.android.server.Watchdog.Monitor;
 
@@ -135,7 +138,6 @@
     private KeyguardManager mKeyguardManager;
     private AudioManagerInternal mAudioManagerInternal;
     private ContentResolver mContentResolver;
-    private SettingsObserver mSettingsObserver;
     private boolean mHasFeatureLeanback;
 
     // The FullUserRecord of the current users. (i.e. The foreground user that isn't a profile)
@@ -189,8 +191,6 @@
                     }
                 }, null /* handler */);
         mContentResolver = mContext.getContentResolver();
-        mSettingsObserver = new SettingsObserver();
-        mSettingsObserver.observe();
         mHasFeatureLeanback = mContext.getPackageManager().hasSystemFeature(
                 PackageManager.FEATURE_LEANBACK);
 
@@ -199,8 +199,20 @@
         instantiateCustomProvider(null);
         instantiateCustomDispatcher(null);
         mRecordThread.start();
+
+        final IntentFilter filter = new IntentFilter(
+                NotificationManager.ACTION_NOTIFICATION_LISTENER_ENABLED_CHANGED);
+        mContext.registerReceiver(mNotificationListenerEnabledChangedReceiver, filter);
     }
 
+    private final BroadcastReceiver mNotificationListenerEnabledChangedReceiver =
+            new BroadcastReceiver() {
+                @Override
+                public void onReceive(Context context, Intent intent) {
+                    updateActiveSessionListeners();
+                }
+    };
+
     private boolean isGlobalPriorityActiveLocked() {
         return mGlobalPrioritySession != null && mGlobalPrioritySession.isActive();
     }
@@ -333,19 +345,21 @@
     }
 
     @Override
-    public void onStartUser(int userId) {
-        if (DEBUG) Log.d(TAG, "onStartUser: " + userId);
+    public void onUserStarting(@NonNull TargetUser user) {
+        if (DEBUG) Log.d(TAG, "onStartUser: " + user);
         updateUser();
     }
 
     @Override
-    public void onSwitchUser(int userId) {
-        if (DEBUG) Log.d(TAG, "onSwitchUser: " + userId);
+    public void onUserSwitching(@Nullable TargetUser from, @NonNull TargetUser to) {
+        if (DEBUG) Log.d(TAG, "onSwitchUser: " + to);
         updateUser();
     }
 
     @Override
-    public void onCleanupUser(int userId) {
+    public void onUserStopped(@NonNull TargetUser targetUser) {
+        int userId = targetUser.getUserIdentifier();
+
         if (DEBUG) Log.d(TAG, "onCleanupUser: " + userId);
         synchronized (mLock) {
             FullUserRecord user = getFullUserRecordLocked(userId);
@@ -1077,25 +1091,6 @@
         }
     }
 
-    final class SettingsObserver extends ContentObserver {
-        private final Uri mSecureSettingsUri = Settings.Secure.getUriFor(
-                Settings.Secure.ENABLED_NOTIFICATION_LISTENERS);
-
-        private SettingsObserver() {
-            super(null);
-        }
-
-        private void observe() {
-            mContentResolver.registerContentObserver(mSecureSettingsUri,
-                    false, this, ALL.getIdentifier());
-        }
-
-        @Override
-        public void onChange(boolean selfChange, Uri uri) {
-            updateActiveSessionListeners();
-        }
-    }
-
     class SessionManagerImpl extends ISessionManager.Stub {
         private static final String EXTRA_WAKELOCK_ACQUIRED =
                 "android.media.AudioService.WAKELOCK_ACQUIRED";
@@ -1923,7 +1918,7 @@
             final int userId = UserHandle.getUserHandleForUid(uid).getIdentifier();
             final long token = Binder.clearCallingIdentity();
             try {
-                // Don't perform sanity check between controllerPackageName and controllerUid.
+                // Don't perform check between controllerPackageName and controllerUid.
                 // When an (activity|service) runs on the another apps process by specifying
                 // android:process in the AndroidManifest.xml, then PID and UID would have the
                 // running process' information instead of the (activity|service) that has created
@@ -1932,7 +1927,8 @@
                 // Context#getPackageName() for getting package name that matches with the PID/UID,
                 // but it doesn't tell which package has created the MediaController, so useless.
                 return hasMediaControlPermission(controllerPid, controllerUid)
-                        || hasEnabledNotificationListener(userId, controllerPackageName, uid);
+                        || hasEnabledNotificationListener(
+                                userId, controllerPackageName, controllerUid);
             } finally {
                 Binder.restoreCallingIdentity(token);
             }
@@ -1996,21 +1992,21 @@
             return resolvedUserId;
         }
 
-        private boolean hasEnabledNotificationListener(int resolvedUserId, String packageName,
-                int uid) {
-            // TODO: revisit this checking code
-            // You may not access another user's content as an enabled listener.
-            final int userId = UserHandle.getUserHandleForUid(resolvedUserId).getIdentifier();
-            if (resolvedUserId != userId) {
+        private boolean hasEnabledNotificationListener(int callingUserId,
+                String controllerPackageName, int controllerUid) {
+            int controllerUserId = UserHandle.getUserHandleForUid(controllerUid).getIdentifier();
+            if (callingUserId != controllerUserId) {
+                // Enabled notification listener only works within the same user.
                 return false;
             }
-            if (mNotificationManager.hasEnabledNotificationListener(packageName,
-                    UserHandle.getUserHandleForUid(uid))) {
+
+            if (mNotificationManager.hasEnabledNotificationListener(controllerPackageName,
+                    UserHandle.getUserHandleForUid(controllerUid))) {
                 return true;
             }
             if (DEBUG) {
-                Log.d(TAG, packageName + " (uid=" + uid + ") doesn't have an enabled "
-                        + "notification listener");
+                Log.d(TAG, controllerPackageName + " (uid=" + controllerUid
+                        + ") doesn't have an enabled notification listener");
             }
             return false;
         }
@@ -2704,5 +2700,4 @@
             obtainMessage(msg, userIdInteger).sendToTarget();
         }
     }
-
 }
diff --git a/services/core/java/com/android/server/media/MediaSessionStack.java b/services/core/java/com/android/server/media/MediaSessionStack.java
index 953aae4..d9b5b6d 100644
--- a/services/core/java/com/android/server/media/MediaSessionStack.java
+++ b/services/core/java/com/android/server/media/MediaSessionStack.java
@@ -20,7 +20,6 @@
 
 import android.media.Session2Token;
 import android.media.session.MediaSession;
-import android.os.Debug;
 import android.os.UserHandle;
 import android.util.Log;
 import android.util.SparseArray;
@@ -187,7 +186,7 @@
      */
     public void updateMediaButtonSessionIfNeeded() {
         if (DEBUG) {
-            Log.d(TAG, "updateMediaButtonSessionIfNeeded, callers=" + Debug.getCallers(2));
+            Log.d(TAG, "updateMediaButtonSessionIfNeeded, callers=" + getCallers(2));
         }
         List<Integer> audioPlaybackUids =
                 mAudioPlayerStateMonitor.getSortedAudioPlaybackClientUids();
@@ -413,4 +412,24 @@
         // so they also need to be cleared.
         mCachedActiveLists.remove(UserHandle.USER_ALL);
     }
+
+    // Code copied from android.os.Debug#getCallers(int)
+    private static String getCallers(final int depth) {
+        final StackTraceElement[] callStack = Thread.currentThread().getStackTrace();
+        StringBuffer sb = new StringBuffer();
+        for (int i = 0; i < depth; i++) {
+            sb.append(getCaller(callStack, i)).append(" ");
+        }
+        return sb.toString();
+    }
+
+    // Code copied from android.os.Debug#getCaller(StackTraceElement[], int)
+    private static String getCaller(StackTraceElement[] callStack, int depth) {
+        // callStack[4] is the caller of the method that called getCallers()
+        if (4 + depth >= callStack.length) {
+            return "<bottom of call stack>";
+        }
+        StackTraceElement caller = callStack[4 + depth];
+        return caller.getClassName() + "." + caller.getMethodName() + ":" + caller.getLineNumber();
+    }
 }
diff --git a/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java b/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java
index 2c089ca..4f7af94 100644
--- a/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java
+++ b/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java
@@ -45,6 +45,7 @@
 import android.os.Looper;
 import android.os.RemoteException;
 import android.os.ServiceManager;
+import android.os.UserHandle;
 import android.text.TextUtils;
 import android.util.Log;
 import android.util.Slog;
@@ -99,7 +100,7 @@
         }
     };
 
-    SystemMediaRoute2Provider(Context context) {
+    SystemMediaRoute2Provider(Context context, UserHandle user) {
         super(sComponentName);
 
         mIsSystemRouteProvider = true;
@@ -117,7 +118,7 @@
         updateDeviceRoute(newAudioRoutes);
 
         // .getInstance returns null if there is no bt adapter available
-        mBtRouteProvider = BluetoothRouteProvider.getInstance(context, (routes) -> {
+        mBtRouteProvider = BluetoothRouteProvider.createInstance(context, (routes) -> {
             publishProviderState();
 
             boolean sessionInfoChanged;
@@ -130,11 +131,12 @@
 
         IntentFilter intentFilter = new IntentFilter(AudioManager.VOLUME_CHANGED_ACTION);
         intentFilter.addAction(AudioManager.STREAM_DEVICES_CHANGED_ACTION);
-        mContext.registerReceiver(new AudioManagerBroadcastReceiver(), intentFilter);
+        mContext.registerReceiverAsUser(new AudioManagerBroadcastReceiver(), user,
+                intentFilter, null, null);
 
         if (mBtRouteProvider != null) {
             mHandler.post(() -> {
-                mBtRouteProvider.start();
+                mBtRouteProvider.start(user);
                 notifyProviderState();
             });
         }
diff --git a/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java b/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java
index 1a749b3..94776f8 100644
--- a/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java
+++ b/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java
@@ -17,6 +17,8 @@
 package com.android.server.media.projection;
 
 import android.Manifest;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.app.ActivityManagerInternal;
 import android.app.AppOpsManager;
 import android.app.IProcessObserver;
@@ -48,6 +50,7 @@
 import com.android.internal.util.DumpUtils;
 import com.android.server.LocalServices;
 import com.android.server.SystemService;
+import com.android.server.SystemService.TargetUser;
 import com.android.server.Watchdog;
 
 import java.io.FileDescriptor;
@@ -122,8 +125,8 @@
     }
 
     @Override
-    public void onSwitchUser(int userId) {
-        mMediaRouter.rebindAsUser(userId);
+    public void onUserSwitching(@Nullable TargetUser from, @NonNull TargetUser to) {
+        mMediaRouter.rebindAsUser(to.getUserIdentifier());
         synchronized (mLock) {
             if (mProjectionGrant != null) {
                 mProjectionGrant.stop();
diff --git a/services/core/java/com/android/server/net/NetworkPolicyLogger.java b/services/core/java/com/android/server/net/NetworkPolicyLogger.java
index 3bd18f9..006d78e 100644
--- a/services/core/java/com/android/server/net/NetworkPolicyLogger.java
+++ b/services/core/java/com/android/server/net/NetworkPolicyLogger.java
@@ -70,9 +70,9 @@
 
     static final int NTWK_BLOCKED_POWER = 0;
     static final int NTWK_ALLOWED_NON_METERED = 1;
-    static final int NTWK_BLOCKED_BLACKLIST = 2;
-    static final int NTWK_ALLOWED_WHITELIST = 3;
-    static final int NTWK_ALLOWED_TMP_WHITELIST = 4;
+    static final int NTWK_BLOCKED_DENYLIST = 2;
+    static final int NTWK_ALLOWED_ALLOWLIST = 3;
+    static final int NTWK_ALLOWED_TMP_ALLOWLIST = 4;
     static final int NTWK_BLOCKED_BG_RESTRICT = 5;
     static final int NTWK_ALLOWED_DEFAULT = 6;
     static final int NTWK_ALLOWED_SYSTEM = 7;
@@ -269,12 +269,12 @@
                 return "blocked by power restrictions";
             case NTWK_ALLOWED_NON_METERED:
                 return "allowed on unmetered network";
-            case NTWK_BLOCKED_BLACKLIST:
-                return "blacklisted on metered network";
-            case NTWK_ALLOWED_WHITELIST:
-                return "whitelisted on metered network";
-            case NTWK_ALLOWED_TMP_WHITELIST:
-                return "temporary whitelisted on metered network";
+            case NTWK_BLOCKED_DENYLIST:
+                return "denylisted on metered network";
+            case NTWK_ALLOWED_ALLOWLIST:
+                return "allowlisted on metered network";
+            case NTWK_ALLOWED_TMP_ALLOWLIST:
+                return "temporary allowlisted on metered network";
             case NTWK_BLOCKED_BG_RESTRICT:
                 return "blocked when background is restricted";
             case NTWK_ALLOWED_DEFAULT:
diff --git a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
index d6557f6..29ee8eb 100644
--- a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
+++ b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
@@ -101,13 +101,13 @@
 import static com.android.internal.util.XmlUtils.writeLongAttribute;
 import static com.android.internal.util.XmlUtils.writeStringAttribute;
 import static com.android.server.NetworkManagementService.LIMIT_GLOBAL_ALERT;
+import static com.android.server.net.NetworkPolicyLogger.NTWK_ALLOWED_ALLOWLIST;
 import static com.android.server.net.NetworkPolicyLogger.NTWK_ALLOWED_DEFAULT;
 import static com.android.server.net.NetworkPolicyLogger.NTWK_ALLOWED_NON_METERED;
 import static com.android.server.net.NetworkPolicyLogger.NTWK_ALLOWED_SYSTEM;
-import static com.android.server.net.NetworkPolicyLogger.NTWK_ALLOWED_TMP_WHITELIST;
-import static com.android.server.net.NetworkPolicyLogger.NTWK_ALLOWED_WHITELIST;
+import static com.android.server.net.NetworkPolicyLogger.NTWK_ALLOWED_TMP_ALLOWLIST;
 import static com.android.server.net.NetworkPolicyLogger.NTWK_BLOCKED_BG_RESTRICT;
-import static com.android.server.net.NetworkPolicyLogger.NTWK_BLOCKED_BLACKLIST;
+import static com.android.server.net.NetworkPolicyLogger.NTWK_BLOCKED_DENYLIST;
 import static com.android.server.net.NetworkPolicyLogger.NTWK_BLOCKED_POWER;
 import static com.android.server.net.NetworkStatsService.ACTION_NETWORK_STATS_UPDATED;
 
@@ -507,7 +507,7 @@
      * UIDs that have been initially white-listed by system to avoid restricted background.
      */
     @GuardedBy("mUidRulesFirstLock")
-    private final SparseBooleanArray mDefaultRestrictBackgroundWhitelistUids =
+    private final SparseBooleanArray mDefaultRestrictBackgroundAllowlistUids =
             new SparseBooleanArray();
 
     /**
@@ -515,7 +515,7 @@
      * but later revoked by user.
      */
     @GuardedBy("mUidRulesFirstLock")
-    private final SparseBooleanArray mRestrictBackgroundWhitelistRevokedUids =
+    private final SparseBooleanArray mRestrictBackgroundAllowlistRevokedUids =
             new SparseBooleanArray();
 
     /** Set of ifaces that are metered. */
@@ -582,7 +582,7 @@
     @GuardedBy("mUidRulesFirstLock")
     private final SparseBooleanArray mInternetPermissionMap = new SparseBooleanArray();
 
-    // TODO: keep whitelist of system-critical services that should never have
+    // TODO: keep allowlist of system-critical services that should never have
     // rules enforced, such as system, phone, and radio UIDs.
 
     // TODO: migrate notifications to SystemUI
@@ -668,26 +668,26 @@
     }
 
     /**
-     * Whitelists pre-defined apps for restrict background, but only if the user didn't already
-     * revoke the whitelist.
+     * Allows pre-defined apps for restrict background, but only if the user didn't already
+     * revoked them.
      *
-     * @return whether any uid has been whitelisted.
+     * @return whether any uid has been allowlisted.
      */
     @GuardedBy("mUidRulesFirstLock")
-    boolean addDefaultRestrictBackgroundWhitelistUidsUL() {
+    boolean addDefaultRestrictBackgroundAllowlistUidsUL() {
         final List<UserInfo> users = mUserManager.getUsers();
         final int numberUsers = users.size();
 
         boolean changed = false;
         for (int i = 0; i < numberUsers; i++) {
             final UserInfo user = users.get(i);
-            changed = addDefaultRestrictBackgroundWhitelistUidsUL(user.id) || changed;
+            changed = addDefaultRestrictBackgroundAllowlistUidsUL(user.id) || changed;
         }
         return changed;
     }
 
     @GuardedBy("mUidRulesFirstLock")
-    private boolean addDefaultRestrictBackgroundWhitelistUidsUL(int userId) {
+    private boolean addDefaultRestrictBackgroundAllowlistUidsUL(int userId) {
         final SystemConfig sysConfig = SystemConfig.getInstance();
         final PackageManager pm = mContext.getPackageManager();
         final ArraySet<String> allowDataUsage = sysConfig.getAllowInDataUsageSave();
@@ -695,7 +695,7 @@
         for (int i = 0; i < allowDataUsage.size(); i++) {
             final String pkg = allowDataUsage.valueAt(i);
             if (LOGD)
-                Slog.d(TAG, "checking restricted background whitelisting for package " + pkg
+                Slog.d(TAG, "checking restricted background allowlisting for package " + pkg
                         + " and user " + userId);
             final ApplicationInfo app;
             try {
@@ -706,20 +706,20 @@
                 continue;
             }
             if (!app.isPrivilegedApp()) {
-                Slog.e(TAG, "addDefaultRestrictBackgroundWhitelistUidsUL(): "
+                Slog.e(TAG, "addDefaultRestrictBackgroundAllowlistUidsUL(): "
                         + "skipping non-privileged app  " + pkg);
                 continue;
             }
             final int uid = UserHandle.getUid(userId, app.uid);
-            mDefaultRestrictBackgroundWhitelistUids.append(uid, true);
+            mDefaultRestrictBackgroundAllowlistUids.append(uid, true);
             if (LOGD)
                 Slog.d(TAG, "Adding uid " + uid + " (user " + userId + ") to default restricted "
-                        + "background whitelist. Revoked status: "
-                        + mRestrictBackgroundWhitelistRevokedUids.get(uid));
-            if (!mRestrictBackgroundWhitelistRevokedUids.get(uid)) {
+                        + "background allowlist. Revoked status: "
+                        + mRestrictBackgroundAllowlistRevokedUids.get(uid));
+            if (!mRestrictBackgroundAllowlistRevokedUids.get(uid)) {
                 if (LOGD)
                     Slog.d(TAG, "adding default package " + pkg + " (uid " + uid + " for user "
-                            + userId + ") to restrict background whitelist");
+                            + userId + ") to restrict background allowlist");
                 setUidPolicyUncheckedUL(uid, POLICY_ALLOW_METERED_BACKGROUND, false);
                 changed = true;
             }
@@ -799,7 +799,7 @@
                                 }
                             });
 
-                    if (addDefaultRestrictBackgroundWhitelistUidsUL()) {
+                    if (addDefaultRestrictBackgroundAllowlistUidsUL()) {
                         writePolicyAL();
                     }
 
@@ -1005,14 +1005,14 @@
                 case ACTION_USER_ADDED:
                     synchronized (mUidRulesFirstLock) {
                         // Remove any persistable state for the given user; both cleaning up after a
-                        // USER_REMOVED, and one last sanity check during USER_ADDED
+                        // USER_REMOVED, and one last check during USER_ADDED
                         removeUserStateUL(userId, true, false);
                         // Removing outside removeUserStateUL since that can also be called when
                         // user resets app preferences.
                         mMeteredRestrictedUids.remove(userId);
                         if (action == ACTION_USER_ADDED) {
-                            // Add apps that are whitelisted by default.
-                            addDefaultRestrictBackgroundWhitelistUidsUL(userId);
+                            // Add apps that are allowlisted by default.
+                            addDefaultRestrictBackgroundAllowlistUidsUL(userId);
                         }
                         // Update global restrict for that user
                         synchronized (mNetworkPoliciesSecondLock) {
@@ -2196,12 +2196,12 @@
             in.setInput(fis, StandardCharsets.UTF_8.name());
 
              // Must save the <restrict-background> tags and convert them to <uid-policy> later,
-             // to skip UIDs that were explicitly blacklisted.
-            final SparseBooleanArray whitelistedRestrictBackground = new SparseBooleanArray();
+             // to skip UIDs that were explicitly denylisted.
+            final SparseBooleanArray allowlistedRestrictBackground = new SparseBooleanArray();
 
             int type;
             int version = VERSION_INIT;
-            boolean insideWhitelist = false;
+            boolean insideAllowlist = false;
             while ((type = in.next()) != END_DOCUMENT) {
                 final String tag = in.getName();
                 if (type == START_TAG) {
@@ -2340,28 +2340,28 @@
                             Slog.w(TAG, "unable to apply policy to UID " + uid + "; ignoring");
                         }
                     } else if (TAG_WHITELIST.equals(tag)) {
-                        insideWhitelist = true;
-                    } else if (TAG_RESTRICT_BACKGROUND.equals(tag) && insideWhitelist) {
+                        insideAllowlist = true;
+                    } else if (TAG_RESTRICT_BACKGROUND.equals(tag) && insideAllowlist) {
                         final int uid = readIntAttribute(in, ATTR_UID);
-                        whitelistedRestrictBackground.append(uid, true);
-                    } else if (TAG_REVOKED_RESTRICT_BACKGROUND.equals(tag) && insideWhitelist) {
+                        allowlistedRestrictBackground.append(uid, true);
+                    } else if (TAG_REVOKED_RESTRICT_BACKGROUND.equals(tag) && insideAllowlist) {
                         final int uid = readIntAttribute(in, ATTR_UID);
-                        mRestrictBackgroundWhitelistRevokedUids.put(uid, true);
+                        mRestrictBackgroundAllowlistRevokedUids.put(uid, true);
                     }
                 } else if (type == END_TAG) {
                     if (TAG_WHITELIST.equals(tag)) {
-                        insideWhitelist = false;
+                        insideAllowlist = false;
                     }
 
                 }
             }
 
-            final int size = whitelistedRestrictBackground.size();
+            final int size = allowlistedRestrictBackground.size();
             for (int i = 0; i < size; i++) {
-                final int uid = whitelistedRestrictBackground.keyAt(i);
+                final int uid = allowlistedRestrictBackground.keyAt(i);
                 final int policy = mUidPolicy.get(uid, POLICY_NONE);
                 if ((policy & POLICY_REJECT_METERED_BACKGROUND) != 0) {
-                    Slog.w(TAG, "ignoring restrict-background-whitelist for " + uid
+                    Slog.w(TAG, "ignoring restrict-background-allowlist for " + uid
                             + " because its policy is " + uidPoliciesToString(policy));
                     continue;
                 }
@@ -2533,13 +2533,13 @@
 
             out.endTag(null, TAG_POLICY_LIST);
 
-            // write all whitelists
+            // write all allowlists
             out.startTag(null, TAG_WHITELIST);
 
-            // revoked restrict background whitelist
-            int size = mRestrictBackgroundWhitelistRevokedUids.size();
+            // revoked restrict background allowlist
+            int size = mRestrictBackgroundAllowlistRevokedUids.size();
             for (int i = 0; i < size; i++) {
-                final int uid = mRestrictBackgroundWhitelistRevokedUids.keyAt(i);
+                final int uid = mRestrictBackgroundAllowlistRevokedUids.keyAt(i);
                 out.startTag(null, TAG_REVOKED_RESTRICT_BACKGROUND);
                 writeIntAttribute(out, ATTR_UID, uid);
                 out.endTag(null, TAG_REVOKED_RESTRICT_BACKGROUND);
@@ -2619,21 +2619,21 @@
         setUidPolicyUncheckedUL(uid, policy, false);
 
         final boolean notifyApp;
-        if (!isUidValidForWhitelistRulesUL(uid)) {
+        if (!isUidValidForAllowlistRulesUL(uid)) {
             notifyApp = false;
         } else {
-            final boolean wasBlacklisted = oldPolicy == POLICY_REJECT_METERED_BACKGROUND;
-            final boolean isBlacklisted = policy == POLICY_REJECT_METERED_BACKGROUND;
-            final boolean wasWhitelisted = oldPolicy == POLICY_ALLOW_METERED_BACKGROUND;
-            final boolean isWhitelisted = policy == POLICY_ALLOW_METERED_BACKGROUND;
-            final boolean wasBlocked = wasBlacklisted || (mRestrictBackground && !wasWhitelisted);
-            final boolean isBlocked = isBlacklisted || (mRestrictBackground && !isWhitelisted);
-            if ((wasWhitelisted && (!isWhitelisted || isBlacklisted))
-                    && mDefaultRestrictBackgroundWhitelistUids.get(uid)
-                    && !mRestrictBackgroundWhitelistRevokedUids.get(uid)) {
+            final boolean wasDenylisted = oldPolicy == POLICY_REJECT_METERED_BACKGROUND;
+            final boolean isDenylisted = policy == POLICY_REJECT_METERED_BACKGROUND;
+            final boolean wasAllowlisted = oldPolicy == POLICY_ALLOW_METERED_BACKGROUND;
+            final boolean isAllowlisted = policy == POLICY_ALLOW_METERED_BACKGROUND;
+            final boolean wasBlocked = wasDenylisted || (mRestrictBackground && !wasAllowlisted);
+            final boolean isBlocked = isDenylisted || (mRestrictBackground && !isAllowlisted);
+            if ((wasAllowlisted && (!isAllowlisted || isDenylisted))
+                    && mDefaultRestrictBackgroundAllowlistUids.get(uid)
+                    && !mRestrictBackgroundAllowlistRevokedUids.get(uid)) {
                 if (LOGD)
-                    Slog.d(TAG, "Adding uid " + uid + " to revoked restrict background whitelist");
-                mRestrictBackgroundWhitelistRevokedUids.append(uid, true);
+                    Slog.d(TAG, "Adding uid " + uid + " to revoked restrict background allowlist");
+                mRestrictBackgroundAllowlistRevokedUids.append(uid, true);
             }
             notifyApp = wasBlocked != isBlocked;
         }
@@ -2700,11 +2700,11 @@
         mLogger.removingUserState(userId);
         boolean changed = false;
 
-        // Remove entries from revoked default restricted background UID whitelist
-        for (int i = mRestrictBackgroundWhitelistRevokedUids.size() - 1; i >= 0; i--) {
-            final int uid = mRestrictBackgroundWhitelistRevokedUids.keyAt(i);
+        // Remove entries from revoked default restricted background UID allowlist
+        for (int i = mRestrictBackgroundAllowlistRevokedUids.size() - 1; i >= 0; i--) {
+            final int uid = mRestrictBackgroundAllowlistRevokedUids.keyAt(i);
             if (UserHandle.getUserId(uid) == userId) {
-                mRestrictBackgroundWhitelistRevokedUids.removeAt(i);
+                mRestrictBackgroundAllowlistRevokedUids.removeAt(i);
                 changed = true;
             }
         }
@@ -2913,7 +2913,7 @@
             Slog.d(TAG, "setRestrictBackgroundUL(): " + restrictBackground + "; reason: " + reason);
             final boolean oldRestrictBackground = mRestrictBackground;
             mRestrictBackground = restrictBackground;
-            // Must whitelist foreground apps before turning data saver mode on.
+            // Must allowlist foreground apps before turning data saver mode on.
             // TODO: there is no need to iterate through all apps here, just those in the foreground,
             // so it could call AM to get the UIDs of such apps, and iterate through them instead.
             updateRulesForRestrictBackgroundUL();
@@ -2966,7 +2966,7 @@
                 Binder.restoreCallingIdentity(token);
             }
             if (policy == POLICY_REJECT_METERED_BACKGROUND) {
-                // App is blacklisted.
+                // App is denylisted.
                 return RESTRICT_BACKGROUND_STATUS_ENABLED;
             }
             if (!mRestrictBackground) {
@@ -3543,25 +3543,25 @@
                     fout.decreaseIndent();
                 }
 
-                size = mDefaultRestrictBackgroundWhitelistUids.size();
+                size = mDefaultRestrictBackgroundAllowlistUids.size();
                 if (size > 0) {
-                    fout.println("Default restrict background whitelist uids:");
+                    fout.println("Default restrict background allowlist uids:");
                     fout.increaseIndent();
                     for (int i = 0; i < size; i++) {
                         fout.print("UID=");
-                        fout.print(mDefaultRestrictBackgroundWhitelistUids.keyAt(i));
+                        fout.print(mDefaultRestrictBackgroundAllowlistUids.keyAt(i));
                         fout.println();
                     }
                     fout.decreaseIndent();
                 }
 
-                size = mRestrictBackgroundWhitelistRevokedUids.size();
+                size = mRestrictBackgroundAllowlistRevokedUids.size();
                 if (size > 0) {
-                    fout.println("Default restrict background whitelist uids revoked by users:");
+                    fout.println("Default restrict background allowlist uids revoked by users:");
                     fout.increaseIndent();
                     for (int i = 0; i < size; i++) {
                         fout.print("UID=");
-                        fout.print(mRestrictBackgroundWhitelistRevokedUids.keyAt(i));
+                        fout.print(mRestrictBackgroundAllowlistRevokedUids.keyAt(i));
                         fout.println();
                     }
                     fout.decreaseIndent();
@@ -3882,7 +3882,7 @@
 
     @GuardedBy("mUidRulesFirstLock")
     void updateRuleForAppIdleUL(int uid) {
-        if (!isUidValidForBlacklistRulesUL(uid)) return;
+        if (!isUidValidForDenylistRulesUL(uid)) return;
 
         if (Trace.isTagEnabled(Trace.TRACE_TAG_NETWORK)) {
             Trace.traceBegin(Trace.TRACE_TAG_NETWORK, "updateRuleForAppIdleUL: " + uid );
@@ -3915,13 +3915,13 @@
         final SparseIntArray blockedUids = new SparseIntArray();
         for (int i = 0; i < ruleCount; i++) {
             final int uid = mUidFirewallStandbyRules.keyAt(i);
-            if (!isUidValidForBlacklistRulesUL(uid)) {
+            if (!isUidValidForDenylistRulesUL(uid)) {
                 continue;
             }
             int oldRules = mUidRules.get(uid);
             if (enableChain) {
                 // Chain wasn't enabled before and the other power-related
-                // chains are whitelists, so we can clear the
+                // chains are allowlists, so we can clear the
                 // MASK_ALL_NETWORKS part of the rules and re-inform listeners if
                 // the effective rules result in blocking network access.
                 oldRules &= MASK_METERED_NETWORKS;
@@ -4079,10 +4079,10 @@
     // TODO: the MEDIA / DRM restriction might not be needed anymore, in which case both
     // methods below could be merged into a isUidValidForRules() method.
     @GuardedBy("mUidRulesFirstLock")
-    private boolean isUidValidForBlacklistRulesUL(int uid) {
+    private boolean isUidValidForDenylistRulesUL(int uid) {
         // allow rules on specific system services, and any apps
         if (uid == android.os.Process.MEDIA_UID || uid == android.os.Process.DRM_UID
-                || isUidValidForWhitelistRulesUL(uid)) {
+                || isUidValidForAllowlistRulesUL(uid)) {
             return true;
         }
 
@@ -4090,7 +4090,7 @@
     }
 
     @GuardedBy("mUidRulesFirstLock")
-    private boolean isUidValidForWhitelistRulesUL(int uid) {
+    private boolean isUidValidForAllowlistRulesUL(int uid) {
         return UserHandle.isApp(uid) && hasInternetPermissionUL(uid);
     }
 
@@ -4235,23 +4235,23 @@
 
     /**
      * Applies network rules to bandwidth controllers based on process state and user-defined
-     * restrictions (blacklist / whitelist).
+     * restrictions (allowlist / denylist).
      *
      * <p>
      * {@code netd} defines 3 firewall chains that govern whether an app has access to metered
      * networks:
      * <ul>
-     * <li>@{code bw_penalty_box}: UIDs added to this chain do not have access (blacklist).
-     * <li>@{code bw_happy_box}: UIDs added to this chain have access (whitelist), unless they're
-     *     also blacklisted.
+     * <li>@{code bw_penalty_box}: UIDs added to this chain do not have access (denylist).
+     * <li>@{code bw_happy_box}: UIDs added to this chain have access (allowlist), unless they're
+     *     also denylisted.
      * <li>@{code bw_data_saver}: when enabled (through {@link #setRestrictBackground(boolean)}),
-     *     no UIDs other than those whitelisted will have access.
+     *     no UIDs other than those allowlisted will have access.
      * <ul>
      *
      * <p>The @{code bw_penalty_box} and @{code bw_happy_box} are primarily managed through the
-     * {@link #setUidPolicy(int, int)} and {@link #addRestrictBackgroundWhitelistedUid(int)} /
-     * {@link #removeRestrictBackgroundWhitelistedUid(int)} methods (for blacklist and whitelist
-     * respectively): these methods set the proper internal state (blacklist / whitelist), then call
+     * {@link #setUidPolicy(int, int)} and {@link #addRestrictBackgroundAllowlistedUid(int)} /
+     * {@link #removeRestrictBackgroundDenylistedUid(int)} methods (for denylist and allowlist
+     * respectively): these methods set the proper internal state (denylist / allowlist), then call
      * this ({@link #updateRulesForDataUsageRestrictionsUL(int)}) to propagate the rules to
      * {@link INetworkManagementService}, but this method should also be called in events (like
      * Data Saver Mode flips or UID state changes) that might affect the foreground app, since the
@@ -4260,7 +4260,7 @@
      * <ul>
      * <li>When Data Saver mode is on, the foreground app should be temporarily added to
      *     {@code bw_happy_box} before the @{code bw_data_saver} chain is enabled.
-     * <li>If the foreground app is blacklisted by the user, it should be temporarily removed from
+     * <li>If the foreground app is denylisted by the user, it should be temporarily removed from
      *     {@code bw_penalty_box}.
      * <li>When the app leaves foreground state, the temporary changes above should be reverted.
      * </ul>
@@ -4285,7 +4285,7 @@
     }
 
     private void updateRulesForDataUsageRestrictionsULInner(int uid) {
-        if (!isUidValidForWhitelistRulesUL(uid)) {
+        if (!isUidValidForAllowlistRulesUL(uid)) {
             if (LOGD) Slog.d(TAG, "no need to update restrict data rules for uid " + uid);
             return;
         }
@@ -4295,8 +4295,8 @@
         final boolean isForeground = isUidForegroundOnRestrictBackgroundUL(uid);
         final boolean isRestrictedByAdmin = isRestrictedByAdminUL(uid);
 
-        final boolean isBlacklisted = (uidPolicy & POLICY_REJECT_METERED_BACKGROUND) != 0;
-        final boolean isWhitelisted = (uidPolicy & POLICY_ALLOW_METERED_BACKGROUND) != 0;
+        final boolean isDenylisted = (uidPolicy & POLICY_REJECT_METERED_BACKGROUND) != 0;
+        final boolean isAllowlisted = (uidPolicy & POLICY_ALLOW_METERED_BACKGROUND) != 0;
         final int oldRule = oldUidRules & MASK_METERED_NETWORKS;
         int newRule = RULE_NONE;
 
@@ -4304,15 +4304,15 @@
         if (isRestrictedByAdmin) {
             newRule = RULE_REJECT_METERED;
         } else if (isForeground) {
-            if (isBlacklisted || (mRestrictBackground && !isWhitelisted)) {
+            if (isDenylisted || (mRestrictBackground && !isAllowlisted)) {
                 newRule = RULE_TEMPORARY_ALLOW_METERED;
-            } else if (isWhitelisted) {
+            } else if (isAllowlisted) {
                 newRule = RULE_ALLOW_METERED;
             }
         } else {
-            if (isBlacklisted) {
+            if (isDenylisted) {
                 newRule = RULE_REJECT_METERED;
-            } else if (mRestrictBackground && isWhitelisted) {
+            } else if (mRestrictBackground && isAllowlisted) {
                 newRule = RULE_ALLOW_METERED;
             }
         }
@@ -4321,8 +4321,8 @@
         if (LOGV) {
             Log.v(TAG, "updateRuleForRestrictBackgroundUL(" + uid + ")"
                     + ": isForeground=" +isForeground
-                    + ", isBlacklisted=" + isBlacklisted
-                    + ", isWhitelisted=" + isWhitelisted
+                    + ", isDenylisted=" + isDenylisted
+                    + ", isAllowlisted=" + isAllowlisted
                     + ", isRestrictedByAdmin=" + isRestrictedByAdmin
                     + ", oldRule=" + uidRulesToString(oldRule)
                     + ", newRule=" + uidRulesToString(newRule)
@@ -4339,49 +4339,49 @@
         // Second step: apply bw changes based on change of state.
         if (newRule != oldRule) {
             if (hasRule(newRule, RULE_TEMPORARY_ALLOW_METERED)) {
-                // Temporarily whitelist foreground app, removing from blacklist if necessary
+                // Temporarily allowlist foreground app, removing from denylist if necessary
                 // (since bw_penalty_box prevails over bw_happy_box).
 
-                setMeteredNetworkWhitelist(uid, true);
+                setMeteredNetworkAllowlist(uid, true);
                 // TODO: if statement below is used to avoid an unnecessary call to netd / iptables,
                 // but ideally it should be just:
-                //    setMeteredNetworkBlacklist(uid, isBlacklisted);
-                if (isBlacklisted) {
-                    setMeteredNetworkBlacklist(uid, false);
+                //    setMeteredNetworkDenylist(uid, isDenylisted);
+                if (isDenylisted) {
+                    setMeteredNetworkDenylist(uid, false);
                 }
             } else if (hasRule(oldRule, RULE_TEMPORARY_ALLOW_METERED)) {
-                // Remove temporary whitelist from app that is not on foreground anymore.
+                // Remove temporary allowlist from app that is not on foreground anymore.
 
                 // TODO: if statements below are used to avoid unnecessary calls to netd / iptables,
                 // but ideally they should be just:
-                //    setMeteredNetworkWhitelist(uid, isWhitelisted);
-                //    setMeteredNetworkBlacklist(uid, isBlacklisted);
-                if (!isWhitelisted) {
-                    setMeteredNetworkWhitelist(uid, false);
+                //    setMeteredNetworkAllowlist(uid, isAllowlisted);
+                //    setMeteredNetworkDenylist(uid, isDenylisted);
+                if (!isAllowlisted) {
+                    setMeteredNetworkAllowlist(uid, false);
                 }
-                if (isBlacklisted || isRestrictedByAdmin) {
-                    setMeteredNetworkBlacklist(uid, true);
+                if (isDenylisted || isRestrictedByAdmin) {
+                    setMeteredNetworkDenylist(uid, true);
                 }
             } else if (hasRule(newRule, RULE_REJECT_METERED)
                     || hasRule(oldRule, RULE_REJECT_METERED)) {
-                // Flip state because app was explicitly added or removed to blacklist.
-                setMeteredNetworkBlacklist(uid, (isBlacklisted || isRestrictedByAdmin));
-                if (hasRule(oldRule, RULE_REJECT_METERED) && isWhitelisted) {
-                    // Since blacklist prevails over whitelist, we need to handle the special case
-                    // where app is whitelisted and blacklisted at the same time (although such
-                    // scenario should be blocked by the UI), then blacklist is removed.
-                    setMeteredNetworkWhitelist(uid, isWhitelisted);
+                // Flip state because app was explicitly added or removed to denylist.
+                setMeteredNetworkDenylist(uid, (isDenylisted || isRestrictedByAdmin));
+                if (hasRule(oldRule, RULE_REJECT_METERED) && isAllowlisted) {
+                    // Since denylist prevails over allowlist, we need to handle the special case
+                    // where app is allowlisted and denylisted at the same time (although such
+                    // scenario should be blocked by the UI), then denylist is removed.
+                    setMeteredNetworkAllowlist(uid, isAllowlisted);
                 }
             } else if (hasRule(newRule, RULE_ALLOW_METERED)
                     || hasRule(oldRule, RULE_ALLOW_METERED)) {
-                // Flip state because app was explicitly added or removed to whitelist.
-                setMeteredNetworkWhitelist(uid, isWhitelisted);
+                // Flip state because app was explicitly added or removed to allowlist.
+                setMeteredNetworkAllowlist(uid, isAllowlisted);
             } else {
                 // All scenarios should have been covered above.
                 Log.wtf(TAG, "Unexpected change of metered UID state for " + uid
                         + ": foreground=" + isForeground
-                        + ", whitelisted=" + isWhitelisted
-                        + ", blacklisted=" + isBlacklisted
+                        + ", allowlisted=" + isAllowlisted
+                        + ", denylisted=" + isDenylisted
                         + ", isRestrictedByAdmin=" + isRestrictedByAdmin
                         + ", newRule=" + uidRulesToString(newUidRules)
                         + ", oldRule=" + uidRulesToString(oldUidRules));
@@ -4397,7 +4397,7 @@
      * listeners in case of change.
      * <p>
      * There are 3 power-related rules that affects whether an app has background access on
-     * non-metered networks, and when the condition applies and the UID is not whitelisted for power
+     * non-metered networks, and when the condition applies and the UID is not allowlisted for power
      * restriction, it's added to the equivalent firewall chain:
      * <ul>
      * <li>App is idle: {@code fw_standby} firewall chain.
@@ -4406,7 +4406,7 @@
      * </ul>
      * <p>
      * This method updates the power-related part of the {@link #mUidRules} for a given uid based on
-     * these modes, the UID process state (foreground or not), and the UIDwhitelist state.
+     * these modes, the UID process state (foreground or not), and the UID allowlist state.
      * <p>
      * <strong>NOTE: </strong>This method does not update the firewall rules on {@code netd}.
      */
@@ -4450,7 +4450,7 @@
     @GuardedBy("mUidRulesFirstLock")
     private int updateRulesForPowerRestrictionsULInner(int uid, int oldUidRules,
             boolean isUidIdle) {
-        if (!isUidValidForBlacklistRulesUL(uid)) {
+        if (!isUidValidForDenylistRulesUL(uid)) {
             if (LOGD) Slog.d(TAG, "no need to update restrict power rules for uid " + uid);
             return RULE_NONE;
         }
@@ -4859,23 +4859,23 @@
         }
     }
 
-    private void setMeteredNetworkBlacklist(int uid, boolean enable) {
-        if (LOGV) Slog.v(TAG, "setMeteredNetworkBlacklist " + uid + ": " + enable);
+    private void setMeteredNetworkDenylist(int uid, boolean enable) {
+        if (LOGV) Slog.v(TAG, "setMeteredNetworkDenylist " + uid + ": " + enable);
         try {
-            mNetworkManager.setUidMeteredNetworkBlacklist(uid, enable);
+            mNetworkManager.setUidMeteredNetworkDenylist(uid, enable);
         } catch (IllegalStateException e) {
-            Log.wtf(TAG, "problem setting blacklist (" + enable + ") rules for " + uid, e);
+            Log.wtf(TAG, "problem setting denylist (" + enable + ") rules for " + uid, e);
         } catch (RemoteException e) {
             // ignored; service lives in system_server
         }
     }
 
-    private void setMeteredNetworkWhitelist(int uid, boolean enable) {
-        if (LOGV) Slog.v(TAG, "setMeteredNetworkWhitelist " + uid + ": " + enable);
+    private void setMeteredNetworkAllowlist(int uid, boolean enable) {
+        if (LOGV) Slog.v(TAG, "setMeteredNetworkAllowlist " + uid + ": " + enable);
         try {
-            mNetworkManager.setUidMeteredNetworkWhitelist(uid, enable);
+            mNetworkManager.setUidMeteredNetworkAllowlist(uid, enable);
         } catch (IllegalStateException e) {
-            Log.wtf(TAG, "problem setting whitelist (" + enable + ") rules for " + uid, e);
+            Log.wtf(TAG, "problem setting allowlist (" + enable + ") rules for " + uid, e);
         } catch (RemoteException e) {
             // ignored; service lives in system_server
         }
@@ -4936,7 +4936,7 @@
     }
 
     /**
-     * Add or remove a uid to the firewall blacklist for all network ifaces.
+     * Add or remove a uid to the firewall denylist for all network ifaces.
      */
     private void setUidFirewallRule(int chain, int uid, int rule) {
         if (Trace.isTagEnabled(Trace.TRACE_TAG_NETWORK)) {
@@ -4966,7 +4966,7 @@
     }
 
     /**
-     * Add or remove a uid to the firewall blacklist for all network ifaces.
+     * Add or remove a uid to the firewall denylist for all network ifaces.
      */
     @GuardedBy("mUidRulesFirstLock")
     private void enableFirewallChainUL(int chain, boolean enable) {
@@ -4995,8 +4995,8 @@
             mNetworkManager.setFirewallUidRule(FIREWALL_CHAIN_STANDBY, uid, FIREWALL_RULE_DEFAULT);
             mNetworkManager
                     .setFirewallUidRule(FIREWALL_CHAIN_POWERSAVE, uid, FIREWALL_RULE_DEFAULT);
-            mNetworkManager.setUidMeteredNetworkWhitelist(uid, false);
-            mNetworkManager.setUidMeteredNetworkBlacklist(uid, false);
+            mNetworkManager.setUidMeteredNetworkAllowlist(uid, false);
+            mNetworkManager.setUidMeteredNetworkDenylist(uid, false);
         } catch (IllegalStateException e) {
             Log.wtf(TAG, "problem resetting firewall uid rules for " + uid, e);
         } catch (RemoteException e) {
@@ -5189,13 +5189,13 @@
             reason = NTWK_ALLOWED_NON_METERED;
         }
         else if (hasRule(uidRules, RULE_REJECT_METERED)) {
-            reason = NTWK_BLOCKED_BLACKLIST;
+            reason = NTWK_BLOCKED_DENYLIST;
         }
         else if (hasRule(uidRules, RULE_ALLOW_METERED)) {
-            reason = NTWK_ALLOWED_WHITELIST;
+            reason = NTWK_ALLOWED_ALLOWLIST;
         }
         else if (hasRule(uidRules, RULE_TEMPORARY_ALLOW_METERED)) {
-            reason = NTWK_ALLOWED_TMP_WHITELIST;
+            reason = NTWK_ALLOWED_TMP_ALLOWLIST;
         }
         else if (isBackgroundRestricted) {
             reason = NTWK_BLOCKED_BG_RESTRICT;
@@ -5208,13 +5208,13 @@
         switch(reason) {
             case NTWK_ALLOWED_DEFAULT:
             case NTWK_ALLOWED_NON_METERED:
-            case NTWK_ALLOWED_TMP_WHITELIST:
-            case NTWK_ALLOWED_WHITELIST:
+            case NTWK_ALLOWED_TMP_ALLOWLIST:
+            case NTWK_ALLOWED_ALLOWLIST:
             case NTWK_ALLOWED_SYSTEM:
                 blocked = false;
                 break;
             case NTWK_BLOCKED_POWER:
-            case NTWK_BLOCKED_BLACKLIST:
+            case NTWK_BLOCKED_DENYLIST:
             case NTWK_BLOCKED_BG_RESTRICT:
                 blocked = true;
                 break;
@@ -5234,7 +5234,7 @@
         public void resetUserState(int userId) {
             synchronized (mUidRulesFirstLock) {
                 boolean changed = removeUserStateUL(userId, false, true);
-                changed = addDefaultRestrictBackgroundWhitelistUidsUL(userId) || changed;
+                changed = addDefaultRestrictBackgroundAllowlistUidsUL(userId) || changed;
                 if (changed) {
                     synchronized (mNetworkPoliciesSecondLock) {
                         writePolicyAL();
diff --git a/services/core/java/com/android/server/net/NetworkStatsSubscriptionsMonitor.java b/services/core/java/com/android/server/net/NetworkStatsSubscriptionsMonitor.java
index 0575ac6..d202a2a 100644
--- a/services/core/java/com/android/server/net/NetworkStatsSubscriptionsMonitor.java
+++ b/services/core/java/com/android/server/net/NetworkStatsSubscriptionsMonitor.java
@@ -100,11 +100,16 @@
             if (match != null) continue;
 
             // Create listener for every newly added sub. Also store subscriberId into it to
-            // prevent binder call to telephony when querying RAT.
+            // prevent binder call to telephony when querying RAT. If the subscriberId is empty
+            // for any reason, such as SIM PIN locked, skip registration.
+            // SubscriberId will be unavailable again if 1. modem crashed 2. reboot
+            // 3. re-insert SIM. If that happens, the listeners will be eventually synchronized
+            // with active sub list once all subscriberIds are ready.
             final String subscriberId = mTeleManager.getSubscriberId(subId);
             if (TextUtils.isEmpty(subscriberId)) {
-                Log.wtf(NetworkStatsService.TAG,
-                        "Empty subscriberId for newly added sub: " + subId);
+                Log.d(NetworkStatsService.TAG, "Empty subscriberId for newly added sub "
+                        + subId + ", skip listener registration");
+                continue;
             }
             final RatTypeListener listener =
                     new RatTypeListener(mExecutor, this, subId, subscriberId);
@@ -113,6 +118,7 @@
             // Register listener to the telephony manager that associated with specific sub.
             mTeleManager.createForSubscriptionId(subId)
                     .listen(listener, PhoneStateListener.LISTEN_SERVICE_STATE);
+            Log.d(NetworkStatsService.TAG, "RAT type listener registered for sub " + subId);
         }
 
         for (final RatTypeListener listener : new ArrayList<>(mRatListeners)) {
@@ -165,6 +171,7 @@
     private void handleRemoveRatTypeListener(@NonNull RatTypeListener listener) {
         mTeleManager.createForSubscriptionId(listener.mSubId)
                 .listen(listener, PhoneStateListener.LISTEN_NONE);
+        Log.d(NetworkStatsService.TAG, "RAT type listener unregistered for sub " + listener.mSubId);
         mRatListeners.remove(listener);
 
         // Removal of subscriptions doesn't generate RAT changed event, fire it for every
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index eefff2fb..0465855 100755
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -31,6 +31,7 @@
 import static android.app.NotificationManager.ACTION_INTERRUPTION_FILTER_CHANGED_INTERNAL;
 import static android.app.NotificationManager.ACTION_NOTIFICATION_CHANNEL_BLOCK_STATE_CHANGED;
 import static android.app.NotificationManager.ACTION_NOTIFICATION_CHANNEL_GROUP_BLOCK_STATE_CHANGED;
+import static android.app.NotificationManager.ACTION_NOTIFICATION_LISTENER_ENABLED_CHANGED;
 import static android.app.NotificationManager.ACTION_NOTIFICATION_POLICY_ACCESS_GRANTED_CHANGED;
 import static android.app.NotificationManager.BUBBLE_PREFERENCE_ALL;
 import static android.app.NotificationManager.EXTRA_AUTOMATIC_ZEN_RULE_ID;
@@ -261,6 +262,7 @@
 import com.android.server.IoThread;
 import com.android.server.LocalServices;
 import com.android.server.SystemService;
+import com.android.server.SystemService.TargetUser;
 import com.android.server.UiThread;
 import com.android.server.lights.LightsManager;
 import com.android.server.lights.LogicalLight;
@@ -322,7 +324,7 @@
     static final boolean DEBUG_INTERRUPTIVENESS = SystemProperties.getBoolean(
             "debug.notification.interruptiveness", false);
 
-    static final int MAX_PACKAGE_NOTIFICATIONS = 25;
+    static final int MAX_PACKAGE_NOTIFICATIONS = 50;
     static final float DEFAULT_MAX_NOTIFICATION_ENQUEUE_RATE = 5f;
 
     // message codes
@@ -2351,11 +2353,11 @@
     }
 
     @Override
-    public void onUnlockUser(@NonNull UserInfo userInfo) {
+    public void onUserUnlocking(@NonNull TargetUser user) {
         mHandler.post(() -> {
             Trace.traceBegin(Trace.TRACE_TAG_SYSTEM_SERVER, "notifHistoryUnlockUser");
             try {
-                mHistoryManager.onUserUnlocked(userInfo.id);
+                mHistoryManager.onUserUnlocked(user.getUserIdentifier());
             } finally {
                 Trace.traceEnd(Trace.TRACE_TAG_SYSTEM_SERVER);
             }
@@ -2363,11 +2365,11 @@
     }
 
     @Override
-    public void onStopUser(@NonNull UserInfo userInfo) {
+    public void onUserStopping(@NonNull TargetUser user) {
         mHandler.post(() -> {
             Trace.traceBegin(Trace.TRACE_TAG_SYSTEM_SERVER, "notifHistoryStopUser");
             try {
-                mHistoryManager.onUserStopped(userInfo.id);
+                mHistoryManager.onUserStopped(user.getUserIdentifier());
             } finally {
                 Trace.traceEnd(Trace.TRACE_TAG_SYSTEM_SERVER);
             }
@@ -6101,7 +6103,8 @@
     protected boolean isBlocked(NotificationRecord r, NotificationUsageStats usageStats) {
         if (isBlocked(r)) {
             if (DBG) {
-                Slog.e(TAG, "Suppressing notification from package by user request.");
+                Slog.e(TAG, "Suppressing notification from package " + r.getSbn().getPackageName()
+                        + " by user request.");
             }
             usageStats.registerBlocked(r);
             return true;
@@ -7287,12 +7290,12 @@
 
     @GuardedBy("mToastQueue")
     private void keepProcessAliveForToastIfNeededLocked(int pid) {
-        int toastCount = 0; // toasts from this pid
+        int toastCount = 0; // toasts from this pid, rendered by the app
         ArrayList<ToastRecord> list = mToastQueue;
         int n = list.size();
         for (int i = 0; i < n; i++) {
             ToastRecord r = list.get(i);
-            if (r.pid == pid) {
+            if (r.pid == pid && r.keepProcessAlive()) {
                 toastCount++;
             }
         }
@@ -7757,6 +7760,13 @@
     @VisibleForTesting
     void updateUriPermissions(@Nullable NotificationRecord newRecord,
             @Nullable NotificationRecord oldRecord, String targetPkg, int targetUserId) {
+        updateUriPermissions(newRecord, oldRecord, targetPkg, targetUserId, false);
+    }
+
+    @VisibleForTesting
+    void updateUriPermissions(@Nullable NotificationRecord newRecord,
+            @Nullable NotificationRecord oldRecord, String targetPkg, int targetUserId,
+            boolean onlyRevokeCurrentTarget) {
         final String key = (newRecord != null) ? newRecord.getKey() : oldRecord.getKey();
         if (DBG) Slog.d(TAG, key + ": updating permissions");
 
@@ -7784,7 +7794,9 @@
         }
 
         // If we have no Uris to grant, but an existing owner, go destroy it
-        if (newUris == null && permissionOwner != null) {
+        // When revoking permissions of a single listener, destroying the owner will revoke
+        // permissions of other listeners who need to keep access.
+        if (newUris == null && permissionOwner != null && !onlyRevokeCurrentTarget) {
             destroyPermissionOwner(permissionOwner, UserHandle.getUserId(oldRecord.getUid()), key);
             permissionOwner = null;
         }
@@ -7807,9 +7819,20 @@
                 final Uri uri = oldUris.valueAt(i);
                 if (newUris == null || !newUris.contains(uri)) {
                     if (DBG) Slog.d(TAG, key + ": revoking " + uri);
-                    int userId = ContentProvider.getUserIdFromUri(
-                            uri, UserHandle.getUserId(oldRecord.getUid()));
-                    revokeUriPermission(permissionOwner, uri, userId);
+                    if (onlyRevokeCurrentTarget) {
+                        // We're revoking permission from one listener only; other listeners may
+                        // still need access because the notification may still exist
+                        revokeUriPermission(permissionOwner, uri,
+                                UserHandle.getUserId(oldRecord.getUid()), targetPkg, targetUserId);
+                    } else {
+                        // This is broad to unilaterally revoke permissions to this Uri as granted
+                        // by this notification.  But this code-path can only be used when the
+                        // reason for revoking is that the notification posted again without this
+                        // Uri, not when removing an individual listener.
+                        revokeUriPermission(permissionOwner, uri,
+                                UserHandle.getUserId(oldRecord.getUid()),
+                                null, UserHandle.USER_ALL);
+                    }
                 }
             }
         }
@@ -7838,8 +7861,10 @@
         }
     }
 
-    private void revokeUriPermission(IBinder owner, Uri uri, int userId) {
+    private void revokeUriPermission(IBinder owner, Uri uri, int sourceUserId, String targetPkg,
+            int targetUserId) {
         if (uri == null || !ContentResolver.SCHEME_CONTENT.equals(uri.getScheme())) return;
+        int userId = ContentProvider.getUserIdFromUri(uri, sourceUserId);
 
         final long ident = Binder.clearCallingIdentity();
         try {
@@ -7847,7 +7872,7 @@
                     owner,
                     ContentProvider.getUriWithoutUserId(uri),
                     Intent.FLAG_GRANT_READ_URI_PERMISSION,
-                    userId);
+                    userId, targetPkg, targetUserId);
         } finally {
             Binder.restoreCallingIdentity(ident);
         }
@@ -9157,6 +9182,17 @@
         }
 
         @Override
+        protected void setPackageOrComponentEnabled(String pkgOrComponent, int userId,
+                boolean isPrimary, boolean enabled) {
+            super.setPackageOrComponentEnabled(pkgOrComponent, userId, isPrimary, enabled);
+
+            getContext().sendBroadcastAsUser(
+                    new Intent(ACTION_NOTIFICATION_LISTENER_ENABLED_CHANGED)
+                            .addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY),
+                    UserHandle.ALL, null);
+        }
+
+        @Override
         protected void loadDefaultsFromConfig() {
             String defaultListenerAccess = mContext.getResources().getString(
                     R.string.config_defaultListenerAccessPackages);
@@ -9218,6 +9254,7 @@
             final NotificationRankingUpdate update;
             synchronized (mNotificationLock) {
                 update = makeRankingUpdateLocked(info);
+                updateUriPermissionsForActiveNotificationsLocked(info, true);
             }
             try {
                 listener.onListenerConnected(update);
@@ -9229,6 +9266,7 @@
         @Override
         @GuardedBy("mNotificationLock")
         protected void onServiceRemovedLocked(ManagedServiceInfo removed) {
+            updateUriPermissionsForActiveNotificationsLocked(removed, false);
             if (removeDisabledHints(removed)) {
                 updateListenerHintsLocked();
                 updateEffectsSuppressorLocked();
@@ -9295,8 +9333,7 @@
 
                 for (final ManagedServiceInfo info : getServices()) {
                     boolean sbnVisible = isVisibleToListener(sbn, info);
-                    boolean oldSbnVisible = oldSbn != null ? isVisibleToListener(oldSbn, info)
-                            : false;
+                    boolean oldSbnVisible = (oldSbn != null) && isVisibleToListener(oldSbn, info);
                     // This notification hasn't been and still isn't visible -> ignore.
                     if (!oldSbnVisible && !sbnVisible) {
                         continue;
@@ -9320,13 +9357,8 @@
                     // This notification became invisible -> remove the old one.
                     if (oldSbnVisible && !sbnVisible) {
                         final StatusBarNotification oldSbnLightClone = oldSbn.cloneLight();
-                        mHandler.post(new Runnable() {
-                            @Override
-                            public void run() {
-                                notifyRemoved(
-                                        info, oldSbnLightClone, update, null, REASON_USER_STOPPED);
-                            }
-                        });
+                        mHandler.post(() -> notifyRemoved(
+                                info, oldSbnLightClone, update, null, REASON_USER_STOPPED));
                         continue;
                     }
 
@@ -9336,12 +9368,7 @@
                     updateUriPermissions(r, old, info.component.getPackageName(), targetUserId);
 
                     final StatusBarNotification sbnToPost = trimCache.ForListener(info);
-                    mHandler.post(new Runnable() {
-                        @Override
-                        public void run() {
-                            notifyPosted(info, sbnToPost, update);
-                        }
-                    });
+                    mHandler.post(() -> notifyPosted(info, sbnToPost, update));
                 }
             } catch (Exception e) {
                 Slog.e(TAG, "Could not notify listeners for " + r.getKey(), e);
@@ -9349,6 +9376,46 @@
         }
 
         /**
+         * Synchronously grant or revoke permissions to Uris for all active and visible
+         * notifications to just the NotificationListenerService provided.
+         */
+        @GuardedBy("mNotificationLock")
+        private void updateUriPermissionsForActiveNotificationsLocked(
+                ManagedServiceInfo info, boolean grant) {
+            try {
+                for (final NotificationRecord r : mNotificationList) {
+                    // When granting permissions, ignore notifications which are invisible.
+                    // When revoking permissions, all notifications are invisible, so process all.
+                    if (grant && !isVisibleToListener(r.getSbn(), info)) {
+                        continue;
+                    }
+                    // If the notification is hidden, permissions are not required by the listener.
+                    if (r.isHidden() && info.targetSdkVersion < Build.VERSION_CODES.P) {
+                        continue;
+                    }
+                    // Grant or revoke access synchronously
+                    final int targetUserId = (info.userid == UserHandle.USER_ALL)
+                            ? UserHandle.USER_SYSTEM : info.userid;
+                    if (grant) {
+                        // Grant permissions by passing arguments as if the notification is new.
+                        updateUriPermissions(/* newRecord */ r, /* oldRecord */ null,
+                                info.component.getPackageName(), targetUserId);
+                    } else {
+                        // Revoke permissions by passing arguments as if the notification was
+                        // removed, but set `onlyRevokeCurrentTarget` to avoid revoking permissions
+                        // granted to *other* targets by this notification's URIs.
+                        updateUriPermissions(/* newRecord */ null, /* oldRecord */ r,
+                                info.component.getPackageName(), targetUserId,
+                                /* onlyRevokeCurrentTarget */ true);
+                    }
+                }
+            } catch (Exception e) {
+                Slog.e(TAG, "Could not " + (grant ? "grant" : "revoke") + " Uri permissions to "
+                        + info.component, e);
+            }
+        }
+
+        /**
          * asynchronously notify all listeners about a removed notification
          */
         @GuardedBy("mNotificationLock")
@@ -9383,18 +9450,11 @@
                 final NotificationStats stats = mAssistants.isServiceTokenValidLocked(info.service)
                         ? notificationStats : null;
                 final NotificationRankingUpdate update = makeRankingUpdateLocked(info);
-                mHandler.post(new Runnable() {
-                    @Override
-                    public void run() {
-                        notifyRemoved(info, sbnLight, update, stats, reason);
-                    }
-                });
+                mHandler.post(() -> notifyRemoved(info, sbnLight, update, stats, reason));
             }
 
             // Revoke access after all listeners have been updated
-            mHandler.post(() -> {
-                updateUriPermissions(null, r, null, UserHandle.USER_SYSTEM);
-            });
+            mHandler.post(() -> updateUriPermissions(null, r, null, UserHandle.USER_SYSTEM));
         }
 
         /**
diff --git a/services/core/java/com/android/server/notification/toast/CustomToastRecord.java b/services/core/java/com/android/server/notification/toast/CustomToastRecord.java
index 2b91a00..17e0b39 100644
--- a/services/core/java/com/android/server/notification/toast/CustomToastRecord.java
+++ b/services/core/java/com/android/server/notification/toast/CustomToastRecord.java
@@ -71,6 +71,13 @@
     }
 
     @Override
+    public boolean keepProcessAlive() {
+        // As custom toasts are rendered by the app, we need to keep the app alive for it to show
+        // the toast.
+        return true;
+    }
+
+    @Override
     public String toString() {
         return "CustomToastRecord{"
                 + Integer.toHexString(System.identityHashCode(this))
diff --git a/services/core/java/com/android/server/notification/toast/ToastRecord.java b/services/core/java/com/android/server/notification/toast/ToastRecord.java
index 7915f70..33906cc 100644
--- a/services/core/java/com/android/server/notification/toast/ToastRecord.java
+++ b/services/core/java/com/android/server/notification/toast/ToastRecord.java
@@ -85,4 +85,14 @@
         }
         pw.println(prefix + this);
     }
+
+    /**
+     * Returns whether it's necessary to bump the process state to keep it alive in order to show
+     * the toast.
+     */
+    public boolean keepProcessAlive() {
+        // By default we assume the toast is rendered by the systemUI. Any toast rendered by the app
+        // should override this method.
+        return false;
+    }
 }
diff --git a/services/core/java/com/android/server/om/IdmapManager.java b/services/core/java/com/android/server/om/IdmapManager.java
index d6b1b27..cb6e960 100644
--- a/services/core/java/com/android/server/om/IdmapManager.java
+++ b/services/core/java/com/android/server/om/IdmapManager.java
@@ -27,6 +27,7 @@
 import android.os.Build.VERSION_CODES;
 import android.os.OverlayablePolicy;
 import android.os.SystemProperties;
+import android.text.TextUtils;
 import android.util.Slog;
 
 import java.io.IOException;
@@ -53,11 +54,20 @@
     }
 
     private final IdmapDaemon mIdmapDaemon;
-    private final OverlayableInfoCallback mOverlayableCallback;
+    private final PackageManagerHelper mPackageManager;
 
-    IdmapManager(final IdmapDaemon idmapDaemon, final OverlayableInfoCallback verifyCallback) {
-        mOverlayableCallback = verifyCallback;
+    /**
+     * Package name of the reference package defined in 'config-signature' tag of
+     * SystemConfig or empty String if tag not defined. This package is vetted on scan by
+     * PackageManagerService that it's a system package and is used to check if overlay matches
+     * its signature in order to fulfill the config_signature policy.
+     */
+    private final String mConfigSignaturePackage;
+
+    IdmapManager(final IdmapDaemon idmapDaemon, final PackageManagerHelper packageManager) {
+        mPackageManager = packageManager;
         mIdmapDaemon = idmapDaemon;
+        mConfigSignaturePackage = packageManager.getConfigSignaturePackage();
     }
 
     /**
@@ -139,7 +149,7 @@
         int fulfilledPolicies = OverlayablePolicy.PUBLIC;
 
         // Overlay matches target signature
-        if (mOverlayableCallback.signaturesMatching(targetPackage.packageName,
+        if (mPackageManager.signaturesMatching(targetPackage.packageName,
                 overlayPackage.packageName, userId)) {
             fulfilledPolicies |= OverlayablePolicy.SIGNATURE;
         }
@@ -149,6 +159,16 @@
             fulfilledPolicies |= OverlayablePolicy.ACTOR_SIGNATURE;
         }
 
+        // If SystemConfig defines 'config-signature' package, given that
+        // this package is vetted by OverlayManagerService that it's a
+        // preinstalled package, check if overlay matches its signature.
+        if (!TextUtils.isEmpty(mConfigSignaturePackage)
+                && mPackageManager.signaturesMatching(mConfigSignaturePackage,
+                                                           overlayPackage.packageName,
+                                                           userId)) {
+            fulfilledPolicies |= OverlayablePolicy.CONFIG_SIGNATURE;
+        }
+
         // Vendor partition (/vendor)
         if (ai.isVendor()) {
             return fulfilledPolicies | OverlayablePolicy.VENDOR_PARTITION;
@@ -183,12 +203,12 @@
         String targetOverlayableName = overlayPackage.targetOverlayableName;
         if (targetOverlayableName != null) {
             try {
-                OverlayableInfo overlayableInfo = mOverlayableCallback.getOverlayableForTarget(
+                OverlayableInfo overlayableInfo = mPackageManager.getOverlayableForTarget(
                         targetPackage.packageName, targetOverlayableName, userId);
                 if (overlayableInfo != null && overlayableInfo.actor != null) {
                     String actorPackageName = OverlayActorEnforcer.getPackageNameForActor(
-                            overlayableInfo.actor, mOverlayableCallback.getNamedActors()).first;
-                    if (mOverlayableCallback.signaturesMatching(actorPackageName,
+                            overlayableInfo.actor, mPackageManager.getNamedActors()).first;
+                    if (mPackageManager.signaturesMatching(actorPackageName,
                             overlayPackage.packageName, userId)) {
                         return true;
                     }
diff --git a/services/core/java/com/android/server/om/OverlayActorEnforcer.java b/services/core/java/com/android/server/om/OverlayActorEnforcer.java
index 2bc3499..8c03c6c 100644
--- a/services/core/java/com/android/server/om/OverlayActorEnforcer.java
+++ b/services/core/java/com/android/server/om/OverlayActorEnforcer.java
@@ -45,7 +45,7 @@
     // By default, the reason is not logged to prevent leaks of why it failed
     private static final boolean DEBUG_REASON = false;
 
-    private final OverlayableInfoCallback mOverlayableCallback;
+    private final PackageManagerHelper mPackageManager;
 
     /**
      * @return nullable actor result with {@link ActorState} failure status
@@ -79,8 +79,8 @@
         return Pair.create(packageName, ActorState.ALLOWED);
     }
 
-    public OverlayActorEnforcer(@NonNull OverlayableInfoCallback overlayableCallback) {
-        mOverlayableCallback = overlayableCallback;
+    public OverlayActorEnforcer(@NonNull PackageManagerHelper packageManager) {
+        mPackageManager = packageManager;
     }
 
     void enforceActor(@NonNull OverlayInfo overlayInfo, @NonNull String methodName,
@@ -110,7 +110,7 @@
                 return ActorState.ALLOWED;
         }
 
-        String[] callingPackageNames = mOverlayableCallback.getPackagesForUid(callingUid);
+        String[] callingPackageNames = mPackageManager.getPackagesForUid(callingUid);
         if (ArrayUtils.isEmpty(callingPackageNames)) {
             return ActorState.NO_PACKAGES_FOR_UID;
         }
@@ -125,12 +125,12 @@
 
         if (TextUtils.isEmpty(targetOverlayableName)) {
             try {
-                if (mOverlayableCallback.doesTargetDefineOverlayable(targetPackageName, userId)) {
+                if (mPackageManager.doesTargetDefineOverlayable(targetPackageName, userId)) {
                     return ActorState.MISSING_TARGET_OVERLAYABLE_NAME;
                 } else {
                     // If there's no overlayable defined, fallback to the legacy permission check
                     try {
-                        mOverlayableCallback.enforcePermission(
+                        mPackageManager.enforcePermission(
                                 android.Manifest.permission.CHANGE_OVERLAY_PACKAGES, methodName);
 
                         // If the previous method didn't throw, check passed
@@ -146,7 +146,7 @@
 
         OverlayableInfo targetOverlayable;
         try {
-            targetOverlayable = mOverlayableCallback.getOverlayableForTarget(targetPackageName,
+            targetOverlayable = mPackageManager.getOverlayableForTarget(targetPackageName,
                     targetOverlayableName, userId);
         } catch (IOException e) {
             return ActorState.UNABLE_TO_GET_TARGET;
@@ -160,7 +160,7 @@
         if (TextUtils.isEmpty(actor)) {
             // If there's no actor defined, fallback to the legacy permission check
             try {
-                mOverlayableCallback.enforcePermission(
+                mPackageManager.enforcePermission(
                         android.Manifest.permission.CHANGE_OVERLAY_PACKAGES, methodName);
 
                 // If the previous method didn't throw, check passed
@@ -170,7 +170,7 @@
             }
         }
 
-        Map<String, Map<String, String>> namedActors = mOverlayableCallback.getNamedActors();
+        Map<String, Map<String, String>> namedActors = mPackageManager.getNamedActors();
         Pair<String, ActorState> actorUriPair = getPackageNameForActor(actor, namedActors);
         ActorState actorUriState = actorUriPair.second;
         if (actorUriState != ActorState.ALLOWED) {
@@ -178,7 +178,7 @@
         }
 
         String packageName = actorUriPair.first;
-        PackageInfo packageInfo = mOverlayableCallback.getPackageInfo(packageName, userId);
+        PackageInfo packageInfo = mPackageManager.getPackageInfo(packageName, userId);
         if (packageInfo == null) {
             return ActorState.MISSING_APP_INFO;
         }
diff --git a/services/core/java/com/android/server/om/OverlayManagerService.java b/services/core/java/com/android/server/om/OverlayManagerService.java
index 3968153..a4debc1 100644
--- a/services/core/java/com/android/server/om/OverlayManagerService.java
+++ b/services/core/java/com/android/server/om/OverlayManagerService.java
@@ -31,6 +31,7 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.annotation.UserIdInt;
 import android.app.ActivityManager;
 import android.app.IActivityManager;
 import android.content.BroadcastReceiver;
@@ -68,6 +69,7 @@
 import com.android.server.LocalServices;
 import com.android.server.SystemConfig;
 import com.android.server.SystemService;
+import com.android.server.SystemService.TargetUser;
 import com.android.server.pm.UserManagerService;
 
 import libcore.util.EmptyArray;
@@ -303,7 +305,11 @@
     }
 
     @Override
-    public void onSwitchUser(final int newUserId) {
+    public void onUserSwitching(@Nullable TargetUser from, @NonNull TargetUser to) {
+        onSwitchUser(to.getUserIdentifier());
+    }
+
+    private void onSwitchUser(@UserIdInt int newUserId) {
         try {
             traceBegin(TRACE_TAG_RRO, "OMS#onSwitchUser " + newUserId);
             // ensure overlays in the settings are up-to-date, and propagate
@@ -1053,8 +1059,7 @@
         }
     }
 
-    private static final class PackageManagerHelperImpl implements PackageManagerHelper,
-            OverlayableInfoCallback {
+    private static final class PackageManagerHelperImpl implements PackageManagerHelper  {
 
         private final Context mContext;
         private final IPackageManager mPackageManager;
@@ -1127,6 +1132,14 @@
             return overlays;
         }
 
+        @Override
+        public String getConfigSignaturePackage() {
+            final String[] pkgs = mPackageManagerInternal.getKnownPackageNames(
+                    PackageManagerInternal.PACKAGE_OVERLAY_CONFIG_SIGNATURE,
+                    UserHandle.USER_SYSTEM);
+            return (pkgs.length == 0) ? null : pkgs[0];
+        }
+
         @Nullable
         @Override
         public OverlayableInfo getOverlayableForTarget(@NonNull String packageName,
diff --git a/services/core/java/com/android/server/om/OverlayableInfoCallback.java b/services/core/java/com/android/server/om/OverlayableInfoCallback.java
deleted file mode 100644
index 5066ecd..0000000
--- a/services/core/java/com/android/server/om/OverlayableInfoCallback.java
+++ /dev/null
@@ -1,83 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.om;
-
-
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.content.om.OverlayableInfo;
-import android.content.pm.PackageInfo;
-import android.content.pm.PackageManager;
-
-import com.android.server.pm.PackageManagerServiceUtils;
-
-import java.io.IOException;
-import java.util.Map;
-
-/**
- * Delegate to the system for querying information about overlayables and packages.
- */
-public interface OverlayableInfoCallback {
-
-    /**
-     * Read from the APK and AndroidManifest of a package to return the overlayable defined for
-     * a given name.
-     *
-     * @throws IOException if the target can't be read
-     */
-    @Nullable
-    OverlayableInfo getOverlayableForTarget(@NonNull String packageName,
-            @NonNull String targetOverlayableName, int userId)
-            throws IOException;
-
-    /**
-     * @see PackageManager#getPackagesForUid(int)
-     */
-    @Nullable
-    String[] getPackagesForUid(int uid);
-
-    /**
-     * @param userId user to filter package visibility by
-     * @see PackageManager#getPackageInfo(String, int)
-     */
-    @Nullable
-    PackageInfo getPackageInfo(@NonNull String packageName, int userId);
-
-    /**
-     * @return map of system pre-defined, uniquely named actors; keys are namespace,
-     * value maps actor name to package name
-     */
-    @NonNull
-    Map<String, Map<String, String>> getNamedActors();
-
-    /**
-     * @return true if the target package has declared an overlayable
-     */
-    boolean doesTargetDefineOverlayable(String targetPackageName, int userId) throws IOException;
-
-    /**
-     * @throws SecurityException containing message if the caller doesn't have the given
-     *                           permission
-     */
-    void enforcePermission(String permission, String message) throws SecurityException;
-
-    /**
-     * @return true if {@link PackageManagerServiceUtils#compareSignatures} run on both packages
-     *     in the system returns {@link PackageManager#SIGNATURE_MATCH}
-     */
-    boolean signaturesMatching(@NonNull String pkgName1, @NonNull String pkgName2, int userId);
-}
diff --git a/services/core/java/com/android/server/om/PackageManagerHelper.java b/services/core/java/com/android/server/om/PackageManagerHelper.java
index ec9c5e6..b1a8b4e 100644
--- a/services/core/java/com/android/server/om/PackageManagerHelper.java
+++ b/services/core/java/com/android/server/om/PackageManagerHelper.java
@@ -17,11 +17,17 @@
 package com.android.server.om;
 
 import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.om.OverlayableInfo;
 import android.content.pm.PackageInfo;
 import android.content.pm.PackageManager;
 import android.content.pm.PackageManagerInternal;
 
+import com.android.server.pm.PackageManagerServiceUtils;
+
+import java.io.IOException;
 import java.util.List;
+import java.util.Map;
 
 /**
  * Delegate for {@link PackageManager} and {@link PackageManagerInternal} functionality,
@@ -30,7 +36,65 @@
  * @hide
  */
 interface PackageManagerHelper {
-    PackageInfo getPackageInfo(@NonNull String packageName, int userId);
-    boolean signaturesMatching(@NonNull String pkgName1, @NonNull String pkgName2, int userId);
+    /**
+     * @return true if the target package has declared an overlayable
+     */
+    boolean doesTargetDefineOverlayable(String targetPackageName, int userId) throws IOException;
+
+    /**
+     * @throws SecurityException containing message if the caller doesn't have the given
+     *                           permission
+     */
+    void enforcePermission(String permission, String message) throws SecurityException;
+
+    /**
+     * Returns the package name of the reference package defined in 'overlay-config-signature' tag
+     * of SystemConfig. This package is vetted on scan by PackageManagerService that it's a system
+     * package and is used to check if overlay matches its signature in order to fulfill the
+     * config_signature policy.
+     */
+    @Nullable
+    String getConfigSignaturePackage();
+
+    /**
+     * @return map of system pre-defined, uniquely named actors; keys are namespace,
+     * value maps actor name to package name
+     */
+    @NonNull
+    Map<String, Map<String, String>> getNamedActors();
+
+    /**
+     * @see PackageManagerInternal#getOverlayPackages(int)
+     */
     List<PackageInfo> getOverlayPackages(int userId);
+
+    /**
+     * Read from the APK and AndroidManifest of a package to return the overlayable defined for
+     * a given name.
+     *
+     * @throws IOException if the target can't be read
+     */
+    @Nullable
+    OverlayableInfo getOverlayableForTarget(@NonNull String packageName,
+            @NonNull String targetOverlayableName, int userId)
+            throws IOException;
+
+    /**
+     * @see PackageManager#getPackagesForUid(int)
+     */
+    @Nullable
+    String[] getPackagesForUid(int uid);
+
+    /**
+     * @param userId user to filter package visibility by
+     * @see PackageManager#getPackageInfo(String, int)
+     */
+    @Nullable
+    PackageInfo getPackageInfo(@NonNull String packageName, int userId);
+
+    /**
+     * @return true if {@link PackageManagerServiceUtils#compareSignatures} run on both packages
+     *     in the system returns {@link PackageManager#SIGNATURE_MATCH}
+     */
+    boolean signaturesMatching(@NonNull String pkgName1, @NonNull String pkgName2, int userId);
 }
diff --git a/services/core/java/com/android/server/pm/ApexManager.java b/services/core/java/com/android/server/pm/ApexManager.java
index 249b6801..07527c2 100644
--- a/services/core/java/com/android/server/pm/ApexManager.java
+++ b/services/core/java/com/android/server/pm/ApexManager.java
@@ -270,11 +270,12 @@
     abstract boolean revertActiveSessions();
 
     /**
-     * Abandons the staged session with the given sessionId.
+     * Abandons the staged session with the given sessionId. Client should handle {@code false}
+     * return value carefully as failure here can leave device in inconsistent state.
      *
-     * @return {@code true} upon success, {@code false} if any remote exception occurs
+     * @return {@code true} upon success, {@code false} if any exception occurs
      */
-    abstract boolean abortStagedSession(int sessionId) throws PackageManagerException;
+    abstract boolean abortStagedSession(int sessionId);
 
     /**
      * Uninstalls given {@code apexPackage}.
@@ -753,17 +754,13 @@
         }
 
         @Override
-        boolean abortStagedSession(int sessionId) throws PackageManagerException {
+        boolean abortStagedSession(int sessionId) {
             try {
                 waitForApexService().abortStagedSession(sessionId);
                 return true;
-            } catch (RemoteException re) {
-                Slog.e(TAG, "Unable to contact apexservice", re);
-                return false;
             } catch (Exception e) {
-                throw new PackageManagerException(
-                        PackageInstaller.SessionInfo.STAGED_SESSION_VERIFICATION_FAILED,
-                        "Failed to abort staged session : " + e.getMessage());
+                Slog.e(TAG, e.getMessage(), e);
+                return false;
             }
         }
 
@@ -1122,7 +1119,7 @@
         }
 
         @Override
-        boolean abortStagedSession(int sessionId) throws PackageManagerException {
+        boolean abortStagedSession(int sessionId) {
             throw new UnsupportedOperationException();
         }
 
diff --git a/services/core/java/com/android/server/pm/AppsFilter.java b/services/core/java/com/android/server/pm/AppsFilter.java
index 92c0c6a..3d7c978 100644
--- a/services/core/java/com/android/server/pm/AppsFilter.java
+++ b/services/core/java/com/android/server/pm/AppsFilter.java
@@ -48,6 +48,7 @@
 import android.util.SparseSetArray;
 
 import com.android.internal.R;
+import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.util.ArrayUtils;
 import com.android.server.FgThread;
@@ -61,6 +62,7 @@
 import java.util.Objects;
 import java.util.Set;
 import java.util.StringTokenizer;
+import java.util.concurrent.Executor;
 
 /**
  * The entity responsible for filtering visibility between apps based on declarations in their
@@ -96,6 +98,12 @@
     private final SparseSetArray<Integer> mQueriesViaComponent = new SparseSetArray<>();
 
     /**
+     * Executor for running reasonably short background tasks such as building the initial
+     * visibility cache.
+     */
+    private final Executor mBackgroundExecutor;
+
+    /**
      * Pending full recompute of mQueriesViaComponent. Occurs when a package adds a new set of
      * protected broadcast. This in turn invalidates all prior additions and require a very
      * computationally expensive recomputing.
@@ -125,6 +133,8 @@
     private PackageParser.SigningDetails mSystemSigningDetails;
     private Set<String> mProtectedBroadcasts = new ArraySet<>();
 
+    private final Object mCacheLock = new Object();
+
     /**
      * This structure maps uid -> uid and indicates whether access from the first should be
      * filtered to the second. It's essentially a cache of the
@@ -132,6 +142,7 @@
      * NOTE: It can only be relied upon after the system is ready to avoid unnecessary update on
      * initial scam and is null until {@link #onSystemReady()} is called.
      */
+    @GuardedBy("mCacheLock")
     private volatile SparseArray<SparseBooleanArray> mShouldFilterCache;
 
     @VisibleForTesting(visibility = PRIVATE)
@@ -139,13 +150,15 @@
             FeatureConfig featureConfig,
             String[] forceQueryableList,
             boolean systemAppsQueryable,
-            @Nullable OverlayReferenceMapper.Provider overlayProvider) {
+            @Nullable OverlayReferenceMapper.Provider overlayProvider,
+            Executor backgroundExecutor) {
         mFeatureConfig = featureConfig;
         mForceQueryableByDevicePackageNames = forceQueryableList;
         mSystemAppsQueryable = systemAppsQueryable;
         mOverlayReferenceMapper = new OverlayReferenceMapper(true /*deferRebuild*/,
                 overlayProvider);
         mStateProvider = stateProvider;
+        mBackgroundExecutor = backgroundExecutor;
     }
 
     /**
@@ -338,7 +351,8 @@
             }
         };
         AppsFilter appsFilter = new AppsFilter(stateProvider, featureConfig,
-                forcedQueryablePackageNames, forceSystemAppsQueryable, null);
+                forcedQueryablePackageNames, forceSystemAppsQueryable, null,
+                injector.getBackgroundExecutor());
         featureConfig.setAppsFilter(appsFilter);
         return appsFilter;
     }
@@ -470,29 +484,26 @@
             if (mImplicitlyQueryable.add(recipientUid, visibleUid) && DEBUG_LOGGING) {
                 Slog.i(TAG, "implicit access granted: " + recipientUid + " -> " + visibleUid);
             }
-            if (mShouldFilterCache != null) {
-                // update the cache in a one-off manner since we've got all the information we need.
-                SparseBooleanArray visibleUids = mShouldFilterCache.get(recipientUid);
-                if (visibleUids == null) {
-                    visibleUids = new SparseBooleanArray();
-                    mShouldFilterCache.put(recipientUid, visibleUids);
+            synchronized (mCacheLock) {
+                if (mShouldFilterCache != null) {
+                    // update the cache in a one-off manner since we've got all the information we
+                    // need.
+                    SparseBooleanArray visibleUids = mShouldFilterCache.get(recipientUid);
+                    if (visibleUids == null) {
+                        visibleUids = new SparseBooleanArray();
+                        mShouldFilterCache.put(recipientUid, visibleUids);
+                    }
+                    visibleUids.put(visibleUid, false);
                 }
-                visibleUids.put(visibleUid, false);
             }
         }
     }
 
     public void onSystemReady() {
-        mStateProvider.runWithState(new StateProvider.CurrentStateCallback() {
-            @Override
-            public void currentState(ArrayMap<String, PackageSetting> settings,
-                    UserInfo[] users) {
-                mShouldFilterCache = new SparseArray<>(users.length * settings.size());
-            }
-        });
-        mFeatureConfig.onSystemReady();
         mOverlayReferenceMapper.rebuildIfDeferred();
-        updateEntireShouldFilterCache();
+        mFeatureConfig.onSystemReady();
+
+        updateEntireShouldFilterCacheAsync();
     }
 
     /**
@@ -510,10 +521,12 @@
             }
             mStateProvider.runWithState((settings, users) -> {
                 addPackageInternal(newPkgSetting, settings);
-                if (mShouldFilterCache != null) {
-                    updateShouldFilterCacheForPackage(
-                            null, newPkgSetting, settings, users, settings.size());
-                } // else, rebuild entire cache when system is ready
+                synchronized (mCacheLock) {
+                    if (mShouldFilterCache != null) {
+                        updateShouldFilterCacheForPackage(mShouldFilterCache, null, newPkgSetting,
+                                settings, users, settings.size());
+                    } // else, rebuild entire cache when system is ready
+                }
             });
         } finally {
             Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
@@ -607,6 +620,7 @@
         mFeatureConfig.updatePackageState(newPkgSetting, false /*removed*/);
     }
 
+    @GuardedBy("mCacheLock")
     private void removeAppIdFromVisibilityCache(int appId) {
         if (mShouldFilterCache == null) {
             return;
@@ -625,33 +639,47 @@
         }
     }
 
+    private void updateEntireShouldFilterCacheAsync() {
+        mBackgroundExecutor.execute(this::updateEntireShouldFilterCache);
+    }
+
     private void updateEntireShouldFilterCache() {
         mStateProvider.runWithState((settings, users) -> {
-            mShouldFilterCache.clear();
+            SparseArray<SparseBooleanArray> cache =
+                    new SparseArray<>(users.length * settings.size());
             for (int i = settings.size() - 1; i >= 0; i--) {
-                updateShouldFilterCacheForPackage(
+                updateShouldFilterCacheForPackage(cache,
                         null /*skipPackage*/, settings.valueAt(i), settings, users, i);
             }
+            synchronized (mCacheLock) {
+                mShouldFilterCache = cache;
+            }
         });
     }
 
     public void onUsersChanged() {
-        if (mShouldFilterCache != null) {
-            updateEntireShouldFilterCache();
+        synchronized (mCacheLock) {
+            if (mShouldFilterCache != null) {
+                updateEntireShouldFilterCache();
+            }
         }
     }
 
     private void updateShouldFilterCacheForPackage(String packageName) {
-        mStateProvider.runWithState((settings, users) -> {
-            updateShouldFilterCacheForPackage(null /* skipPackage */, settings.get(packageName),
-                    settings, users, settings.size() /*maxIndex*/);
-        });
-
+        synchronized (mCacheLock) {
+            if (mShouldFilterCache != null) {
+                mStateProvider.runWithState((settings, users) -> {
+                    updateShouldFilterCacheForPackage(mShouldFilterCache, null /* skipPackage */,
+                            settings.get(packageName), settings, users,
+                            settings.size() /*maxIndex*/);
+                });
+            }
+        }
     }
 
-    private void updateShouldFilterCacheForPackage(@Nullable String skipPackageName,
-            PackageSetting subjectSetting, ArrayMap<String, PackageSetting> allSettings,
-            UserInfo[] allUsers, int maxIndex) {
+    private void updateShouldFilterCacheForPackage(SparseArray<SparseBooleanArray> cache,
+            @Nullable String skipPackageName, PackageSetting subjectSetting, ArrayMap<String,
+            PackageSetting> allSettings, UserInfo[] allUsers, int maxIndex) {
         for (int i = Math.min(maxIndex, allSettings.size() - 1); i >= 0; i--) {
             PackageSetting otherSetting = allSettings.valueAt(i);
             if (subjectSetting.appId == otherSetting.appId) {
@@ -668,17 +696,17 @@
                 for (int ou = 0; ou < userCount; ou++) {
                     int otherUser = allUsers[ou].id;
                     int subjectUid = UserHandle.getUid(subjectUser, subjectSetting.appId);
-                    if (!mShouldFilterCache.contains(subjectUid)) {
-                        mShouldFilterCache.put(subjectUid, new SparseBooleanArray(appxUidCount));
+                    if (!cache.contains(subjectUid)) {
+                        cache.put(subjectUid, new SparseBooleanArray(appxUidCount));
                     }
                     int otherUid = UserHandle.getUid(otherUser, otherSetting.appId);
-                    if (!mShouldFilterCache.contains(otherUid)) {
-                        mShouldFilterCache.put(otherUid, new SparseBooleanArray(appxUidCount));
+                    if (!cache.contains(otherUid)) {
+                        cache.put(otherUid, new SparseBooleanArray(appxUidCount));
                     }
-                    mShouldFilterCache.get(subjectUid).put(otherUid,
+                    cache.get(subjectUid).put(otherUid,
                             shouldFilterApplicationInternal(
                                     subjectUid, subjectSetting, otherSetting, otherUser));
-                    mShouldFilterCache.get(otherUid).put(subjectUid,
+                    cache.get(otherUid).put(subjectUid,
                             shouldFilterApplicationInternal(
                                     otherUid, otherSetting, subjectSetting, subjectUser));
                 }
@@ -712,7 +740,8 @@
      * This method recomputes all component / intent-based visibility and is intended to match the
      * relevant logic of {@link #addPackageInternal(PackageSetting, ArrayMap)}
      */
-    private void recomputeComponentVisibility(ArrayMap<String, PackageSetting> existingSettings) {
+    private void recomputeComponentVisibility(
+            ArrayMap<String, PackageSetting> existingSettings) {
         mQueriesViaComponent.clear();
         for (int i = existingSettings.size() - 1; i >= 0; i--) {
             PackageSetting setting = existingSettings.valueAt(i);
@@ -854,15 +883,17 @@
                 }
             }
 
-            removeAppIdFromVisibilityCache(setting.appId);
-            if (mShouldFilterCache != null && setting.sharedUser != null) {
-                for (int i = setting.sharedUser.packages.size() - 1; i >= 0; i--) {
-                    PackageSetting siblingSetting = setting.sharedUser.packages.valueAt(i);
-                    if (siblingSetting == setting) {
-                        continue;
+            synchronized (mCacheLock) {
+                removeAppIdFromVisibilityCache(setting.appId);
+                if (mShouldFilterCache != null && setting.sharedUser != null) {
+                    for (int i = setting.sharedUser.packages.size() - 1; i >= 0; i--) {
+                        PackageSetting siblingSetting = setting.sharedUser.packages.valueAt(i);
+                        if (siblingSetting == setting) {
+                            continue;
+                        }
+                        updateShouldFilterCacheForPackage(mShouldFilterCache, setting.name,
+                                siblingSetting, settings, users, settings.size());
                     }
-                    updateShouldFilterCacheForPackage(
-                            setting.name, siblingSetting, settings, users, settings.size());
                 }
             }
         });
@@ -888,26 +919,29 @@
                     || callingAppId == targetPkgSetting.appId) {
                 return false;
             }
-            if (mShouldFilterCache != null) { // use cache
-                SparseBooleanArray shouldFilterTargets = mShouldFilterCache.get(callingUid);
-                final int targetUid = UserHandle.getUid(userId, targetPkgSetting.appId);
-                if (shouldFilterTargets == null) {
-                    Slog.wtf(TAG, "Encountered calling uid with no cached rules: " + callingUid);
-                    return true;
-                }
-                int indexOfTargetUid = shouldFilterTargets.indexOfKey(targetUid);
-                if (indexOfTargetUid < 0) {
-                    Slog.w(TAG, "Encountered calling -> target with no cached rules: "
-                            + callingUid + " -> " + targetUid);
-                    return true;
-                }
-                if (!shouldFilterTargets.valueAt(indexOfTargetUid)) {
-                    return false;
-                }
-            } else {
-                if (!shouldFilterApplicationInternal(
-                        callingUid, callingSetting, targetPkgSetting, userId)) {
-                    return false;
+            synchronized (mCacheLock) {
+                if (mShouldFilterCache != null) { // use cache
+                    SparseBooleanArray shouldFilterTargets = mShouldFilterCache.get(callingUid);
+                    final int targetUid = UserHandle.getUid(userId, targetPkgSetting.appId);
+                    if (shouldFilterTargets == null) {
+                        Slog.wtf(TAG, "Encountered calling uid with no cached rules: "
+                                + callingUid);
+                        return true;
+                    }
+                    int indexOfTargetUid = shouldFilterTargets.indexOfKey(targetUid);
+                    if (indexOfTargetUid < 0) {
+                        Slog.w(TAG, "Encountered calling -> target with no cached rules: "
+                                + callingUid + " -> " + targetUid);
+                        return true;
+                    }
+                    if (!shouldFilterTargets.valueAt(indexOfTargetUid)) {
+                        return false;
+                    }
+                } else {
+                    if (!shouldFilterApplicationInternal(
+                            callingUid, callingSetting, targetPkgSetting, userId)) {
+                        return false;
+                    }
                 }
             }
             if (DEBUG_LOGGING || mFeatureConfig.isLoggingEnabled(callingAppId)) {
diff --git a/services/core/java/com/android/server/pm/Installer.java b/services/core/java/com/android/server/pm/Installer.java
index 672ad5e..cd383b9 100644
--- a/services/core/java/com/android/server/pm/Installer.java
+++ b/services/core/java/com/android/server/pm/Installer.java
@@ -23,6 +23,8 @@
 import android.content.Context;
 import android.content.pm.PackageStats;
 import android.os.Build;
+import android.os.CreateAppDataArgs;
+import android.os.CreateAppDataResult;
 import android.os.IBinder;
 import android.os.IBinder.DeathRecipient;
 import android.os.IInstalld;
@@ -39,7 +41,10 @@
 import dalvik.system.VMRuntime;
 
 import java.io.FileDescriptor;
+import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.List;
+import java.util.concurrent.CompletableFuture;
 
 public class Installer extends SystemService {
     private static final String TAG = "Installer";
@@ -176,37 +181,140 @@
         }
     }
 
+    private static CreateAppDataArgs buildCreateAppDataArgs(String uuid, String packageName,
+            int userId, int flags, int appId, String seInfo, int targetSdkVersion) {
+        final CreateAppDataArgs args = new CreateAppDataArgs();
+        args.uuid = uuid;
+        args.packageName = packageName;
+        args.userId = userId;
+        args.flags = flags;
+        args.appId = appId;
+        args.seInfo = seInfo;
+        args.targetSdkVersion = targetSdkVersion;
+        return args;
+    }
+
+    private static CreateAppDataResult buildPlaceholderCreateAppDataResult() {
+        final CreateAppDataResult result = new CreateAppDataResult();
+        result.ceDataInode = -1;
+        result.exceptionCode = 0;
+        result.exceptionMessage = null;
+        return result;
+    }
+
+    /**
+     * @deprecated callers are encouraged to migrate to using {@link Batch} to
+     *             more efficiently handle operations in bulk.
+     */
+    @Deprecated
     public long createAppData(String uuid, String packageName, int userId, int flags, int appId,
             String seInfo, int targetSdkVersion) throws InstallerException {
-        if (!checkBeforeRemote()) return -1;
+        final CreateAppDataArgs args = buildCreateAppDataArgs(uuid, packageName, userId, flags,
+                appId, seInfo, targetSdkVersion);
+        final CreateAppDataResult result = createAppData(args);
+        if (result.exceptionCode == 0) {
+            return result.ceDataInode;
+        } else {
+            throw new InstallerException(result.exceptionMessage);
+        }
+    }
+
+    public @NonNull CreateAppDataResult createAppData(@NonNull CreateAppDataArgs args)
+            throws InstallerException {
+        if (!checkBeforeRemote()) {
+            return buildPlaceholderCreateAppDataResult();
+        }
         try {
-            return mInstalld.createAppData(uuid, packageName, userId, flags, appId, seInfo,
-                    targetSdkVersion);
+            return mInstalld.createAppData(args);
+        } catch (Exception e) {
+            throw InstallerException.from(e);
+        }
+    }
+
+    public @NonNull CreateAppDataResult[] createAppDataBatched(@NonNull CreateAppDataArgs[] args)
+            throws InstallerException {
+        if (!checkBeforeRemote()) {
+            final CreateAppDataResult[] results = new CreateAppDataResult[args.length];
+            Arrays.fill(results, buildPlaceholderCreateAppDataResult());
+            return results;
+        }
+        try {
+            return mInstalld.createAppDataBatched(args);
         } catch (Exception e) {
             throw InstallerException.from(e);
         }
     }
 
     /**
-     * Batched version of createAppData for use with multiple packages.
+     * Class that collects multiple {@code installd} operations together in an
+     * attempt to more efficiently execute them in bulk.
+     * <p>
+     * Instead of returning results immediately, {@link CompletableFuture}
+     * instances are returned which can be used to chain follow-up work for each
+     * request.
+     * <p>
+     * The creator of this object <em>must</em> invoke {@link #execute()}
+     * exactly once to begin execution of all pending operations. Once execution
+     * has been kicked off, no additional events can be enqueued into this
+     * instance, but multiple instances can safely exist in parallel.
      */
-    public void createAppDataBatched(String[] uuids, String[] packageNames, int userId, int flags,
-            int[] appIds, String[] seInfos, int[] targetSdkVersions) throws InstallerException {
-        if (!checkBeforeRemote()) return;
-        final int batchSize = 256;
-        for (int i = 0; i < uuids.length; i += batchSize) {
-            int to = i + batchSize;
-            if (to > uuids.length) {
-                to = uuids.length;
-            }
+    public static class Batch {
+        private static final int CREATE_APP_DATA_BATCH_SIZE = 256;
 
-            try {
-                mInstalld.createAppDataBatched(Arrays.copyOfRange(uuids, i, to),
-                        Arrays.copyOfRange(packageNames, i, to), userId, flags,
-                        Arrays.copyOfRange(appIds, i, to), Arrays.copyOfRange(seInfos, i, to),
-                        Arrays.copyOfRange(targetSdkVersions, i, to));
-            } catch (Exception e) {
-                throw InstallerException.from(e);
+        private boolean mExecuted;
+
+        private final List<CreateAppDataArgs> mArgs = new ArrayList<>();
+        private final List<CompletableFuture<Long>> mFutures = new ArrayList<>();
+
+        /**
+         * Enqueue the given {@code installd} operation to be executed in the
+         * future when {@link #execute(Installer)} is invoked.
+         * <p>
+         * Callers of this method are not required to hold a monitor lock on an
+         * {@link Installer} object.
+         */
+        public synchronized @NonNull CompletableFuture<Long> createAppData(String uuid,
+                String packageName, int userId, int flags, int appId, String seInfo,
+                int targetSdkVersion) {
+            if (mExecuted) throw new IllegalStateException();
+
+            final CreateAppDataArgs args = buildCreateAppDataArgs(uuid, packageName, userId, flags,
+                    appId, seInfo, targetSdkVersion);
+            final CompletableFuture<Long> future = new CompletableFuture<>();
+            mArgs.add(args);
+            mFutures.add(future);
+            return future;
+        }
+
+        /**
+         * Execute all pending {@code installd} operations that have been
+         * collected by this batch in a blocking fashion.
+         * <p>
+         * Callers of this method <em>must</em> hold a monitor lock on the given
+         * {@link Installer} object.
+         */
+        public synchronized void execute(@NonNull Installer installer) throws InstallerException {
+            if (mExecuted) throw new IllegalStateException();
+            mExecuted = true;
+
+            final int size = mArgs.size();
+            for (int i = 0; i < size; i += CREATE_APP_DATA_BATCH_SIZE) {
+                final CreateAppDataArgs[] args = new CreateAppDataArgs[Math.min(size - i,
+                        CREATE_APP_DATA_BATCH_SIZE)];
+                for (int j = 0; j < args.length; j++) {
+                    args[j] = mArgs.get(i + j);
+                }
+                final CreateAppDataResult[] results = installer.createAppDataBatched(args);
+                for (int j = 0; j < args.length; j++) {
+                    final CreateAppDataResult result = results[j];
+                    final CompletableFuture<Long> future = mFutures.get(i + j);
+                    if (result.exceptionCode == 0) {
+                        future.complete(result.ceDataInode);
+                    } else {
+                        future.completeExceptionally(
+                                new InstallerException(result.exceptionMessage));
+                    }
+                }
             }
         }
     }
diff --git a/services/core/java/com/android/server/pm/InstantAppRegistry.java b/services/core/java/com/android/server/pm/InstantAppRegistry.java
index 0eaac41..9646b9c 100644
--- a/services/core/java/com/android/server/pm/InstantAppRegistry.java
+++ b/services/core/java/com/android/server/pm/InstantAppRegistry.java
@@ -52,6 +52,7 @@
 import com.android.internal.util.XmlUtils;
 import com.android.server.pm.parsing.PackageInfoUtils;
 import com.android.server.pm.parsing.pkg.AndroidPackage;
+import com.android.server.pm.permission.PermissionManagerServiceInternal;
 
 import libcore.io.IoUtils;
 import libcore.util.HexEncoding;
@@ -112,6 +113,7 @@
     private static final String ATTR_GRANTED = "granted";
 
     private final PackageManagerService mService;
+    private final PermissionManagerServiceInternal mPermissionManager;
     private final CookiePersistence mCookiePersistence;
 
     /** State for uninstalled instant apps */
@@ -131,8 +133,10 @@
     @GuardedBy("mService.mLock")
     private SparseArray<SparseBooleanArray> mInstalledInstantAppUids;
 
-    public InstantAppRegistry(PackageManagerService service) {
+    public InstantAppRegistry(PackageManagerService service,
+            PermissionManagerServiceInternal permissionManager) {
         mService = service;
+        mPermissionManager = permissionManager;
         mCookiePersistence = new CookiePersistence(BackgroundThread.getHandler().getLooper());
     }
 
@@ -861,7 +865,8 @@
         String[] requestedPermissions = new String[pkg.getRequestedPermissions().size()];
         pkg.getRequestedPermissions().toArray(requestedPermissions);
 
-        Set<String> permissions = ps.getPermissionsState().getPermissions(userId);
+        Set<String> permissions = mPermissionManager.getGrantedPermissions(
+                pkg.getPackageName(), userId);
         String[] grantedPermissions = new String[permissions.size()];
         permissions.toArray(grantedPermissions);
 
diff --git a/services/core/java/com/android/server/pm/InstantAppResolver.java b/services/core/java/com/android/server/pm/InstantAppResolver.java
index 0b0f139..79f8dc1 100644
--- a/services/core/java/com/android/server/pm/InstantAppResolver.java
+++ b/services/core/java/com/android/server/pm/InstantAppResolver.java
@@ -380,7 +380,7 @@
                 sanitizeIntent(request.origIntent),
                 // This must only expose the secured version of the host
                 request.hostDigestPrefixSecure,
-                UserHandle.getUserHandleForUid(request.userId),
+                UserHandle.of(request.userId),
                 request.isRequesterInstantApp,
                 request.token
         );
diff --git a/services/core/java/com/android/server/pm/OWNERS b/services/core/java/com/android/server/pm/OWNERS
index fe6aad7..e48862e 100644
--- a/services/core/java/com/android/server/pm/OWNERS
+++ b/services/core/java/com/android/server/pm/OWNERS
@@ -14,16 +14,16 @@
 per-file StagingManager.java = dariofreni@google.com, ioffe@google.com, olilan@google.com
 
 # dex
-per-file AbstractStatsBase.java = agampe@google.com, calin@google.com, ngeoffray@google.com
-per-file BackgroundDexOptService.java = agampe@google.com, calin@google.com, ngeoffray@google.com
-per-file CompilerStats.java = agampe@google.com, calin@google.com, ngeoffray@google.com
-per-file DynamicCodeLoggingService.java = alanstokes@google.com, agampe@google.com, calin@google.com, ngeoffray@google.com
-per-file InstructionSets.java = agampe@google.com, calin@google.com, ngeoffray@google.com
-per-file OtaDexoptService.java = agampe@google.com, calin@google.com, ngeoffray@google.com
-per-file OtaDexoptShellCommand.java = agampe@google.com, calin@google.com, ngeoffray@google.com
-per-file PackageDexOptimizer.java = agampe@google.com, calin@google.com, ngeoffray@google.com
-per-file PackageManagerServiceCompilerMapping.java = agampe@google.com, calin@google.com, ngeoffray@google.com
-per-file PackageUsage.java = agampe@google.com, calin@google.com, ngeoffray@google.com
+per-file AbstractStatsBase.java = calin@google.com, ngeoffray@google.com
+per-file BackgroundDexOptService.java = calin@google.com, ngeoffray@google.com
+per-file CompilerStats.java = calin@google.com, ngeoffray@google.com
+per-file DynamicCodeLoggingService.java = alanstokes@google.com, calin@google.com, ngeoffray@google.com
+per-file InstructionSets.java = calin@google.com, ngeoffray@google.com
+per-file OtaDexoptService.java = calin@google.com, ngeoffray@google.com
+per-file OtaDexoptShellCommand.java = calin@google.com, ngeoffray@google.com
+per-file PackageDexOptimizer.java = calin@google.com, ngeoffray@google.com
+per-file PackageManagerServiceCompilerMapping.java = calin@google.com, ngeoffray@google.com
+per-file PackageUsage.java = calin@google.com, ngeoffray@google.com
 
 # multi user / cross profile
 per-file CrossProfileAppsServiceImpl.java = omakoto@google.com, yamasani@google.com
diff --git a/services/core/java/com/android/server/pm/PackageInstallerService.java b/services/core/java/com/android/server/pm/PackageInstallerService.java
index 312dcdd..4b246c3 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerService.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerService.java
@@ -442,7 +442,7 @@
         // After reboot housekeeping.
         for (int i = 0; i < mSessions.size(); ++i) {
             PackageInstallerSession session = mSessions.valueAt(i);
-            session.onAfterSessionRead();
+            session.onAfterSessionRead(mSessions);
         }
     }
 
@@ -613,6 +613,9 @@
                 throw new IllegalArgumentException(
                     "APEX files can only be installed as part of a staged session.");
             }
+            if (params.isMultiPackage) {
+                throw new IllegalArgumentException("A multi-session can't be set as APEX.");
+            }
         }
 
         if (params.isStaged && !isCalledBySystemOrShell(callingUid)) {
@@ -1281,19 +1284,47 @@
             pw.increaseIndent();
 
             List<PackageInstallerSession> finalizedSessions = new ArrayList<>();
+            List<PackageInstallerSession> orphanedChildSessions = new ArrayList<>();
             int N = mSessions.size();
             for (int i = 0; i < N; i++) {
                 final PackageInstallerSession session = mSessions.valueAt(i);
-                if (session.isStagedAndInTerminalState()) {
+
+                final PackageInstallerSession rootSession = session.hasParentSessionId()
+                        ? getSession(session.getParentSessionId())
+                        : session;
+                // Do not print orphaned child sessions as active install sessions
+                if (rootSession == null) {
+                    orphanedChildSessions.add(session);
+                    continue;
+                }
+
+                // Do not print finalized staged session as active install sessions
+                if (rootSession.isStagedAndInTerminalState()) {
                     finalizedSessions.add(session);
                     continue;
                 }
+
                 session.dump(pw);
                 pw.println();
             }
             pw.println();
             pw.decreaseIndent();
 
+            if (!orphanedChildSessions.isEmpty()) {
+                // Presence of orphaned sessions indicate leak in cleanup for multi-package and
+                // should be cleaned up.
+                pw.println("Orphaned install sessions:");
+                pw.increaseIndent();
+                N = orphanedChildSessions.size();
+                for (int i = 0; i < N; i++) {
+                    final PackageInstallerSession session = orphanedChildSessions.get(i);
+                    session.dump(pw);
+                    pw.println();
+                }
+                pw.println();
+                pw.decreaseIndent();
+            }
+
             pw.println("Finalized install sessions:");
             pw.increaseIndent();
             N = finalizedSessions.size();
diff --git a/services/core/java/com/android/server/pm/PackageInstallerSession.java b/services/core/java/com/android/server/pm/PackageInstallerSession.java
index 1dabfdb..4f3d360 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerSession.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerSession.java
@@ -122,7 +122,7 @@
 import android.util.ExceptionUtils;
 import android.util.MathUtils;
 import android.util.Slog;
-import android.util.SparseIntArray;
+import android.util.SparseArray;
 import android.util.apk.ApkSignatureVerifier;
 
 import com.android.internal.R;
@@ -169,7 +169,7 @@
     private static final int MSG_STREAM_VALIDATE_AND_COMMIT = 2;
     private static final int MSG_INSTALL = 3;
     private static final int MSG_ON_PACKAGE_INSTALLED = 4;
-    private static final int MSG_SESSION_VERIFICATION_FAILURE = 5;
+    private static final int MSG_SESSION_VALIDATION_FAILURE = 5;
 
     /** XML constants used for persisting a session */
     static final String TAG_SESSION = "session";
@@ -336,7 +336,7 @@
     @GuardedBy("mLock")
     private PackageParser.SigningDetails mSigningDetails;
     @GuardedBy("mLock")
-    private SparseIntArray mChildSessionIds = new SparseIntArray();
+    private SparseArray<PackageInstallerSession> mChildSessions = new SparseArray<>();
     @GuardedBy("mLock")
     private int mParentSessionId;
 
@@ -475,10 +475,10 @@
                             packageName, returnCode, message, extras);
 
                     break;
-                case MSG_SESSION_VERIFICATION_FAILURE:
+                case MSG_SESSION_VALIDATION_FAILURE:
                     final int error = msg.arg1;
                     final String detailMessage = (String) msg.obj;
-                    onSessionVerificationFailure(error, detailMessage);
+                    onSessionValidationFailure(error, detailMessage);
                     break;
             }
 
@@ -589,7 +589,9 @@
         this.mShouldBeSealed = sealed;
         if (childSessionIds != null) {
             for (int childSessionId : childSessionIds) {
-                mChildSessionIds.put(childSessionId, 0);
+                // Null values will be resolved to actual object references in
+                // #onAfterSessionRead later.
+                mChildSessions.put(childSessionId, null);
             }
         }
         this.mParentSessionId = parentSessionId;
@@ -708,10 +710,7 @@
             info.isStaged = params.isStaged;
             info.rollbackDataPolicy = params.rollbackDataPolicy;
             info.parentSessionId = mParentSessionId;
-            info.childSessionIds = mChildSessionIds.copyKeys();
-            if (info.childSessionIds == null) {
-                info.childSessionIds = EMPTY_CHILD_SESSION_ARRAY;
-            }
+            info.childSessionIds = getChildSessionIdsLocked();
             info.isStagedSessionApplied = mStagedSessionApplied;
             info.isStagedSessionReady = mStagedSessionReady;
             info.isStagedSessionFailed = mStagedSessionFailed;
@@ -1159,27 +1158,22 @@
             return;
         }
         if (isMultiPackage()) {
-            final SparseIntArray remainingSessions;
-            final int[] childSessionIds;
             synchronized (mLock) {
-                remainingSessions = mChildSessionIds.clone();
-                childSessionIds = mChildSessionIds.copyKeys();
-            }
-            final IntentSender childIntentSender =
-                    new ChildStatusIntentReceiver(remainingSessions, statusReceiver)
-                            .getIntentSender();
-            boolean sealFailed = false;
-            for (int i = childSessionIds.length - 1; i >= 0; --i) {
-                final int childSessionId = childSessionIds[i];
-                // seal all children, regardless if any of them fail; we'll throw/return
-                // as appropriate once all children have been processed
-                if (!mSessionProvider.getSession(childSessionId)
-                        .markAsSealed(childIntentSender, forTransfer)) {
-                    sealFailed = true;
+                final IntentSender childIntentSender =
+                        new ChildStatusIntentReceiver(mChildSessions.clone(), statusReceiver)
+                                .getIntentSender();
+                boolean sealFailed = false;
+                for (int i = mChildSessions.size() - 1; i >= 0; --i) {
+                    // seal all children, regardless if any of them fail; we'll throw/return
+                    // as appropriate once all children have been processed
+                    if (!mChildSessions.valueAt(i)
+                            .markAsSealed(childIntentSender, forTransfer)) {
+                        sealFailed = true;
+                    }
                 }
-            }
-            if (sealFailed) {
-                return;
+                if (sealFailed) {
+                    return;
+                }
             }
         }
 
@@ -1218,21 +1212,20 @@
         }
 
         if (isMultiPackage()) {
-            final int[] childSessionIds;
+            final List<PackageInstallerSession> childSessions;
             synchronized (mLock) {
-                childSessionIds = mChildSessionIds.copyKeys();
+                childSessions = getChildSessionsLocked();
             }
-            int childCount = childSessionIds.length;
+            int childCount = childSessions.size();
 
             // This will contain all child sessions that do not encounter an unrecoverable failure
             ArrayList<PackageInstallerSession> nonFailingSessions = new ArrayList<>(childCount);
 
             for (int i = childCount - 1; i >= 0; --i) {
-                final int childSessionId = childSessionIds[i];
                 // commit all children, regardless if any of them fail; we'll throw/return
                 // as appropriate once all children have been processed
                 try {
-                    PackageInstallerSession session = mSessionProvider.getSession(childSessionId);
+                    PackageInstallerSession session = childSessions.get(i);
                     allSessionsReady &= session.streamValidateAndCommit();
                     nonFailingSessions.add(session);
                 } catch (PackageManagerException e) {
@@ -1246,14 +1239,14 @@
             // the parent
             if (unrecoverableFailure != null) {
                 // {@link #streamValidateAndCommit()} calls
-                // {@link #onSessionVerificationFailure(PackageManagerException)}, but we don't
+                // {@link #onSessionValidationFailure(PackageManagerException)}, but we don't
                 // expect it to ever do so for parent sessions. Call that on this parent to clean
                 // it up and notify listeners of the error.
-                onSessionVerificationFailure(unrecoverableFailure);
+                onSessionValidationFailure(unrecoverableFailure);
                 // fail other child sessions that did not already fail
                 for (int i = nonFailingSessions.size() - 1; i >= 0; --i) {
                     PackageInstallerSession session = nonFailingSessions.get(i);
-                    session.onSessionVerificationFailure(unrecoverableFailure);
+                    session.onSessionValidationFailure(unrecoverableFailure);
                 }
             }
         }
@@ -1293,7 +1286,7 @@
     }
 
     private class ChildStatusIntentReceiver {
-        private final SparseIntArray mChildSessionsRemaining;
+        private final SparseArray<PackageInstallerSession> mChildSessionsRemaining;
         private final IntentSender mStatusReceiver;
         private final IIntentSender.Stub mLocalSender = new IIntentSender.Stub() {
             @Override
@@ -1303,7 +1296,7 @@
             }
         };
 
-        private ChildStatusIntentReceiver(SparseIntArray remainingSessions,
+        private ChildStatusIntentReceiver(SparseArray<PackageInstallerSession> remainingSessions,
                 IntentSender statusReceiver) {
             this.mChildSessionsRemaining = remainingSessions;
             this.mStatusReceiver = statusReceiver;
@@ -1332,15 +1325,13 @@
                 final int status = intent.getIntExtra(PackageInstaller.EXTRA_STATUS,
                         PackageInstaller.STATUS_FAILURE);
                 final int sessionIndex = mChildSessionsRemaining.indexOfKey(sessionId);
+                final String message = intent.getStringExtra(PackageInstaller.EXTRA_STATUS_MESSAGE);
                 if (PackageInstaller.STATUS_SUCCESS == status) {
                     mChildSessionsRemaining.removeAt(sessionIndex);
                     if (mChildSessionsRemaining.size() == 0) {
-                        try {
-                            intent.putExtra(PackageInstaller.EXTRA_SESSION_ID,
-                                    PackageInstallerSession.this.sessionId);
-                            mStatusReceiver.sendIntent(mContext, 0, intent, null, null);
-                        } catch (IntentSender.SendIntentException ignore) {
-                        }
+                        destroyInternal();
+                        dispatchSessionFinished(PackageManager.INSTALL_SUCCEEDED,
+                                "Session installed", null);
                     }
                 } else if (PackageInstaller.STATUS_PENDING_USER_ACTION == status) {
                     try {
@@ -1351,10 +1342,9 @@
                     intent.putExtra(PackageInstaller.EXTRA_SESSION_ID,
                             PackageInstallerSession.this.sessionId);
                     mChildSessionsRemaining.clear(); // we're done. Don't send any more.
-                    try {
-                        mStatusReceiver.sendIntent(mContext, 0, intent, null, null);
-                    } catch (IntentSender.SendIntentException ignore) {
-                    }
+                    destroyInternal();
+                    dispatchSessionFinished(INSTALL_FAILED_INTERNAL_ERROR,
+                            "Child session " + sessionId + " failed: " + message, null);
                 }
             });
         }
@@ -1413,8 +1403,6 @@
     private boolean markAsSealed(@NonNull IntentSender statusReceiver, boolean forTransfer) {
         Objects.requireNonNull(statusReceiver);
 
-        List<PackageInstallerSession> childSessions = getChildSessionsNotLocked();
-
         synchronized (mLock) {
             assertCallerIsOwnerOrRootLocked();
             assertPreparedAndNotDestroyedLocked("commit");
@@ -1446,7 +1434,7 @@
             }
 
             try {
-                sealLocked(childSessions);
+                sealLocked();
             } catch (PackageManagerException e) {
                 return false;
             }
@@ -1487,74 +1475,20 @@
         return true;
     }
 
-    /** Return a list of child sessions or null if the session is not multipackage
-     *
-     * <p> This method is handy to prevent potential deadlocks (b/123391593)
-     */
-    private @Nullable List<PackageInstallerSession> getChildSessionsNotLocked() {
-        if (Thread.holdsLock(mLock)) {
-            Slog.wtf(TAG, "Calling thread " + Thread.currentThread().getName()
-                    + " is holding mLock", new Throwable());
-        }
+    @GuardedBy("mLock")
+    private @Nullable List<PackageInstallerSession> getChildSessionsLocked() {
         List<PackageInstallerSession> childSessions = null;
         if (isMultiPackage()) {
-            final int[] childSessionIds = getChildSessionIds();
-            childSessions = new ArrayList<>(childSessionIds.length);
-            for (int childSessionId : childSessionIds) {
-                childSessions.add(mSessionProvider.getSession(childSessionId));
+            int size = mChildSessions.size();
+            childSessions = new ArrayList<>(size);
+            for (int i = 0; i < size; ++i) {
+                childSessions.add(mChildSessions.valueAt(i));
             }
         }
         return childSessions;
     }
 
     /**
-     * Assert multipackage install has consistent sessions.
-     *
-     * @throws PackageManagerException if child sessions don't match parent session
-     *                                  in respect to staged and enable rollback parameters.
-     */
-    @GuardedBy("mLock")
-    private void assertMultiPackageConsistencyLocked(
-            @NonNull List<PackageInstallerSession> childSessions) throws PackageManagerException {
-        for (PackageInstallerSession childSession : childSessions) {
-            // It might be that the parent session is loaded before all of it's child sessions are,
-            // e.g. when reading sessions from XML. Those sessions will be null here, and their
-            // conformance with the multipackage params will be checked when they're loaded.
-            if (childSession == null) {
-                continue;
-            }
-            assertConsistencyWithLocked(childSession);
-        }
-    }
-
-    /**
-     * Assert consistency with the given session.
-     *
-     * @throws PackageManagerException if other sessions doesn't match this session
-     *                                  in respect to staged and enable rollback parameters.
-     */
-    @GuardedBy("mLock")
-    private void assertConsistencyWithLocked(PackageInstallerSession other)
-            throws PackageManagerException {
-        // Session groups must be consistent wrt to isStaged parameter. Non-staging session
-        // cannot be grouped with staging sessions.
-        if (this.params.isStaged != other.params.isStaged) {
-            throw new PackageManagerException(
-                PackageManager.INSTALL_FAILED_MULTIPACKAGE_INCONSISTENCY,
-                "Multipackage Inconsistency: session " + other.sessionId
-                    + " and session " + sessionId
-                    + " have inconsistent staged settings");
-        }
-        if (this.params.getEnableRollback() != other.params.getEnableRollback()) {
-            throw new PackageManagerException(
-                PackageManager.INSTALL_FAILED_MULTIPACKAGE_INCONSISTENCY,
-                "Multipackage Inconsistency: session " + other.sessionId
-                    + " and session " + sessionId
-                    + " have inconsistent rollback settings");
-        }
-    }
-
-    /**
      * Seal the session to prevent further modification.
      *
      * <p>The session will be sealed after calling this method even if it failed.
@@ -1563,23 +1497,16 @@
      *                                 session was sealed this is the only possible exception.
      */
     @GuardedBy("mLock")
-    private void sealLocked(List<PackageInstallerSession> childSessions)
+    private void sealLocked()
             throws PackageManagerException {
         try {
             assertNoWriteFileTransfersOpenLocked();
             assertPreparedAndNotDestroyedLocked("sealing of session");
-
             mSealed = true;
-
-            if (childSessions != null) {
-                assertMultiPackageConsistencyLocked(childSessions);
-            }
-        } catch (PackageManagerException e) {
-            throw onSessionVerificationFailure(e);
         } catch (Throwable e) {
             // Convert all exceptions into package manager exceptions as only those are handled
             // in the code above.
-            throw onSessionVerificationFailure(new PackageManagerException(e));
+            throw onSessionValidationFailure(new PackageManagerException(e));
         }
     }
 
@@ -1613,20 +1540,20 @@
             }
             return true;
         } catch (PackageManagerException e) {
-            throw onSessionVerificationFailure(e);
+            throw onSessionValidationFailure(e);
         } catch (Throwable e) {
             // Convert all exceptions into package manager exceptions as only those are handled
             // in the code above.
-            throw onSessionVerificationFailure(new PackageManagerException(e));
+            throw onSessionValidationFailure(new PackageManagerException(e));
         }
     }
 
-    private PackageManagerException onSessionVerificationFailure(PackageManagerException e) {
-        onSessionVerificationFailure(e.error, ExceptionUtils.getCompleteMessage(e));
+    private PackageManagerException onSessionValidationFailure(PackageManagerException e) {
+        onSessionValidationFailure(e.error, ExceptionUtils.getCompleteMessage(e));
         return e;
     }
 
-    private void onSessionVerificationFailure(int error, String detailMessage) {
+    private void onSessionValidationFailure(int error, String detailMessage) {
         // Session is sealed but could not be verified, we need to destroy it.
         destroyInternal();
         // Dispatch message to remove session from PackageInstallerService.
@@ -1653,26 +1580,47 @@
      * If session should be sealed, then it's sealed to prevent further modification.
      * If the session can't be sealed then it's destroyed.
      *
-     * Additionally for staged APEX sessions read+validate the package and populate req'd fields.
+     * Additionally for staged APEX/APK sessions read+validate the package and populate req'd
+     * fields.
      *
      * <p> This is meant to be called after all of the sessions are loaded and added to
      * PackageInstallerService
+     *
+     * @param allSessions All sessions loaded by PackageInstallerService, guaranteed to be
+     *                    immutable by the caller during the method call. Used to resolve child
+     *                    sessions Ids to actual object reference.
      */
-    void onAfterSessionRead() {
+    void onAfterSessionRead(SparseArray<PackageInstallerSession> allSessions) {
         synchronized (mLock) {
+            // Resolve null values to actual object references
+            for (int i = mChildSessions.size() - 1; i >= 0; --i) {
+                int childSessionId = mChildSessions.keyAt(i);
+                PackageInstallerSession childSession = allSessions.get(childSessionId);
+                if (childSession != null) {
+                    mChildSessions.setValueAt(i, childSession);
+                } else {
+                    Slog.e(TAG, "Child session not existed: " + childSessionId);
+                    mChildSessions.removeAt(i);
+                }
+            }
+
             if (!mShouldBeSealed || isStagedAndInTerminalState()) {
                 return;
             }
-        }
-        List<PackageInstallerSession> childSessions = getChildSessionsNotLocked();
-        synchronized (mLock) {
             try {
-                sealLocked(childSessions);
+                sealLocked();
 
                 if (isApexInstallation()) {
                     // APEX installations rely on certain fields to be populated after reboot.
                     // E.g. mPackageName.
                     validateApexInstallLocked();
+                } else {
+                    // Populate mPackageName for this APK session which is required by the staging
+                    // manager to check duplicate apk-in-apex.
+                    PackageInstallerSession parent = allSessions.get(mParentSessionId);
+                    if (parent != null && parent.isStagedSessionReady()) {
+                        validateApkInstallLocked();
+                    }
                 }
             } catch (PackageManagerException e) {
                 Slog.e(TAG, "Package not valid", e);
@@ -1708,14 +1656,12 @@
             throw new SecurityException("Can only transfer sessions that use public options");
         }
 
-        List<PackageInstallerSession> childSessions = getChildSessionsNotLocked();
-
         synchronized (mLock) {
             assertCallerIsOwnerOrRootLocked();
             assertPreparedAndNotSealedLocked("transfer");
 
             try {
-                sealLocked(childSessions);
+                sealLocked();
             } catch (PackageManagerException e) {
                 throw new IllegalArgumentException("Package is not valid", e);
             }
@@ -1746,14 +1692,8 @@
             return;
         }
 
-        // For a multiPackage session, read the child sessions
-        // outside of the lock, because reading the child
-        // sessions with the lock held could lead to deadlock
-        // (b/123391593).
-        List<PackageInstallerSession> childSessions = getChildSessionsNotLocked();
-
         try {
-            verifyNonStaged(childSessions);
+            verifyNonStaged();
         } catch (PackageManagerException e) {
             final String completeMsg = ExceptionUtils.getCompleteMessage(e);
             Slog.e(TAG, "Commit of session " + sessionId + " failed: " + completeMsg);
@@ -1762,7 +1702,7 @@
         }
     }
 
-    private void verifyNonStaged(List<PackageInstallerSession> childSessions)
+    private void verifyNonStaged()
             throws PackageManagerException {
         final PackageManagerService.VerificationParams verifyingSession =
                 makeVerificationParams();
@@ -1770,6 +1710,10 @@
             return;
         }
         if (isMultiPackage()) {
+            final List<PackageInstallerSession> childSessions;
+            synchronized (mLock) {
+                childSessions = getChildSessionsLocked();
+            }
             List<PackageManagerService.VerificationParams> verifyingChildSessions =
                     new ArrayList<>(childSessions.size());
             boolean success = true;
@@ -1803,7 +1747,7 @@
         }
     }
 
-    private void installNonStaged(List<PackageInstallerSession> childSessions)
+    private void installNonStaged()
             throws PackageManagerException {
         final PackageManagerService.InstallParams installingSession =
                 makeInstallParams();
@@ -1811,6 +1755,10 @@
             return;
         }
         if (isMultiPackage()) {
+            final List<PackageInstallerSession> childSessions;
+            synchronized (mLock) {
+                childSessions = getChildSessionsLocked();
+            }
             List<PackageManagerService.InstallParams> installingChildSessions =
                     new ArrayList<>(childSessions.size());
             boolean success = true;
@@ -2004,9 +1952,8 @@
             return;
         }
 
-        List<PackageInstallerSession> childSessions = getChildSessionsNotLocked();
         try {
-            installNonStaged(childSessions);
+            installNonStaged();
         } catch (PackageManagerException e) {
             final String completeMsg = ExceptionUtils.getCompleteMessage(e);
             Slog.e(TAG, "Commit of session " + sessionId + " failed: " + completeMsg);
@@ -2091,7 +2038,7 @@
         if (ps == null) {
             return 0;
         }
-        final File apkDirOrPath = ps.codePath;
+        final File apkDirOrPath = ps.getCodePath();
         if (apkDirOrPath == null) {
             return 0;
         }
@@ -2741,15 +2688,6 @@
         }
     }
 
-    /**
-     * Adds a child session ID without any safety / sanity checks. This should only be used to
-     * build a session from XML or similar.
-     */
-    @GuardedBy("mLock")
-    void addChildSessionIdLocked(int sessionId) {
-        mChildSessionIds.put(sessionId, 0);
-    }
-
     public void open() throws IOException {
         if (mActiveCount.getAndIncrement() == 0) {
             mCallback.onSessionActiveChanged(this, true);
@@ -2796,24 +2734,27 @@
         }
     }
 
-    @Override
-    public void abandon() {
-        if (hasParentSessionId()) {
-            throw new IllegalStateException(
-                    "Session " + sessionId + " is a child of multi-package session "
-                            + getParentSessionId() +  " and may not be abandoned directly.");
-        }
-
-        List<PackageInstallerSession> childSessions = getChildSessionsNotLocked();
+    private void abandonNonStaged() {
         synchronized (mLock) {
-            if (params.isStaged && mDestroyed) {
+            assertCallerIsOwnerOrRootLocked();
+            if (mRelinquished) {
+                if (LOGD) Slog.d(TAG, "Ignoring abandon after commit relinquished control");
+                return;
+            }
+            destroyInternal();
+        }
+        dispatchSessionFinished(INSTALL_FAILED_ABORTED, "Session was abandoned", null);
+    }
+
+    private void abandonStaged() {
+        synchronized (mLock) {
+            if (mDestroyed) {
                 // If a user abandons staged session in an unsafe state, then system will try to
                 // abandon the destroyed staged session when it is safe on behalf of the user.
                 assertCallerIsOwnerOrRootOrSystemLocked();
             } else {
                 assertCallerIsOwnerOrRootLocked();
             }
-
             if (isStagedAndInTerminalState()) {
                 // We keep the session in the database if it's in a finalized state. It will be
                 // removed by PackageInstallerService when the last update time is old enough.
@@ -2821,26 +2762,35 @@
                 // do it now.
                 return;
             }
-            if (mCommitted && params.isStaged) {
-                mDestroyed = true;
+            mDestroyed = true;
+            if (mCommitted) {
                 if (!mStagingManager.abortCommittedSessionLocked(this)) {
                     // Do not clean up the staged session from system. It is not safe yet.
                     mCallback.onStagedSessionChanged(this);
                     return;
                 }
-                cleanStageDir(childSessions);
             }
-
-            if (mRelinquished) {
-                Slog.d(TAG, "Ignoring abandon after commit relinquished control");
-                return;
-            }
+            cleanStageDir(getChildSessionsLocked());
             destroyInternal();
         }
         dispatchSessionFinished(INSTALL_FAILED_ABORTED, "Session was abandoned", null);
     }
 
     @Override
+    public void abandon() {
+        if (hasParentSessionId()) {
+            throw new IllegalStateException(
+                    "Session " + sessionId + " is a child of multi-package session "
+                            + getParentSessionId() +  " and may not be abandoned directly.");
+        }
+        if (params.isStaged) {
+            abandonStaged();
+        } else {
+            abandonNonStaged();
+        }
+    }
+
+    @Override
     public boolean isMultiPackage() {
         return params.isMultiPackage;
     }
@@ -2990,7 +2940,7 @@
                         synchronized (mLock) {
                             mDataLoaderFinished = true;
                         }
-                        dispatchSessionVerificationFailure(INSTALL_FAILED_MEDIA_UNAVAILABLE,
+                        dispatchSessionValidationFailure(INSTALL_FAILED_MEDIA_UNAVAILABLE,
                                 "Failure to obtain data loader");
                         return;
                     }
@@ -3040,7 +2990,7 @@
                             synchronized (mLock) {
                                 mDataLoaderFinished = true;
                             }
-                            dispatchSessionVerificationFailure(INSTALL_FAILED_MEDIA_UNAVAILABLE,
+                            dispatchSessionValidationFailure(INSTALL_FAILED_MEDIA_UNAVAILABLE,
                                     "Failed to prepare image.");
                             if (manualStartAndDestroy) {
                                 dataLoader.destroy(dataLoaderId);
@@ -3061,7 +3011,7 @@
                             synchronized (mLock) {
                                 mDataLoaderFinished = true;
                             }
-                            dispatchSessionVerificationFailure(INSTALL_FAILED_MEDIA_UNAVAILABLE,
+                            dispatchSessionValidationFailure(INSTALL_FAILED_MEDIA_UNAVAILABLE,
                                     "DataLoader reported unrecoverable failure.");
                             break;
                     }
@@ -3117,7 +3067,7 @@
                             synchronized (mLock) {
                                 mDataLoaderFinished = true;
                             }
-                            dispatchSessionVerificationFailure(INSTALL_FAILED_MEDIA_UNAVAILABLE,
+                            dispatchSessionValidationFailure(INSTALL_FAILED_MEDIA_UNAVAILABLE,
                                     "Image is missing pages required for installation.");
                             break;
                     }
@@ -3142,15 +3092,22 @@
         return false;
     }
 
-    private void dispatchSessionVerificationFailure(int error, String detailMessage) {
-        mHandler.obtainMessage(MSG_SESSION_VERIFICATION_FAILURE, error, -1,
+    private void dispatchSessionValidationFailure(int error, String detailMessage) {
+        mHandler.obtainMessage(MSG_SESSION_VALIDATION_FAILURE, error, -1,
                 detailMessage).sendToTarget();
     }
 
     @GuardedBy("mLock")
     private int[] getChildSessionIdsLocked() {
-        final int[] childSessionIds = mChildSessionIds.copyKeys();
-        return childSessionIds != null ? childSessionIds : EMPTY_CHILD_SESSION_ARRAY;
+        int size = mChildSessions.size();
+        if (size == 0) {
+            return EMPTY_CHILD_SESSION_ARRAY;
+        }
+        final int[] childSessionIds = new int[size];
+        for (int i = 0; i < size; ++i) {
+            childSessionIds[i] = mChildSessions.keyAt(i);
+        }
+        return childSessionIds;
     }
 
     @Override
@@ -3192,6 +3149,16 @@
             throw new IllegalStateException("Multi-session " + childSessionId
                     + " can't be a child.");
         }
+        if (params.isStaged != childSession.params.isStaged) {
+            throw new IllegalStateException("Multipackage Inconsistency: session "
+                    + childSession.sessionId + " and session " + sessionId
+                    + " have inconsistent staged settings");
+        }
+        if (params.getEnableRollback() != childSession.params.getEnableRollback()) {
+            throw new IllegalStateException("Multipackage Inconsistency: session "
+                    + childSession.sessionId + " and session " + sessionId
+                    + " have inconsistent rollback settings");
+        }
 
         try {
             acquireTransactionLock();
@@ -3205,12 +3172,12 @@
                 assertCallerIsOwnerOrRootLocked();
                 assertPreparedAndNotSealedLocked("addChildSessionId");
 
-                final int indexOfSession = mChildSessionIds.indexOfKey(childSessionId);
+                final int indexOfSession = mChildSessions.indexOfKey(childSessionId);
                 if (indexOfSession >= 0) {
                     return;
                 }
                 childSession.setParentSessionId(this.sessionId);
-                addChildSessionIdLocked(childSessionId);
+                mChildSessions.put(childSessionId, childSession);
             }
         } finally {
             releaseTransactionLock();
@@ -3220,30 +3187,23 @@
 
     @Override
     public void removeChildSessionId(int sessionId) {
-        final PackageInstallerSession session = mSessionProvider.getSession(sessionId);
-        try {
-            acquireTransactionLock();
-            if (session != null) {
+        synchronized (mLock) {
+            assertCallerIsOwnerOrRootLocked();
+            assertPreparedAndNotSealedLocked("removeChildSessionId");
+
+            final int indexOfSession = mChildSessions.indexOfKey(sessionId);
+            if (indexOfSession < 0) {
+                // not added in the first place; no-op
+                return;
+            }
+            PackageInstallerSession session = mChildSessions.valueAt(indexOfSession);
+            try {
+                acquireTransactionLock();
                 session.acquireTransactionLock();
-            }
-
-            synchronized (mLock) {
-                assertCallerIsOwnerOrRootLocked();
-                assertPreparedAndNotSealedLocked("removeChildSessionId");
-
-                final int indexOfSession = mChildSessionIds.indexOfKey(sessionId);
-                if (indexOfSession < 0) {
-                    // not added in the first place; no-op
-                    return;
-                }
-                if (session != null) {
-                    session.setParentSessionId(SessionInfo.INVALID_ID);
-                }
-                mChildSessionIds.removeAt(indexOfSession);
-            }
-        } finally {
-            releaseTransactionLock();
-            if (session != null) {
+                session.setParentSessionId(SessionInfo.INVALID_ID);
+                mChildSessions.removeAt(indexOfSession);
+            } finally {
+                releaseTransactionLock();
                 session.releaseTransactionLock();
             }
         }
@@ -3321,7 +3281,8 @@
     /** {@hide} */
     void setStagedSessionReady() {
         synchronized (mLock) {
-            if (mDestroyed) return; // Do not allow destroyed staged session to change state
+            // Do not allow destroyed/failed staged session to change state
+            if (mDestroyed || mStagedSessionFailed) return;
             mStagedSessionReady = true;
             mStagedSessionApplied = false;
             mStagedSessionFailed = false;
@@ -3332,33 +3293,38 @@
     }
 
     /** {@hide} */
-    void setStagedSessionFailed(@StagedSessionErrorCode int errorCode,
-                                String errorMessage) {
+    void setStagedSessionFailed(@StagedSessionErrorCode int errorCode, String errorMessage) {
+        List<PackageInstallerSession> childSessions;
         synchronized (mLock) {
-            if (mDestroyed) return; // Do not allow destroyed staged session to change state
+            // Do not allow destroyed/failed staged session to change state
+            if (mDestroyed || mStagedSessionFailed) return;
             mStagedSessionReady = false;
             mStagedSessionApplied = false;
             mStagedSessionFailed = true;
             mStagedSessionErrorCode = errorCode;
             mStagedSessionErrorMessage = errorMessage;
             Slog.d(TAG, "Marking session " + sessionId + " as failed: " + errorMessage);
+            childSessions = getChildSessionsLocked();
         }
-        cleanStageDirNotLocked();
+        cleanStageDir(childSessions);
         mCallback.onStagedSessionChanged(this);
     }
 
     /** {@hide} */
     void setStagedSessionApplied() {
+        List<PackageInstallerSession> childSessions;
         synchronized (mLock) {
-            if (mDestroyed) return; // Do not allow destroyed staged session to change state
+            // Do not allow destroyed/failed staged session to change state
+            if (mDestroyed || mStagedSessionFailed) return;
             mStagedSessionReady = false;
             mStagedSessionApplied = true;
             mStagedSessionFailed = false;
             mStagedSessionErrorCode = SessionInfo.STAGED_SESSION_NO_ERROR;
             mStagedSessionErrorMessage = "";
             Slog.d(TAG, "Marking session " + sessionId + " as applied");
+            childSessions = getChildSessionsLocked();
         }
-        cleanStageDirNotLocked();
+        cleanStageDir(childSessions);
         mCallback.onStagedSessionChanged(this);
     }
 
@@ -3400,7 +3366,7 @@
     private void destroyInternal() {
         synchronized (mLock) {
             mSealed = true;
-            if (!params.isStaged || isStagedAndInTerminalState()) {
+            if (!params.isStaged) {
                 mDestroyed = true;
             }
             // Force shut down all bridges
@@ -3426,23 +3392,10 @@
         }
     }
 
-    /**
-     * <b>must not hold {@link #mLock}</b>
-     */
-    private void cleanStageDirNotLocked() {
-        if (Thread.holdsLock(mLock)) {
-            Slog.wtf(TAG, "Calling thread " + Thread.currentThread().getName()
-                    + " is holding mLock", new Throwable());
-        }
-        cleanStageDir(getChildSessionsNotLocked());
-    }
-
     private void cleanStageDir(List<PackageInstallerSession> childSessions) {
         if (childSessions != null) {
             for (PackageInstallerSession childSession : childSessions) {
-                if (childSession != null) {
-                    childSession.cleanStageDir();
-                }
+                childSession.cleanStageDir();
             }
         } else {
             cleanStageDir();
@@ -3502,7 +3455,7 @@
         pw.printPair("params.isMultiPackage", params.isMultiPackage);
         pw.printPair("params.isStaged", params.isStaged);
         pw.printPair("mParentSessionId", mParentSessionId);
-        pw.printPair("mChildSessionIds", mChildSessionIds);
+        pw.printPair("mChildSessionIds", getChildSessionIdsLocked());
         pw.printPair("mStagedSessionApplied", mStagedSessionApplied);
         pw.printPair("mStagedSessionFailed", mStagedSessionFailed);
         pw.printPair("mStagedSessionReady", mStagedSessionReady);
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 8d50c73..5fd7374 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -120,6 +120,7 @@
 import static com.android.server.pm.InstructionSets.getDexCodeInstructionSets;
 import static com.android.server.pm.InstructionSets.getPreferredInstructionSet;
 import static com.android.server.pm.PackageManagerServiceCompilerMapping.getDefaultCompilerFilter;
+import static com.android.server.pm.PackageManagerServiceUtils.comparePackageSignatures;
 import static com.android.server.pm.PackageManagerServiceUtils.compareSignatures;
 import static com.android.server.pm.PackageManagerServiceUtils.compressedFileExists;
 import static com.android.server.pm.PackageManagerServiceUtils.decompressFile;
@@ -247,6 +248,8 @@
 import android.os.Environment;
 import android.os.FileUtils;
 import android.os.Handler;
+import android.os.HandlerExecutor;
+import android.os.HandlerThread;
 import android.os.IBinder;
 import android.os.Looper;
 import android.os.Message;
@@ -364,7 +367,6 @@
 import com.android.server.pm.permission.BasePermission;
 import com.android.server.pm.permission.PermissionManagerService;
 import com.android.server.pm.permission.PermissionManagerServiceInternal;
-import com.android.server.pm.permission.PermissionsState;
 import com.android.server.policy.PermissionPolicyInternal;
 import com.android.server.rollback.RollbackManagerInternal;
 import com.android.server.security.VerityUtils;
@@ -417,7 +419,9 @@
 import java.util.Objects;
 import java.util.Set;
 import java.util.UUID;
+import java.util.concurrent.CompletableFuture;
 import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.Executor;
 import java.util.concurrent.ExecutorService;
 import java.util.concurrent.Future;
 import java.util.concurrent.TimeUnit;
@@ -922,6 +926,7 @@
         private final Object mLock;
         private final Installer mInstaller;
         private final Object mInstallLock;
+        private final Executor mBackgroundExecutor;
 
         // ----- producers -----
         private final Singleton<ComponentResolver> mComponentResolverProducer;
@@ -943,6 +948,7 @@
 
         Injector(Context context, Object lock, Installer installer,
                 Object installLock, PackageAbiHelper abiHelper,
+                Executor backgroundExecutor,
                 Producer<ComponentResolver> componentResolverProducer,
                 Producer<PermissionManagerServiceInternal> permissionManagerProducer,
                 Producer<UserManagerService> userManagerProducer,
@@ -964,6 +970,7 @@
             mInstaller = installer;
             mAbiHelper = abiHelper;
             mInstallLock = installLock;
+            mBackgroundExecutor = backgroundExecutor;
             mComponentResolverProducer = new Singleton<>(componentResolverProducer);
             mPermissionManagerProducer = new Singleton<>(permissionManagerProducer);
             mUserManagerProducer = new Singleton<>(userManagerProducer);
@@ -1077,6 +1084,10 @@
         public PlatformCompat getCompatibility() {
             return mPlatformCompatProducer.get(this, mPackageManager);
         }
+
+        public Executor getBackgroundExecutor() {
+            return mBackgroundExecutor;
+        }
     }
 
     @VisibleForTesting(visibility = Visibility.PRIVATE)
@@ -1128,6 +1139,7 @@
         public @Nullable String storageManagerPackage;
         public @Nullable String defaultTextClassifierPackage;
         public @Nullable String systemTextClassifierPackage;
+        public @Nullable String overlayConfigSignaturePackage;
         public ViewCompiler viewCompiler;
         public @Nullable String wellbeingPackage;
         public @Nullable String retailDemoPackage;
@@ -1660,6 +1672,7 @@
     final @Nullable String mServicesExtensionPackageName;
     final @Nullable String mSharedSystemSharedLibraryPackageName;
     final @Nullable String mRetailDemoPackage;
+    final @Nullable String mOverlayConfigSignaturePackage;
 
     private final PackageUsage mPackageUsage = new PackageUsage();
     private final CompilerStats mCompilerStats = new CompilerStats();
@@ -1804,7 +1817,7 @@
                     synchronized (mLock) {
                         removeMessages(WRITE_SETTINGS);
                         removeMessages(WRITE_PACKAGE_RESTRICTIONS);
-                        mSettings.writeLPr();
+                        writeSettingsLPrTEMP();
                         mDirtyUsers.clear();
                     }
                     Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
@@ -1824,6 +1837,7 @@
                     Process.setThreadPriority(Process.THREAD_PRIORITY_DEFAULT);
                     synchronized (mLock) {
                         removeMessages(WRITE_PACKAGE_LIST);
+                        mPermissionManager.writePermissionsStateToPackageSettingsTEMP();
                         mSettings.writePackageListLPr(msg.arg1);
                     }
                     Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
@@ -2229,7 +2243,7 @@
                     sendPackageBroadcast(Intent.ACTION_PACKAGE_ADDED, packageName,
                             extras, 0 /*flags*/,
                             installerPackageName, null /*finishedReceiver*/,
-                            updateUserIds, instantUserIds, null /* broadcastWhitelist */);
+                            updateUserIds, instantUserIds, null /* broadcastAllowList */);
                 }
                 // if the required verifier is defined, but, is not the installer of record
                 // for the package, it gets notified
@@ -2239,7 +2253,7 @@
                     sendPackageBroadcast(Intent.ACTION_PACKAGE_ADDED, packageName,
                             extras, 0 /*flags*/,
                             mRequiredVerifierPackage, null /*finishedReceiver*/,
-                            updateUserIds, instantUserIds, null /* broadcastWhitelist */);
+                            updateUserIds, instantUserIds, null /* broadcastAllowList */);
                 }
                 // If package installer is defined, notify package installer about new
                 // app installed
@@ -2247,7 +2261,7 @@
                     sendPackageBroadcast(Intent.ACTION_PACKAGE_ADDED, packageName,
                             extras, Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND /*flags*/,
                             mRequiredInstallerPackage, null /*finishedReceiver*/,
-                            firstUserIds, instantUserIds, null /* broadcastWhitelist */);
+                            firstUserIds, instantUserIds, null /* broadcastAllowList */);
                 }
 
                 // Send replaced for users that don't see the package for the first time
@@ -2260,19 +2274,19 @@
                         sendPackageBroadcast(Intent.ACTION_PACKAGE_REPLACED, packageName,
                                 extras, 0 /*flags*/,
                                 installerPackageName, null /*finishedReceiver*/,
-                                updateUserIds, instantUserIds, null /*broadcastWhitelist*/);
+                                updateUserIds, instantUserIds, null /*broadcastAllowList*/);
                     }
                     if (notifyVerifier) {
                         sendPackageBroadcast(Intent.ACTION_PACKAGE_REPLACED, packageName,
                                 extras, 0 /*flags*/,
                                 mRequiredVerifierPackage, null /*finishedReceiver*/,
-                                updateUserIds, instantUserIds, null /*broadcastWhitelist*/);
+                                updateUserIds, instantUserIds, null /*broadcastAllowList*/);
                     }
                     sendPackageBroadcast(Intent.ACTION_MY_PACKAGE_REPLACED,
                             null /*package*/, null /*extras*/, 0 /*flags*/,
                             packageName /*targetPackage*/,
                             null /*finishedReceiver*/, updateUserIds, instantUserIds,
-                            null /*broadcastWhitelist*/);
+                            null /*broadcastAllowList*/);
                 } else if (launchedForRestore && !res.pkg.isSystem()) {
                     // First-install and we did a restore, so we're responsible for the
                     // first-launch broadcast.
@@ -2504,7 +2518,7 @@
                 }
 
                 mSettings.onVolumeForgotten(fsUuid);
-                mSettings.writeLPr();
+                writeSettingsLPrTEMP();
             }
         }
     };
@@ -2577,17 +2591,21 @@
         t.traceBegin("create package manager");
         final Object lock = new Object();
         final Object installLock = new Object();
+        HandlerThread backgroundThread = new HandlerThread("PackageManagerBg");
+        backgroundThread.start();
+        Handler backgroundHandler = new Handler(backgroundThread.getLooper());
 
         Injector injector = new Injector(
                 context, lock, installer, installLock, new PackageAbiHelperImpl(),
+                new HandlerExecutor(backgroundHandler),
                 (i, pm) ->
                         new ComponentResolver(i.getUserManagerService(), pm.mPmInternal, lock),
                 (i, pm) ->
-                        PermissionManagerService.create(context, lock),
+                PermissionManagerService.create(context, lock),
                 (i, pm) ->
-                        new UserManagerService(context, pm,
-                                new UserDataPreparer(installer, installLock, context, onlyCore),
-                                lock),
+                new UserManagerService(context, pm,
+                        new UserDataPreparer(installer, installLock, context, onlyCore),
+                        lock),
                 (i, pm) ->
                         new Settings(Environment.getDataDirectory(),
                                 i.getPermissionManagerServiceInternal().getPermissionSettings(),
@@ -2817,6 +2835,7 @@
         mIncidentReportApproverPackage = testParams.incidentReportApproverPackage;
         mServicesExtensionPackageName = testParams.servicesExtensionPackageName;
         mSharedSystemSharedLibraryPackageName = testParams.sharedSystemSharedLibraryPackageName;
+        mOverlayConfigSignaturePackage = testParams.overlayConfigSignaturePackage;
 
         mResolveComponentName = testParams.resolveComponentName;
         mPackages.putAll(testParams.packages);
@@ -2966,7 +2985,7 @@
             mHandler = new PackageHandler(mHandlerThread.getLooper());
             mProcessLoggingHandler = new ProcessLoggingHandler();
             Watchdog.getInstance().addThread(mHandler, WATCHDOG_TIMEOUT);
-            mInstantAppRegistry = new InstantAppRegistry(this);
+            mInstantAppRegistry = new InstantAppRegistry(this, mPermissionManager);
 
             ArrayMap<String, SystemConfig.SharedLibraryEntry> libConfig
                     = systemConfig.getSharedLibraries();
@@ -3006,7 +3025,7 @@
             final int packageSettingCount = mSettings.mPackages.size();
             for (int i = packageSettingCount - 1; i >= 0; i--) {
                 PackageSetting ps = mSettings.mPackages.valueAt(i);
-                if (!isExternal(ps) && (ps.codePath == null || !ps.codePath.exists())
+                if (!isExternal(ps) && (ps.getCodePath() == null || !ps.getCodePath().exists())
                         && mSettings.getDisabledSystemPkgLPr(ps.name) != null) {
                     mSettings.mPackages.removeAt(i);
                     mSettings.enableSystemPackageLPw(ps.name);
@@ -3171,11 +3190,11 @@
                             logCriticalInfo(Log.WARN,
                                     "Expecting better updated system app for " + ps.name
                                     + "; removing system app.  Last known"
-                                    + " codePath=" + ps.codePathString
+                                    + " codePath=" + ps.getCodePathString()
                                     + ", versionCode=" + ps.versionCode
                                     + "; scanned versionCode=" + scannedPkg.getLongVersionCode());
                             removePackageLI(scannedPkg, true);
-                            mExpectingBetter.put(ps.name, ps.codePath);
+                            mExpectingBetter.put(ps.name, ps.getCodePath());
                         }
 
                         continue;
@@ -3198,14 +3217,14 @@
                         // code path, but, changes the package name.
                         final PackageSetting disabledPs =
                                 mSettings.getDisabledSystemPkgLPr(ps.name);
-                        if (disabledPs.codePath == null || !disabledPs.codePath.exists()
+                        if (disabledPs.getCodePath() == null || !disabledPs.getCodePath().exists()
                                 || disabledPs.pkg == null) {
                             possiblyDeletedUpdatedSystemApps.add(ps.name);
                         } else {
                             // We're expecting that the system app should remain disabled, but add
                             // it to expecting better to recover in case the data version cannot
                             // be scanned.
-                            mExpectingBetter.put(disabledPs.name, disabledPs.codePath);
+                            mExpectingBetter.put(disabledPs.name, disabledPs.getCodePath());
                         }
                     }
                 }
@@ -3382,6 +3401,7 @@
             mAppPredictionServicePackage = getAppPredictionServicePackageName();
             mIncidentReportApproverPackage = getIncidentReportApproverPackageName();
             mRetailDemoPackage = getRetailDemoPackageName();
+            mOverlayConfigSignaturePackage = getOverlayConfigSignaturePackageName();
 
             // Now that we know all of the shared libraries, update all clients to have
             // the correct library paths.
@@ -3422,6 +3442,7 @@
                     + ((SystemClock.uptimeMillis()-startTime)/1000f)
                     + " seconds");
 
+            mPermissionManager.readPermissionsStateFromPackageSettingsTEMP();
             // If the platform SDK has changed since the last time we booted,
             // we need to re-grant app permission to catch any new ones that
             // appear.  This is really a hack, and means that apps can in some
@@ -3476,6 +3497,7 @@
                     return;
                 }
                 int count = 0;
+                final Installer.Batch batch = new Installer.Batch();
                 for (String pkgName : deferPackages) {
                     AndroidPackage pkg = null;
                     synchronized (mLock) {
@@ -3485,13 +3507,14 @@
                         }
                     }
                     if (pkg != null) {
-                        synchronized (mInstallLock) {
-                            prepareAppDataAndMigrateLIF(pkg, UserHandle.USER_SYSTEM, storageFlags,
-                                    true /* maybeMigrateAppData */);
-                        }
+                        prepareAppDataAndMigrate(batch, pkg, UserHandle.USER_SYSTEM, storageFlags,
+                                true /* maybeMigrateAppData */);
                         count++;
                     }
                 }
+                synchronized (mInstallLock) {
+                    executeBatchLI(batch);
+                }
                 traceLog.traceEnd();
                 Slog.i(TAG, "Deferred reconcileAppsData finished " + count + " packages");
             }, "prepareAppData");
@@ -3539,7 +3562,7 @@
 
             // can downgrade to reader
             t.traceBegin("write settings");
-            mSettings.writeLPr();
+            writeSettingsLPrTEMP();
             t.traceEnd();
             EventLog.writeEvent(EventLogTags.BOOT_PROGRESS_PMS_READY,
                     SystemClock.uptimeMillis());
@@ -3743,7 +3766,7 @@
                         Slog.e(TAG, "updateAllSharedLibrariesLPw failed: ", e);
                     }
                     mPermissionManager.updatePermissions(pkg.getPackageName(), pkg);
-                    mSettings.writeLPr();
+                    writeSettingsLPrTEMP();
                 }
             } catch (PackageManagerException e) {
                 // Whoops! Something went very wrong; roll back to the stub and disable the package
@@ -3754,9 +3777,8 @@
                         // If we don't, installing the system package fails during scan
                         enableSystemPackageLPw(stubPkg);
                     }
-                    installPackageFromSystemLIF(stubPkg.getCodePath(),
-                            null /*allUserHandles*/, null /*origUserHandles*/,
-                            null /*origPermissionsState*/, true /*writeSettings*/);
+                    installPackageFromSystemLIF(stubPkg.getCodePath(), null /*allUserHandles*/,
+                            null /*origUserHandles*/, true /*writeSettings*/);
                 } catch (PackageManagerException pme) {
                     // Serious WTF; we have to be able to install the stub
                     Slog.wtf(TAG, "Failed to restore system package:" + stubPkg.getPackageName(),
@@ -3770,7 +3792,7 @@
                             stubPs.setEnabled(COMPONENT_ENABLED_STATE_DISABLED,
                                     UserHandle.USER_SYSTEM, "android");
                         }
-                        mSettings.writeLPr();
+                        writeSettingsLPrTEMP();
                     }
                 }
                 return false;
@@ -4380,14 +4402,13 @@
         final PackageUserState state = ps.readUserState(userId);
         AndroidPackage p = ps.pkg;
         if (p != null) {
-            final PermissionsState permissionsState = ps.getPermissionsState();
-
             // Compute GIDs only if requested
             final int[] gids = (flags & PackageManager.GET_GIDS) == 0
-                    ? EMPTY_INT_ARRAY : permissionsState.computeGids(userId);
+                    ? EMPTY_INT_ARRAY : mPermissionManager.getPackageGids(ps.name, userId);
             // Compute granted permissions only if package has requested permissions
             final Set<String> permissions = ArrayUtils.isEmpty(p.getRequestedPermissions())
-                    ? Collections.emptySet() : permissionsState.getPermissions(userId);
+                    ? Collections.emptySet()
+                    : mPermissionManager.getGrantedPermissions(ps.name, userId);
 
             PackageInfo packageInfo = PackageInfoUtils.generate(p, gids, flags,
                     ps.firstInstallTime, ps.lastUpdateTime, permissions, state, userId, ps);
@@ -4858,13 +4879,13 @@
                 }
                 // TODO: Shouldn't this be checking for package installed state for userId and
                 // return null?
-                return ps.getPermissionsState().computeGids(userId);
+                return mPermissionManager.getPackageGids(packageName, userId);
             }
             if ((flags & MATCH_KNOWN_PACKAGES) != 0) {
                 final PackageSetting ps = mSettings.mPackages.get(packageName);
                 if (ps != null && ps.isMatch(flags)
                         && !shouldFilterApplicationLocked(ps, callingUid, userId)) {
-                    return ps.getPermissionsState().computeGids(userId);
+                    return mPermissionManager.getPackageGids(packageName, userId);
                 }
             }
         }
@@ -5785,10 +5806,6 @@
     private void updateSequenceNumberLP(PackageSetting pkgSetting, int[] userList) {
         for (int i = userList.length - 1; i >= 0; --i) {
             final int userId = userList[i];
-            // don't add instant app to the list of updates
-            if (pkgSetting.getInstantApp(userId)) {
-                continue;
-            }
             SparseArray<String> changedPackages = mChangedPackages.get(userId);
             if (changedPackages == null) {
                 changedPackages = new SparseArray<>();
@@ -5833,6 +5850,11 @@
             for (int i = sequenceNumber; i < mChangedPackagesSequenceNumber; i++) {
                 final String packageName = changedPackages.get(i);
                 if (packageName != null) {
+                    // Filter out the changes if the calling package should not be able to see it.
+                    final PackageSetting ps = mSettings.mPackages.get(packageName);
+                    if (shouldFilterApplicationLocked(ps, callingUid, userId)) {
+                        continue;
+                    }
                     packageNames.add(packageName);
                 }
             }
@@ -7815,7 +7837,7 @@
     // low 'int'-sized word: relative priority among 'always' results.
     private long getDomainVerificationStatusLPr(PackageSetting ps, int userId) {
         long result = ps.getDomainVerificationStatusForUser(userId);
-        // if none available, get the master status
+        // if none available, get the status
         if (result >> 32 == INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_UNDEFINED) {
             if (ps.getIntentFilterVerificationInfo() != null) {
                 result = ((long)ps.getIntentFilterVerificationInfo().getStatus()) << 32;
@@ -8546,6 +8568,15 @@
             if (listUninstalled) {
                 list = new ArrayList<>(mSettings.mPackages.size());
                 for (PackageSetting ps : mSettings.mPackages.values()) {
+                    if (listFactory) {
+                        if (!ps.isSystem()) {
+                            continue;
+                        }
+                        PackageSetting psDisabled = mSettings.getDisabledSystemPkgLPr(ps);
+                        if (psDisabled != null) {
+                            ps = psDisabled;
+                        }
+                    }
                     if (filterSharedLibPackageLPr(ps, callingUid, userId, flags)) {
                         continue;
                     }
@@ -8560,7 +8591,16 @@
             } else {
                 list = new ArrayList<>(mPackages.size());
                 for (AndroidPackage p : mPackages.values()) {
-                    final PackageSetting ps = getPackageSetting(p.getPackageName());
+                    PackageSetting ps = getPackageSetting(p.getPackageName());
+                    if (listFactory) {
+                        if (!p.isSystem()) {
+                            continue;
+                        }
+                        PackageSetting psDisabled = mSettings.getDisabledSystemPkgLPr(ps);
+                        if (psDisabled != null) {
+                            ps = psDisabled;
+                        }
+                    }
                     if (filterSharedLibPackageLPr(ps, callingUid, userId, flags)) {
                         continue;
                     }
@@ -9145,7 +9185,7 @@
                 : getLastModifiedTime(parsedPackage);
         final VersionInfo settingsVersionForPackage = getSettingsVersionForPackage(parsedPackage);
         if (ps != null && !forceCollect
-                && ps.codePathString.equals(parsedPackage.getCodePath())
+                && ps.getCodePathString().equals(parsedPackage.getCodePath())
                 && ps.timeStamp == lastModifiedTime
                 && !isCompatSignatureUpdateNeeded(settingsVersionForPackage)
                 && !isRecoverSignatureUpdateNeeded(settingsVersionForPackage)) {
@@ -9378,8 +9418,8 @@
             }
         }
 
-        final boolean newPkgChangedPaths =
-                pkgAlreadyExists && !pkgSetting.codePathString.equals(parsedPackage.getCodePath());
+        final boolean newPkgChangedPaths = pkgAlreadyExists
+                && !pkgSetting.getCodePathString().equals(parsedPackage.getCodePath());
         final boolean newPkgVersionGreater =
                 pkgAlreadyExists && parsedPackage.getLongVersionCode() > pkgSetting.versionCode;
         final boolean isSystemPkgBetter = scanSystemPartition && isSystemPkgUpdated
@@ -9398,11 +9438,11 @@
                     "System package updated;"
                     + " name: " + pkgSetting.name
                     + "; " + pkgSetting.versionCode + " --> " + parsedPackage.getLongVersionCode()
-                    + "; " + pkgSetting.codePathString + " --> " + parsedPackage.getCodePath());
+                    + "; " + pkgSetting.getCodePathString()
+                            + " --> " + parsedPackage.getCodePath());
 
             final InstallArgs args = createInstallArgsForExisting(
-                    pkgSetting.codePathString,
-                    pkgSetting.resourcePathString, getAppDexInstructionSets(
+                    pkgSetting.getCodePathString(), getAppDexInstructionSets(
                             pkgSetting.primaryCpuAbiString, pkgSetting.secondaryCpuAbiString));
             args.cleanUpResourcesLI();
             synchronized (mLock) {
@@ -9477,11 +9517,10 @@
                                 + " name: " + pkgSetting.name
                                 + "; " + pkgSetting.versionCode + " --> "
                                 + parsedPackage.getLongVersionCode()
-                                + "; " + pkgSetting.codePathString + " --> "
+                                + "; " + pkgSetting.getCodePathString() + " --> "
                                 + parsedPackage.getCodePath());
                 InstallArgs args = createInstallArgsForExisting(
-                        pkgSetting.codePathString,
-                        pkgSetting.resourcePathString, getAppDexInstructionSets(
+                        pkgSetting.getCodePathString(), getAppDexInstructionSets(
                                 pkgSetting.primaryCpuAbiString, pkgSetting.secondaryCpuAbiString));
                 synchronized (mInstallLock) {
                     args.cleanUpResourcesLI();
@@ -9494,7 +9533,7 @@
                 logCriticalInfo(Log.INFO,
                         "System package disabled;"
                                 + " name: " + pkgSetting.name
-                                + "; old: " + pkgSetting.codePathString + " @ "
+                                + "; old: " + pkgSetting.getCodePathString() + " @ "
                                 + pkgSetting.versionCode
                                 + "; new: " + parsedPackage.getCodePath() + " @ "
                                 + parsedPackage.getCodePath());
@@ -10330,6 +10369,7 @@
                 mInstaller.rmPackageDir(codePath.getAbsolutePath());
                 if (codePathParent.getName().startsWith(RANDOM_DIR_PREFIX)) {
                     mInstaller.rmPackageDir(codePathParent.getAbsolutePath());
+                    removeCachedResult(codePathParent);
                 }
             } catch (InstallerException e) {
                 Slog.w(TAG, "Failed to remove code path", e);
@@ -10339,6 +10379,16 @@
         }
     }
 
+    private void removeCachedResult(@NonNull File codePath) {
+        if (mCacheDir == null) {
+            return;
+        }
+
+        final PackageCacher cacher = new PackageCacher(mCacheDir);
+        // Find and delete the cached result belong to the given codePath.
+        cacher.cleanCachedResult(codePath);
+    }
+
     private int[] resolveUserIds(int userId) {
         return (userId == UserHandle.USER_ALL) ? mUserManager.getUserIds() : new int[] { userId };
     }
@@ -11320,7 +11370,7 @@
                         if (changedAbiCodePath == null) {
                             changedAbiCodePath = new ArrayList<>();
                         }
-                        changedAbiCodePath.add(ps.codePathString);
+                        changedAbiCodePath.add(ps.getCodePathString());
                     }
                 }
             }
@@ -11416,7 +11466,6 @@
 
         // Initialize package source and resource directories
         final File destCodeFile = new File(parsedPackage.getCodePath());
-        final File destResourceFile = new File(parsedPackage.getCodePath());
 
         // We keep references to the derived CPU Abis from settings in oder to reuse
         // them in the case where we're not upgrading or booting for the first time.
@@ -11474,7 +11523,7 @@
             // REMOVE SharedUserSetting from method; update in a separate call
             pkgSetting = Settings.createNewSetting(parsedPackage.getPackageName(),
                     originalPkgSetting, disabledPkgSetting, realPkgName, sharedUserSetting,
-                    destCodeFile, destResourceFile, parsedPackage.getNativeLibraryRootDir(),
+                    destCodeFile, parsedPackage.getNativeLibraryRootDir(),
                     AndroidPackageUtils.getRawPrimaryCpuAbi(parsedPackage),
                     AndroidPackageUtils.getRawSecondaryCpuAbi(parsedPackage),
                     parsedPackage.getVersionCode(), pkgFlags, pkgPrivateFlags, user,
@@ -11492,7 +11541,7 @@
             // secondaryCpuAbi are not known at this point so we always update them
             // to null here, only to reset them at a later point.
             Settings.updatePackageSetting(pkgSetting, disabledPkgSetting, sharedUserSetting,
-                    destCodeFile, destResourceFile, parsedPackage.getNativeLibraryDir(),
+                    destCodeFile, parsedPackage.getNativeLibraryDir(),
                     AndroidPackageUtils.getPrimaryCpuAbi(parsedPackage, pkgSetting),
                     AndroidPackageUtils.getSecondaryCpuAbi(parsedPackage, pkgSetting),
                     PackageInfoUtils.appInfoFlags(parsedPackage, pkgSetting),
@@ -11632,8 +11681,9 @@
 
         if (DEBUG_ABI_SELECTION) {
             Slog.d(TAG, "Resolved nativeLibraryRoot for " + parsedPackage.getPackageName()
-                    + " to root=" + parsedPackage.getNativeLibraryRootDir() + ", isa="
-                    + parsedPackage.isNativeLibraryRootRequiresIsa());
+                    + " to root=" + parsedPackage.getNativeLibraryRootDir()
+                    + ", to dir=" + parsedPackage.getNativeLibraryDir()
+                    + ", isa=" + parsedPackage.isNativeLibraryRootRequiresIsa());
         }
 
         // Push the derived path down into PackageSettings so we know what to
@@ -11641,9 +11691,10 @@
         pkgSetting.legacyNativeLibraryPathString = parsedPackage.getNativeLibraryRootDir();
 
         if (DEBUG_ABI_SELECTION) {
-            Log.d(TAG, "Abis for package[" + parsedPackage.getPackageName() + "] are" +
-                    " primary=" + AndroidPackageUtils.getRawPrimaryCpuAbi(parsedPackage) +
-                    " secondary=" + AndroidPackageUtils.getRawSecondaryCpuAbi(parsedPackage));
+            Log.d(TAG, "Abis for package[" + parsedPackage.getPackageName() + "] are"
+                    + " primary=" + pkgSetting.primaryCpuAbiString
+                    + " secondary=" + pkgSetting.primaryCpuAbiString
+                    + " abiOverride=" + pkgSetting.cpuAbiOverrideString);
         }
 
         if ((scanFlags & SCAN_BOOTING) == 0 && pkgSetting.sharedUser != null) {
@@ -12061,15 +12112,13 @@
                     if (known != null) {
                         if (DEBUG_PACKAGE_SCANNING) {
                             Log.d(TAG, "Examining " + pkg.getCodePath()
-                                    + " and requiring known paths " + known.codePathString
-                                    + " & " + known.resourcePathString);
+                                    + " and requiring known path " + known.getCodePathString());
                         }
-                        if (!pkg.getCodePath().equals(known.codePathString)
-                                || !pkg.getCodePath().equals(known.resourcePathString)) {
+                        if (!pkg.getCodePath().equals(known.getCodePathString())) {
                             throw new PackageManagerException(INSTALL_FAILED_PACKAGE_CHANGED,
                                     "Application package " + pkg.getPackageName()
                                     + " found at " + pkg.getCodePath()
-                                    + " but expected at " + known.codePathString
+                                    + " but expected at " + known.getCodePathString()
                                     + "; ignoring.");
                         }
                     } else {
@@ -12117,12 +12166,8 @@
                 if (sharedUserSetting != null && sharedUserSetting.isPrivileged()) {
                     // Exempt SharedUsers signed with the platform key.
                     PackageSetting platformPkgSetting = mSettings.mPackages.get("android");
-                    if ((platformPkgSetting.signatures.mSigningDetails
-                            != PackageParser.SigningDetails.UNKNOWN)
-                            && (compareSignatures(
-                                    platformPkgSetting.signatures.mSigningDetails.signatures,
-                            pkg.getSigningDetails().signatures)
-                                            != PackageManager.SIGNATURE_MATCH)) {
+                    if (!comparePackageSignatures(platformPkgSetting,
+                            pkg.getSigningDetails().signatures)) {
                         throw new PackageManagerException("Apps that share a user with a " +
                                 "privileged app must themselves be marked as privileged. " +
                                 pkg.getPackageName() + " shares privileged user " +
@@ -12169,12 +12214,8 @@
                     if (pkg.getTargetSdkVersion() < Build.VERSION_CODES.Q) {
                         final PackageSetting platformPkgSetting =
                                 mSettings.getPackageLPr("android");
-                        if ((platformPkgSetting.signatures.mSigningDetails
-                                    != PackageParser.SigningDetails.UNKNOWN)
-                                && (compareSignatures(
-                                        platformPkgSetting.signatures.mSigningDetails.signatures,
-                                pkg.getSigningDetails().signatures)
-                                    != PackageManager.SIGNATURE_MATCH)) {
+                        if (!comparePackageSignatures(platformPkgSetting,
+                                pkg.getSigningDetails().signatures)) {
                             throw new PackageManagerException("Overlay "
                                     + pkg.getPackageName()
                                     + " must target Q or later, "
@@ -12183,24 +12224,35 @@
                     }
 
                     // A non-preloaded overlay package, without <overlay android:targetName>, will
-                    // only be used if it is signed with the same certificate as its target. If the
-                    // target is already installed, check this here to augment the last line of
-                    // defence which is OMS.
+                    // only be used if it is signed with the same certificate as its target OR if
+                    // it is signed with the same certificate as a reference package declared
+                    // in 'config-signature' tag of SystemConfig.
+                    // If the target is already installed or 'config-signature' tag in SystemConfig
+                    // is set, check this here to augment the last line of defence which is OMS.
                     if (pkg.getOverlayTargetName() == null) {
                         final PackageSetting targetPkgSetting =
                                 mSettings.getPackageLPr(pkg.getOverlayTarget());
                         if (targetPkgSetting != null) {
-                            if ((targetPkgSetting.signatures.mSigningDetails
-                                        != PackageParser.SigningDetails.UNKNOWN)
-                                    && (compareSignatures(
-                                            targetPkgSetting.signatures.mSigningDetails.signatures,
-                                    pkg.getSigningDetails().signatures)
-                                        != PackageManager.SIGNATURE_MATCH)) {
-                                throw new PackageManagerException("Overlay "
-                                        + pkg.getPackageName() + " and target "
-                                        + pkg.getOverlayTarget() + " signed with"
-                                        + " different certificates, and the overlay lacks"
-                                        + " <overlay android:targetName>");
+                            if (!comparePackageSignatures(targetPkgSetting,
+                                    pkg.getSigningDetails().signatures)) {
+                                // check reference signature
+                                if (mOverlayConfigSignaturePackage == null) {
+                                    throw new PackageManagerException("Overlay "
+                                            + pkg.getPackageName() + " and target "
+                                            + pkg.getOverlayTarget() + " signed with"
+                                            + " different certificates, and the overlay lacks"
+                                            + " <overlay android:targetName>");
+                                }
+                                final PackageSetting refPkgSetting =
+                                        mSettings.getPackageLPr(mOverlayConfigSignaturePackage);
+                                if (!comparePackageSignatures(refPkgSetting,
+                                        pkg.getSigningDetails().signatures)) {
+                                    throw new PackageManagerException("Overlay "
+                                            + pkg.getPackageName() + " signed with a different "
+                                            + "certificate than both the reference package and "
+                                            + "target " + pkg.getOverlayTarget() + ", and the "
+                                            + "overlay lacks <overlay android:targetName>");
+                                }
                             }
                         }
                     }
@@ -13043,7 +13095,7 @@
                 return true;
             }
             if (sendRemoved) {
-                killApplication(packageName, UserHandle.getUid(userId, pkgSetting.appId),
+                killApplication(packageName, pkgSetting.appId, userId,
                         "hiding pkg");
                 sendApplicationHiddenForUser(packageName, pkgSetting, userId);
                 return true;
@@ -14595,7 +14647,7 @@
     private void sendFirstLaunchBroadcast(String pkgName, String installerPkg,
             int[] userIds, int[] instantUserIds) {
         sendPackageBroadcast(Intent.ACTION_PACKAGE_FIRST_LAUNCH, pkgName, null, 0,
-                installerPkg, null, userIds, instantUserIds, null /* broadcastWhitelist */);
+                installerPkg, null, userIds, instantUserIds, null /* broadcastAllowList */);
     }
 
     private abstract class HandlerParams {
@@ -15578,9 +15630,8 @@
      * Create args that describe an existing installed package. Typically used
      * when cleaning up old installs, or used as a move source.
      */
-    private InstallArgs createInstallArgsForExisting(String codePath,
-            String resourcePath, String[] instructionSets) {
-        return new FileInstallArgs(codePath, resourcePath, instructionSets);
+    private InstallArgs createInstallArgsForExisting(String codePath, String[] instructionSets) {
+        return new FileInstallArgs(codePath, instructionSets);
     }
 
     static abstract class InstallArgs {
@@ -15661,10 +15712,8 @@
         abstract boolean doRename(int status, ParsedPackage parsedPackage);
         abstract int doPostInstall(int status, int uid);
 
-        /** @see PackageSettingBase#codePathString */
+        /** @see PackageSettingBase#getCodePath() */
         abstract String getCodePath();
-        /** @see PackageSettingBase#resourcePathString */
-        abstract String getResourcePath();
 
         // Need installer lock especially for dex file removal.
         abstract void cleanUpResourcesLI();
@@ -15735,14 +15784,13 @@
         }
 
         /** Existing install */
-        FileInstallArgs(String codePath, String resourcePath, String[] instructionSets) {
+        FileInstallArgs(String codePath, String[] instructionSets) {
             super(OriginInfo.fromNothing(), null, null, 0, InstallSource.EMPTY,
                     null, null, instructionSets, null, null, null, MODE_DEFAULT, null, 0,
                     PackageParser.SigningDetails.UNKNOWN,
                     PackageManager.INSTALL_REASON_UNKNOWN, false,
                     DataLoaderType.NONE);
             this.codeFile = (codePath != null) ? new File(codePath) : null;
-            this.resourceFile = (resourcePath != null) ? new File(resourcePath) : null;
         }
 
         int copyApk() {
@@ -15758,7 +15806,6 @@
             if (origin.staged) {
                 if (DEBUG_INSTALL) Slog.d(TAG, origin.file + " already staged; skipping copy");
                 codeFile = origin.file;
-                resourceFile = origin.file;
                 return PackageManager.INSTALL_SUCCEEDED;
             }
 
@@ -15767,7 +15814,6 @@
                 final File tempDir =
                         mInstallerService.allocateStageDirLegacy(volumeUuid, isEphemeral);
                 codeFile = tempDir;
-                resourceFile = tempDir;
             } catch (IOException e) {
                 Slog.w(TAG, "Failed to create copy file: " + e);
                 return PackageManager.INSTALL_FAILED_INSUFFICIENT_STORAGE;
@@ -15838,7 +15884,6 @@
 
             // Reflect the rename internally
             codeFile = afterCodeFile;
-            resourceFile = afterCodeFile;
 
             // Reflect the rename in scanned details
             try {
@@ -15867,11 +15912,6 @@
             return (codeFile != null) ? codeFile.getAbsolutePath() : null;
         }
 
-        @Override
-        String getResourcePath() {
-            return (resourceFile != null) ? resourceFile.getAbsolutePath() : null;
-        }
-
         private boolean cleanUp() {
             if (codeFile == null || !codeFile.exists()) {
                 return false;
@@ -15884,10 +15924,6 @@
 
             removeCodePathLI(codeFile);
 
-            if (resourceFile != null && !FileUtils.contains(codeFile, resourceFile)) {
-                resourceFile.delete();
-            }
-
             return true;
         }
 
@@ -15919,7 +15955,6 @@
      */
     class MoveInstallArgs extends InstallArgs {
         private File codeFile;
-        private File resourceFile;
 
         /** New install */
         MoveInstallArgs(InstallParams params) {
@@ -15942,7 +15977,6 @@
 
             final String toPathName = new File(move.fromCodePath).getName();
             codeFile = new File(Environment.getDataAppDirectory(move.toUuid), toPathName);
-            resourceFile = codeFile;
             if (DEBUG_INSTALL) Slog.d(TAG, "codeFile after move is " + codeFile);
 
             return PackageManager.INSTALL_SUCCEEDED;
@@ -15979,11 +16013,6 @@
             return (codeFile != null) ? codeFile.getAbsolutePath() : null;
         }
 
-        @Override
-        String getResourcePath() {
-            return (resourceFile != null) ? resourceFile.getAbsolutePath() : null;
-        }
-
         private boolean cleanUp(String volumeUuid) {
             final String toPathName = new File(move.fromCodePath).getName();
             final File codeFile = new File(Environment.getDataAppDirectory(volumeUuid),
@@ -16254,7 +16283,7 @@
             res.setReturnCode(PackageManager.INSTALL_SUCCEEDED);
             //to update install status
             Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "writeSettings");
-            mSettings.writeLPr();
+            writeSettingsLPrTEMP();
             Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
         }
 
@@ -16769,7 +16798,6 @@
                         // installed.  We need to make sure to delete the older one's .apk.
                         res.removedInfo.args = createInstallArgsForExisting(
                                 oldPackage.getCodePath(),
-                                oldPackage.getCodePath(),
                                 getAppDexInstructionSets(
                                         AndroidPackageUtils.getPrimaryCpuAbi(oldPackage,
                                                 deletedPkgSetting),
@@ -17271,7 +17299,7 @@
 
         if (DEBUG_INSTALL) Slog.d(TAG, "installPackageLI: path=" + tmpPackageFile);
 
-        // Sanity check
+        // Validity check
         if (instantApp && onExternal) {
             Slog.i(TAG, "Incompatible ephemeral install; external=" + onExternal);
             throw new PrepareFailure(PackageManager.INSTALL_FAILED_SESSION_INVALID);
@@ -17415,7 +17443,7 @@
                     }
                 }
 
-                // Quick sanity check that we're signed correctly if updating;
+                // Quick validity check that we're signed correctly if updating;
                 // we'll check this again later when scanning, but we want to
                 // bail early here before tripping over redefined permissions.
                 final KeySetManagerService ksms = mSettings.mKeySetManagerService;
@@ -17583,11 +17611,9 @@
                 synchronized (mLock) {
                     pkgSetting = mSettings.getPackageLPr(pkgName);
                 }
-                String abiOverride =
-                        (pkgSetting == null || TextUtils.isEmpty(pkgSetting.cpuAbiOverrideString)
-                        ? args.abiOverride : pkgSetting.cpuAbiOverrideString);
                 boolean isUpdatedSystemAppFromExistingSetting = pkgSetting != null
                         && pkgSetting.getPkgState().isUpdatedSystemApp();
+                final String abiOverride = deriveAbiOverride(args.abiOverride, pkgSetting);
                 AndroidPackage oldPackage = mPackages.get(pkgName);
                 boolean isUpdatedSystemAppInferred = oldPackage != null && oldPackage.isSystem();
                 final Pair<PackageAbiHelper.Abis, PackageAbiHelper.NativeLibraryPaths>
@@ -18550,7 +18576,6 @@
         // user handle installed state
         int[] allUsers;
         /** enabled state of the uninstalled application */
-        final int origEnabledState;
         synchronized (mLock) {
             uninstalledPs = mSettings.mPackages.get(packageName);
             if (uninstalledPs == null) {
@@ -18566,10 +18591,6 @@
             }
 
             disabledSystemPs = mSettings.getDisabledSystemPkgLPr(packageName);
-            // Save the enabled state before we delete the package. When deleting a stub
-            // application we always set the enabled state to 'disabled'.
-            origEnabledState = uninstalledPs == null
-                    ? COMPONENT_ENABLED_STATE_DEFAULT : uninstalledPs.getEnabled(userId);
             // Static shared libs can be declared by any package, so let us not
             // allow removing a package if it provides a lib others depend on.
             pkg = mPackages.get(packageName);
@@ -18648,20 +18669,32 @@
             if (stubPkg != null && stubPkg.isStub()) {
                 final PackageSetting stubPs;
                 synchronized (mLock) {
-                    // restore the enabled state of the stub; the state is overwritten when
-                    // the stub is uninstalled
                     stubPs = mSettings.getPackageLPr(stubPkg.getPackageName());
-                    if (stubPs != null) {
-                        stubPs.setEnabled(origEnabledState, userId, "android");
-                    }
                 }
-                if (origEnabledState == COMPONENT_ENABLED_STATE_DEFAULT
-                        || origEnabledState == COMPONENT_ENABLED_STATE_ENABLED) {
-                    if (DEBUG_COMPRESSION) {
-                        Slog.i(TAG, "Enabling system stub after removal; pkg: "
-                                + stubPkg.getPackageName());
+
+                if (stubPs != null) {
+                    boolean enable = false;
+                    for (int aUserId : allUsers) {
+                        if (stubPs.getInstalled(aUserId)) {
+                            int enabled = stubPs.getEnabled(aUserId);
+                            if (enabled == COMPONENT_ENABLED_STATE_DEFAULT
+                                    || enabled == COMPONENT_ENABLED_STATE_ENABLED) {
+                                enable = true;
+                                break;
+                            }
+                        }
                     }
-                    enableCompressedPackage(stubPkg, stubPs);
+
+                    if (enable) {
+                        if (DEBUG_COMPRESSION) {
+                            Slog.i(TAG, "Enabling system stub after removal; pkg: "
+                                    + stubPkg.getPackageName());
+                        }
+                        enableCompressedPackage(stubPkg, stubPs);
+                    } else if (DEBUG_COMPRESSION) {
+                        Slog.i(TAG, "System stub disabled for all users, leaving uncompressed "
+                                + "after removal; pkg: " + stubPkg.getPackageName());
+                    }
                 }
             }
         }
@@ -18715,14 +18748,14 @@
             packageSender.sendPackageBroadcast(Intent.ACTION_PACKAGE_REPLACED, removedPackage,
                     extras, 0, null /*targetPackage*/, null, null, null, broadcastAllowList);
             packageSender.sendPackageBroadcast(Intent.ACTION_MY_PACKAGE_REPLACED, null, null, 0,
-                    removedPackage, null, null, null, null /* broadcastWhitelist */);
+                    removedPackage, null, null, null, null /* broadcastAllowList */);
             if (installerPackageName != null) {
                 packageSender.sendPackageBroadcast(Intent.ACTION_PACKAGE_ADDED,
                         removedPackage, extras, 0 /*flags*/,
-                        installerPackageName, null, null, null, null /* broadcastWhitelist */);
+                        installerPackageName, null, null, null, null /* broadcastAllowList */);
                 packageSender.sendPackageBroadcast(Intent.ACTION_PACKAGE_REPLACED,
                         removedPackage, extras, 0 /*flags*/,
-                        installerPackageName, null, null, null, null /* broadcastWhitelist */);
+                        installerPackageName, null, null, null, null /* broadcastAllowList */);
             }
         }
 
@@ -18848,6 +18881,10 @@
                     if (outInfo != null) {
                         outInfo.removedAppId = removedAppId;
                     }
+                    if ((deletedPs.sharedUser == null || deletedPs.sharedUser.packages.size() == 0)
+                            && !isUpdatedSystemApp(deletedPs)) {
+                        mPermissionManager.removePermissionsStateTEMP(removedAppId);
+                    }
                     mPermissionManager.updatePermissions(deletedPs.name, null);
                     if (deletedPs.sharedUser != null) {
                         // Remove permissions associated with package. Since runtime
@@ -18857,10 +18894,10 @@
                         // package is successful and this causes a change in gids.
                         boolean shouldKill = false;
                         for (int userId : UserManagerService.getInstance().getUserIds()) {
-                            final int userIdToKill = mSettings.updateSharedUserPermsLPw(deletedPs,
-                                    userId);
-                            shouldKill |= userIdToKill == UserHandle.USER_ALL
-                                    || userIdToKill >= UserHandle.USER_SYSTEM;
+                            final int userIdToKill = mPermissionManager
+                                    .revokeSharedUserPermissionsForDeletedPackageTEMP(deletedPs,
+                                            userId);
+                            shouldKill |= userIdToKill != UserHandle.USER_NULL;
                         }
                         // If gids changed, kill all affected packages.
                         if (shouldKill) {
@@ -18904,7 +18941,7 @@
             // can downgrade to reader
             if (writeSettings) {
                 // Save settings now
-                mSettings.writeLPr();
+                writeSettingsLPrTEMP();
             }
             if (installedStateChanged) {
                 mSettings.writeKernelMappingLPr(deletedPs);
@@ -18990,9 +19027,8 @@
         // Install the system package
         if (DEBUG_REMOVE) Slog.d(TAG, "Re-installing system package: " + disabledPs);
         try {
-            installPackageFromSystemLIF(disabledPs.codePathString, allUserHandles,
-                    outInfo == null ? null : outInfo.origUsers, deletedPs.getPermissionsState(),
-                    writeSettings);
+            installPackageFromSystemLIF(disabledPs.getCodePathString(), allUserHandles,
+                    outInfo == null ? null : outInfo.origUsers, writeSettings);
         } catch (PackageManagerException e) {
             Slog.w(TAG, "Failed to restore system package:" + deletedPkg.getPackageName() + ": "
                     + e.getMessage());
@@ -19005,8 +19041,15 @@
                 // and re-enable it afterward.
                 final PackageSetting stubPs = mSettings.mPackages.get(deletedPkg.getPackageName());
                 if (stubPs != null) {
-                    stubPs.setEnabled(
-                            COMPONENT_ENABLED_STATE_DISABLED, UserHandle.USER_SYSTEM, "android");
+                    int userId = action.user == null
+                            ? UserHandle.USER_ALL : action.user.getIdentifier();
+                    if (userId == UserHandle.USER_ALL) {
+                        for (int aUserId : allUserHandles) {
+                            stubPs.setEnabled(COMPONENT_ENABLED_STATE_DISABLED, aUserId, "android");
+                        }
+                    } else if (userId >= UserHandle.USER_SYSTEM) {
+                        stubPs.setEnabled(COMPONENT_ENABLED_STATE_DISABLED, userId, "android");
+                    }
                 }
             }
         }
@@ -19016,9 +19059,8 @@
      * Installs a package that's already on the system partition.
      */
     private AndroidPackage installPackageFromSystemLIF(@NonNull String codePathString,
-            @Nullable int[] allUserHandles, @Nullable int[] origUserHandles,
-            @Nullable PermissionsState origPermissionState, boolean writeSettings)
-                    throws PackageManagerException {
+            @Nullable int[] allUserHandles, @Nullable int[] origUserHandles, boolean writeSettings)
+            throws PackageManagerException {
         final File codePath = new File(codePathString);
         @ParseFlags int parseFlags =
                 mDefParseFlags
@@ -19055,12 +19097,8 @@
         synchronized (mLock) {
             PackageSetting ps = mSettings.mPackages.get(pkg.getPackageName());
 
-            // Propagate the permissions state as we do not want to drop on the floor
-            // runtime permissions. The update permissions method below will take
-            // care of removing obsolete permissions and grant install permissions.
-            if (origPermissionState != null) {
-                ps.getPermissionsState().copyFrom(origPermissionState);
-            }
+            // The update permissions method below will take care of removing obsolete permissions
+            // and granting install permissions.
             mPermissionManager.updatePermissions(pkg.getPackageName(), pkg);
 
             final boolean applyUserRestrictions
@@ -19094,7 +19132,7 @@
             }
             // can downgrade to reader here
             if (writeSettings) {
-                mSettings.writeLPr();
+                writeSettingsLPrTEMP();
             }
         }
         return pkg;
@@ -19115,7 +19153,7 @@
         // Delete application code and resources only for parent packages
         if (deleteCodeAndResources && (outInfo != null)) {
             outInfo.args = createInstallArgsForExisting(
-                    ps.codePathString, ps.resourcePathString, getAppDexInstructionSets(
+                    ps.getCodePathString(), getAppDexInstructionSets(
                             ps.primaryCpuAbiString, ps.secondaryCpuAbiString));
             if (DEBUG_SD_INSTALL) Slog.i(TAG, "args=" + outInfo.args);
         }
@@ -19168,7 +19206,7 @@
             } else {
                 ps.pkgPrivateFlags &= ~ApplicationInfo.PRIVATE_FLAG_REQUIRED_FOR_SYSTEM_USER;
             }
-            mSettings.writeLPr();
+            writeSettingsLPrTEMP();
         }
         return true;
     }
@@ -19652,7 +19690,7 @@
 
         final String[] packageNames = { packageName };
         final long[] ceDataInodes = { ps.getCeDataInode(userId) };
-        final String[] codePaths = { ps.codePathString };
+        final String[] codePaths = { ps.getCodePathString() };
 
         try {
             mInstaller.getAppSize(ps.volumeUuid, packageNames, userId, 0,
@@ -20360,7 +20398,7 @@
                     (parser1, userId1) -> {
                         synchronized (mLock) {
                             mSettings.readAllDomainVerificationsLPr(parser1, userId1);
-                            mSettings.writeLPr();
+                            writeSettingsLPrTEMP();
                         }
                     });
         } catch (Exception e) {
@@ -20806,6 +20844,11 @@
         return ensureSystemPackageName(contentCaptureServiceComponentName.getPackageName());
     }
 
+    public String getOverlayConfigSignaturePackageName() {
+        return ensureSystemPackageName(SystemConfig.getInstance()
+                .getOverlayConfigSignaturePackage());
+    }
+
     @Nullable
     private String getRetailDemoPackageName() {
         final String predefinedPkgName = mContext.getString(R.string.config_retailDemoPackage);
@@ -21534,7 +21577,7 @@
             // had been set as a preferred activity.  We try to clean this up
             // the next time we encounter that preferred activity, but it is
             // possible for the user flow to never be able to return to that
-            // situation so here we do a sanity check to make sure we haven't
+            // situation so here we do a validity check to make sure we haven't
             // left any junk around.
             ArrayList<PreferredActivity> removed = new ArrayList<>();
             for (int i=0; i<mSettings.mPreferredActivities.size(); i++) {
@@ -21706,6 +21749,8 @@
     protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
         if (!DumpUtils.checkDumpAndUsageStatsPermission(mContext, TAG, pw)) return;
 
+        mPermissionManager.writePermissionsStateToPackageSettingsTEMP();
+
         DumpState dumpState = new DumpState();
         boolean fullPreferred = false;
         boolean checkin = false;
@@ -21901,7 +21946,7 @@
                 dumpState.setDump(DumpState.DUMP_SERVICE_PERMISSIONS);
             } else if ("write".equals(cmd)) {
                 synchronized (mLock) {
-                    mSettings.writeLPr();
+                    writeSettingsLPrTEMP();
                     pw.println("Settings written.");
                     return;
                 }
@@ -22569,11 +22614,11 @@
             synchronized (mInstallLock) {
                 final AndroidPackage pkg;
                 try {
-                    pkg = scanPackageTracedLI(ps.codePath, parseFlags, SCAN_INITIAL, 0, null);
+                    pkg = scanPackageTracedLI(ps.getCodePath(), parseFlags, SCAN_INITIAL, 0, null);
                     loaded.add(pkg);
 
                 } catch (PackageManagerException e) {
-                    Slog.w(TAG, "Failed to scan " + ps.codePath + ": " + e.getMessage());
+                    Slog.w(TAG, "Failed to scan " + ps.getCodePath() + ": " + e.getMessage());
                 }
 
                 if (!Build.FINGERPRINT.equals(ver.fingerprint)) {
@@ -22619,7 +22664,7 @@
             // Yay, everything is now upgraded
             ver.forceCurrent();
 
-            mSettings.writeLPr();
+            writeSettingsLPrTEMP();
         }
 
         for (PackageFreezer freezer : freezers) {
@@ -22644,33 +22689,33 @@
 
         final ArrayList<AndroidPackage> unloaded = new ArrayList<>();
         synchronized (mInstallLock) {
-        synchronized (mLock) {
-            final List<PackageSetting> packages = mSettings.getVolumePackagesLPr(volumeUuid);
-            for (PackageSetting ps : packages) {
-                if (ps.pkg == null) continue;
+            synchronized (mLock) {
+                final List<PackageSetting> packages = mSettings.getVolumePackagesLPr(volumeUuid);
+                for (PackageSetting ps : packages) {
+                    if (ps.pkg == null) continue;
 
-                final AndroidPackage pkg = ps.pkg;
-                final int deleteFlags = PackageManager.DELETE_KEEP_DATA;
-                final PackageRemovedInfo outInfo = new PackageRemovedInfo(this);
+                    final AndroidPackage pkg = ps.pkg;
+                    final int deleteFlags = PackageManager.DELETE_KEEP_DATA;
+                    final PackageRemovedInfo outInfo = new PackageRemovedInfo(this);
 
-                try (PackageFreezer freezer = freezePackageForDelete(ps.name, deleteFlags,
-                        "unloadPrivatePackagesInner")) {
-                    if (deletePackageLIF(ps.name, null, false, null, deleteFlags, outInfo,
-                            false, null)) {
-                        unloaded.add(pkg);
-                    } else {
-                        Slog.w(TAG, "Failed to unload " + ps.codePath);
+                    try (PackageFreezer freezer = freezePackageForDelete(ps.name, deleteFlags,
+                            "unloadPrivatePackagesInner")) {
+                        if (deletePackageLIF(ps.name, null, false, null, deleteFlags, outInfo,
+                                false, null)) {
+                            unloaded.add(pkg);
+                        } else {
+                            Slog.w(TAG, "Failed to unload " + ps.getCodePath());
+                        }
                     }
+
+                    // Try very hard to release any references to this package
+                    // so we don't risk the system server being killed due to
+                    // open FDs
+                    AttributeCache.instance().removePackage(ps.name);
                 }
 
-                // Try very hard to release any references to this package
-                // so we don't risk the system server being killed due to
-                // open FDs
-                AttributeCache.instance().removePackage(ps.name);
+                writeSettingsLPrTEMP();
             }
-
-            mSettings.writeLPr();
-        }
         }
 
         if (DEBUG_INSTALL) Slog.d(TAG, "Unloaded packages " + unloaded);
@@ -22713,12 +22758,20 @@
             final int packageCount = mSettings.mPackages.size();
             for (int i = 0; i < packageCount; i++) {
                 final PackageSetting ps = mSettings.mPackages.valueAt(i);
-                codePaths.add(ps.codePath.getAbsolutePath());
+                codePaths.add(ps.getCodePath().getAbsolutePath());
             }
             return codePaths;
         }
     }
 
+    private void executeBatchLI(@NonNull Installer.Batch batch) {
+        try {
+            batch.execute(mInstaller);
+        } catch (InstallerException e) {
+            Slog.w(TAG, "Failed to execute pending operations", e);
+        }
+    }
+
     /**
      * Examine all apps present on given mounted volume, and destroy apps that
      * aren't expected, either due to uninstallation or reinstallation on
@@ -22858,6 +22911,8 @@
 
         // Ensure that data directories are ready to roll for all packages
         // installed for this volume and user
+        Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "prepareAppDataAndMigrate");
+        Installer.Batch batch = new Installer.Batch();
         final List<PackageSetting> packages;
         synchronized (mLock) {
             packages = mSettings.getVolumePackagesLPr(volumeUuid);
@@ -22878,10 +22933,12 @@
             }
 
             if (ps.getInstalled(userId)) {
-                prepareAppDataAndMigrateLIF(ps.pkg, userId, flags, migrateAppData);
+                prepareAppDataAndMigrate(batch, ps.pkg, userId, flags, migrateAppData);
                 preparedCount++;
             }
         }
+        executeBatchLI(batch);
+        Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
 
         Slog.v(TAG, "reconcileAppsData finished " + preparedCount + " packages");
         return result;
@@ -22906,6 +22963,7 @@
             mSettings.writeKernelMappingLPr(ps);
         }
 
+        Installer.Batch batch = new Installer.Batch();
         UserManagerInternal umInternal = mInjector.getUserManagerInternal();
         StorageManagerInternal smInternal = mInjector.getStorageManagerInternal();
         for (UserInfo user : mUserManager.getUsers(false /*excludeDying*/)) {
@@ -22920,16 +22978,20 @@
 
             if (ps.getInstalled(user.id)) {
                 // TODO: when user data is locked, mark that we're still dirty
-                prepareAppDataLIF(pkg, user.id, flags);
-
-                if (umInternal.isUserUnlockingOrUnlocked(user.id)) {
-                    // Prepare app data on external storage; currently this is used to
-                    // setup any OBB dirs that were created by the installer correctly.
-                    int uid = UserHandle.getUid(user.id, UserHandle.getAppId(pkg.getUid()));
-                    smInternal.prepareAppDataAfterInstall(pkg.getPackageName(), uid);
-                }
+                prepareAppData(batch, pkg, user.id, flags).thenRun(() -> {
+                    // Note: this code block is executed with the Installer lock
+                    // already held, since it's invoked as a side-effect of
+                    // executeBatchLI()
+                    if (umInternal.isUserUnlockingOrUnlocked(user.id)) {
+                        // Prepare app data on external storage; currently this is used to
+                        // setup any OBB dirs that were created by the installer correctly.
+                        int uid = UserHandle.getUid(user.id, UserHandle.getAppId(pkg.getUid()));
+                        smInternal.prepareAppDataAfterInstall(pkg.getPackageName(), uid);
+                    }
+                });
             }
         }
+        executeBatchLI(batch);
     }
 
     /**
@@ -22940,26 +23002,33 @@
      * will try recovering system apps by wiping data; third-party app data is
      * left intact.
      */
-    private void prepareAppDataLIF(AndroidPackage pkg, int userId, int flags) {
+    private @NonNull CompletableFuture<?> prepareAppData(@NonNull Installer.Batch batch,
+            @Nullable AndroidPackage pkg, int userId, int flags) {
         if (pkg == null) {
             Slog.wtf(TAG, "Package was null!", new Throwable());
-            return;
+            return CompletableFuture.completedFuture(null);
         }
-        prepareAppDataLeafLIF(pkg, userId, flags);
+        return prepareAppDataLeaf(batch, pkg, userId, flags);
     }
 
-    private void prepareAppDataAndMigrateLIF(AndroidPackage pkg, int userId, int flags,
-            boolean maybeMigrateAppData) {
-        prepareAppDataLIF(pkg, userId, flags);
-
-        if (maybeMigrateAppData && maybeMigrateAppDataLIF(pkg, userId)) {
-            // We may have just shuffled around app data directories, so
-            // prepare them one more time
-            prepareAppDataLIF(pkg, userId, flags);
-        }
+    private @NonNull CompletableFuture<?> prepareAppDataAndMigrate(@NonNull Installer.Batch batch,
+            @NonNull AndroidPackage pkg, int userId, int flags, boolean maybeMigrateAppData) {
+        return prepareAppData(batch, pkg, userId, flags).thenRun(() -> {
+            // Note: this code block is executed with the Installer lock
+            // already held, since it's invoked as a side-effect of
+            // executeBatchLI()
+            if (maybeMigrateAppData && maybeMigrateAppDataLIF(pkg, userId)) {
+                // We may have just shuffled around app data directories, so
+                // prepare them one more time
+                final Installer.Batch batchInner = new Installer.Batch();
+                prepareAppData(batchInner, pkg, userId, flags);
+                executeBatchLI(batchInner);
+            }
+        });
     }
 
-    private void prepareAppDataLeafLIF(AndroidPackage pkg, int userId, int flags) {
+    private @NonNull CompletableFuture<?> prepareAppDataLeaf(@NonNull Installer.Batch batch,
+            @NonNull AndroidPackage pkg, int userId, int flags) {
         if (DEBUG_APP_DATA) {
             Slog.v(TAG, "prepareAppData for " + pkg.getPackageName() + " u" + userId + " 0x"
                     + Integer.toHexString(flags));
@@ -22979,57 +23048,67 @@
         Preconditions.checkNotNull(pkgSeInfo);
 
         final String seInfo = pkgSeInfo + (pkg.getSeInfoUser() != null ? pkg.getSeInfoUser() : "");
-        long ceDataInode = -1;
-        try {
-            ceDataInode = mInstaller.createAppData(volumeUuid, packageName, userId, flags,
-                    appId, seInfo, pkg.getTargetSdkVersion());
-        } catch (InstallerException e) {
-            if (pkg.isSystem()) {
-                logCriticalInfo(Log.ERROR, "Failed to create app data for " + packageName
-                        + ", but trying to recover: " + e);
-                destroyAppDataLeafLIF(pkg, userId, flags);
-                try {
-                    ceDataInode = mInstaller.createAppData(volumeUuid, packageName, userId, flags,
-                            appId, seInfo, pkg.getTargetSdkVersion());
-                    logCriticalInfo(Log.DEBUG, "Recovery succeeded!");
-                } catch (InstallerException e2) {
-                    logCriticalInfo(Log.DEBUG, "Recovery failed!");
-                }
-            } else {
-                Slog.e(TAG, "Failed to create app data for " + packageName + ": " + e);
-            }
-        }
-        // Prepare the application profiles only for upgrades and first boot (so that we don't
-        // repeat the same operation at each boot).
-        // We only have to cover the upgrade and first boot here because for app installs we
-        // prepare the profiles before invoking dexopt (in installPackageLI).
-        //
-        // We also have to cover non system users because we do not call the usual install package
-        // methods for them.
-        //
-        // NOTE: in order to speed up first boot time we only create the current profile and do not
-        // update the content of the reference profile. A system image should already be configured
-        // with the right profile keys and the profiles for the speed-profile prebuilds should
-        // already be copied. That's done in #performDexOptUpgrade.
-        //
-        // TODO(calin, mathieuc): We should use .dm files for prebuilds profiles instead of
-        // manually copying them in #performDexOptUpgrade. When we do that we should have a more
-        // granular check here and only update the existing profiles.
-        if (mIsUpgrade || mFirstBoot || (userId != UserHandle.USER_SYSTEM)) {
-            mArtManagerService.prepareAppProfiles(pkg, userId,
-                /* updateReferenceProfileContent= */ false);
-        }
+        final int targetSdkVersion = pkg.getTargetSdkVersion();
 
-        if ((flags & StorageManager.FLAG_STORAGE_CE) != 0 && ceDataInode != -1) {
-            // TODO: mark this structure as dirty so we persist it!
-            synchronized (mLock) {
-                if (ps != null) {
-                    ps.setCeDataInode(ceDataInode, userId);
-                }
-            }
-        }
+        return batch.createAppData(volumeUuid, packageName, userId, flags, appId, seInfo,
+                targetSdkVersion).whenComplete((ceDataInode, e) -> {
+                    // Note: this code block is executed with the Installer lock
+                    // already held, since it's invoked as a side-effect of
+                    // executeBatchLI()
+                    if ((e != null) && pkg.isSystem()) {
+                        logCriticalInfo(Log.ERROR, "Failed to create app data for " + packageName
+                                + ", but trying to recover: " + e);
+                        destroyAppDataLeafLIF(pkg, userId, flags);
+                        try {
+                            ceDataInode = mInstaller.createAppData(volumeUuid, packageName, userId,
+                                    flags, appId, seInfo, pkg.getTargetSdkVersion());
+                            logCriticalInfo(Log.DEBUG, "Recovery succeeded!");
+                        } catch (InstallerException e2) {
+                            logCriticalInfo(Log.DEBUG, "Recovery failed!");
+                        }
+                    } else if (e != null) {
+                        Slog.e(TAG, "Failed to create app data for " + packageName + ": " + e);
+                    }
 
-        prepareAppDataContentsLeafLIF(pkg, ps, userId, flags);
+                    // Prepare the application profiles only for upgrades and
+                    // first boot (so that we don't repeat the same operation at
+                    // each boot).
+                    //
+                    // We only have to cover the upgrade and first boot here
+                    // because for app installs we prepare the profiles before
+                    // invoking dexopt (in installPackageLI).
+                    //
+                    // We also have to cover non system users because we do not
+                    // call the usual install package methods for them.
+                    //
+                    // NOTE: in order to speed up first boot time we only create
+                    // the current profile and do not update the content of the
+                    // reference profile. A system image should already be
+                    // configured with the right profile keys and the profiles
+                    // for the speed-profile prebuilds should already be copied.
+                    // That's done in #performDexOptUpgrade.
+                    //
+                    // TODO(calin, mathieuc): We should use .dm files for
+                    // prebuilds profiles instead of manually copying them in
+                    // #performDexOptUpgrade. When we do that we should have a
+                    // more granular check here and only update the existing
+                    // profiles.
+                    if (mIsUpgrade || mFirstBoot || (userId != UserHandle.USER_SYSTEM)) {
+                        mArtManagerService.prepareAppProfiles(pkg, userId,
+                            /* updateReferenceProfileContent= */ false);
+                    }
+
+                    if ((flags & StorageManager.FLAG_STORAGE_CE) != 0 && ceDataInode != -1) {
+                        // TODO: mark this structure as dirty so we persist it!
+                        synchronized (mLock) {
+                            if (ps != null) {
+                                ps.setCeDataInode(ceDataInode, userId);
+                            }
+                        }
+                    }
+
+                    prepareAppDataContentsLeafLIF(pkg, ps, userId, flags);
+                });
     }
 
     private void prepareAppDataContentsLIF(AndroidPackage pkg, @Nullable PackageSetting pkgSetting,
@@ -23548,6 +23627,8 @@
         synchronized (mLock) {
             mDirtyUsers.remove(userId);
             mUserNeedsBadging.delete(userId);
+            mPermissionManager.onUserRemoved(userId);
+            mPermissionManager.writePermissionsStateToPackageSettingsTEMP();
             mSettings.removeUserLPw(userId);
             mPendingBroadcasts.remove(userId);
             mInstantAppRegistry.onUserRemovedLPw(userId);
@@ -23630,13 +23711,27 @@
         }
     }
 
-    void onNewUserCreated(final int userId) {
-        mPermissionManager.onNewUserCreated(userId);
+    void onNewUserCreated(@UserIdInt int userId, boolean convertedFromPreCreated) {
+        if (DEBUG_PERMISSIONS) {
+            Slog.d(TAG, "onNewUserCreated(id=" + userId
+                    + ", convertedFromPreCreated=" + convertedFromPreCreated + ")");
+        }
+        if (!convertedFromPreCreated) {
+            mPermissionManager.onNewUserCreated(userId);
+            return;
+        }
+        if (!readPermissionStateForUser(userId)) {
+            // Could not read the existing permissions, re-grant them.
+            Slog.i(TAG, "re-granting permissions for pre-created user " + userId);
+            mPermissionManager.onNewUserCreated(userId);
+        }
     }
 
     boolean readPermissionStateForUser(@UserIdInt int userId) {
         synchronized (mPackages) {
+            mPermissionManager.writePermissionsStateToPackageSettingsTEMP();
             mSettings.readPermissionStateForUserSyncLPr(userId);
+            mPermissionManager.readPermissionsStateFromPackageSettingsTEMP();
             return mPmInternal.isPermissionUpgradeNeeded(userId);
         }
     }
@@ -24321,6 +24416,8 @@
                     return TextUtils.isEmpty(mRetailDemoPackage)
                             ? ArrayUtils.emptyArray(String.class)
                             : new String[] {mRetailDemoPackage};
+                case PackageManagerInternal.PACKAGE_OVERLAY_CONFIG_SIGNATURE:
+                    return filterOnlySystemPackages(getOverlayConfigSignaturePackageName());
                 default:
                     return ArrayUtils.emptyArray(String.class);
             }
@@ -24911,9 +25008,7 @@
 
         @Override
         public int[] getPermissionGids(String permissionName, int userId) {
-            synchronized (mLock) {
-                return getPermissionGidsLocked(permissionName, userId);
-            }
+            return mPermissionManager.getPermissionGids(permissionName, userId);
         }
 
         @Override
@@ -25086,7 +25181,7 @@
                 if (async) {
                     scheduleWriteSettingsLocked();
                 } else {
-                    mSettings.writeLPr();
+                    writeSettingsLPrTEMP();
                 }
             }
         }
@@ -25133,7 +25228,7 @@
                     return;
                 }
                 mSettings.mReadExternalStorageEnforced = enforced ? Boolean.TRUE : Boolean.FALSE;
-                mSettings.writeLPr();
+                writeSettingsLPrTEMP();
             }
         }
 
@@ -25234,16 +25329,6 @@
         return null;
     }
 
-    @GuardedBy("mLock")
-    public int[] getPermissionGidsLocked(String permissionName, int userId) {
-        BasePermission perm
-                = mPermissionManager.getPermissionSettings().getPermission(permissionName);
-        if (perm != null) {
-            return perm.computeGids(userId);
-        }
-        return null;
-    }
-
     @Override
     public int getRuntimePermissionsVersion(@UserIdInt int userId) {
         Preconditions.checkArgumentNonnegative(userId);
@@ -25377,18 +25462,7 @@
         int mode = mInjector.getAppOpsManager().checkOpNoThrow(
                 AppOpsManager.OP_AUTO_REVOKE_PERMISSIONS_IF_UNUSED,
                 Binder.getCallingUid(), packageName);
-        if (mode == MODE_ALLOWED) {
-            return false;
-        } else if (mode == MODE_IGNORED) {
-            return true;
-        } else {
-            synchronized (mLock) {
-                boolean manifestWhitelisted =
-                        mPackages.get(packageName).getAutoRevokePermissions()
-                                == ApplicationInfo.AUTO_REVOKE_DISALLOWED;
-                return manifestWhitelisted;
-            }
-        }
+        return mode == MODE_IGNORED;
     }
 
     @Override
@@ -25668,6 +25742,17 @@
     public List<String> getMimeGroup(String packageName, String mimeGroup) {
         return mSettings.mPackages.get(packageName).getMimeGroup(mimeGroup);
     }
+
+    /**
+     * Temporary method that wraps mSettings.writeLPr() and calls
+     * mPermissionManager.writePermissionsStateToPackageSettingsTEMP() beforehand.
+     *
+     * TODO(zhanghai): This should be removed once we finish migration of permission storage.
+     */
+    private void writeSettingsLPrTEMP() {
+        mPermissionManager.writePermissionsStateToPackageSettingsTEMP();
+        mSettings.writeLPr();
+    }
 }
 
 interface PackageSender {
@@ -25676,9 +25761,9 @@
      * @param instantUserIds User IDs where the action occurred on an instant application
      */
     void sendPackageBroadcast(final String action, final String pkg,
-        final Bundle extras, final int flags, final String targetPkg,
-        final IIntentReceiver finishedReceiver, final int[] userIds, int[] instantUserIds,
-        @Nullable SparseArray<int[]> broadcastWhitelist);
+            final Bundle extras, final int flags, final String targetPkg,
+            final IIntentReceiver finishedReceiver, final int[] userIds, int[] instantUserIds,
+            @Nullable SparseArray<int[]> broadcastAllowList);
     void sendPackageAddedForNewUsers(String packageName, boolean sendBootCompleted,
             boolean includeStopped, int appId, int[] userIds, int[] instantUserIds,
             int dataLoaderType);
diff --git a/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java b/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java
index 03f4708..491b4fc 100644
--- a/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java
+++ b/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java
@@ -34,7 +34,6 @@
 import android.content.Intent;
 import android.content.pm.PackageInfoLite;
 import android.content.pm.PackageManager;
-import android.content.pm.PackageManagerInternal;
 import android.content.pm.PackageParser;
 import android.content.pm.PackageParser.PackageParserException;
 import android.content.pm.ResolveInfo;
@@ -68,7 +67,6 @@
 import com.android.server.pm.dex.DexManager;
 import com.android.server.pm.dex.PackageDexUsage;
 import com.android.server.pm.parsing.pkg.AndroidPackage;
-import com.android.server.pm.permission.PermissionsState;
 
 import dalvik.system.VMRuntime;
 
@@ -488,6 +486,18 @@
     }
 
     /**
+     * Returns true if the signature set of the package is identical to the specified signature
+     * set or if the signing details of the package are unknown.
+     */
+    public static boolean comparePackageSignatures(PackageSetting pkgSetting,
+            Signature[] signatures) {
+        return pkgSetting.signatures.mSigningDetails
+                == PackageParser.SigningDetails.UNKNOWN
+                || compareSignatures(pkgSetting.signatures.mSigningDetails.signatures, signatures)
+                == PackageManager.SIGNATURE_MATCH;
+    }
+
+    /**
      * Used for backward compatibility to make sure any packages with
      * certificate chains get upgraded to the new style. {@code existingSigs}
      * will be in the old format (since they were stored on disk from before the
@@ -956,20 +966,6 @@
     }
 
     /**
-     * Returns the {@link PermissionsState} for the given package. If the {@link PermissionsState}
-     * could not be found, {@code null} will be returned.
-     */
-    public static PermissionsState getPermissionsState(
-            PackageManagerInternal packageManagerInternal, AndroidPackage pkg) {
-        final PackageSetting packageSetting = packageManagerInternal.getPackageSetting(
-                pkg.getPackageName());
-        if (packageSetting == null) {
-            return null;
-        }
-        return packageSetting.getPermissionsState();
-    }
-
-    /**
      * Recursively create target directory
      */
     public static void makeDirRecursive(File targetDir, int mode) throws ErrnoException {
diff --git a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
index 668f375..7e2ef41 100644
--- a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
+++ b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
@@ -423,13 +423,15 @@
             final List<ApplicationInfo> list;
             if (packageName == null) {
                 final ParceledListSlice<ApplicationInfo> packages =
-                        mInterface.getInstalledApplications(
-                                PackageManager.MATCH_SYSTEM_ONLY, UserHandle.USER_SYSTEM);
+                        mInterface.getInstalledApplications(PackageManager.MATCH_SYSTEM_ONLY
+                                        | PackageManager.MATCH_UNINSTALLED_PACKAGES,
+                                UserHandle.USER_SYSTEM);
                 list = packages.getList();
             } else {
                 list = new ArrayList<>(1);
-                list.add(mInterface.getApplicationInfo(packageName,
-                        PackageManager.MATCH_SYSTEM_ONLY, UserHandle.USER_SYSTEM));
+                list.add(mInterface.getApplicationInfo(packageName, PackageManager.MATCH_SYSTEM_ONLY
+                                | PackageManager.MATCH_UNINSTALLED_PACKAGES,
+                        UserHandle.USER_SYSTEM));
             }
             for (ApplicationInfo info : list) {
                 if (info.isUpdatedSystemApp()) {
@@ -1041,7 +1043,9 @@
                 + "; isStaged = " + session.isStaged()
                 + "; isReady = " + session.isStagedSessionReady()
                 + "; isApplied = " + session.isStagedSessionApplied()
-                + "; isFailed = " + session.isStagedSessionFailed() + ";");
+                + "; isFailed = " + session.isStagedSessionFailed()
+                + "; errorMsg = " + session.getStagedSessionErrorMessage()
+                + ";");
     }
 
     private Intent parseIntentAndUser() throws URISyntaxException {
@@ -3195,12 +3199,24 @@
         return 0;
     }
 
+    private long getFileStatSize(File file) {
+        final ParcelFileDescriptor pfd = openFileForSystem(file.getPath(), "r");
+        if (pfd == null) {
+            throw new IllegalArgumentException("Error: Can't open file: " + file.getPath());
+        }
+        try {
+            return pfd.getStatSize();
+        } finally {
+            IoUtils.closeQuietly(pfd);
+        }
+    }
+
     private void processArgForLocalFile(String arg, PackageInstaller.Session session) {
         final String inPath = arg;
 
         final File file = new File(inPath);
         final String name = file.getName();
-        final long size = file.length();
+        final long size = getFileStatSize(file);
         final Metadata metadata = Metadata.forLocalFile(inPath);
 
         byte[] v4signatureBytes = null;
@@ -3336,7 +3352,7 @@
             session = new PackageInstaller.Session(
                     mInterface.getPackageInstaller().openSession(sessionId));
             if (!session.isMultiPackage() && !session.isStaged()) {
-                // Sanity check that all .dm files match an apk.
+                // Validity check that all .dm files match an apk.
                 // (The installer does not support standalone .dm files and will not process them.)
                 try {
                     DexMetadataHelper.validateDexPaths(session.getNames());
diff --git a/services/core/java/com/android/server/pm/PackageSetting.java b/services/core/java/com/android/server/pm/PackageSetting.java
index 432d7f3..a3a7273 100644
--- a/services/core/java/com/android/server/pm/PackageSetting.java
+++ b/services/core/java/com/android/server/pm/PackageSetting.java
@@ -71,13 +71,13 @@
     private PackageStateUnserialized pkgState = new PackageStateUnserialized();
 
     @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
-    public PackageSetting(String name, String realName, File codePath, File resourcePath,
+    public PackageSetting(String name, String realName, @NonNull File codePath,
             String legacyNativeLibraryPathString, String primaryCpuAbiString,
             String secondaryCpuAbiString, String cpuAbiOverrideString,
             long pVersionCode, int pkgFlags, int privateFlags,
             int sharedUserId, String[] usesStaticLibraries,
             long[] usesStaticLibrariesVersions, Map<String, ArraySet<String>> mimeGroups) {
-        super(name, realName, codePath, resourcePath, legacyNativeLibraryPathString,
+        super(name, realName, codePath, legacyNativeLibraryPathString,
                 primaryCpuAbiString, secondaryCpuAbiString, cpuAbiOverrideString,
                 pVersionCode, pkgFlags, privateFlags,
                 usesStaticLibraries, usesStaticLibrariesVersions);
diff --git a/services/core/java/com/android/server/pm/PackageSettingBase.java b/services/core/java/com/android/server/pm/PackageSettingBase.java
index 834303c..6010344 100644
--- a/services/core/java/com/android/server/pm/PackageSettingBase.java
+++ b/services/core/java/com/android/server/pm/PackageSettingBase.java
@@ -64,10 +64,8 @@
      * this is path to single base APK file; for cluster packages this is
      * path to the cluster directory.
      */
-    File codePath;
-    String codePathString;
-    File resourcePath;
-    String resourcePathString;
+    private File mCodePath;
+    private String mCodePathString;
 
     String[] usesStaticLibraries;
     long[] usesStaticLibrariesVersions;
@@ -138,7 +136,7 @@
 
     boolean forceQueryableOverride;
 
-    PackageSettingBase(String name, String realName, File codePath, File resourcePath,
+    PackageSettingBase(String name, String realName, @NonNull File codePath,
             String legacyNativeLibraryPathString, String primaryCpuAbiString,
             String secondaryCpuAbiString, String cpuAbiOverrideString,
             long pVersionCode, int pkgFlags, int pkgPrivateFlags,
@@ -148,10 +146,7 @@
         this.realName = realName;
         this.usesStaticLibraries = usesStaticLibraries;
         this.usesStaticLibrariesVersions = usesStaticLibrariesVersions;
-        this.codePath = codePath;
-        this.codePathString = codePath.toString();
-        this.resourcePath = resourcePath;
-        this.resourcePathString = resourcePath.toString();
+        setCodePath(codePath);
         this.legacyNativeLibraryPathString = legacyNativeLibraryPathString;
         this.primaryCpuAbiString = primaryCpuAbiString;
         this.secondaryCpuAbiString = secondaryCpuAbiString;
@@ -235,8 +230,7 @@
     }
 
     private void doCopy(PackageSettingBase orig) {
-        codePath = orig.codePath;
-        codePathString = orig.codePathString;
+        setCodePath(orig.getCodePath());
         cpuAbiOverrideString = orig.cpuAbiOverrideString;
         firstInstallTime = orig.firstInstallTime;
         installPermissionsFixed = orig.installPermissionsFixed;
@@ -246,8 +240,6 @@
         legacyNativeLibraryPathString = orig.legacyNativeLibraryPathString;
         // Intentionally skip mOldCodePaths; it's not relevant for copies
         primaryCpuAbiString = orig.primaryCpuAbiString;
-        resourcePath = orig.resourcePath;
-        resourcePathString = orig.resourcePathString;
         secondaryCpuAbiString = orig.secondaryCpuAbiString;
         signatures = orig.signatures;
         timeStamp = orig.timeStamp;
@@ -705,6 +697,20 @@
         return userState.harmfulAppWarning;
     }
 
+    PackageSettingBase setCodePath(@NonNull File codePath) {
+        this.mCodePath = codePath;
+        this.mCodePathString = codePath.toString();
+        return this;
+    }
+
+    File getCodePath() {
+        return mCodePath;
+    }
+
+    String getCodePathString() {
+        return mCodePathString;
+    }
+
     /**
      * @see PackageUserState#overrideLabelAndIcon(ComponentName, String, Integer)
      *
@@ -727,10 +733,7 @@
 
     protected PackageSettingBase updateFrom(PackageSettingBase other) {
         super.copyFrom(other);
-        this.codePath = other.codePath;
-        this.codePathString = other.codePathString;
-        this.resourcePath = other.resourcePath;
-        this.resourcePathString = other.resourcePathString;
+        setCodePath(other.getCodePath());
         this.usesStaticLibraries = other.usesStaticLibraries;
         this.usesStaticLibrariesVersions = other.usesStaticLibrariesVersions;
         this.legacyNativeLibraryPathString = other.legacyNativeLibraryPathString;
diff --git a/services/core/java/com/android/server/pm/Settings.java b/services/core/java/com/android/server/pm/Settings.java
index 1805713..659e2a3 100644
--- a/services/core/java/com/android/server/pm/Settings.java
+++ b/services/core/java/com/android/server/pm/Settings.java
@@ -102,6 +102,7 @@
 import com.android.permission.persistence.RuntimePermissionsPersistence;
 import com.android.permission.persistence.RuntimePermissionsState;
 import com.android.server.LocalServices;
+import com.android.server.pm.Installer.Batch;
 import com.android.server.pm.Installer.InstallerException;
 import com.android.server.pm.parsing.PackageInfoUtils;
 import com.android.server.pm.parsing.pkg.AndroidPackage;
@@ -538,7 +539,7 @@
             return null;
         }
         p.getPkgState().setUpdatedSystemApp(false);
-        PackageSetting ret = addPackageLPw(name, p.realName, p.codePath, p.resourcePath,
+        PackageSetting ret = addPackageLPw(name, p.realName, p.getCodePath(),
                 p.legacyNativeLibraryPathString, p.primaryCpuAbiString,
                 p.secondaryCpuAbiString, p.cpuAbiOverrideString,
                 p.appId, p.versionCode, p.pkgFlags, p.pkgPrivateFlags,
@@ -558,7 +559,7 @@
         mDisabledSysPackages.remove(name);
     }
 
-    PackageSetting addPackageLPw(String name, String realName, File codePath, File resourcePath,
+    PackageSetting addPackageLPw(String name, String realName, File codePath,
             String legacyNativeLibraryPathString, String primaryCpuAbiString,
             String secondaryCpuAbiString, String cpuAbiOverrideString, int uid, long vc, int
             pkgFlags, int pkgPrivateFlags, String[] usesStaticLibraries,
@@ -572,10 +573,9 @@
                     "Adding duplicate package, keeping first: " + name);
             return null;
         }
-        p = new PackageSetting(name, realName, codePath, resourcePath,
-                legacyNativeLibraryPathString, primaryCpuAbiString, secondaryCpuAbiString,
-                cpuAbiOverrideString, vc, pkgFlags, pkgPrivateFlags,
-                0 /*userId*/, usesStaticLibraries, usesStaticLibraryNames,
+        p = new PackageSetting(name, realName, codePath, legacyNativeLibraryPathString,
+                primaryCpuAbiString, secondaryCpuAbiString, cpuAbiOverrideString, vc, pkgFlags,
+                pkgPrivateFlags, 0 /*userId*/, usesStaticLibraries, usesStaticLibraryNames,
                 mimeGroups);
         p.appId = uid;
         if (registerExistingAppIdLPw(uid, p, name)) {
@@ -635,7 +635,7 @@
      */
     static @NonNull PackageSetting createNewSetting(String pkgName, PackageSetting originalPkg,
             PackageSetting disabledPkg, String realPkgName, SharedUserSetting sharedUser,
-            File codePath, File resourcePath, String legacyNativeLibraryPath, String primaryCpuAbi,
+            File codePath, String legacyNativeLibraryPath, String primaryCpuAbi,
             String secondaryCpuAbi, long versionCode, int pkgFlags, int pkgPrivateFlags,
             UserHandle installUser, boolean allowInstall, boolean instantApp,
             boolean virtualPreload, UserManagerService userManager,
@@ -646,12 +646,11 @@
             if (PackageManagerService.DEBUG_UPGRADE) Log.v(PackageManagerService.TAG, "Package "
                     + pkgName + " is adopting original package " + originalPkg.name);
             pkgSetting = new PackageSetting(originalPkg, pkgName /*realPkgName*/);
-            pkgSetting.codePath = codePath;
+            pkgSetting.setCodePath(codePath);
             pkgSetting.legacyNativeLibraryPathString = legacyNativeLibraryPath;
             pkgSetting.pkgFlags = pkgFlags;
             pkgSetting.pkgPrivateFlags = pkgPrivateFlags;
             pkgSetting.primaryCpuAbiString = primaryCpuAbi;
-            pkgSetting.resourcePath = resourcePath;
             pkgSetting.secondaryCpuAbiString = secondaryCpuAbi;
             // NOTE: Create a deeper copy of the package signatures so we don't
             // overwrite the signatures in the original package setting.
@@ -662,7 +661,7 @@
             // Update new package state.
             pkgSetting.setTimeStamp(codePath.lastModified());
         } else {
-            pkgSetting = new PackageSetting(pkgName, realPkgName, codePath, resourcePath,
+            pkgSetting = new PackageSetting(pkgName, realPkgName, codePath,
                     legacyNativeLibraryPath, primaryCpuAbi, secondaryCpuAbi,
                     null /*cpuAbiOverrideString*/, versionCode, pkgFlags, pkgPrivateFlags,
                     0 /*sharedUserId*/, usesStaticLibraries,
@@ -756,10 +755,9 @@
      */
     static void updatePackageSetting(@NonNull PackageSetting pkgSetting,
             @Nullable PackageSetting disabledPkg, @Nullable SharedUserSetting sharedUser,
-            @NonNull File codePath, File resourcePath,
-            @Nullable String legacyNativeLibraryPath, @Nullable String primaryCpuAbi,
-            @Nullable String secondaryCpuAbi, int pkgFlags, int pkgPrivateFlags,
-            @NonNull UserManagerService userManager,
+            @NonNull File codePath, @Nullable String legacyNativeLibraryPath,
+            @Nullable String primaryCpuAbi, @Nullable String secondaryCpuAbi, int pkgFlags,
+            int pkgPrivateFlags, @NonNull UserManagerService userManager,
             @Nullable String[] usesStaticLibraries, @Nullable long[] usesStaticLibrariesVersions,
             @Nullable Set<String> mimeGroupNames)
                     throws PackageManagerException {
@@ -773,12 +771,12 @@
                     "Updating application package " + pkgName + " failed");
         }
 
-        if (!pkgSetting.codePath.equals(codePath)) {
+        if (!pkgSetting.getCodePath().equals(codePath)) {
             final boolean isSystem = pkgSetting.isSystem();
             Slog.i(PackageManagerService.TAG,
                     "Update" + (isSystem ? " system" : "")
                     + " package " + pkgName
-                    + " code path from " + pkgSetting.codePathString
+                    + " code path from " + pkgSetting.getCodePathString()
                     + " to " + codePath.toString()
                     + "; Retain data and using new");
             if (!isSystem) {
@@ -800,19 +798,7 @@
                 // internal to external storage or vice versa.
                 pkgSetting.legacyNativeLibraryPathString = legacyNativeLibraryPath;
             }
-            pkgSetting.codePath = codePath;
-            pkgSetting.codePathString = codePath.toString();
-        }
-        if (!pkgSetting.resourcePath.equals(resourcePath)) {
-            final boolean isSystem = pkgSetting.isSystem();
-            Slog.i(PackageManagerService.TAG,
-                    "Update" + (isSystem ? " system" : "")
-                    + " package " + pkgName
-                    + " resource path from " + pkgSetting.resourcePathString
-                    + " to " + resourcePath.toString()
-                    + "; Retain data and using new");
-            pkgSetting.resourcePath = resourcePath;
-            pkgSetting.resourcePathString = resourcePath.toString();
+            pkgSetting.setCodePath(codePath);
         }
         // If what we are scanning is a system (and possibly privileged) package,
         // then make it so, regardless of whether it was previously installed only
@@ -969,93 +955,6 @@
         }
     }
 
-    /*
-     * Update the shared user setting when a package with a shared user id is removed. The gids
-     * associated with each permission of the deleted package are removed from the shared user'
-     * gid list only if its not in use by other permissions of packages in the shared user setting.
-     *
-     * @return the affected user id
-     */
-    @UserIdInt
-    int updateSharedUserPermsLPw(PackageSetting deletedPs, int userId) {
-        if ((deletedPs == null) || (deletedPs.pkg == null)) {
-            Slog.i(PackageManagerService.TAG,
-                    "Trying to update info for null package. Just ignoring");
-            return UserHandle.USER_NULL;
-        }
-
-        // No sharedUserId
-        if (deletedPs.sharedUser == null) {
-            return UserHandle.USER_NULL;
-        }
-
-        SharedUserSetting sus = deletedPs.sharedUser;
-
-        int affectedUserId = UserHandle.USER_NULL;
-        // Update permissions
-        for (String eachPerm : deletedPs.pkg.getRequestedPermissions()) {
-            BasePermission bp = mPermissions.getPermission(eachPerm);
-            if (bp == null) {
-                continue;
-            }
-
-            // Check if another package in the shared user needs the permission.
-            boolean used = false;
-            for (PackageSetting pkg : sus.packages) {
-                if (pkg.pkg != null
-                        && !pkg.pkg.getPackageName().equals(deletedPs.pkg.getPackageName())
-                        && pkg.pkg.getRequestedPermissions().contains(eachPerm)) {
-                    used = true;
-                    break;
-                }
-            }
-            if (used) {
-                continue;
-            }
-
-            PermissionsState permissionsState = sus.getPermissionsState();
-            PackageSetting disabledPs = getDisabledSystemPkgLPr(deletedPs.pkg.getPackageName());
-
-            // If the package is shadowing is a disabled system package,
-            // do not drop permissions that the shadowed package requests.
-            if (disabledPs != null) {
-                boolean reqByDisabledSysPkg = false;
-                for (String permission : disabledPs.pkg.getRequestedPermissions()) {
-                    if (permission.equals(eachPerm)) {
-                        reqByDisabledSysPkg = true;
-                        break;
-                    }
-                }
-                if (reqByDisabledSysPkg) {
-                    continue;
-                }
-            }
-
-            // Try to revoke as an install permission which is for all users.
-            // The package is gone - no need to keep flags for applying policy.
-            permissionsState.updatePermissionFlags(bp, userId,
-                    PackageManager.MASK_PERMISSION_FLAGS_ALL, 0);
-
-            if (permissionsState.revokeInstallPermission(bp) ==
-                    PermissionsState.PERMISSION_OPERATION_SUCCESS_GIDS_CHANGED) {
-                affectedUserId = UserHandle.USER_ALL;
-            }
-
-            // Try to revoke as an install permission which is per user.
-            if (permissionsState.revokeRuntimePermission(bp, userId) ==
-                    PermissionsState.PERMISSION_OPERATION_SUCCESS_GIDS_CHANGED) {
-                if (affectedUserId == UserHandle.USER_NULL) {
-                    affectedUserId = userId;
-                } else if (affectedUserId != userId) {
-                    // Multiple users affected.
-                    affectedUserId = UserHandle.USER_ALL;
-                }
-            }
-        }
-
-        return affectedUserId;
-    }
-
     int removePackageLPw(String name) {
         final PackageSetting p = mPackages.get(name);
         if (p != null) {
@@ -2710,7 +2609,7 @@
 
     private void writePackageListLPrInternal(int creatingUserId) {
         // Only derive GIDs for active users (not dying)
-        final List<UserInfo> users = getUsers(UserManagerService.getInstance(), true);
+        final List<UserInfo> users = getActiveUsers(UserManagerService.getInstance(), true);
         int[] userIds = new int[users.size()];
         for (int i = 0; i < userIds.length; i++) {
             userIds[i] = users.get(i).id;
@@ -2812,14 +2711,11 @@
         if (pkg.realName != null) {
             serializer.attribute(null, "realName", pkg.realName);
         }
-        serializer.attribute(null, "codePath", pkg.codePathString);
+        serializer.attribute(null, "codePath", pkg.getCodePathString());
         serializer.attribute(null, "ft", Long.toHexString(pkg.timeStamp));
         serializer.attribute(null, "it", Long.toHexString(pkg.firstInstallTime));
         serializer.attribute(null, "ut", Long.toHexString(pkg.lastUpdateTime));
         serializer.attribute(null, "version", String.valueOf(pkg.versionCode));
-        if (!pkg.resourcePathString.equals(pkg.codePathString)) {
-            serializer.attribute(null, "resourcePath", pkg.resourcePathString);
-        }
         if (pkg.legacyNativeLibraryPathString != null) {
             serializer.attribute(null, "nativeLibraryPath", pkg.legacyNativeLibraryPathString);
         }
@@ -2857,10 +2753,7 @@
         if (pkg.realName != null) {
             serializer.attribute(null, "realName", pkg.realName);
         }
-        serializer.attribute(null, "codePath", pkg.codePathString);
-        if (!pkg.resourcePathString.equals(pkg.codePathString)) {
-            serializer.attribute(null, "resourcePath", pkg.resourcePathString);
-        }
+        serializer.attribute(null, "codePath", pkg.getCodePathString());
 
         if (pkg.legacyNativeLibraryPathString != null) {
             serializer.attribute(null, "nativeLibraryPath", pkg.legacyNativeLibraryPathString);
@@ -3559,13 +3452,10 @@
         String name = parser.getAttributeValue(null, ATTR_NAME);
         String realName = parser.getAttributeValue(null, "realName");
         String codePathStr = parser.getAttributeValue(null, "codePath");
-        String resourcePathStr = parser.getAttributeValue(null, "resourcePath");
 
         String legacyCpuAbiStr = parser.getAttributeValue(null, "requiredCpuAbi");
         String legacyNativeLibraryPathStr = parser.getAttributeValue(null, "nativeLibraryPath");
 
-        String parentPackageName = parser.getAttributeValue(null, "parentPackageName");
-
         String primaryCpuAbiStr = parser.getAttributeValue(null, "primaryCpuAbi");
         String secondaryCpuAbiStr = parser.getAttributeValue(null, "secondaryCpuAbi");
         String cpuAbiOverrideStr = parser.getAttributeValue(null, "cpuAbiOverride");
@@ -3574,9 +3464,6 @@
             primaryCpuAbiStr = legacyCpuAbiStr;
         }
 
-        if (resourcePathStr == null) {
-            resourcePathStr = codePathStr;
-        }
         String version = parser.getAttributeValue(null, "version");
         long versionCode = 0;
         if (version != null) {
@@ -3593,9 +3480,8 @@
             pkgPrivateFlags |= ApplicationInfo.PRIVATE_FLAG_PRIVILEGED;
         }
         PackageSetting ps = new PackageSetting(name, realName, new File(codePathStr),
-                new File(resourcePathStr), legacyNativeLibraryPathStr, primaryCpuAbiStr,
-                secondaryCpuAbiStr, cpuAbiOverrideStr, versionCode, pkgFlags, pkgPrivateFlags,
-                0 /*sharedUserId*/, null, null, null);
+                legacyNativeLibraryPathStr, primaryCpuAbiStr, secondaryCpuAbiStr, cpuAbiOverrideStr,
+                versionCode, pkgFlags, pkgPrivateFlags, 0 /*sharedUserId*/, null, null, null);
         String timeStampStr = parser.getAttributeValue(null, "ft");
         if (timeStampStr != null) {
             try {
@@ -3666,7 +3552,6 @@
         String idStr = null;
         String sharedIdStr = null;
         String codePathStr = null;
-        String resourcePathStr = null;
         String legacyCpuAbiString = null;
         String legacyNativeLibraryPathStr = null;
         String primaryCpuAbiString = null;
@@ -3700,7 +3585,6 @@
             uidError = parser.getAttributeValue(null, "uidError");
             sharedIdStr = parser.getAttributeValue(null, "sharedUserId");
             codePathStr = parser.getAttributeValue(null, "codePath");
-            resourcePathStr = parser.getAttributeValue(null, "resourcePath");
 
             legacyCpuAbiString = parser.getAttributeValue(null, "requiredCpuAbi");
 
@@ -3818,9 +3702,6 @@
                         + " sharedUserId=" + sharedIdStr);
             final int userId = idStr != null ? Integer.parseInt(idStr) : 0;
             final int sharedUserId = sharedIdStr != null ? Integer.parseInt(sharedIdStr) : 0;
-            if (resourcePathStr == null) {
-                resourcePathStr = codePathStr;
-            }
             if (realName != null) {
                 realName = realName.intern();
             }
@@ -3834,10 +3715,10 @@
                                 + parser.getPositionDescription());
             } else if (userId > 0) {
                 packageSetting = addPackageLPw(name.intern(), realName, new File(codePathStr),
-                        new File(resourcePathStr), legacyNativeLibraryPathStr, primaryCpuAbiString,
-                        secondaryCpuAbiString, cpuAbiOverrideString, userId, versionCode, pkgFlags,
-                        pkgPrivateFlags, null /*usesStaticLibraries*/,
-                        null /*usesStaticLibraryVersions*/, null /*mimeGroups*/);
+                        legacyNativeLibraryPathStr, primaryCpuAbiString, secondaryCpuAbiString,
+                        cpuAbiOverrideString, userId, versionCode, pkgFlags, pkgPrivateFlags,
+                        null /*usesStaticLibraries*/, null /*usesStaticLibraryVersions*/,
+                        null /*mimeGroups*/);
                 if (PackageManagerService.DEBUG_SETTINGS)
                     Log.i(PackageManagerService.TAG, "Reading package " + name + ": userId="
                             + userId + " pkg=" + packageSetting);
@@ -3852,8 +3733,8 @@
                 }
             } else if (sharedIdStr != null) {
                 if (sharedUserId > 0) {
-                    packageSetting = new PackageSetting(name.intern(), realName, new File(
-                            codePathStr), new File(resourcePathStr), legacyNativeLibraryPathStr,
+                    packageSetting = new PackageSetting(name.intern(), realName,
+                            new File(codePathStr), legacyNativeLibraryPathStr,
                             primaryCpuAbiString, secondaryCpuAbiString, cpuAbiOverrideString,
                             versionCode, pkgFlags, pkgPrivateFlags, sharedUserId,
                             null /*usesStaticLibraries*/,
@@ -4181,24 +4062,12 @@
         final TimingsTraceAndSlog t = new TimingsTraceAndSlog(TAG + "Timing",
                 Trace.TRACE_TAG_PACKAGE_MANAGER);
         t.traceBegin("createNewUser-" + userHandle);
-        String[] volumeUuids;
-        String[] names;
-        int[] appIds;
-        String[] seinfos;
-        int[] targetSdkVersions;
-        int packagesCount;
+        Installer.Batch batch = new Installer.Batch();
         final boolean skipPackageWhitelist = userTypeInstallablePackages == null;
         synchronized (mLock) {
-            Collection<PackageSetting> packages = mPackages.values();
-            packagesCount = packages.size();
-            volumeUuids = new String[packagesCount];
-            names = new String[packagesCount];
-            appIds = new int[packagesCount];
-            seinfos = new String[packagesCount];
-            targetSdkVersions = new int[packagesCount];
-            Iterator<PackageSetting> packagesIterator = packages.iterator();
-            for (int i = 0; i < packagesCount; i++) {
-                PackageSetting ps = packagesIterator.next();
+            final int size = mPackages.size();
+            for (int i = 0; i < size; i++) {
+                final PackageSetting ps = mPackages.valueAt(i);
                 if (ps.pkg == null) {
                     continue;
                 }
@@ -4220,18 +4089,15 @@
                 }
                 // Need to create a data directory for all apps under this user. Accumulate all
                 // required args and call the installer after mPackages lock has been released
-                volumeUuids[i] = ps.volumeUuid;
-                names[i] = ps.name;
-                appIds[i] = ps.appId;
-                seinfos[i] = AndroidPackageUtils.getSeInfo(ps.pkg, ps);
-                targetSdkVersions[i] = ps.pkg.getTargetSdkVersion();
+                final String seInfo = AndroidPackageUtils.getSeInfo(ps.pkg, ps);
+                batch.createAppData(ps.volumeUuid, ps.name, userHandle,
+                        StorageManager.FLAG_STORAGE_CE | StorageManager.FLAG_STORAGE_DE, ps.appId,
+                        seInfo, ps.pkg.getTargetSdkVersion());
             }
         }
         t.traceBegin("createAppData");
-        final int flags = StorageManager.FLAG_STORAGE_CE | StorageManager.FLAG_STORAGE_DE;
         try {
-            installer.createAppDataBatched(volumeUuids, names, userHandle, flags, appIds, seinfos,
-                    targetSdkVersions);
+            batch.execute(installer);
         } catch (InstallerException e) {
             Slog.w(TAG, "Failed to prepare app data", e);
         }
@@ -4456,25 +4322,43 @@
     }
 
     /**
-     * Return all users on the device, including partial or dying users.
+     * Returns all users on the device, including pre-created and dying users.
+     *
      * @param userManager UserManagerService instance
      * @return the list of users
      */
     private static List<UserInfo> getAllUsers(UserManagerService userManager) {
-        return getUsers(userManager, false);
+        return getUsers(userManager, /* excludeDying= */ false, /* excludePreCreated= */ false);
     }
 
     /**
-     * Return the list of users on the device. Clear the calling identity before calling into
-     * UserManagerService.
+     * Returns the list of users on the device, excluding pre-created ones.
+     *
      * @param userManager UserManagerService instance
      * @param excludeDying Indicates whether to exclude any users marked for deletion.
+     *
      * @return the list of users
      */
-    private static List<UserInfo> getUsers(UserManagerService userManager, boolean excludeDying) {
+    private static List<UserInfo> getActiveUsers(UserManagerService userManager,
+            boolean excludeDying) {
+        return getUsers(userManager, excludeDying, /* excludePreCreated= */ true);
+    }
+
+    /**
+     * Returns the list of users on the device.
+     *
+     * @param userManager UserManagerService instance
+     * @param excludeDying Indicates whether to exclude any users marked for deletion.
+     * @param excludePreCreated Indicates whether to exclude any pre-created users.
+     *
+     * @return the list of users
+     */
+    private static List<UserInfo> getUsers(UserManagerService userManager, boolean excludeDying,
+            boolean excludePreCreated) {
         long id = Binder.clearCallingIdentity();
         try {
-            return userManager.getUsers(excludeDying);
+            return userManager.getUsers(/* excludePartial= */ true, excludeDying,
+                    excludePreCreated);
         } catch (NullPointerException npe) {
             // packagemanager not yet initialized
         } finally {
@@ -4657,13 +4541,17 @@
             pw.print(prefix); pw.print("  sharedUser="); pw.println(ps.sharedUser);
         }
         pw.print(prefix); pw.print("  pkg="); pw.println(pkg);
-        pw.print(prefix); pw.print("  codePath="); pw.println(ps.codePathString);
+        pw.print(prefix); pw.print("  codePath="); pw.println(ps.getCodePathString());
         if (permissionNames == null) {
-            pw.print(prefix); pw.print("  resourcePath="); pw.println(ps.resourcePathString);
+            pw.print(prefix); pw.print("  resourcePath="); pw.println(ps.getCodePathString());
             pw.print(prefix); pw.print("  legacyNativeLibraryDir=");
             pw.println(ps.legacyNativeLibraryPathString);
+            pw.print(prefix); pw.print("  extractNativeLibs=");
+            pw.println((ps.pkgFlags & ApplicationInfo.FLAG_EXTRACT_NATIVE_LIBS) != 0
+                    ? "true" : "false");
             pw.print(prefix); pw.print("  primaryCpuAbi="); pw.println(ps.primaryCpuAbiString);
             pw.print(prefix); pw.print("  secondaryCpuAbi="); pw.println(ps.secondaryCpuAbiString);
+            pw.print(prefix); pw.print("  cpuAbiOverride="); pw.println(ps.cpuAbiOverrideString);
         }
         pw.print(prefix); pw.print("  versionCode="); pw.print(ps.versionCode);
         if (pkg != null) {
@@ -5559,32 +5447,11 @@
             // Make sure we do not
             mHandler.removeMessages(userId);
 
-            for (SettingBase sb : mPackages.values()) {
-                revokeRuntimePermissionsAndClearFlags(sb, userId);
-            }
-
-            for (SettingBase sb : mSharedUsers.values()) {
-                revokeRuntimePermissionsAndClearFlags(sb, userId);
-            }
-
             mPermissionUpgradeNeeded.delete(userId);
             mVersions.delete(userId);
             mFingerprints.remove(userId);
         }
 
-        private void revokeRuntimePermissionsAndClearFlags(SettingBase sb, int userId) {
-            PermissionsState permissionsState = sb.getPermissionsState();
-            for (PermissionState permissionState
-                    : permissionsState.getRuntimePermissionStates(userId)) {
-                BasePermission bp = mPermissions.getPermission(permissionState.getName());
-                if (bp != null) {
-                    permissionsState.revokeRuntimePermission(bp, userId);
-                    permissionsState.updatePermissionFlags(bp, userId,
-                            PackageManager.MASK_PERMISSION_FLAGS_ALL, 0);
-                }
-            }
-        }
-
         public void deleteUserRuntimePermissionsFile(int userId) {
             mPersistence.deleteForUser(UserHandle.of(userId));
         }
diff --git a/services/core/java/com/android/server/pm/ShortcutService.java b/services/core/java/com/android/server/pm/ShortcutService.java
index 2eefac1..8412077 100644
--- a/services/core/java/com/android/server/pm/ShortcutService.java
+++ b/services/core/java/com/android/server/pm/ShortcutService.java
@@ -108,6 +108,7 @@
 import com.android.internal.util.StatLogger;
 import com.android.server.LocalServices;
 import com.android.server.SystemService;
+import com.android.server.SystemService.TargetUser;
 import com.android.server.pm.ShortcutUser.PackageWithUser;
 import com.android.server.uri.UriGrantsManagerInternal;
 
@@ -259,7 +260,8 @@
     private static final int PACKAGE_MATCH_FLAGS =
             PackageManager.MATCH_DIRECT_BOOT_AWARE
                     | PackageManager.MATCH_DIRECT_BOOT_UNAWARE
-                    | PackageManager.MATCH_UNINSTALLED_PACKAGES;
+                    | PackageManager.MATCH_UNINSTALLED_PACKAGES
+                    | PackageManager.MATCH_DISABLED_COMPONENTS;
 
     private static final int SYSTEM_APP_MASK =
             ApplicationInfo.FLAG_SYSTEM | ApplicationInfo.FLAG_UPDATED_SYSTEM_APP;
@@ -277,12 +279,6 @@
         }
     };
 
-    private static Predicate<ResolveInfo> ACTIVITY_NOT_SYSTEM_NOR_ENABLED = (ri) -> {
-        final ApplicationInfo ai = ri.activityInfo.applicationInfo;
-        final boolean isSystemApp = ai != null && (ai.flags & SYSTEM_APP_MASK) != 0;
-        return !isSystemApp && !ri.activityInfo.enabled;
-    };
-
     private static Predicate<ResolveInfo> ACTIVITY_NOT_INSTALLED = (ri) ->
             !isInstalled(ri.activityInfo);
 
@@ -614,13 +610,13 @@
         }
 
         @Override
-        public void onStopUser(int userHandle) {
-            mService.handleStopUser(userHandle);
+        public void onUserStopping(@NonNull TargetUser user) {
+            mService.handleStopUser(user.getUserIdentifier());
         }
 
         @Override
-        public void onUnlockUser(int userId) {
-            mService.handleUnlockUser(userId);
+        public void onUserUnlocking(@NonNull TargetUser user) {
+            mService.handleUnlockUser(user.getUserIdentifier());
         }
     }
 
@@ -3684,10 +3680,8 @@
         final long start = getStatStartTime();
         final long token = injectClearCallingIdentity();
         try {
-            return mIPackageManager.getPackageInfo(
-                    packageName, PACKAGE_MATCH_FLAGS | PackageManager.MATCH_DISABLED_COMPONENTS
-                            | (getSignatures ? PackageManager.GET_SIGNING_CERTIFICATES : 0),
-                    userId);
+            return mIPackageManager.getPackageInfo(packageName, PACKAGE_MATCH_FLAGS
+                    | (getSignatures ? PackageManager.GET_SIGNING_CERTIFICATES : 0), userId);
         } catch (RemoteException e) {
             // Shouldn't happen.
             Slog.wtf(TAG, "RemoteException", e);
@@ -3720,8 +3714,7 @@
         final long start = getStatStartTime();
         final long token = injectClearCallingIdentity();
         try {
-            return mIPackageManager.getApplicationInfo(packageName,
-                    PACKAGE_MATCH_FLAGS | PackageManager.MATCH_DISABLED_COMPONENTS, userId);
+            return mIPackageManager.getApplicationInfo(packageName, PACKAGE_MATCH_FLAGS, userId);
         } catch (RemoteException e) {
             // Shouldn't happen.
             Slog.wtf(TAG, "RemoteException", e);
@@ -3752,9 +3745,8 @@
         final long start = getStatStartTime();
         final long token = injectClearCallingIdentity();
         try {
-            return mIPackageManager.getActivityInfo(activity, (PACKAGE_MATCH_FLAGS
-                    | PackageManager.MATCH_DISABLED_COMPONENTS | PackageManager.GET_META_DATA),
-                    userId);
+            return mIPackageManager.getActivityInfo(activity,
+                    PACKAGE_MATCH_FLAGS | PackageManager.GET_META_DATA, userId);
         } catch (RemoteException e) {
             // Shouldn't happen.
             Slog.wtf(TAG, "RemoteException", e);
@@ -3799,8 +3791,7 @@
     List<PackageInfo> injectGetPackagesWithUninstalled(@UserIdInt int userId)
             throws RemoteException {
         final ParceledListSlice<PackageInfo> parceledList =
-                mIPackageManager.getInstalledPackages(
-                        PACKAGE_MATCH_FLAGS | PackageManager.MATCH_DISABLED_COMPONENTS, userId);
+                mIPackageManager.getInstalledPackages(PACKAGE_MATCH_FLAGS, userId);
         if (parceledList == null) {
             return Collections.emptyList();
         }
@@ -3835,6 +3826,41 @@
         return (ai != null) && ((ai.flags & flags) == flags);
     }
 
+    // Due to b/38267327, ActivityInfo.enabled may not reflect the current state of the component
+    // and we need to check the enabled state via PackageManager.getComponentEnabledSetting.
+    private boolean isEnabled(@Nullable ActivityInfo ai, int userId) {
+        if (ai == null) {
+            return false;
+        }
+
+        int enabledFlag;
+        final long token = injectClearCallingIdentity();
+        try {
+            enabledFlag = mIPackageManager.getComponentEnabledSetting(
+                    ai.getComponentName(), userId);
+        } catch (RemoteException e) {
+            // Shouldn't happen.
+            Slog.wtf(TAG, "RemoteException", e);
+            return false;
+        } finally {
+            injectRestoreCallingIdentity(token);
+        }
+
+        if ((enabledFlag == PackageManager.COMPONENT_ENABLED_STATE_DEFAULT && ai.enabled)
+                || enabledFlag == PackageManager.COMPONENT_ENABLED_STATE_ENABLED) {
+            return true;
+        }
+        return false;
+    }
+
+    private static boolean isSystem(@Nullable ActivityInfo ai) {
+        return (ai != null) && isSystem(ai.applicationInfo);
+    }
+
+    private static boolean isSystem(@Nullable ApplicationInfo ai) {
+        return (ai != null) && (ai.flags & SYSTEM_APP_MASK) != 0;
+    }
+
     private static boolean isInstalled(@Nullable ApplicationInfo ai) {
         return (ai != null) && ai.enabled && (ai.flags & ApplicationInfo.FLAG_INSTALLED) != 0;
     }
@@ -3899,12 +3925,6 @@
         return intent;
     }
 
-    private static boolean isSystemApp(@Nullable final ApplicationInfo ai) {
-        final int systemAppMask =
-                ApplicationInfo.FLAG_SYSTEM | ApplicationInfo.FLAG_UPDATED_SYSTEM_APP;
-        return ai != null && ((ai.flags & systemAppMask) != 0);
-    }
-
     /**
      * Same as queryIntentActivitiesAsUser, except it makes sure the package is installed,
      * and only returns exported activities.
@@ -3937,7 +3957,10 @@
         }
         // Make sure the package is installed.
         resolved.removeIf(ACTIVITY_NOT_INSTALLED);
-        resolved.removeIf(ACTIVITY_NOT_SYSTEM_NOR_ENABLED);
+        resolved.removeIf((ri) -> {
+            final ActivityInfo ai = ri.activityInfo;
+            return !isSystem(ai) && !isEnabled(ai, userId);
+        });
         if (exportedOnly) {
             resolved.removeIf(ACTIVITY_NOT_EXPORTED);
         }
diff --git a/services/core/java/com/android/server/pm/StagingManager.java b/services/core/java/com/android/server/pm/StagingManager.java
index 89bdb3e..105a9b0 100644
--- a/services/core/java/com/android/server/pm/StagingManager.java
+++ b/services/core/java/com/android/server/pm/StagingManager.java
@@ -56,6 +56,7 @@
 import android.os.storage.IStorageManager;
 import android.os.storage.StorageManager;
 import android.text.TextUtils;
+import android.util.ArraySet;
 import android.util.IntArray;
 import android.util.Slog;
 import android.util.SparseArray;
@@ -84,6 +85,7 @@
 import java.io.IOException;
 import java.util.ArrayList;
 import java.util.List;
+import java.util.Set;
 import java.util.concurrent.LinkedBlockingQueue;
 import java.util.concurrent.TimeUnit;
 import java.util.function.Consumer;
@@ -322,9 +324,6 @@
         }
         final long activeVersion = activePackage.applicationInfo.longVersionCode;
         if (activeVersion != session.params.requiredInstalledVersionCode) {
-            if (!mApexManager.abortStagedSession(session.sessionId)) {
-                Slog.e(TAG, "Failed to abort apex session " + session.sessionId);
-            }
             throw new PackageManagerException(
                     SessionInfo.STAGED_SESSION_VERIFICATION_FAILED,
                     "Installed version of APEX package " + activePackage.packageName
@@ -338,14 +337,11 @@
             throws PackageManagerException {
         final long activeVersion = activePackage.applicationInfo.longVersionCode;
         final long newVersionCode = newPackage.applicationInfo.longVersionCode;
-        boolean isAppDebuggable = (activePackage.applicationInfo.flags
+        final boolean isAppDebuggable = (activePackage.applicationInfo.flags
                 & ApplicationInfo.FLAG_DEBUGGABLE) != 0;
         final boolean allowsDowngrade = PackageManagerServiceUtils.isDowngradePermitted(
                 session.params.installFlags, isAppDebuggable);
         if (activeVersion > newVersionCode && !allowsDowngrade) {
-            if (!mApexManager.abortStagedSession(session.sessionId)) {
-                Slog.e(TAG, "Failed to abort apex session " + session.sessionId);
-            }
             throw new PackageManagerException(
                     SessionInfo.STAGED_SESSION_VERIFICATION_FAILED,
                     "Downgrade of APEX package " + newPackage.packageName
@@ -595,13 +591,14 @@
             // If checkpoint is supported, then we only resume sessions if we are in checkpointing
             // mode. If not, we fail all sessions.
             if (supportsCheckpoint() && !needsCheckpoint()) {
-                String errorMsg = "Reverting back to safe state. Marking " + session.sessionId
-                        + " as failed";
-                if (!TextUtils.isEmpty(mFailureReason)) {
-                    errorMsg = errorMsg + ": " + mFailureReason;
+                String revertMsg = "Reverting back to safe state. Marking "
+                        + session.sessionId + " as failed.";
+                final String reasonForRevert = getReasonForRevert();
+                if (!TextUtils.isEmpty(reasonForRevert)) {
+                    revertMsg += " Reason for revert: " + reasonForRevert;
                 }
-                Slog.d(TAG, errorMsg);
-                session.setStagedSessionFailed(SessionInfo.STAGED_SESSION_UNKNOWN, errorMsg);
+                Slog.d(TAG, revertMsg);
+                session.setStagedSessionFailed(SessionInfo.STAGED_SESSION_UNKNOWN, revertMsg);
                 return;
             }
         } catch (RemoteException e) {
@@ -656,6 +653,7 @@
         try {
             if (hasApex) {
                 checkInstallationOfApkInApexSuccessful(session);
+                checkDuplicateApkInApex(session);
                 snapshotAndRestoreForApexSession(session);
                 Slog.i(TAG, "APEX packages in session " + session.sessionId
                         + " were successfully activated. Proceeding with APK packages, if any");
@@ -705,6 +703,16 @@
         }
     }
 
+    private String getReasonForRevert() {
+        if (!TextUtils.isEmpty(mFailureReason)) {
+            return mFailureReason;
+        }
+        if (!TextUtils.isEmpty(mNativeFailureReason)) {
+            return "Session reverted due to crashing native process: " + mNativeFailureReason;
+        }
+        return "";
+    }
+
     private List<String> findAPKsInDir(File stageDir) {
         List<String> ret = new ArrayList<>();
         if (stageDir != null && stageDir.exists()) {
@@ -835,35 +843,38 @@
         return null;
     }
 
-    private void verifyApksInSession(PackageInstallerSession session)
+    /**
+     * Throws a PackageManagerException if there are duplicate packages in apk and apk-in-apex.
+     */
+    private void checkDuplicateApkInApex(@NonNull PackageInstallerSession session)
             throws PackageManagerException {
-
-        final PackageInstallerSession apksToVerify = extractApksInSession(
-                session,  /* preReboot */ true);
-        if (apksToVerify == null) {
+        if (!session.isMultiPackage()) {
             return;
         }
+        final int[] childSessionIds = session.getChildSessionIds();
+        final Set<String> apkNames = new ArraySet<>();
+        synchronized (mStagedSessions) {
+            for (int id : childSessionIds) {
+                final PackageInstallerSession s = mStagedSessions.get(id);
+                if (!isApexSession(s)) {
+                    apkNames.add(s.getPackageName());
+                }
+            }
+        }
+        final List<PackageInstallerSession> apexSessions = extractApexSessions(session);
+        for (PackageInstallerSession apexSession : apexSessions) {
+            String packageName = apexSession.getPackageName();
+            for (String apkInApex : mApexManager.getApksInApex(packageName)) {
+                if (!apkNames.add(apkInApex)) {
+                    throw new PackageManagerException(
+                            SessionInfo.STAGED_SESSION_ACTIVATION_FAILED,
+                            "Package: " + packageName + " in session: "
+                                    + apexSession.sessionId + " has duplicate apk-in-apex: "
+                                    + apkInApex, null);
 
-        final LocalIntentReceiverAsync receiver = new LocalIntentReceiverAsync(
-                (Intent result) -> {
-                    int status = result.getIntExtra(PackageInstaller.EXTRA_STATUS,
-                            PackageInstaller.STATUS_FAILURE);
-                    if (status != PackageInstaller.STATUS_SUCCESS) {
-                        final String errorMessage = result.getStringExtra(
-                                PackageInstaller.EXTRA_STATUS_MESSAGE);
-                        Slog.e(TAG, "Failure to verify APK staged session "
-                                + session.sessionId + " [" + errorMessage + "]");
-                        session.setStagedSessionFailed(
-                                SessionInfo.STAGED_SESSION_VERIFICATION_FAILED, errorMessage);
-                        mPreRebootVerificationHandler.onPreRebootVerificationComplete(
-                                session.sessionId);
-                        return;
-                    }
-                    mPreRebootVerificationHandler.notifyPreRebootVerification_Apk_Complete(
-                            session.sessionId);
-                });
-
-        apksToVerify.commit(receiver.getIntentSender(), false);
+                }
+            }
+        }
     }
 
     private void installApksInSession(@NonNull PackageInstallerSession session)
@@ -908,10 +919,21 @@
         mPreRebootVerificationHandler.startPreRebootVerification(session.sessionId);
     }
 
-    private int parentOrOwnSessionId(PackageInstallerSession session) {
+    private int getSessionIdForParentOrSelf(PackageInstallerSession session) {
         return session.hasParentSessionId() ? session.getParentSessionId() : session.sessionId;
     }
 
+    private PackageInstallerSession getParentSessionOrSelf(PackageInstallerSession session) {
+        return session.hasParentSessionId()
+                ? getStagedSession(session.getParentSessionId())
+                : session;
+    }
+
+    private boolean isRollback(PackageInstallerSession session) {
+        final PackageInstallerSession root = getParentSessionOrSelf(session);
+        return root.params.installReason == PackageManager.INSTALL_REASON_ROLLBACK;
+    }
+
     /**
      * <p> Check if the session provided is non-overlapping with the active staged sessions.
      *
@@ -937,6 +959,8 @@
         boolean supportsCheckpoint = ((StorageManager) mContext.getSystemService(
                 Context.STORAGE_SERVICE)).isCheckpointSupported();
 
+        final boolean isRollback = isRollback(session);
+
         synchronized (mStagedSessions) {
             for (int i = 0; i < mStagedSessions.size(); i++) {
                 final PackageInstallerSession stagedSession = mStagedSessions.valueAt(i);
@@ -951,8 +975,8 @@
                 }
                 // Check if stagedSession has an active parent session or not
                 if (stagedSession.hasParentSessionId()) {
-                    int parentId = stagedSession.getParentSessionId();
-                    PackageInstallerSession parentSession = mStagedSessions.get(parentId);
+                    final int parentId = stagedSession.getParentSessionId();
+                    final PackageInstallerSession parentSession = mStagedSessions.get(parentId);
                     if (parentSession == null || parentSession.isStagedAndInTerminalState()
                             || parentSession.isDestroyed()) {
                         // Parent session has been abandoned or terminated already
@@ -968,21 +992,37 @@
                     continue;
                 }
 
-                // If session is not among the active sessions, then it cannot have same package
-                // name as any of the active sessions.
+                // New session cannot have same package name as one of the active sessions
                 if (session.getPackageName().equals(stagedSession.getPackageName())) {
-                    throw new PackageManagerException(
-                            PackageManager.INSTALL_FAILED_OTHER_STAGED_SESSION_IN_PROGRESS,
-                            "Package: " + session.getPackageName() + " in session: "
-                                    + session.sessionId + " has been staged already by session: "
-                                    + stagedSession.sessionId, null);
+                    if (isRollback) {
+                        // If the new session is a rollback, then it gets priority. The existing
+                        // session is failed to unblock rollback.
+                        final PackageInstallerSession root = getParentSessionOrSelf(stagedSession);
+                        if (!ensureActiveApexSessionIsAborted(root)) {
+                            Slog.e(TAG, "Failed to abort apex session " + root.sessionId);
+                            // Safe to ignore active apex session abort failure since session
+                            // will be marked failed on next step and staging directory for session
+                            // will be deleted.
+                        }
+                        root.setStagedSessionFailed(
+                                SessionInfo.STAGED_SESSION_CONFLICT,
+                                "Session was blocking rollback session: " + session.sessionId);
+                        Slog.i(TAG, "Session " + root.sessionId + " is marked failed due to "
+                                + "blocking rollback session: " + session.sessionId);
+                    } else {
+                        throw new PackageManagerException(
+                                PackageManager.INSTALL_FAILED_OTHER_STAGED_SESSION_IN_PROGRESS,
+                                "Package: " + session.getPackageName() + " in session: "
+                                        + session.sessionId + " has been staged already by session:"
+                                        + " " + stagedSession.sessionId, null);
+                    }
                 }
 
                 // Staging multiple root sessions is not allowed if device doesn't support
                 // checkpoint. If session and stagedSession do not have common ancestor, they are
                 // from two different root sessions.
-                if (!supportsCheckpoint
-                        && parentOrOwnSessionId(session) != parentOrOwnSessionId(stagedSession)) {
+                if (!supportsCheckpoint && getSessionIdForParentOrSelf(session)
+                        != getSessionIdForParentOrSelf(stagedSession)) {
                     throw new PackageManagerException(
                             PackageManager.INSTALL_FAILED_OTHER_STAGED_SESSION_IN_PROGRESS,
                             "Cannot stage multiple sessions without checkpoint support", null);
@@ -1042,23 +1082,11 @@
 
         // A session could be marked ready once its pre-reboot verification ends
         if (session.isStagedSessionReady()) {
-            if (sessionContainsApex(session)) {
-                try {
-                    ApexSessionInfo apexSession =
-                            mApexManager.getStagedSessionInfo(session.sessionId);
-                    if (apexSession == null || isApexSessionFinalized(apexSession)) {
-                        Slog.w(TAG,
-                                "Cannot abort session " + session.sessionId
-                                        + " because it is not active.");
-                    } else {
-                        mApexManager.abortStagedSession(session.sessionId);
-                    }
-                } catch (Exception e) {
-                    // Failed to contact apexd service. The apex might still be staged. We can still
-                    // safely cleanup the staged session since pre-reboot verification is complete.
-                    // Also, cleaning up the stageDir prevents the apex from being activated.
-                    Slog.w(TAG, "Could not contact apexd to abort staged session " + sessionId);
-                }
+            if (!ensureActiveApexSessionIsAborted(session)) {
+                // Failed to ensure apex session is aborted, so it can still be staged. We can still
+                // safely cleanup the staged session since pre-reboot verification is complete.
+                // Also, cleaning up the stageDir prevents the apex from being activated.
+                Slog.e(TAG, "Failed to abort apex session " + session.sessionId);
             }
         }
 
@@ -1068,6 +1096,22 @@
         return true;
     }
 
+    /**
+     * Ensure that there is no active apex session staged in apexd for the given session.
+     *
+     * @return returns true if it is ensured that there is no active apex session, otherwise false
+     */
+    private boolean ensureActiveApexSessionIsAborted(PackageInstallerSession session) {
+        if (!sessionContainsApex(session)) {
+            return true;
+        }
+        final ApexSessionInfo apexSession = mApexManager.getStagedSessionInfo(session.sessionId);
+        if (apexSession == null || isApexSessionFinalized(apexSession)) {
+            return true;
+        }
+        return mApexManager.abortStagedSession(session.sessionId);
+    }
+
     private boolean isApexSessionFinalized(ApexSessionInfo session) {
         /* checking if the session is in a final state, i.e., not active anymore */
         return session.isUnknown || session.isActivationFailed || session.isSuccess
@@ -1294,8 +1338,8 @@
                         + sessionId);
                 return;
             }
-            if (session.isDestroyed()) {
-                // No point in running verification on a destroyed session
+            if (session.isDestroyed() || session.isStagedSessionFailed()) {
+                // No point in running verification on a destroyed/failed session
                 onPreRebootVerificationComplete(sessionId);
                 return;
             }
@@ -1348,6 +1392,17 @@
             obtainMessage(MSG_PRE_REBOOT_VERIFICATION_START, sessionId, 0).sendToTarget();
         }
 
+        private void onPreRebootVerificationFailure(PackageInstallerSession session,
+                @SessionInfo.StagedSessionErrorCode int errorCode, String errorMessage) {
+            if (!ensureActiveApexSessionIsAborted(session)) {
+                Slog.e(TAG, "Failed to abort apex session " + session.sessionId);
+                // Safe to ignore active apex session abortion failure since session will be marked
+                // failed on next step and staging directory for session will be deleted.
+            }
+            session.setStagedSessionFailed(errorCode, errorMessage);
+            onPreRebootVerificationComplete(session.sessionId);
+        }
+
         // Things to do when pre-reboot verification completes for a particular sessionId
         private void onPreRebootVerificationComplete(int sessionId) {
             // Remove it from mVerificationRunning so that verification is considered complete
@@ -1432,8 +1487,7 @@
                         validateApexSignature(apexPackages.get(i));
                     }
                 } catch (PackageManagerException e) {
-                    session.setStagedSessionFailed(e.error, e.getMessage());
-                    onPreRebootVerificationComplete(session.sessionId);
+                    onPreRebootVerificationFailure(session, e.error, e.getMessage());
                     return;
                 }
 
@@ -1460,16 +1514,42 @@
             try {
                 Slog.d(TAG, "Running a pre-reboot verification for APKs in session "
                         + session.sessionId + " by performing a dry-run install");
-
                 // verifyApksInSession will notify the handler when APK verification is complete
                 verifyApksInSession(session);
-                // TODO(b/118865310): abort the session on apexd.
             } catch (PackageManagerException e) {
-                session.setStagedSessionFailed(e.error, e.getMessage());
-                onPreRebootVerificationComplete(session.sessionId);
+                onPreRebootVerificationFailure(session, e.error, e.getMessage());
             }
         }
 
+        private void verifyApksInSession(PackageInstallerSession session)
+                throws PackageManagerException {
+
+            final PackageInstallerSession apksToVerify = extractApksInSession(
+                    session,  /* preReboot */ true);
+            if (apksToVerify == null) {
+                return;
+            }
+
+            final LocalIntentReceiverAsync receiver = new LocalIntentReceiverAsync(
+                    (Intent result) -> {
+                        final int status = result.getIntExtra(PackageInstaller.EXTRA_STATUS,
+                                PackageInstaller.STATUS_FAILURE);
+                        if (status != PackageInstaller.STATUS_SUCCESS) {
+                            final String errorMessage = result.getStringExtra(
+                                    PackageInstaller.EXTRA_STATUS_MESSAGE);
+                            Slog.e(TAG, "Failure to verify APK staged session "
+                                    + session.sessionId + " [" + errorMessage + "]");
+                            onPreRebootVerificationFailure(session,
+                                    SessionInfo.STAGED_SESSION_ACTIVATION_FAILED, errorMessage);
+                            return;
+                        }
+                        mPreRebootVerificationHandler.notifyPreRebootVerification_Apk_Complete(
+                                session.sessionId);
+                    });
+
+            apksToVerify.commit(receiver.getIntentSender(), false);
+        }
+
         /**
          * Pre-reboot verification state for wrapping up:
          * <p><ul>
@@ -1487,9 +1567,8 @@
             } catch (Exception e) {
                 // Failed to get hold of StorageManager
                 Slog.e(TAG, "Failed to get hold of StorageManager", e);
-                session.setStagedSessionFailed(SessionInfo.STAGED_SESSION_UNKNOWN,
+                onPreRebootVerificationFailure(session, SessionInfo.STAGED_SESSION_UNKNOWN,
                         "Failed to get hold of StorageManager");
-                onPreRebootVerificationComplete(session.sessionId);
                 return;
             }
 
diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java
index b0d3d53..c1aebd3 100644
--- a/services/core/java/com/android/server/pm/UserManagerService.java
+++ b/services/core/java/com/android/server/pm/UserManagerService.java
@@ -84,9 +84,11 @@
 import android.security.GateKeeper;
 import android.service.gatekeeper.IGateKeeperService;
 import android.stats.devicepolicy.DevicePolicyEnums;
+import android.text.TextUtils;
 import android.util.ArrayMap;
 import android.util.ArraySet;
 import android.util.AtomicFile;
+import android.util.IndentingPrintWriter;
 import android.util.IntArray;
 import android.util.Slog;
 import android.util.SparseArray;
@@ -103,13 +105,13 @@
 import com.android.internal.util.DumpUtils;
 import com.android.internal.util.FastXmlSerializer;
 import com.android.internal.util.FrameworkStatsLog;
-import com.android.internal.util.IndentingPrintWriter;
 import com.android.internal.util.Preconditions;
 import com.android.internal.util.XmlUtils;
 import com.android.internal.widget.LockPatternUtils;
 import com.android.server.LocalServices;
 import com.android.server.LockGuard;
 import com.android.server.SystemService;
+import com.android.server.SystemService.TargetUser;
 import com.android.server.am.UserState;
 import com.android.server.storage.DeviceStorageMonitorInternal;
 import com.android.server.utils.TimingsTraceAndSlog;
@@ -133,6 +135,7 @@
 import java.io.PrintWriter;
 import java.nio.charset.StandardCharsets;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.Collections;
 import java.util.LinkedList;
 import java.util.List;
@@ -156,6 +159,10 @@
     private static final String LOG_TAG = "UserManagerService";
     static final boolean DBG = false; // DO NOT SUBMIT WITH TRUE
     private static final boolean DBG_WITH_STACKTRACE = false; // DO NOT SUBMIT WITH TRUE
+
+    // TODO(b/164159026): remove once owner_name issue on automotive is fixed
+    // Can be used to track getUsers() / userWithNameLU() behavior
+    public static final boolean DBG_CACHED_USERINFOS = false; // DO NOT SUBMIT WITH TRUE
     // Can be used for manual testing of id recycling
     private static final boolean RELEASE_DELETED_USER_ID = false; // DO NOT SUBMIT WITH TRUE
 
@@ -269,6 +276,25 @@
     private DevicePolicyManagerInternal mDevicePolicyManagerInternal;
 
     /**
+     * Reference to the {@link UserHandle#SYSTEM} user's UserInfo; it's {@code name} was either
+     * manually set, or it's {@code null}.
+     *
+     * <p>The reference is set just once, but it's {@code name} is updated when it's manually set.
+     */
+    @GuardedBy("mUsersLock")
+    private UserInfo mSystemUserInfo;
+
+    /**
+     * Reference to the {@link UserHandle#SYSTEM} user's UserInfo, with its {@code name} set to
+     * the localized value of {@code owner_name}.
+     *
+     * <p>The reference is set just once, but it's {@code name} is updated everytime the reference
+     * is used and the locale changed.
+     */
+    @GuardedBy("mUsersLock")
+    private UserInfo mSystemUserInfoWithName;
+
+    /**
      * Internal non-parcelable wrapper for UserInfo that is not exposed to other system apps.
      */
     @VisibleForTesting
@@ -491,20 +517,23 @@
             states = new SparseIntArray();
             invalidateIsUserUnlockedCache();
         }
-        public int get(int userId) {
+        public int get(@UserIdInt int userId) {
             return states.get(userId);
         }
-        public int get(int userId, int fallback) {
+        public int get(@UserIdInt int userId, int fallback) {
             return states.indexOfKey(userId) >= 0 ? states.get(userId) : fallback;
         }
-        public void put(int userId, int state) {
+        public void put(@UserIdInt int userId, int state) {
             states.put(userId, state);
             invalidateIsUserUnlockedCache();
         }
-        public void delete(int userId) {
+        public void delete(@UserIdInt int userId) {
             states.delete(userId);
             invalidateIsUserUnlockedCache();
         }
+        public boolean has(@UserIdInt int userId) {
+            return states.get(userId, UserHandle.USER_NULL) != UserHandle.USER_NULL;
+        }
         @Override
         public String toString() {
             return states.toString();
@@ -553,9 +582,9 @@
         }
 
         @Override
-        public void onStartUser(@UserIdInt int userId) {
+        public void onUserStarting(@NonNull TargetUser targetUser) {
             synchronized (mUms.mUsersLock) {
-                final UserData user = mUms.getUserDataLU(userId);
+                final UserData user = mUms.getUserDataLU(targetUser.getUserIdentifier());
                 if (user != null) {
                     user.startRealtime = SystemClock.elapsedRealtime();
                 }
@@ -563,9 +592,9 @@
         }
 
         @Override
-        public void onUnlockUser(@UserIdInt int userId) {
+        public void onUserUnlocking(@NonNull TargetUser targetUser) {
             synchronized (mUms.mUsersLock) {
-                final UserData user = mUms.getUserDataLU(userId);
+                final UserData user = mUms.getUserDataLU(targetUser.getUserIdentifier());
                 if (user != null) {
                     user.unlockRealtime = SystemClock.elapsedRealtime();
                 }
@@ -573,9 +602,9 @@
         }
 
         @Override
-        public void onStopUser(@UserIdInt int userId) {
+        public void onUserStopping(@NonNull TargetUser targetUser) {
             synchronized (mUms.mUsersLock) {
-                final UserData user = mUms.getUserDataLU(userId);
+                final UserData user = mUms.getUserDataLU(targetUser.getUserIdentifier());
                 if (user != null) {
                     user.startRealtime = 0;
                     user.unlockRealtime = 0;
@@ -772,7 +801,7 @@
                         || (excludePreCreated && ui.preCreated)) {
                     continue;
                 }
-                users.add(userWithName(ui));
+                users.add(userWithNameLU(ui));
             }
             return users;
         }
@@ -841,7 +870,7 @@
                 userInfo.name = null;
                 userInfo.iconPath = null;
             } else {
-                userInfo = userWithName(userInfo);
+                userInfo = userWithNameLU(userInfo);
             }
             users.add(userInfo);
         }
@@ -1298,22 +1327,57 @@
     public UserInfo getUserInfo(@UserIdInt int userId) {
         checkManageOrCreateUsersPermission("query user");
         synchronized (mUsersLock) {
-            return userWithName(getUserInfoLU(userId));
+            return userWithNameLU(getUserInfoLU(userId));
         }
     }
 
     /**
      * Returns a UserInfo object with the name filled in, for Owner, or the original
      * if the name is already set.
+     *
+     * <p>Note:</p> the Owner name is localized, so the current value must be checked every time
+     * this method is called.
      */
-    private UserInfo userWithName(UserInfo orig) {
-        if (orig != null && orig.name == null && orig.id == UserHandle.USER_SYSTEM) {
-            UserInfo withName = new UserInfo(orig);
-            withName.name = getOwnerName();
-            return withName;
-        } else {
-            return orig;
+    private UserInfo userWithNameLU(UserInfo orig) {
+        // Only the system user uses the owner_name string.
+        if (orig == null || orig.id != UserHandle.USER_SYSTEM) return orig;
+
+        if (mSystemUserInfo == null) {
+            mSystemUserInfo = orig;
+            if (DBG_CACHED_USERINFOS) {
+                Slog.d(LOG_TAG, "Set mSystemUserInfo:" + mSystemUserInfo.toFullString());
+            }
         }
+
+        if (mSystemUserInfo.name != null) {
+            if (DBG_CACHED_USERINFOS) {
+                Slog.v(LOG_TAG, "Returning mSystemUserInfo: " + mSystemUserInfo.toFullString());
+            }
+            return mSystemUserInfo;
+        }
+
+        final String ownerName = getOwnerName();
+
+        if (mSystemUserInfoWithName == null) {
+            mSystemUserInfoWithName = new UserInfo(orig);
+            mSystemUserInfoWithName.name = ownerName;
+            if (DBG_CACHED_USERINFOS) {
+                Slog.d(LOG_TAG, "Set mSystemUserInfoWithName: "
+                        + mSystemUserInfoWithName.toFullString());
+            }
+        } else if (!TextUtils.equals(ownerName, mSystemUserInfoWithName.name)) {
+            if (DBG_CACHED_USERINFOS) {
+                Slog.d(LOG_TAG, "Updating mSystemUserInfoWithName.name from  "
+                        + mSystemUserInfoWithName.name + " to " + ownerName);
+            }
+            mSystemUserInfoWithName.name = ownerName;
+        }
+
+        if (DBG_CACHED_USERINFOS) {
+            Slog.v(LOG_TAG, "Returning mSystemUserInfoWithName:"
+                    + mSystemUserInfoWithName.toFullString());
+        }
+        return mSystemUserInfoWithName;
     }
 
     /** Returns whether the given user type is one of the FULL user types. */
@@ -1466,7 +1530,7 @@
         }
         final int userId = UserHandle.getUserId(Binder.getCallingUid());
         synchronized (mUsersLock) {
-            UserInfo userInfo = userWithName(getUserInfoLU(userId));
+            UserInfo userInfo = userWithNameLU(getUserInfoLU(userId));
             return userInfo == null ? "" : userInfo.name;
         }
     }
@@ -1581,6 +1645,13 @@
             Slog.w(LOG_TAG, "getUserInfo: unknown user #" + userId);
             return null;
         }
+
+        if (DBG_CACHED_USERINFOS && userId == UserHandle.USER_SYSTEM && userData != null
+                && userData.info != mSystemUserInfo) {
+            Slog.wtf(LOG_TAG, "getUserInfoLU(): system user on userData (" + userData.info
+                    + ") is not the same as mSystemUserInfo (" + mSystemUserInfo + ")");
+        }
+
         return userData != null ? userData.info : null;
     }
 
@@ -3496,7 +3567,7 @@
             }
 
             t.traceBegin("PM.onNewUserCreated-" + userId);
-            mPm.onNewUserCreated(userId);
+            mPm.onNewUserCreated(userId, /* convertedFromPreCreated= */ false);
             t.traceEnd();
             if (preCreate) {
                 // Must start user (which will be stopped right away, through
@@ -3548,6 +3619,13 @@
         if (preCreatedUserData == null) {
             return null;
         }
+        synchronized (mUserStates) {
+            if (mUserStates.has(preCreatedUserData.info.id)) {
+                Slog.w(LOG_TAG, "Cannot reuse pre-created user "
+                        + preCreatedUserData.info.id + " because it didn't stop yet");
+                return null;
+            }
+        }
         final UserInfo preCreatedUser = preCreatedUserData.info;
         final int newFlags = preCreatedUser.flags | flags;
         if (!checkUserTypeConsistency(newFlags)) {
@@ -3569,10 +3647,7 @@
             writeUserListLP();
         }
         updateUserIds();
-        if (!mPm.readPermissionStateForUser(preCreatedUser.id)) {
-            // Could not read the existing permissions, re-grant them.
-            mPm.onNewUserCreated(preCreatedUser.id);
-        }
+        mPm.onNewUserCreated(preCreatedUser.id, /* convertedFromPreCreated= */ true);
         dispatchUserAdded(preCreatedUser);
         return preCreatedUser;
     }
@@ -4786,6 +4861,7 @@
                     }
                 }
             }
+
             pw.println();
             pw.println("Device properties:");
             pw.println("  Device owner id:" + mDeviceOwnerUserId);
@@ -4803,8 +4879,23 @@
                 }
             }
             synchronized (mUserStates) {
-                pw.println("  Started users state: " + mUserStates);
+                pw.print("  Started users state: [");
+                final int size = mUserStates.states.size();
+                for (int i = 0; i < size; i++) {
+                    final int userId = mUserStates.states.keyAt(i);
+                    final int state = mUserStates.states.valueAt(i);
+                    pw.print(userId);
+                    pw.print('=');
+                    pw.print(UserState.stateToString(state));
+                    if (i != size - 1) pw.print(", ");
+                }
+                pw.println(']');
             }
+            synchronized (mUsersLock) {
+                pw.print("  Cached user IDs: ");
+                pw.println(Arrays.toString(mUserIds));
+            }
+
         } // synchronized (mPackagesLock)
 
         // Dump some capabilities
@@ -4818,6 +4909,17 @@
         pw.println("  Is split-system user: " + UserManager.isSplitSystemUser());
         pw.println("  Is headless-system mode: " + UserManager.isHeadlessSystemUserMode());
         pw.println("  User version: " + mUserVersion);
+        pw.println("  Owner name: " + getOwnerName());
+        if (mSystemUserInfo == null) {
+            pw.println("  (mSystemUserInfo not set)");
+        } else {
+            pw.println("  System user: " +  mSystemUserInfo.toFullString());
+        }
+        if (mSystemUserInfoWithName == null) {
+            pw.println("  (mSystemUserInfoWithName not set)");
+        } else {
+            pw.println("  System user (with name): " +  mSystemUserInfoWithName.toFullString());
+        }
 
         // Dump UserTypes
         pw.println();
@@ -4827,9 +4929,17 @@
             mUserTypes.valueAt(i).dump(pw, "        ");
         }
 
-        // Dump package whitelist
-        pw.println();
-        mSystemPackageInstaller.dump(pw);
+        // TODO: create IndentingPrintWriter at the beginning of dump() and use the proper
+        // indentation methods instead of explicit printing "  "
+        try (IndentingPrintWriter ipw = new IndentingPrintWriter(pw)) {
+
+            // Dump SystemPackageInstaller info
+            ipw.println();
+            mSystemPackageInstaller.dump(ipw);
+
+            // NOTE: pw's not available after this point as it's auto-closed by ipw, so new dump
+            // statements should use ipw below
+        }
     }
 
     private static void dumpTimeAgo(PrintWriter pw, StringBuilder sb, long nowTime, long time) {
@@ -5187,6 +5297,7 @@
             return userData == null ? null : userData.info;
         }
 
+        @Override
         public @NonNull UserInfo[] getUserInfos() {
             synchronized (mUsersLock) {
                 int userSize = mUsers.size();
diff --git a/services/core/java/com/android/server/pm/UserRestrictionsUtils.java b/services/core/java/com/android/server/pm/UserRestrictionsUtils.java
index 3c1d189..f7e9e34 100644
--- a/services/core/java/com/android/server/pm/UserRestrictionsUtils.java
+++ b/services/core/java/com/android/server/pm/UserRestrictionsUtils.java
@@ -139,6 +139,11 @@
             UserManager.DISALLOW_CONFIG_PRIVATE_DNS
     });
 
+    public static final Set<String> DEPRECATED_USER_RESTRICTIONS = Sets.newArraySet(
+            UserManager.DISALLOW_ADD_MANAGED_PROFILE,
+            UserManager.DISALLOW_REMOVE_MANAGED_PROFILE
+    );
+
     /**
      * Set of user restriction which we don't want to persist.
      */
diff --git a/services/core/java/com/android/server/pm/UserSystemPackageInstaller.java b/services/core/java/com/android/server/pm/UserSystemPackageInstaller.java
index 492b84a..b95404f 100644
--- a/services/core/java/com/android/server/pm/UserSystemPackageInstaller.java
+++ b/services/core/java/com/android/server/pm/UserSystemPackageInstaller.java
@@ -28,15 +28,14 @@
 import android.util.ArrayMap;
 import android.util.ArraySet;
 import android.util.DebugUtils;
+import android.util.IndentingPrintWriter;
 import android.util.Slog;
 
 import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.util.IndentingPrintWriter;
 import com.android.server.LocalServices;
 import com.android.server.SystemConfig;
 import com.android.server.pm.parsing.pkg.AndroidPackage;
 
-import java.io.PrintWriter;
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 import java.util.ArrayList;
@@ -723,13 +722,7 @@
         return userTypeList;
     }
 
-    void dump(PrintWriter pw) {
-        try (IndentingPrintWriter ipw = new IndentingPrintWriter(pw, "    ")) {
-            dumpIndented(ipw);
-        }
-    }
-
-    private void dumpIndented(IndentingPrintWriter pw) {
+    void dump(IndentingPrintWriter pw) {
         final int mode = getWhitelistMode();
         pw.println("Whitelisted packages per user type");
 
diff --git a/services/core/java/com/android/server/pm/dex/OWNERS b/services/core/java/com/android/server/pm/dex/OWNERS
index fcc1f6c..5a4431e 100644
--- a/services/core/java/com/android/server/pm/dex/OWNERS
+++ b/services/core/java/com/android/server/pm/dex/OWNERS
@@ -1,4 +1,2 @@
-agampe@google.com
 calin@google.com
 ngeoffray@google.com
-sehr@google.com
diff --git a/services/core/java/com/android/server/pm/permission/BasePermission.java b/services/core/java/com/android/server/pm/permission/BasePermission.java
index cfa0449..962638b 100644
--- a/services/core/java/com/android/server/pm/permission/BasePermission.java
+++ b/services/core/java/com/android/server/pm/permission/BasePermission.java
@@ -38,7 +38,6 @@
 
 import com.android.server.pm.DumpState;
 import com.android.server.pm.PackageManagerService;
-import com.android.server.pm.PackageSetting;
 import com.android.server.pm.PackageSettingBase;
 import com.android.server.pm.parsing.PackageInfoUtils;
 import com.android.server.pm.parsing.pkg.AndroidPackage;
@@ -420,8 +419,7 @@
     }
 
     public void enforceDeclaredUsedAndRuntimeOrDevelopment(AndroidPackage pkg,
-            PackageSetting pkgSetting) {
-        final PermissionsState permsState = pkgSetting.getPermissionsState();
+            PermissionsState permsState) {
         int index = pkg.getRequestedPermissions().indexOf(name);
         if (!permsState.hasRequestedPermission(name) && index == -1) {
             throw new SecurityException("Package " + pkg.getPackageName()
diff --git a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
index 6e0efb0..f5dd918 100644
--- a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
+++ b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
@@ -83,6 +83,7 @@
 import android.content.pm.ParceledListSlice;
 import android.content.pm.PermissionGroupInfo;
 import android.content.pm.PermissionInfo;
+import android.content.pm.UserInfo;
 import android.content.pm.parsing.component.ParsedPermission;
 import android.content.pm.parsing.component.ParsedPermissionGroup;
 import android.content.pm.permission.SplitPermissionInfoParcelable;
@@ -120,6 +121,7 @@
 import android.util.Slog;
 import android.util.SparseArray;
 import android.util.SparseBooleanArray;
+import android.util.TimingsTraceLog;
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
@@ -137,7 +139,6 @@
 import com.android.server.ServiceThread;
 import com.android.server.SystemConfig;
 import com.android.server.Watchdog;
-import com.android.server.am.ActivityManagerService;
 import com.android.server.pm.ApexManager;
 import com.android.server.pm.PackageManagerServiceUtils;
 import com.android.server.pm.PackageSetting;
@@ -161,6 +162,7 @@
 import java.lang.annotation.RetentionPolicy;
 import java.util.ArrayList;
 import java.util.Collection;
+import java.util.Collections;
 import java.util.HashMap;
 import java.util.Iterator;
 import java.util.List;
@@ -226,6 +228,9 @@
     /** Internal connection to the user manager */
     private final UserManagerInternal mUserManagerInt;
 
+    /** Maps from App ID to PermissionsState */
+    private final SparseArray<PermissionsState> mAppIdStates = new SparseArray<>();
+
     /** Permission controller: User space permission management */
     private PermissionControllerManager mPermissionControllerManager;
 
@@ -670,11 +675,6 @@
         if (pkg == null) {
             return 0;
         }
-        final PackageSetting ps = (PackageSetting) mPackageManagerInt.getPackageSetting(
-                pkg.getPackageName());
-        if (ps == null) {
-            return 0;
-        }
         synchronized (mLock) {
             if (mSettings.getPermissionLocked(permName) == null) {
                 return 0;
@@ -683,7 +683,11 @@
         if (mPackageManagerInt.filterAppAccess(pkg, callingUid, userId)) {
             return 0;
         }
-        PermissionsState permissionsState = ps.getPermissionsState();
+        final PermissionsState permissionsState = getPermissionsState(pkg);
+        if (permissionsState == null) {
+            Slog.e(TAG, "Missing permissions state for " + packageName);
+            return 0;
+        }
         return permissionsState.getPermissionFlags(permName, userId);
     }
 
@@ -770,9 +774,7 @@
         }
 
         final AndroidPackage pkg = mPackageManagerInt.getPackage(packageName);
-        final PackageSetting ps = (PackageSetting) mPackageManagerInt.getPackageSetting(
-                packageName);
-        if (pkg == null || ps == null) {
+        if (pkg == null) {
             Log.e(TAG, "Unknown package: " + packageName);
             return;
         }
@@ -788,7 +790,12 @@
             throw new IllegalArgumentException("Unknown permission: " + permName);
         }
 
-        final PermissionsState permissionsState = ps.getPermissionsState();
+        final PermissionsState permissionsState = getPermissionsState(pkg);
+        if (permissionsState == null) {
+            Slog.e(TAG, "Missing permissions state for " + packageName);
+            return;
+        }
+
         final boolean hadState =
                 permissionsState.getRuntimePermissionState(permName, userId) != null;
         if (!hadState) {
@@ -863,12 +870,11 @@
 
         final boolean[] changed = new boolean[1];
         mPackageManagerInt.forEachPackage(pkg -> {
-            final PackageSetting ps = (PackageSetting) mPackageManagerInt.getPackageSetting(
-                    pkg.getPackageName());
-            if (ps == null) {
+            final PermissionsState permissionsState = getPermissionsState(pkg);
+            if (permissionsState == null) {
+                Slog.e(TAG, "Missing permissions state for " + pkg.getPackageName());
                 return;
             }
-            final PermissionsState permissionsState = ps.getPermissionsState();
             changed[0] |= permissionsState.updatePermissionFlagsForAllPermissions(
                     userId, effectiveFlagMask, effectiveFlagValues);
             mOnPermissionChangeListeners.onPermissionsChanged(pkg.getUid());
@@ -880,7 +886,7 @@
     }
 
     @Override
-    public int checkPermission(String permName, String pkgName, int userId) {
+    public int checkPermission(String permName, String pkgName, @UserIdInt int userId) {
         // Not using Objects.requireNonNull() here for compatibility reasons.
         if (permName == null || pkgName == null) {
             return PackageManager.PERMISSION_DENIED;
@@ -922,22 +928,11 @@
         }
 
         final int uid = UserHandle.getUid(userId, pkg.getUid());
-
-        try {
-            enforceCrossUserOrProfilePermission(Binder.getCallingUid(), UserHandle.getUserId(uid),
-                    false, false, "checkPermissionInternal");
-        } catch (Exception e) {
-            EventLog.writeEvent(0x534e4554, "153996875", "checkPermission", uid);
-
-            throw e;
-        }
-
-        final PackageSetting ps = (PackageSetting) mPackageManagerInt.getPackageSetting(
-                pkg.getPackageName());
-        if (ps == null) {
+        final PermissionsState permissionsState = getPermissionsState(pkg);
+        if (permissionsState == null) {
+            Slog.e(TAG, "Missing permissions state for " + pkg.getPackageName());
             return PackageManager.PERMISSION_DENIED;
         }
-        final PermissionsState permissionsState = ps.getPermissionsState();
 
         if (checkSinglePermissionInternal(uid, permissionsState, permissionName)) {
             return PackageManager.PERMISSION_GRANTED;
@@ -1148,9 +1143,9 @@
 
         final long identity = Binder.clearCallingIdentity();
         try {
-            final PermissionsState permissionsState =
-                    PackageManagerServiceUtils.getPermissionsState(mPackageManagerInt, pkg);
+            final PermissionsState permissionsState = getPermissionsState(pkg);
             if (permissionsState == null) {
+                Slog.e(TAG, "Missing permissions state for " + packageName);
                 return null;
             }
 
@@ -1460,7 +1455,13 @@
             throw new IllegalArgumentException("Unknown package: " + packageName);
         }
 
-        bp.enforceDeclaredUsedAndRuntimeOrDevelopment(pkg, ps);
+        final PermissionsState permissionsState = getPermissionsState(pkg);
+        if (permissionsState == null) {
+            Slog.e(TAG, "Missing permissions state for " + pkg.getPackageName());
+            return;
+        }
+
+        bp.enforceDeclaredUsedAndRuntimeOrDevelopment(pkg, permissionsState);
 
         // If a permission review is required for legacy apps we represent
         // their permissions as always granted runtime ones since we need
@@ -1473,8 +1474,6 @@
 
         final int uid = UserHandle.getUid(userId, UserHandle.getAppId(pkg.getUid()));
 
-        final PermissionsState permissionsState = ps.getPermissionsState();
-
         final int flags = permissionsState.getPermissionFlags(permName, userId);
         if ((flags & PackageManager.FLAG_PERMISSION_SYSTEM_FIXED) != 0) {
             Log.e(TAG, "Cannot grant system fixed permission "
@@ -1608,9 +1607,7 @@
                 "revokeRuntimePermission");
 
         final AndroidPackage pkg = mPackageManagerInt.getPackage(packageName);
-        final PackageSetting ps = (PackageSetting) mPackageManagerInt.getPackageSetting(
-                packageName);
-        if (pkg == null || ps == null) {
+        if (pkg == null) {
             Log.e(TAG, "Unknown package: " + packageName);
             return;
         }
@@ -1622,7 +1619,13 @@
             throw new IllegalArgumentException("Unknown permission: " + permName);
         }
 
-        bp.enforceDeclaredUsedAndRuntimeOrDevelopment(pkg, ps);
+        final PermissionsState permissionsState = getPermissionsState(pkg);
+        if (permissionsState == null) {
+            Slog.e(TAG, "Missing permissions state for " + pkg.getPackageName());
+            return;
+        }
+
+        bp.enforceDeclaredUsedAndRuntimeOrDevelopment(pkg, permissionsState);
 
         // If a permission review is required for legacy apps we represent
         // their permissions as always granted runtime ones since we need
@@ -1633,8 +1636,6 @@
             return;
         }
 
-        final PermissionsState permissionsState = ps.getPermissionsState();
-
         final int flags = permissionsState.getPermissionFlags(permName, userId);
         // Only the system may revoke SYSTEM_FIXED permissions.
         if ((flags & PackageManager.FLAG_PERMISSION_SYSTEM_FIXED) != 0
@@ -2465,6 +2466,83 @@
         }
     }
 
+    private void onUserRemoved(@UserIdInt int userId) {
+        synchronized (mLock) {
+            final int appIdStatesSize = mAppIdStates.size();
+            for (int i = 0; i < appIdStatesSize; i++) {
+                PermissionsState permissionsState = mAppIdStates.valueAt(i);
+                for (PermissionState permissionState
+                        : permissionsState.getRuntimePermissionStates(userId)) {
+                    BasePermission bp = mSettings.getPermission(permissionState.getName());
+                    if (bp != null) {
+                        permissionsState.revokeRuntimePermission(bp, userId);
+                        permissionsState.updatePermissionFlags(bp, userId,
+                                PackageManager.MASK_PERMISSION_FLAGS_ALL, 0);
+                    }
+                }
+            }
+        }
+    }
+
+    @NonNull
+    private Set<String> getGrantedPermissions(@NonNull String packageName,
+            @UserIdInt int userId) {
+        final PackageSetting ps = mPackageManagerInt.getPackageSetting(packageName);
+        if (ps == null) {
+            return Collections.emptySet();
+        }
+        final PermissionsState permissionsState = getPermissionsState(ps);
+        if (permissionsState == null) {
+            Slog.e(TAG, "Missing permissions state for " + packageName);
+            return Collections.emptySet();
+        }
+        if (!ps.getInstantApp(userId)) {
+            return permissionsState.getPermissions(userId);
+        } else {
+            // Install permission state is shared among all users, but instant app state is
+            // per-user, so we can only filter it here unless we make install permission state
+            // per-user as well.
+            final Set<String> instantPermissions = new ArraySet<>(permissionsState.getPermissions(
+                    userId));
+            instantPermissions.removeIf(permissionName -> {
+                BasePermission permission = mSettings.getPermission(permissionName);
+                if (permission == null) {
+                    return true;
+                }
+                if (!permission.isInstant()) {
+                    EventLog.writeEvent(0x534e4554, "140256621", UserHandle.getUid(userId,
+                            ps.getAppId()), permissionName);
+                    return true;
+                }
+                return false;
+            });
+            return instantPermissions;
+        }
+    }
+
+    @Nullable
+    private int[] getPermissionGids(@NonNull String permissionName, @UserIdInt int userId) {
+        BasePermission permission = mSettings.getPermission(permissionName);
+        if (permission == null) {
+            return null;
+        }
+        return permission.computeGids(userId);
+    }
+
+    @Nullable
+    private int[] getPackageGids(@NonNull String packageName, @UserIdInt int userId) {
+        final PackageSetting ps = mPackageManagerInt.getPackageSetting(packageName);
+        if (ps == null) {
+            return null;
+        }
+        final PermissionsState permissionsState = getPermissionsState(ps);
+        if (permissionsState == null) {
+            Slog.e(TAG, "Missing permissions state for " + packageName);
+            return null;
+        }
+        return permissionsState.computeGids(userId);
+    }
+
     /**
      * Restore the permission state for a package.
      *
@@ -2499,15 +2577,14 @@
         if (ps == null) {
             return;
         }
+        final PermissionsState permissionsState = getOrCreatePermissionsState(ps);
 
-        final PermissionsState permissionsState = ps.getPermissionsState();
-
-        final int[] currentUserIds = UserManagerService.getInstance().getUserIds();
+        final int[] userIds = getAllUserIds();
 
         boolean runtimePermissionsRevoked = false;
         int[] updatedUserIds = EMPTY_INT_ARRAY;
 
-        for (int userId : currentUserIds) {
+        for (int userId : userIds) {
             if (permissionsState.isMissing(userId)) {
                 Collection<String> requestedPermissions;
                 int targetSdkVersion;
@@ -2572,8 +2649,8 @@
                 // changed runtime permissions here are promotion of an install to
                 // runtime and revocation of a runtime from a shared user.
                 synchronized (mLock) {
-                    updatedUserIds = revokeUnusedSharedUserPermissionsLocked(ps.getSharedUser(),
-                            currentUserIds);
+                    updatedUserIds = revokeUnusedSharedUserPermissionsLocked(
+                            ps.getSharedUser().getPackages(), permissionsState, userIds);
                     if (!ArrayUtils.isEmpty(updatedUserIds)) {
                         runtimePermissionsRevoked = true;
                     }
@@ -2728,7 +2805,7 @@
                             // a runtime permission being downgraded to an install one.
                             // Also in permission review mode we keep dangerous permissions
                             // for legacy apps
-                            for (int userId : currentUserIds) {
+                            for (int userId : userIds) {
                                 if (origPermissions.getRuntimePermissionState(
                                         perm, userId) != null) {
                                     // Revoke the runtime permission and clear the flags.
@@ -2751,7 +2828,7 @@
                             boolean hardRestricted = bp.isHardRestricted();
                             boolean softRestricted = bp.isSoftRestricted();
 
-                            for (int userId : currentUserIds) {
+                            for (int userId : userIds) {
                                 // If permission policy is not ready we don't deal with restricted
                                 // permissions as the policy may whitelist some permissions. Once
                                 // the policy is initialized we would re-evaluate permissions.
@@ -2890,7 +2967,7 @@
                             boolean hardRestricted = bp.isHardRestricted();
                             boolean softRestricted = bp.isSoftRestricted();
 
-                            for (int userId : currentUserIds) {
+                            for (int userId : userIds) {
                                 // If permission policy is not ready we don't deal with restricted
                                 // permissions as the policy may whitelist some permissions. Once
                                 // the policy is initialized we would re-evaluate permissions.
@@ -3042,13 +3119,15 @@
 
         synchronized (mLock) {
             updatedUserIds = revokePermissionsNoLongerImplicitLocked(permissionsState, pkg,
-                    currentUserIds, updatedUserIds);
+                    userIds, updatedUserIds);
             updatedUserIds = setInitialGrantForNewImplicitPermissionsLocked(origPermissions,
-                    permissionsState, pkg, newImplicitPermissions, currentUserIds, updatedUserIds);
-            updatedUserIds = checkIfLegacyStorageOpsNeedToBeUpdated(pkg, replace, currentUserIds,
+                    permissionsState, pkg, newImplicitPermissions, userIds, updatedUserIds);
+            updatedUserIds = checkIfLegacyStorageOpsNeedToBeUpdated(pkg, replace, userIds,
                     updatedUserIds);
         }
 
+        // TODO: Kill UIDs whose GIDs or runtime permissions changed. This might be more important
+        //  for shared users.
         // Persist the runtime permissions state for users with changes. If permissions
         // were revoked because no app in the shared user declares them we have to
         // write synchronously to avoid losing runtime permissions state.
@@ -3062,6 +3141,25 @@
     }
 
     /**
+     * Returns all relevant user ids.  This list include the current set of created user ids as well
+     * as pre-created user ids.
+     * @return user ids for created users and pre-created users
+     */
+    private int[] getAllUserIds() {
+        final TimingsTraceLog t = new TimingsTraceLog(TAG, Trace.TRACE_TAG_SYSTEM_SERVER);
+        t.traceBegin("getAllUserIds");
+        List<UserInfo> users = UserManagerService.getInstance().getUsers(
+                /*excludePartial=*/ true, /*excludeDying=*/ true, /*excludePreCreated=*/ false);
+        int size = users.size();
+        final int[] userIds = new int[size];
+        for (int i = 0; i < size; i++) {
+            userIds[i] = users.get(i).id;
+        }
+        t.traceEnd();
+        return userIds;
+    }
+
+    /**
      * Revoke permissions that are not implicit anymore and that have
      * {@link PackageManager#FLAG_PERMISSION_REVOKE_WHEN_REQUESTED} set.
      *
@@ -3493,37 +3591,15 @@
                     final PackageSetting disabledPs = mPackageManagerInt
                             .getDisabledSystemPackage(pkg.getPackageName());
                     final AndroidPackage disabledPkg = disabledPs == null ? null : disabledPs.pkg;
-                    if (disabledPs != null
-                            && disabledPs.getPermissionsState().hasInstallPermission(perm)) {
-                        // If the original was granted this permission, we take
-                        // that grant decision as read and propagate it to the
-                        // update.
-                        if ((privilegedPermission && disabledPs.isPrivileged())
-                                || (oemPermission && disabledPs.isOem()
-                                        && canGrantOemPermission(disabledPs, perm))) {
-                            allowed = true;
-                        }
-                    } else {
-                        // The system apk may have been updated with an older
-                        // version of the one on the data partition, but which
-                        // granted a new system permission that it didn't have
-                        // before.  In this case we do want to allow the app to
-                        // now get the new permission if the ancestral apk is
-                        // privileged to get it.
-                        if (disabledPs != null && disabledPkg != null
-                                && isPackageRequestingPermission(disabledPkg, perm)
-                                && ((privilegedPermission && disabledPs.isPrivileged())
-                                        || (oemPermission && disabledPs.isOem()
-                                                && canGrantOemPermission(disabledPs, perm)))) {
-                            allowed = true;
-                        }
+                    if (disabledPkg != null && isPackageRequestingPermission(disabledPkg, perm)
+                            && ((privilegedPermission && disabledPkg.isPrivileged())
+                                    || (oemPermission && canGrantOemPermission(disabledPkg,
+                                            perm)))) {
+                        allowed = true;
                     }
                 } else {
-                    final PackageSetting ps = (PackageSetting) mPackageManagerInt.getPackageSetting(
-                            pkg.getPackageName());
                     allowed = (privilegedPermission && pkg.isPrivileged())
-                            || (oemPermission && pkg.isOem()
-                                    && canGrantOemPermission(ps, perm));
+                            || (oemPermission && canGrantOemPermission(pkg, perm));
                 }
                 // In any case, don't grant a privileged permission to privileged vendor apps, if
                 // the permission's protectionLevel does not have the extra 'vendorPrivileged'
@@ -3674,16 +3750,16 @@
         return false;
     }
 
-    private static boolean canGrantOemPermission(PackageSetting ps, String permission) {
-        if (!ps.isOem()) {
+    private static boolean canGrantOemPermission(AndroidPackage pkg, String permission) {
+        if (!pkg.isOem()) {
             return false;
         }
         // all oem permissions must explicitly be granted or denied
         final Boolean granted =
-                SystemConfig.getInstance().getOemPermissions(ps.name).get(permission);
+                SystemConfig.getInstance().getOemPermissions(pkg.getPackageName()).get(permission);
         if (granted == null) {
             throw new IllegalStateException("OEM permission" + permission + " requested by package "
-                    + ps.name + " must be explicitly declared granted or not");
+                    + pkg.getPackageName() + " must be explicitly declared granted or not");
         }
         return Boolean.TRUE == granted;
     }
@@ -3696,12 +3772,11 @@
         }
 
         // Legacy apps have the permission and get user consent on launch.
-        final PackageSetting ps = (PackageSetting) mPackageManagerInt.getPackageSetting(
-                pkg.getPackageName());
-        if (ps == null) {
+        final PermissionsState permissionsState = getPermissionsState(pkg);
+        if (permissionsState == null) {
+            Slog.e(TAG, "Missing permissions state for " + pkg.getPackageName());
             return false;
         }
-        final PermissionsState permissionsState = ps.getPermissionsState();
         return permissionsState.isPermissionReviewRequired(userId);
     }
 
@@ -3726,14 +3801,12 @@
 
     private void grantRequestedRuntimePermissionsForUser(AndroidPackage pkg, int userId,
             String[] grantedPermissions, int callingUid, PermissionCallback callback) {
-        PackageSetting ps = (PackageSetting) mPackageManagerInt.getPackageSetting(
-                pkg.getPackageName());
-        if (ps == null) {
+        final PermissionsState permissionsState = getPermissionsState(pkg);
+        if (permissionsState == null) {
+            Slog.e(TAG, "Missing permissions state for " + pkg.getPackageName());
             return;
         }
 
-        PermissionsState permissionsState = ps.getPermissionsState();
-
         final int immutableFlags = PackageManager.FLAG_PERMISSION_SYSTEM_FIXED
                 | PackageManager.FLAG_PERMISSION_POLICY_FIXED;
 
@@ -3777,9 +3850,9 @@
     private void setWhitelistedRestrictedPermissionsForUsers(@NonNull AndroidPackage pkg,
             @UserIdInt int[] userIds, @Nullable List<String> permissions, int callingUid,
             @PermissionWhitelistFlags int whitelistFlags, PermissionCallback callback) {
-        final PermissionsState permissionsState =
-                PackageManagerServiceUtils.getPermissionsState(mPackageManagerInt, pkg);
+        final PermissionsState permissionsState = getPermissionsState(pkg);
         if (permissionsState == null) {
+            Slog.e(TAG, "Missing permissions state for " + pkg.getPackageName());
             return;
         }
 
@@ -3897,9 +3970,11 @@
                 for (int j = 0; j < oldGrantedCount; j++) {
                     final String permission = oldPermsForUser.valueAt(j);
                     // Sometimes we create a new permission state instance during update.
-                    final PermissionsState newPermissionsState =
-                            PackageManagerServiceUtils.getPermissionsState(mPackageManagerInt,
-                                    pkg);
+                    final PermissionsState newPermissionsState = getPermissionsState(pkg);
+                    if (permissionsState == null) {
+                        Slog.e(TAG, "Missing permissions state for " + pkg.getPackageName());
+                        continue;
+                    }
                     if (!newPermissionsState.hasPermission(permission, userId)) {
                         callback.onPermissionRevoked(pkg.getUid(), userId, null);
                         break;
@@ -3909,12 +3984,100 @@
         }
     }
 
+    @UserIdInt
+    private int revokeSharedUserPermissionsForDeletedPackage(@NonNull PackageSetting deletedPs,
+            @UserIdInt int userId) {
+        if ((deletedPs == null) || (deletedPs.pkg == null)) {
+            Slog.i(TAG, "Trying to update info for null package. Just ignoring");
+            return UserHandle.USER_NULL;
+        }
+
+        SharedUserSetting sus = deletedPs.getSharedUser();
+
+        // No sharedUserId
+        if (sus == null) {
+            return UserHandle.USER_NULL;
+        }
+
+        int affectedUserId = UserHandle.USER_NULL;
+        // Update permissions
+        for (String eachPerm : deletedPs.pkg.getRequestedPermissions()) {
+            BasePermission bp = mSettings.getPermission(eachPerm);
+            if (bp == null) {
+                continue;
+            }
+
+            // Check if another package in the shared user needs the permission.
+            boolean used = false;
+            final List<AndroidPackage> pkgs = sus.getPackages();
+            if (pkgs != null) {
+                for (AndroidPackage pkg : pkgs) {
+                    if (pkg != null
+                            && !pkg.getPackageName().equals(deletedPs.pkg.getPackageName())
+                            && pkg.getRequestedPermissions().contains(eachPerm)) {
+                        used = true;
+                        break;
+                    }
+                }
+            }
+            if (used) {
+                continue;
+            }
+
+            PermissionsState permissionsState = getPermissionsState(deletedPs.pkg);
+            if (permissionsState == null) {
+                Slog.e(TAG, "Missing permissions state for " + deletedPs.pkg.getPackageName());
+                continue;
+            }
+
+            PackageSetting disabledPs = mPackageManagerInt.getDisabledSystemPackage(
+                    deletedPs.pkg.getPackageName());
+
+            // If the package is shadowing is a disabled system package,
+            // do not drop permissions that the shadowed package requests.
+            if (disabledPs != null) {
+                boolean reqByDisabledSysPkg = false;
+                for (String permission : disabledPs.pkg.getRequestedPermissions()) {
+                    if (permission.equals(eachPerm)) {
+                        reqByDisabledSysPkg = true;
+                        break;
+                    }
+                }
+                if (reqByDisabledSysPkg) {
+                    continue;
+                }
+            }
+
+            // Try to revoke as an install permission which is for all users.
+            // The package is gone - no need to keep flags for applying policy.
+            permissionsState.updatePermissionFlags(bp, userId,
+                    PackageManager.MASK_PERMISSION_FLAGS_ALL, 0);
+
+            if (permissionsState.revokeInstallPermission(bp)
+                    == PermissionsState.PERMISSION_OPERATION_SUCCESS_GIDS_CHANGED) {
+                affectedUserId = UserHandle.USER_ALL;
+            }
+
+            // Try to revoke as a runtime permission which is per user.
+            if (permissionsState.revokeRuntimePermission(bp, userId)
+                    == PermissionsState.PERMISSION_OPERATION_SUCCESS_GIDS_CHANGED) {
+                if (affectedUserId == UserHandle.USER_NULL) {
+                    affectedUserId = userId;
+                } else if (affectedUserId != userId) {
+                    // Multiple users affected.
+                    affectedUserId = UserHandle.USER_ALL;
+                }
+            }
+        }
+
+        return affectedUserId;
+    }
+
     @GuardedBy("mLock")
     private int[] revokeUnusedSharedUserPermissionsLocked(
-            SharedUserSetting suSetting, int[] allUserIds) {
+            List<AndroidPackage> pkgList, PermissionsState permissionsState, int[] allUserIds) {
         // Collect all used permissions in the UID
         final ArraySet<String> usedPermissions = new ArraySet<>();
-        final List<AndroidPackage> pkgList = suSetting.getPackages();
         if (pkgList == null || pkgList.size() == 0) {
             return EmptyArray.INT;
         }
@@ -3932,7 +4095,6 @@
             }
         }
 
-        PermissionsState permissionsState = suSetting.getPermissionsState();
         // Prune install permissions
         List<PermissionState> installPermStates = permissionsState.getInstallPermissionStates();
         final int installPermCount = installPermStates.size();
@@ -4218,12 +4380,11 @@
                         }
                     } else {
                         mPackageManagerInt.forEachPackage(p -> {
-                            PackageSetting ps = mPackageManagerInt.getPackageSetting(
-                                    p.getPackageName());
-                            if (ps == null) {
+                            final PermissionsState permissionsState = getPermissionsState(p);
+                            if (permissionsState == null) {
+                                Slog.e(TAG, "Missing permissions state for " + p.getPackageName());
                                 return;
                             }
-                            PermissionsState permissionsState = ps.getPermissionsState();
                             if (permissionsState.getInstallPermissionState(bp.getName()) != null) {
                                 permissionsState.revokeInstallPermission(bp);
                                 permissionsState.updatePermissionFlags(bp, UserHandle.USER_ALL,
@@ -4387,7 +4548,7 @@
      * @param checkShell whether to prevent shell from access if there's a debugging restriction
      * @param message the message to log on security exception
      */
-    private void enforceCrossUserPermission(int callingUid, int userId,
+    private void enforceCrossUserPermission(int callingUid, @UserIdInt int userId,
             boolean requireFullPermission, boolean checkShell,
             boolean requirePermissionWhenSameUser, String message) {
         if (userId < 0) {
@@ -4399,12 +4560,12 @@
         }
         final int callingUserId = UserHandle.getUserId(callingUid);
         if (hasCrossUserPermission(
-                Binder.getCallingPid(), callingUid, callingUserId, userId, requireFullPermission,
+                callingUid, callingUserId, userId, requireFullPermission,
                 requirePermissionWhenSameUser)) {
             return;
         }
         String errorMessage = buildInvalidCrossUserPermissionMessage(
-                message, requireFullPermission);
+                callingUid, userId, message, requireFullPermission);
         Slog.w(TAG, errorMessage);
         throw new SecurityException(errorMessage);
     }
@@ -4423,57 +4584,40 @@
      * @param checkShell whether to prevent shell from access if there's a debugging restriction
      * @param message the message to log on security exception
      */
-    private void enforceCrossUserOrProfilePermission(int callingUid, int userId,
+    private void enforceCrossUserOrProfilePermission(int callingUid, @UserIdInt int userId,
             boolean requireFullPermission, boolean checkShell,
             String message) {
-        int callingPid = Binder.getCallingPid();
-        final int callingUserId = UserHandle.getUserId(callingUid);
-
         if (userId < 0) {
             throw new IllegalArgumentException("Invalid userId " + userId);
         }
-
-        if (callingUserId == userId) {
+        if (checkShell) {
+            PackageManagerServiceUtils.enforceShellRestriction(mUserManagerInt,
+                    UserManager.DISALLOW_DEBUGGING_FEATURES, callingUid, userId);
+        }
+        final int callingUserId = UserHandle.getUserId(callingUid);
+        if (hasCrossUserPermission(callingUid, callingUserId, userId, requireFullPermission,
+                /*requirePermissionWhenSameUser= */ false)) {
             return;
         }
-
-        // Prevent endless loop between when checking permission while checking a permission
-        if (callingPid == ActivityManagerService.MY_PID) {
+        final boolean isSameProfileGroup = isSameProfileGroup(callingUserId, userId);
+        if (isSameProfileGroup && PermissionChecker.checkPermissionForPreflight(
+                mContext,
+                android.Manifest.permission.INTERACT_ACROSS_PROFILES,
+                PermissionChecker.PID_UNKNOWN,
+                callingUid,
+                mPackageManagerInt.getPackage(callingUid).getPackageName())
+                == PermissionChecker.PERMISSION_GRANTED) {
             return;
         }
-
-        long token = Binder.clearCallingIdentity();
-        try {
-            if (checkShell) {
-                PackageManagerServiceUtils.enforceShellRestriction(mUserManagerInt,
-                        UserManager.DISALLOW_DEBUGGING_FEATURES, callingUid, userId);
-            }
-            if (hasCrossUserPermission(callingPid, callingUid, callingUserId, userId,
-                    requireFullPermission, /*requirePermissionWhenSameUser= */ false)) {
-                return;
-            }
-            final boolean isSameProfileGroup = isSameProfileGroup(callingUserId, userId);
-            if (isSameProfileGroup && PermissionChecker.checkPermissionForPreflight(
-                    mContext,
-                    android.Manifest.permission.INTERACT_ACROSS_PROFILES,
-                    PermissionChecker.PID_UNKNOWN,
-                    callingUid,
-                    mPackageManagerInt.getPackage(callingUid).getPackageName())
-                    == PermissionChecker.PERMISSION_GRANTED) {
-                return;
-            }
-
-            String errorMessage = buildInvalidCrossUserOrProfilePermissionMessage(
-                    message, requireFullPermission, isSameProfileGroup);
-            Slog.w(TAG, errorMessage);
-            throw new SecurityException(errorMessage);
-        } finally {
-            Binder.restoreCallingIdentity(token);
-        }
+        String errorMessage = buildInvalidCrossUserOrProfilePermissionMessage(
+                callingUid, userId, message, requireFullPermission, isSameProfileGroup);
+        Slog.w(TAG, errorMessage);
+        throw new SecurityException(errorMessage);
     }
 
-    private boolean hasCrossUserPermission(int callingPid, int callingUid, int callingUserId,
-            int userId, boolean requireFullPermission, boolean requirePermissionWhenSameUser) {
+    private boolean hasCrossUserPermission(
+            int callingUid, int callingUserId, int userId, boolean requireFullPermission,
+            boolean requirePermissionWhenSameUser) {
         if (!requirePermissionWhenSameUser && userId == callingUserId) {
             return true;
         }
@@ -4481,11 +4625,15 @@
             return true;
         }
         if (requireFullPermission) {
-            return mContext.checkPermission(Manifest.permission.INTERACT_ACROSS_USERS_FULL,
-                    callingPid, callingUid) == PackageManager.PERMISSION_GRANTED;
+            return hasPermission(Manifest.permission.INTERACT_ACROSS_USERS_FULL);
         }
-        return mContext.checkPermission(android.Manifest.permission.INTERACT_ACROSS_USERS,
-                callingPid, callingUid) == PackageManager.PERMISSION_GRANTED;
+        return hasPermission(android.Manifest.permission.INTERACT_ACROSS_USERS_FULL)
+                || hasPermission(Manifest.permission.INTERACT_ACROSS_USERS);
+    }
+
+    private boolean hasPermission(String permission) {
+        return mContext.checkCallingOrSelfPermission(permission)
+                == PackageManager.PERMISSION_GRANTED;
     }
 
     private boolean isSameProfileGroup(@UserIdInt int callerUserId, @UserIdInt int userId) {
@@ -4497,44 +4645,48 @@
         }
     }
 
-    private static String buildInvalidCrossUserPermissionMessage(
-            String message, boolean requireFullPermission) {
+    private static String buildInvalidCrossUserPermissionMessage(int callingUid,
+            @UserIdInt int userId, String message, boolean requireFullPermission) {
         StringBuilder builder = new StringBuilder();
         if (message != null) {
             builder.append(message);
             builder.append(": ");
         }
-        builder.append("Requires ");
+        builder.append("UID ");
+        builder.append(callingUid);
+        builder.append(" requires ");
         builder.append(android.Manifest.permission.INTERACT_ACROSS_USERS_FULL);
-        if (requireFullPermission) {
-            builder.append(".");
-            return builder.toString();
+        if (!requireFullPermission) {
+            builder.append(" or ");
+            builder.append(android.Manifest.permission.INTERACT_ACROSS_USERS);
         }
-        builder.append(" or ");
-        builder.append(android.Manifest.permission.INTERACT_ACROSS_USERS);
+        builder.append(" to access user ");
+        builder.append(userId);
         builder.append(".");
         return builder.toString();
     }
 
-    private static String buildInvalidCrossUserOrProfilePermissionMessage(
-            String message, boolean requireFullPermission, boolean isSameProfileGroup) {
+    private static String buildInvalidCrossUserOrProfilePermissionMessage(int callingUid,
+            @UserIdInt int userId, String message, boolean requireFullPermission,
+            boolean isSameProfileGroup) {
         StringBuilder builder = new StringBuilder();
         if (message != null) {
             builder.append(message);
             builder.append(": ");
         }
-        builder.append("Requires ");
+        builder.append("UID ");
+        builder.append(callingUid);
+        builder.append(" requires ");
         builder.append(android.Manifest.permission.INTERACT_ACROSS_USERS_FULL);
-        if (requireFullPermission) {
-            builder.append(".");
-            return builder.toString();
-        }
-        builder.append(" or ");
-        builder.append(android.Manifest.permission.INTERACT_ACROSS_USERS);
-        if (isSameProfileGroup) {
+        if (!requireFullPermission) {
             builder.append(" or ");
-            builder.append(android.Manifest.permission.INTERACT_ACROSS_PROFILES);
+            builder.append(android.Manifest.permission.INTERACT_ACROSS_USERS);
+            if (isSameProfileGroup) {
+                builder.append(" or ");
+                builder.append(android.Manifest.permission.INTERACT_ACROSS_PROFILES);
+            }
         }
+        builder.append(" to access user ");
         builder.append(".");
         return builder.toString();
     }
@@ -4643,6 +4795,67 @@
         return mBackgroundPermissions;
     }
 
+    @Nullable
+    private PermissionsState getPermissionsState(@NonNull PackageSetting ps) {
+        return getPermissionsState(ps.getAppId());
+    }
+
+    @Nullable
+    private PermissionsState getPermissionsState(@NonNull AndroidPackage pkg) {
+        return getPermissionsState(pkg.getUid());
+    }
+
+    @Nullable
+    private PermissionsState getPermissionsState(int appId) {
+        synchronized (mLock) {
+            return mAppIdStates.get(appId);
+        }
+    }
+
+    @Nullable
+    private PermissionsState getOrCreatePermissionsState(@NonNull PackageSetting ps) {
+        return getOrCreatePermissionsState(ps.getAppId());
+    }
+
+    @Nullable
+    private PermissionsState getOrCreatePermissionsState(int appId) {
+        synchronized (mLock) {
+            PermissionsState state = mAppIdStates.get(appId);
+            if (state == null) {
+                state = new PermissionsState();
+                mAppIdStates.put(appId, state);
+            }
+            return state;
+        }
+    }
+
+    private void removePermissionsState(int appId) {
+        synchronized (mLock) {
+            mAppIdStates.remove(appId);
+        }
+    }
+
+    private void readPermissionsStateFromPackageSettings() {
+        mPackageManagerInt.forEachPackageSetting(ps -> {
+            synchronized (mLock) {
+                mAppIdStates.put(ps.getAppId(), new PermissionsState(ps.getPermissionsState()));
+            }
+        });
+    }
+
+    private void writePermissionsStateToPackageSettings() {
+        mPackageManagerInt.forEachPackageSetting(ps -> {
+            synchronized (mLock) {
+                final PermissionsState permissionsState = mAppIdStates.get(ps.getAppId());
+                if (permissionsState == null) {
+                    Slog.e(TAG, "Missing permissions state for " + ps.name);
+                    return;
+                }
+                ps.getPermissionsState().copyFrom(permissionsState);
+            }
+        });
+    }
+
     private class PermissionManagerServiceInternalImpl extends PermissionManagerServiceInternal {
         @Override
         public void systemReady() {
@@ -4675,6 +4888,45 @@
             PermissionManagerService.this.removeAllPermissions(pkg, chatty);
         }
         @Override
+        public void readPermissionsStateFromPackageSettingsTEMP() {
+            PermissionManagerService.this.readPermissionsStateFromPackageSettings();
+        }
+        @Override
+        public void writePermissionsStateToPackageSettingsTEMP() {
+            PermissionManagerService.this.writePermissionsStateToPackageSettings();
+        }
+        @Override
+        public void onUserRemoved(@UserIdInt int userId) {
+            PermissionManagerService.this.onUserRemoved(userId);
+        }
+        @Override
+        public void removePermissionsStateTEMP(int appId) {
+            PermissionManagerService.this.removePermissionsState(appId);
+        }
+        @Override
+        @UserIdInt
+        public int revokeSharedUserPermissionsForDeletedPackageTEMP(
+                @NonNull PackageSetting deletedPs, @UserIdInt int userId) {
+            return PermissionManagerService.this.revokeSharedUserPermissionsForDeletedPackage(
+                    deletedPs, userId);
+        }
+        @NonNull
+        @Override
+        public Set<String> getGrantedPermissions(@NonNull String packageName,
+                @UserIdInt int userId) {
+            return PermissionManagerService.this.getGrantedPermissions(packageName, userId);
+        }
+        @Nullable
+        @Override
+        public int[] getPermissionGids(@NonNull String permissionName, @UserIdInt int userId) {
+            return PermissionManagerService.this.getPermissionGids(permissionName, userId);
+        }
+        @Nullable
+        @Override
+        public int[] getPackageGids(@NonNull String packageName, @UserIdInt int userId) {
+            return PermissionManagerService.this.getPackageGids(packageName, userId);
+        }
+        @Override
         public void grantRequestedRuntimePermissions(AndroidPackage pkg, int[] userIds,
                 String[] grantedPermissions, int callingUid) {
             PermissionManagerService.this.grantRequestedRuntimePermissions(
diff --git a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceInternal.java b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceInternal.java
index 2e83b23..f319bf4 100644
--- a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceInternal.java
+++ b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceInternal.java
@@ -24,10 +24,12 @@
 import android.content.pm.PermissionInfo;
 import android.permission.PermissionManagerInternal;
 
+import com.android.server.pm.PackageSetting;
 import com.android.server.pm.parsing.pkg.AndroidPackage;
 
 import java.util.ArrayList;
 import java.util.List;
+import java.util.Set;
 import java.util.function.Consumer;
 
 /**
@@ -263,6 +265,71 @@
     public abstract void addAllPermissionGroups(@NonNull AndroidPackage pkg, boolean chatty);
     public abstract void removeAllPermissions(@NonNull AndroidPackage pkg, boolean chatty);
 
+    /**
+     * Read {@code PermissionsState} from package settings.
+     *
+     * TODO(zhanghai): This is a temporary method because we should not expose
+     * {@code PackageSetting} which is a implementation detail that permission should not know.
+     * Instead, it should retrieve the legacy state via a defined API.
+     */
+    public abstract void readPermissionsStateFromPackageSettingsTEMP();
+
+    /**
+     * Write {@code PermissionsState} from to settings.
+     *
+     * TODO(zhanghai): This is a temporary method and should be removed once we migrated persistence
+     * for permission.
+     */
+    public abstract void writePermissionsStateToPackageSettingsTEMP();
+
+    /**
+     * Notify that a user has been removed and its permission state should be removed as well.
+     */
+    public abstract void onUserRemoved(@UserIdInt int userId);
+
+    /**
+     * Remove the {@code PermissionsState} associated with an app ID, called the same time as the
+     * removal of a {@code PackageSetitng}.
+     *
+     * TODO(zhanghai): This is a temporary method before we figure out a way to get notified of app
+     * ID removal via API.
+     */
+    public abstract void removePermissionsStateTEMP(int appId);
+
+    /**
+     * Update the shared user setting when a package with a shared user id is removed. The gids
+     * associated with each permission of the deleted package are removed from the shared user'
+     * gid list only if its not in use by other permissions of packages in the shared user setting.
+     *
+     * TODO(zhanghai): We should not need this when permission no longer sees an incomplete package
+     * state where the updated system package is uninstalled but the disabled system package is yet
+     * to be installed. Then we should handle this in restorePermissionState().
+     *
+     * @return the affected user id, may be a real user ID, USER_ALL, or USER_NULL when none.
+     */
+    @UserIdInt
+    public abstract int revokeSharedUserPermissionsForDeletedPackageTEMP(
+            @NonNull PackageSetting deletedPs, @UserIdInt int userId);
+
+    /**
+     * Get all the permissions granted to a package.
+     */
+    @NonNull
+    public abstract Set<String> getGrantedPermissions(@NonNull String packageName,
+            @UserIdInt int userId);
+
+    /**
+     * Get the GIDs of a permission.
+     */
+    @Nullable
+    public abstract int[] getPermissionGids(@NonNull String permissionName, @UserIdInt int userId);
+
+    /**
+     * Get the GIDs computed from the permission state of a package.
+     */
+    @Nullable
+    public abstract int[] getPackageGids(@NonNull String packageName, @UserIdInt int userId);
+
     /** Retrieve the packages that have requested the given app op permission */
     public abstract @Nullable String[] getAppOpPermissionPackages(
             @NonNull String permName, int callingUid);
diff --git a/services/core/java/com/android/server/pm/permission/TEST_MAPPING b/services/core/java/com/android/server/pm/permission/TEST_MAPPING
index 65dc320..c0d71ac 100644
--- a/services/core/java/com/android/server/pm/permission/TEST_MAPPING
+++ b/services/core/java/com/android/server/pm/permission/TEST_MAPPING
@@ -18,6 +18,14 @@
             ]
         },
         {
+            "name": "CtsAppSecurityHostTestCases",
+            "options": [
+                {
+                    "include-filter": "android.appsecurity.cts.AppSecurityTests#rebootWithDuplicatePermission"
+                }
+            ]
+        },
+        {
             "name": "CtsPermission2TestCases",
             "options": [
                 {
@@ -29,17 +37,6 @@
             ]
         },
         {
-            "name": "CtsPermissionHostTestCases"
-        },
-        {
-            "name": "CtsAppSecurityHostTestCases",
-            "options": [
-                {
-                    "include-filter": "android.appsecurity.cts.AppSecurityTests#rebootWithDuplicatePermission"
-                }
-            ]
-        },
-        {
             "name": "CtsStatsdHostTestCases",
             "options": [
                 {
diff --git a/services/core/java/com/android/server/policy/PermissionPolicyService.java b/services/core/java/com/android/server/policy/PermissionPolicyService.java
index 37f088b..ae2b040 100644
--- a/services/core/java/com/android/server/policy/PermissionPolicyService.java
+++ b/services/core/java/com/android/server/policy/PermissionPolicyService.java
@@ -67,6 +67,7 @@
 import com.android.server.FgThread;
 import com.android.server.LocalServices;
 import com.android.server.SystemService;
+import com.android.server.SystemService.TargetUser;
 import com.android.server.pm.parsing.pkg.AndroidPackage;
 import com.android.server.pm.permission.PermissionManagerServiceInternal;
 import com.android.server.policy.PermissionPolicyInternal.OnInitializedCallback;
@@ -88,7 +89,7 @@
 public final class PermissionPolicyService extends SystemService {
     private static final String LOG_TAG = PermissionPolicyService.class.getSimpleName();
     private static final boolean DEBUG = false;
-    private static final long USER_SENSITIVE_UPDATE_DELAY_MS = 10000;
+    private static final long USER_SENSITIVE_UPDATE_DELAY_MS = 60000;
 
     private final Object mLock = new Object();
 
@@ -282,6 +283,11 @@
                 manager.updateUserSensitiveForApp(uid);
             }
         }, UserHandle.ALL, intentFilter, null, null);
+
+        PermissionControllerManager manager = new PermissionControllerManager(
+                getUserContext(getContext(), Process.myUserHandle()), FgThread.getHandler());
+        FgThread.getHandler().postDelayed(manager::updateUserSensitive,
+                USER_SENSITIVE_UPDATE_DELAY_MS);
     }
 
     /**
@@ -347,7 +353,11 @@
     }
 
     @Override
-    public void onStartUser(@UserIdInt int userId) {
+    public void onUserStarting(@NonNull TargetUser user) {
+        onStartUser(user.getUserIdentifier());
+    }
+
+    private void onStartUser(@UserIdInt int userId) {
         if (DEBUG) Slog.i(LOG_TAG, "onStartUser(" + userId + ")");
 
         if (isStarted(userId)) {
@@ -373,11 +383,11 @@
     }
 
     @Override
-    public void onStopUser(@UserIdInt int userId) {
-        if (DEBUG) Slog.i(LOG_TAG, "onStopUser(" + userId + ")");
+    public void onUserStopping(@NonNull TargetUser user) {
+        if (DEBUG) Slog.i(LOG_TAG, "onStopUser(" + user + ")");
 
         synchronized (mLock) {
-            mIsStarted.delete(userId);
+            mIsStarted.delete(user.getUserIdentifier());
         }
     }
 
@@ -420,8 +430,7 @@
                 throw new IllegalStateException(e);
             }
 
-            FgThread.getHandler().postDelayed(permissionControllerManager::updateUserSensitive,
-                    USER_SENSITIVE_UPDATE_DELAY_MS);
+            permissionControllerManager.updateUserSensitive();
 
             packageManagerInternal.updateRuntimePermissionsFingerprint(userId);
         }
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index 03868e9..548cd70 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -450,7 +450,6 @@
     volatile int mPendingWakeKey = PENDING_KEY_NULL;
 
     int mRecentAppsHeldModifiers;
-    boolean mLanguageSwitchKeyPressed;
 
     int mCameraLensCoverState = CAMERA_LENS_COVER_ABSENT;
     boolean mHaveBuiltInKeyboard;
@@ -933,6 +932,8 @@
             }
         }
 
+        final boolean handledByPowerManager = mPowerManagerInternal.interceptPowerKeyDown(event);
+
         GestureLauncherService gestureService = LocalServices.getService(
                 GestureLauncherService.class);
         boolean gesturedServiceIntercepted = false;
@@ -952,7 +953,8 @@
         // If the power key has still not yet been handled, then detect short
         // press, long press, or multi press and decide what to do.
         mPowerKeyHandled = hungUp || mScreenshotChordVolumeDownKeyTriggered
-                || mA11yShortcutChordVolumeUpKeyTriggered || gesturedServiceIntercepted;
+                || mA11yShortcutChordVolumeUpKeyTriggered || gesturedServiceIntercepted
+                || handledByPowerManager;
         if (!mPowerKeyHandled) {
             if (interactive) {
                 // When interactive, we're already awake.
@@ -2935,12 +2937,6 @@
             mWindowManagerFuncs.switchKeyboardLayout(event.getDeviceId(), direction);
             return -1;
         }
-        if (mLanguageSwitchKeyPressed && !down
-                && (keyCode == KeyEvent.KEYCODE_LANGUAGE_SWITCH
-                        || keyCode == KeyEvent.KEYCODE_SPACE)) {
-            mLanguageSwitchKeyPressed = false;
-            return -1;
-        }
 
         if (isValidGlobalKey(keyCode)
                 && mGlobalKeyManager.handleGlobalKey(mContext, keyCode, event)) {
diff --git a/services/core/java/com/android/server/power/Notifier.java b/services/core/java/com/android/server/power/Notifier.java
index 8f0fd60..d4375eb 100644
--- a/services/core/java/com/android/server/power/Notifier.java
+++ b/services/core/java/com/android/server/power/Notifier.java
@@ -94,6 +94,7 @@
     private static final int MSG_USER_ACTIVITY = 1;
     private static final int MSG_BROADCAST = 2;
     private static final int MSG_WIRELESS_CHARGING_STARTED = 3;
+    private static final int MSG_BROADCAST_ENHANCED_PREDICTION = 4;
     private static final int MSG_PROFILE_TIMED_OUT = 5;
     private static final int MSG_WIRED_CHARGING_STARTED = 6;
 
@@ -701,6 +702,16 @@
         mPolicy.userActivity();
     }
 
+    void postEnhancedDischargePredictionBroadcast(long delayMs) {
+        mHandler.sendEmptyMessageDelayed(MSG_BROADCAST_ENHANCED_PREDICTION, delayMs);
+    }
+
+    private void sendEnhancedDischargePredictionBroadcast() {
+        Intent intent = new Intent(PowerManager.ACTION_ENHANCED_DISCHARGE_PREDICTION_CHANGED)
+                .addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
+        mContext.sendBroadcastAsUser(intent, UserHandle.ALL);
+    }
+
     private void sendNextBroadcast() {
         final int powerState;
         synchronized (mLock) {
@@ -872,6 +883,10 @@
                 case MSG_WIRELESS_CHARGING_STARTED:
                     showWirelessChargingStarted(msg.arg1, msg.arg2);
                     break;
+                case MSG_BROADCAST_ENHANCED_PREDICTION:
+                    removeMessages(MSG_BROADCAST_ENHANCED_PREDICTION);
+                    sendEnhancedDischargePredictionBroadcast();
+                    break;
                 case MSG_PROFILE_TIMED_OUT:
                     lockProfile(msg.arg1);
                     break;
diff --git a/services/core/java/com/android/server/power/PowerManagerService.java b/services/core/java/com/android/server/power/PowerManagerService.java
index a2b46a0..9ff164a 100644
--- a/services/core/java/com/android/server/power/PowerManagerService.java
+++ b/services/core/java/com/android/server/power/PowerManagerService.java
@@ -56,6 +56,7 @@
 import android.os.IPowerManager;
 import android.os.Looper;
 import android.os.Message;
+import android.os.ParcelDuration;
 import android.os.PowerManager;
 import android.os.PowerManager.ServiceType;
 import android.os.PowerManager.WakeData;
@@ -86,13 +87,16 @@
 import android.util.TimeUtils;
 import android.util.proto.ProtoOutputStream;
 import android.view.Display;
+import android.view.KeyEvent;
 
 import com.android.internal.BrightnessSynchronizer;
+import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.app.IAppOpsService;
 import com.android.internal.app.IBatteryStats;
 import com.android.internal.os.BackgroundThread;
 import com.android.internal.util.DumpUtils;
+import com.android.internal.util.Preconditions;
 import com.android.server.EventLogTags;
 import com.android.server.LockGuard;
 import com.android.server.RescueParty;
@@ -237,6 +241,18 @@
     private static final int HALT_MODE_REBOOT = 1;
     private static final int HALT_MODE_REBOOT_SAFE_MODE = 2;
 
+    /**
+     * How stale we'll allow the enhanced discharge prediction values to get before considering them
+     * invalid.
+     */
+    private static final long ENHANCED_DISCHARGE_PREDICTION_TIMEOUT_MS = 30 * 60 * 1000L;
+
+    /**
+     * The minimum amount of time between sending consequent
+     * {@link PowerManager#ACTION_ENHANCED_DISCHARGE_PREDICTION_CHANGED} broadcasts.
+     */
+    private static final long ENHANCED_DISCHARGE_PREDICTION_BROADCAST_MIN_DELAY_MS = 60 * 1000L;
+
     private final Context mContext;
     private final ServiceThread mHandlerThread;
     private final Handler mHandler;
@@ -379,6 +395,34 @@
     // The current battery level percentage.
     private int mBatteryLevel;
 
+    /**
+     * The lock that should be held when interacting with {@link #mEnhancedDischargeTimeElapsed},
+     * {@link #mLastEnhancedDischargeTimeUpdatedElapsed}, and
+     * {@link #mEnhancedDischargePredictionIsPersonalized}.
+     */
+    private final Object mEnhancedDischargeTimeLock = new Object();
+
+    /**
+     * The time (in the elapsed realtime timebase) at which the battery level will reach 0%. This
+     * is provided as an enhanced estimate and only valid if
+     * {@link #mLastEnhancedDischargeTimeUpdatedElapsed} is greater than 0.
+     */
+    @GuardedBy("mEnhancedDischargeTimeLock")
+    private long mEnhancedDischargeTimeElapsed;
+
+    /**
+     * Timestamp (in the elapsed realtime timebase) of last update to enhanced battery estimate
+     * data.
+     */
+    @GuardedBy("mEnhancedDischargeTimeLock")
+    private long mLastEnhancedDischargeTimeUpdatedElapsed;
+
+    /**
+     * Whether or not the current enhanced discharge prediction is personalized to the user.
+     */
+    @GuardedBy("mEnhancedDischargeTimeLock")
+    private boolean mEnhancedDischargePredictionIsPersonalized;
+
     // The battery level percentage at the time the dream started.
     // This is used to terminate a dream and go to sleep if the battery is
     // draining faster than it is charging and the user activity timeout has expired.
@@ -493,9 +537,6 @@
     private boolean mProximityPositive;
 
     // Screen brightness setting limits.
-    private float mScreenBrightnessSettingMinimum;
-    private float mScreenBrightnessSettingMaximum;
-    private float mScreenBrightnessSettingDefault;
     public final float mScreenBrightnessMinimum;
     public final float mScreenBrightnessMaximum;
     public final float mScreenBrightnessDefault;
@@ -895,19 +936,13 @@
                 || def == INVALID_BRIGHTNESS_IN_CONFIG) {
             mScreenBrightnessMinimum = BrightnessSynchronizer.brightnessIntToFloat(
                     mContext.getResources().getInteger(com.android.internal.R.integer
-                            .config_screenBrightnessSettingMinimum),
-                    PowerManager.BRIGHTNESS_OFF + 1, PowerManager.BRIGHTNESS_ON,
-                    PowerManager.BRIGHTNESS_MIN, PowerManager.BRIGHTNESS_MAX);
+                            .config_screenBrightnessSettingMinimum));
             mScreenBrightnessMaximum = BrightnessSynchronizer.brightnessIntToFloat(
                     mContext.getResources().getInteger(com.android.internal.R.integer
-                            .config_screenBrightnessSettingMaximum),
-                    PowerManager.BRIGHTNESS_OFF + 1, PowerManager.BRIGHTNESS_ON,
-                    PowerManager.BRIGHTNESS_MIN, PowerManager.BRIGHTNESS_MAX);
+                            .config_screenBrightnessSettingMaximum));
             mScreenBrightnessDefault = BrightnessSynchronizer.brightnessIntToFloat(
                     mContext.getResources().getInteger(com.android.internal.R.integer
-                            .config_screenBrightnessSettingDefault),
-                    PowerManager.BRIGHTNESS_OFF + 1, PowerManager.BRIGHTNESS_ON,
-                    PowerManager.BRIGHTNESS_MIN, PowerManager.BRIGHTNESS_MAX);
+                            .config_screenBrightnessSettingDefault));
         } else {
             mScreenBrightnessMinimum = min;
             mScreenBrightnessMaximum = max;
@@ -916,18 +951,14 @@
         if (doze == INVALID_BRIGHTNESS_IN_CONFIG) {
             mScreenBrightnessDoze = BrightnessSynchronizer.brightnessIntToFloat(
                     mContext.getResources().getInteger(com.android.internal.R.integer
-                            .config_screenBrightnessDoze), PowerManager.BRIGHTNESS_OFF + 1,
-                    PowerManager.BRIGHTNESS_ON, PowerManager.BRIGHTNESS_MIN,
-                    PowerManager.BRIGHTNESS_MAX);
+                            .config_screenBrightnessDoze));
         } else {
             mScreenBrightnessDoze = doze;
         }
         if (dim == INVALID_BRIGHTNESS_IN_CONFIG) {
             mScreenBrightnessDim = BrightnessSynchronizer.brightnessIntToFloat(
                     mContext.getResources().getInteger(com.android.internal.R.integer
-                            .config_screenBrightnessDim), PowerManager.BRIGHTNESS_OFF + 1,
-                    PowerManager.BRIGHTNESS_ON, PowerManager.BRIGHTNESS_MIN,
-                    PowerManager.BRIGHTNESS_MAX);
+                            .config_screenBrightnessDim));
         } else {
             mScreenBrightnessDim = dim;
         }
@@ -942,19 +973,13 @@
                 || vrDef == INVALID_BRIGHTNESS_IN_CONFIG) {
             mScreenBrightnessMinimumVr = BrightnessSynchronizer.brightnessIntToFloat(
                     mContext.getResources().getInteger(com.android.internal.R.integer
-                            .config_screenBrightnessForVrSettingMinimum),
-                    PowerManager.BRIGHTNESS_OFF + 1, PowerManager.BRIGHTNESS_ON,
-                    PowerManager.BRIGHTNESS_MIN, PowerManager.BRIGHTNESS_MAX);
+                            .config_screenBrightnessForVrSettingMinimum));
             mScreenBrightnessMaximumVr = BrightnessSynchronizer.brightnessIntToFloat(
                     mContext.getResources().getInteger(com.android.internal.R.integer
-                            .config_screenBrightnessForVrSettingMaximum),
-                    PowerManager.BRIGHTNESS_OFF + 1, PowerManager.BRIGHTNESS_ON,
-                    PowerManager.BRIGHTNESS_MIN, PowerManager.BRIGHTNESS_MAX);
+                            .config_screenBrightnessForVrSettingMaximum));
             mScreenBrightnessDefaultVr = BrightnessSynchronizer.brightnessIntToFloat(
                     mContext.getResources().getInteger(com.android.internal.R.integer
-                            .config_screenBrightnessForVrSettingDefault),
-                    PowerManager.BRIGHTNESS_OFF + 1, PowerManager.BRIGHTNESS_ON,
-                    PowerManager.BRIGHTNESS_MIN, PowerManager.BRIGHTNESS_MAX);
+                            .config_screenBrightnessForVrSettingDefault));
         } else {
             mScreenBrightnessMinimumVr = vrMin;
             mScreenBrightnessMaximumVr = vrMax;
@@ -1030,14 +1055,6 @@
             mBatteryManagerInternal = getLocalService(BatteryManagerInternal.class);
             mAttentionDetector.systemReady(mContext);
 
-            PowerManager pm = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
-            mScreenBrightnessSettingMinimum = pm.getBrightnessConstraint(
-                    PowerManager.BRIGHTNESS_CONSTRAINT_TYPE_MINIMUM);
-            mScreenBrightnessSettingMaximum = pm.getBrightnessConstraint(
-                    PowerManager.BRIGHTNESS_CONSTRAINT_TYPE_MAXIMUM);
-            mScreenBrightnessSettingDefault = pm.getBrightnessConstraint(
-                    PowerManager.BRIGHTNESS_CONSTRAINT_TYPE_DEFAULT);
-
             SensorManager sensorManager = new SystemSensorManager(mContext, mHandler.getLooper());
 
             // The notifier runs on the system server's main looper so as not to interfere
@@ -2818,7 +2835,7 @@
                 // Keep the brightness steady during boot. This requires the
                 // bootloader brightness and the default brightness to be identical.
                 autoBrightness = false;
-                screenBrightnessOverride = mScreenBrightnessSettingDefault;
+                screenBrightnessOverride = mScreenBrightnessDefault;
             } else if (isValidBrightness(mScreenBrightnessOverrideFromWindowManager)) {
                 autoBrightness = false;
                 screenBrightnessOverride = mScreenBrightnessOverrideFromWindowManager;
@@ -3601,8 +3618,7 @@
                 mDozeScreenStateOverrideFromDreamManager = screenState;
                 mDozeScreenBrightnessOverrideFromDreamManager = screenBrightness;
                 mDozeScreenBrightnessOverrideFromDreamManagerFloat =
-                        BrightnessSynchronizer.brightnessIntToFloat(mContext,
-                                mDozeScreenBrightnessOverrideFromDreamManager);
+                        BrightnessSynchronizer.brightnessIntToFloat(mDozeScreenBrightnessOverrideFromDreamManager);
                 mDirty |= DIRTY_SETTINGS;
                 updatePowerStateLocked();
             }
@@ -3764,6 +3780,13 @@
             pw.println("  mProximityPositive=" + mProximityPositive);
             pw.println("  mBootCompleted=" + mBootCompleted);
             pw.println("  mSystemReady=" + mSystemReady);
+            synchronized (mEnhancedDischargeTimeLock) {
+                pw.println("  mEnhancedDischargeTimeElapsed=" + mEnhancedDischargeTimeElapsed);
+                pw.println("  mLastEnhancedDischargeTimeUpdatedElapsed="
+                        + mLastEnhancedDischargeTimeUpdatedElapsed);
+                pw.println("  mEnhancedDischargePredictionIsPersonalized="
+                        + mEnhancedDischargePredictionIsPersonalized);
+            }
             pw.println("  mHalAutoSuspendModeEnabled=" + mHalAutoSuspendModeEnabled);
             pw.println("  mHalInteractiveModeEnabled=" + mHalInteractiveModeEnabled);
             pw.println("  mWakeLockSummary=0x" + Integer.toHexString(mWakeLockSummary));
@@ -3869,9 +3892,9 @@
             pw.println("  mDrawWakeLockOverrideFromSidekick=" + mDrawWakeLockOverrideFromSidekick);
             pw.println("  mDozeScreenBrightnessOverrideFromDreamManager="
                     + mDozeScreenBrightnessOverrideFromDreamManager);
-            pw.println("  mScreenBrightnessSettingMinimumFloat=" + mScreenBrightnessSettingMinimum);
-            pw.println("  mScreenBrightnessSettingMaximumFloat=" + mScreenBrightnessSettingMaximum);
-            pw.println("  mScreenBrightnessSettingDefaultFloat=" + mScreenBrightnessSettingDefault);
+            pw.println("  mScreenBrightnessMinimum=" + mScreenBrightnessMinimum);
+            pw.println("  mScreenBrightnessMaximum=" + mScreenBrightnessMaximum);
+            pw.println("  mScreenBrightnessDefault=" + mScreenBrightnessDefault);
             pw.println("  mDoubleTapWakeEnabled=" + mDoubleTapWakeEnabled);
             pw.println("  mIsVrModeEnabled=" + mIsVrModeEnabled);
             pw.println("  mForegroundProfile=" + mForegroundProfile);
@@ -3979,6 +4002,16 @@
             proto.write(PowerManagerServiceDumpProto.IS_PROXIMITY_POSITIVE, mProximityPositive);
             proto.write(PowerManagerServiceDumpProto.IS_BOOT_COMPLETED, mBootCompleted);
             proto.write(PowerManagerServiceDumpProto.IS_SYSTEM_READY, mSystemReady);
+            synchronized (mEnhancedDischargeTimeLock) {
+                proto.write(PowerManagerServiceDumpProto.ENHANCED_DISCHARGE_TIME_ELAPSED,
+                        mEnhancedDischargeTimeElapsed);
+                proto.write(
+                        PowerManagerServiceDumpProto.LAST_ENHANCED_DISCHARGE_TIME_UPDATED_ELAPSED,
+                        mLastEnhancedDischargeTimeUpdatedElapsed);
+                proto.write(
+                        PowerManagerServiceDumpProto.IS_ENHANCED_DISCHARGE_PREDICTION_PERSONALIZED,
+                        mEnhancedDischargePredictionIsPersonalized);
+            }
             proto.write(
                     PowerManagerServiceDumpProto.IS_HAL_AUTO_SUSPEND_MODE_ENABLED,
                     mHalAutoSuspendModeEnabled);
@@ -3986,7 +4019,8 @@
                     PowerManagerServiceDumpProto.IS_HAL_AUTO_INTERACTIVE_MODE_ENABLED,
                     mHalInteractiveModeEnabled);
 
-            final long activeWakeLocksToken = proto.start(PowerManagerServiceDumpProto.ACTIVE_WAKE_LOCKS);
+            final long activeWakeLocksToken = proto.start(
+                    PowerManagerServiceDumpProto.ACTIVE_WAKE_LOCKS);
             proto.write(
                     PowerManagerServiceDumpProto.ActiveWakeLocksProto.IS_CPU,
                     (mWakeLockSummary & WAKE_LOCK_CPU) != 0);
@@ -4228,15 +4262,15 @@
             proto.write(
                     PowerServiceSettingsAndConfigurationDumpProto.ScreenBrightnessSettingLimitsProto
                             .SETTING_MINIMUM_FLOAT,
-                    mScreenBrightnessSettingMinimum);
+                    mScreenBrightnessMinimum);
             proto.write(
                     PowerServiceSettingsAndConfigurationDumpProto.ScreenBrightnessSettingLimitsProto
                             .SETTING_MAXIMUM_FLOAT,
-                    mScreenBrightnessSettingMaximum);
+                    mScreenBrightnessMaximum);
             proto.write(
                     PowerServiceSettingsAndConfigurationDumpProto.ScreenBrightnessSettingLimitsProto
                             .SETTING_DEFAULT_FLOAT,
-                    mScreenBrightnessSettingDefault);
+                    mScreenBrightnessDefault);
             proto.end(screenBrightnessSettingLimitsToken);
 
             proto.write(
@@ -4289,6 +4323,7 @@
         if (wcd != null) {
             wcd.dumpDebug(proto, PowerManagerServiceDumpProto.WIRELESS_CHARGER_DETECTOR);
         }
+
         proto.flush();
     }
 
@@ -5049,6 +5084,96 @@
         }
 
         @Override // Binder call
+        public void setBatteryDischargePrediction(@NonNull ParcelDuration timeRemaining,
+                boolean isPersonalized) {
+            // Get current time before acquiring the lock so that the calculated end time is as
+            // accurate as possible.
+            final long nowElapsed = SystemClock.elapsedRealtime();
+            mContext.enforceCallingOrSelfPermission(android.Manifest.permission.DEVICE_POWER, null);
+
+            final long timeRemainingMs = timeRemaining.getDuration().toMillis();
+                // A non-positive number means the battery should be dead right now...
+            Preconditions.checkArgumentPositive(timeRemainingMs,
+                    "Given time remaining is not positive: " + timeRemainingMs);
+
+            final long ident = Binder.clearCallingIdentity();
+            try {
+                synchronized (mLock) {
+                    if (mIsPowered) {
+                        throw new IllegalStateException(
+                                "Discharge prediction can't be set while the device is charging");
+                    }
+                }
+
+                final long broadcastDelayMs;
+                synchronized (mEnhancedDischargeTimeLock) {
+                    if (mLastEnhancedDischargeTimeUpdatedElapsed > nowElapsed) {
+                        // Another later call made it into the block first. Keep the latest info.
+                        return;
+                    }
+                    broadcastDelayMs = Math.max(0,
+                            ENHANCED_DISCHARGE_PREDICTION_BROADCAST_MIN_DELAY_MS
+                                    - (nowElapsed - mLastEnhancedDischargeTimeUpdatedElapsed));
+
+                    // No need to persist the discharge prediction values since they'll most likely
+                    // be wrong immediately after a reboot anyway.
+                    mEnhancedDischargeTimeElapsed = nowElapsed + timeRemainingMs;
+                    mEnhancedDischargePredictionIsPersonalized = isPersonalized;
+                    mLastEnhancedDischargeTimeUpdatedElapsed = nowElapsed;
+                }
+                mNotifier.postEnhancedDischargePredictionBroadcast(broadcastDelayMs);
+            } finally {
+                Binder.restoreCallingIdentity(ident);
+            }
+        }
+
+        private boolean isEnhancedDischargePredictionValidLocked(long nowElapsed) {
+            return mLastEnhancedDischargeTimeUpdatedElapsed > 0
+                    && nowElapsed < mEnhancedDischargeTimeElapsed
+                    && nowElapsed - mLastEnhancedDischargeTimeUpdatedElapsed
+                    < ENHANCED_DISCHARGE_PREDICTION_TIMEOUT_MS;
+        }
+
+        @Override // Binder call
+        public ParcelDuration getBatteryDischargePrediction() {
+            final long ident = Binder.clearCallingIdentity();
+            try {
+                synchronized (mLock) {
+                    if (mIsPowered) {
+                        return null;
+                    }
+                }
+                synchronized (mEnhancedDischargeTimeLock) {
+                    // Get current time after acquiring the lock so that the calculated duration
+                    // is as accurate as possible.
+                    final long nowElapsed = SystemClock.elapsedRealtime();
+                    if (isEnhancedDischargePredictionValidLocked(nowElapsed)) {
+                        return new ParcelDuration(mEnhancedDischargeTimeElapsed - nowElapsed);
+                    }
+                }
+                return new ParcelDuration(mBatteryStats.computeBatteryTimeRemaining());
+            } catch (RemoteException e) {
+                // Shouldn't happen in-process.
+            } finally {
+                Binder.restoreCallingIdentity(ident);
+            }
+            return null;
+        }
+
+        @Override // Binder call
+        public boolean isBatteryDischargePredictionPersonalized() {
+            final long ident = Binder.clearCallingIdentity();
+            try {
+                synchronized (mEnhancedDischargeTimeLock) {
+                    return isEnhancedDischargePredictionValidLocked(SystemClock.elapsedRealtime())
+                            && mEnhancedDischargePredictionIsPersonalized;
+                }
+            } finally {
+                Binder.restoreCallingIdentity(ident);
+            }
+        }
+
+        @Override // Binder call
         public boolean isDeviceIdleMode() {
             final long ident = Binder.clearCallingIdentity();
             try {
@@ -5404,6 +5529,29 @@
         }
     }
 
+    /**
+     * If the user presses power while the proximity sensor is enabled and keeping
+     * the screen off, then turn the screen back on by telling display manager to
+     * ignore the proximity sensor.  We don't turn off the proximity sensor because
+     * we still want it to be reenabled if it's state changes.
+     *
+     * @return True if the proximity sensor was successfully ignored and we should
+     * consume the key event.
+     */
+    private boolean interceptPowerKeyDownInternal(KeyEvent event) {
+        synchronized (mLock) {
+            // DisplayPowerController only reports proximity positive (near) if it's
+            // positive and the proximity wasn't already being ignored. So it reliably
+            // also tells us that we're not already ignoring the proximity sensor.
+            if (mDisplayPowerRequest.useProximitySensor && mProximityPositive) {
+                mDisplayManagerInternal.ignoreProximitySensorUntilChanged();
+                return true;
+            }
+        }
+
+        return false;
+    }
+
     @VisibleForTesting
     final class LocalService extends PowerManagerInternal {
         @Override
@@ -5536,5 +5684,10 @@
         public WakeData getLastWakeup() {
             return getLastWakeupInternal();
         }
+
+        @Override
+        public boolean interceptPowerKeyDown(KeyEvent event) {
+            return interceptPowerKeyDownInternal(event);
+        }
     }
 }
diff --git a/services/core/java/com/android/server/power/ThermalManagerService.java b/services/core/java/com/android/server/power/ThermalManagerService.java
index ef4954a..ae0db44 100644
--- a/services/core/java/com/android/server/power/ThermalManagerService.java
+++ b/services/core/java/com/android/server/power/ThermalManagerService.java
@@ -200,7 +200,7 @@
         final int count = mTemperatureMap.size();
         for (int i = 0; i < count; i++) {
             Temperature t = mTemperatureMap.valueAt(i);
-            if (t.getStatus() >= newStatus) {
+            if (t.getType() == Temperature.TYPE_SKIN && t.getStatus() >= newStatus) {
                 newStatus = t.getStatus();
             }
         }
diff --git a/services/core/java/com/android/server/role/RoleManagerService.java b/services/core/java/com/android/server/role/RoleManagerService.java
index 392792d..a291cef 100644
--- a/services/core/java/com/android/server/role/RoleManagerService.java
+++ b/services/core/java/com/android/server/role/RoleManagerService.java
@@ -34,14 +34,11 @@
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
-import android.content.PermissionChecker;
 import android.content.pm.PackageManager;
 import android.content.pm.PackageManager.NameNotFoundException;
 import android.content.pm.PackageManagerInternal;
 import android.content.pm.Signature;
-import android.database.CursorWindow;
 import android.os.Binder;
-import android.os.Bundle;
 import android.os.Handler;
 import android.os.RemoteCallback;
 import android.os.RemoteCallbackList;
@@ -74,6 +71,7 @@
 import com.android.server.FgThread;
 import com.android.server.LocalServices;
 import com.android.server.SystemService;
+import com.android.server.SystemService.TargetUser;
 import com.android.server.pm.permission.PermissionManagerServiceInternal;
 
 import java.io.ByteArrayOutputStream;
@@ -217,8 +215,8 @@
     }
 
     @Override
-    public void onStartUser(@UserIdInt int userId) {
-        maybeGrantDefaultRolesSync(userId);
+    public void onUserStarting(@NonNull TargetUser user) {
+        maybeGrantDefaultRolesSync(user.getUserIdentifier());
     }
 
     @MainThread
diff --git a/services/core/java/com/android/server/rollback/RollbackManagerService.java b/services/core/java/com/android/server/rollback/RollbackManagerService.java
index ce1156b..04a5ca5 100644
--- a/services/core/java/com/android/server/rollback/RollbackManagerService.java
+++ b/services/core/java/com/android/server/rollback/RollbackManagerService.java
@@ -16,10 +16,12 @@
 
 package com.android.server.rollback;
 
+import android.annotation.NonNull;
 import android.content.Context;
 
 import com.android.server.LocalServices;
 import com.android.server.SystemService;
+import com.android.server.SystemService.TargetUser;
 
 /**
  * Service that manages APK level rollbacks. Publishes
@@ -43,8 +45,8 @@
     }
 
     @Override
-    public void onUserUnlocking(TargetUser user) {
-        mService.onUnlockUser(user.getUserHandle().getIdentifier());
+    public void onUserUnlocking(@NonNull TargetUser user) {
+        mService.onUnlockUser(user.getUserIdentifier());
     }
 
     @Override
diff --git a/services/core/java/com/android/server/search/SearchManagerService.java b/services/core/java/com/android/server/search/SearchManagerService.java
index fea68d3..7091c47 100644
--- a/services/core/java/com/android/server/search/SearchManagerService.java
+++ b/services/core/java/com/android/server/search/SearchManagerService.java
@@ -16,9 +16,7 @@
 
 package com.android.server.search;
 
-import android.app.ActivityManager;
-import android.app.ActivityTaskManager;
-import android.app.IActivityTaskManager;
+import android.annotation.NonNull;
 import android.app.ISearchManager;
 import android.app.SearchManager;
 import android.app.SearchableInfo;
@@ -26,18 +24,14 @@
 import android.content.ContentResolver;
 import android.content.Context;
 import android.content.Intent;
-import android.content.pm.PackageManager;
 import android.content.pm.ResolveInfo;
-import android.content.res.Configuration;
 import android.database.ContentObserver;
 import android.os.Binder;
 import android.os.Bundle;
 import android.os.Handler;
-import android.os.RemoteException;
 import android.os.UserHandle;
 import android.os.UserManager;
 import android.provider.Settings;
-import android.service.voice.VoiceInteractionService;
 import android.util.Log;
 import android.util.SparseArray;
 
@@ -48,6 +42,7 @@
 import com.android.internal.util.IndentingPrintWriter;
 import com.android.server.LocalServices;
 import com.android.server.SystemService;
+import com.android.server.SystemService.TargetUser;
 import com.android.server.statusbar.StatusBarManagerInternal;
 
 import java.io.FileDescriptor;
@@ -76,18 +71,13 @@
         }
 
         @Override
-        public void onUnlockUser(final int userId) {
-            mService.mHandler.post(new Runnable() {
-                @Override
-                public void run() {
-                    mService.onUnlockUser(userId);
-                }
-            });
+        public void onUserUnlocking(@NonNull TargetUser user) {
+            mService.mHandler.post(() -> mService.onUnlockUser(user.getUserIdentifier()));
         }
 
         @Override
-        public void onCleanupUser(int userHandle) {
-            mService.onCleanupUser(userHandle);
+        public void onUserStopped(@NonNull TargetUser user) {
+            mService.onCleanupUser(user.getUserIdentifier());
         }
     }
 
diff --git a/services/core/java/com/android/server/slice/SliceManagerService.java b/services/core/java/com/android/server/slice/SliceManagerService.java
index 7c8c494..5aedfc1 100644
--- a/services/core/java/com/android/server/slice/SliceManagerService.java
+++ b/services/core/java/com/android/server/slice/SliceManagerService.java
@@ -26,6 +26,7 @@
 import static android.os.Process.SYSTEM_UID;
 
 import android.Manifest.permission;
+import android.annotation.NonNull;
 import android.app.AppOpsManager;
 import android.app.slice.ISliceManager;
 import android.app.slice.SliceSpec;
@@ -38,9 +39,7 @@
 import android.content.Intent;
 import android.content.IntentFilter;
 import android.content.pm.PackageManager;
-import android.content.pm.PackageManagerInternal;
 import android.content.pm.ProviderInfo;
-import android.content.pm.ResolveInfo;
 import android.net.Uri;
 import android.os.Binder;
 import android.os.Handler;
@@ -59,7 +58,6 @@
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.app.AssistUtils;
-import com.android.internal.util.Preconditions;
 import com.android.server.LocalServices;
 import com.android.server.ServiceThread;
 import com.android.server.SystemService;
@@ -74,9 +72,7 @@
 import java.io.FileDescriptor;
 import java.io.IOException;
 import java.util.ArrayList;
-import java.util.List;
 import java.util.Objects;
-import java.util.function.Supplier;
 
 public class SliceManagerService extends ISliceManager.Stub {
 
@@ -84,16 +80,13 @@
     private final Object mLock = new Object();
 
     private final Context mContext;
-    private final PackageManagerInternal mPackageManagerInternal;
     private final AppOpsManager mAppOps;
     private final AssistUtils mAssistUtils;
 
     @GuardedBy("mLock")
     private final ArrayMap<Uri, PinnedSliceState> mPinnedSlicesByUri = new ArrayMap<>();
     @GuardedBy("mLock")
-    private final SparseArray<PackageMatchingCache> mAssistantLookup = new SparseArray<>();
-    @GuardedBy("mLock")
-    private final SparseArray<PackageMatchingCache> mHomeLookup = new SparseArray<>();
+    private final SparseArray<String> mLastAssistantPackage = new SparseArray<>();
     private final Handler mHandler;
 
     private final SlicePermissionManager mPermissions;
@@ -106,8 +99,6 @@
     @VisibleForTesting
     SliceManagerService(Context context, Looper looper) {
         mContext = context;
-        mPackageManagerInternal = Objects.requireNonNull(
-                LocalServices.getService(PackageManagerInternal.class));
         mAppOps = context.getSystemService(AppOpsManager.class);
         mAssistUtils = new AssistUtils(context);
         mHandler = new Handler(looper);
@@ -169,8 +160,7 @@
         mHandler.post(() -> {
             if (slicePkg != null && !Objects.equals(pkg, slicePkg)) {
                 mAppUsageStats.reportEvent(slicePkg, user,
-                        isAssistant(pkg, user) || isDefaultHomeApp(pkg, user)
-                                ? SLICE_PINNED_PRIV : SLICE_PINNED);
+                        isAssistant(pkg, user) ? SLICE_PINNED_PRIV : SLICE_PINNED);
             }
         });
     }
@@ -435,38 +425,19 @@
     private boolean hasFullSliceAccess(String pkg, int userId) {
         long ident = Binder.clearCallingIdentity();
         try {
-            boolean ret = isDefaultHomeApp(pkg, userId) || isAssistant(pkg, userId)
-                    || isGrantedFullAccess(pkg, userId);
-            return ret;
+            return isAssistant(pkg, userId) || isGrantedFullAccess(pkg, userId);
         } finally {
             Binder.restoreCallingIdentity(ident);
         }
     }
 
     private boolean isAssistant(String pkg, int userId) {
-        return getAssistantMatcher(userId).matches(pkg);
-    }
-
-    private boolean isDefaultHomeApp(String pkg, int userId) {
-        return getHomeMatcher(userId).matches(pkg);
-    }
-
-    private PackageMatchingCache getAssistantMatcher(int userId) {
-        PackageMatchingCache matcher = mAssistantLookup.get(userId);
-        if (matcher == null) {
-            matcher = new PackageMatchingCache(() -> getAssistant(userId));
-            mAssistantLookup.put(userId, matcher);
+        if (pkg == null) return false;
+        if (!pkg.equals(mLastAssistantPackage.get(userId))) {
+            // Failed on cached value, try updating.
+            mLastAssistantPackage.put(userId, getAssistant(userId));
         }
-        return matcher;
-    }
-
-    private PackageMatchingCache getHomeMatcher(int userId) {
-        PackageMatchingCache matcher = mHomeLookup.get(userId);
-        if (matcher == null) {
-            matcher = new PackageMatchingCache(() -> getDefaultHome(userId));
-            mHomeLookup.put(userId, matcher);
-        }
-        return matcher;
+        return pkg.equals(mLastAssistantPackage.get(userId));
     }
 
     private String getAssistant(int userId) {
@@ -477,51 +448,6 @@
         return cn.getPackageName();
     }
 
-    // Based on getDefaultHome in ShortcutService.
-    // TODO: Unify if possible
-    @VisibleForTesting
-    protected String getDefaultHome(int userId) {
-        final long token = Binder.clearCallingIdentity();
-        try {
-            final List<ResolveInfo> allHomeCandidates = new ArrayList<>();
-
-            // Default launcher from package manager.
-            final ComponentName defaultLauncher = mPackageManagerInternal
-                    .getHomeActivitiesAsUser(allHomeCandidates, userId);
-
-            ComponentName detected = null;
-            if (defaultLauncher != null) {
-                detected = defaultLauncher;
-            }
-
-            if (detected == null) {
-                // If we reach here, that means it's the first check since the user was created,
-                // and there's already multiple launchers and there's no default set.
-                // Find the system one with the highest priority.
-                // (We need to check the priority too because of FallbackHome in Settings.)
-                // If there's no system launcher yet, then no one can access slices, until
-                // the user explicitly sets one.
-                final int size = allHomeCandidates.size();
-
-                int lastPriority = Integer.MIN_VALUE;
-                for (int i = 0; i < size; i++) {
-                    final ResolveInfo ri = allHomeCandidates.get(i);
-                    if (!ri.activityInfo.applicationInfo.isSystemApp()) {
-                        continue;
-                    }
-                    if (ri.priority < lastPriority) {
-                        continue;
-                    }
-                    detected = ri.activityInfo.getComponentName();
-                    lastPriority = ri.priority;
-                }
-            }
-            return detected != null ? detected.getPackageName() : null;
-        } finally {
-            Binder.restoreCallingIdentity(token);
-        }
-    }
-
     private boolean isGrantedFullAccess(String pkg, int userId) {
         return mPermissions.hasFullAccess(pkg, userId);
     }
@@ -570,30 +496,6 @@
         return mPermissions.getAllPackagesGranted(pkg);
     }
 
-    /**
-     * Holder that caches a package that has access to a slice.
-     */
-    static class PackageMatchingCache {
-
-        private final Supplier<String> mPkgSource;
-        private String mCurrentPkg;
-
-        public PackageMatchingCache(Supplier<String> pkgSource) {
-            mPkgSource = pkgSource;
-        }
-
-        public boolean matches(String pkgCandidate) {
-            if (pkgCandidate == null) return false;
-
-            if (Objects.equals(pkgCandidate, mCurrentPkg)) {
-                return true;
-            }
-            // Failed on cached value, try updating.
-            mCurrentPkg = mPkgSource.get();
-            return Objects.equals(pkgCandidate, mCurrentPkg);
-        }
-    }
-
     public static class Lifecycle extends SystemService {
         private SliceManagerService mService;
 
@@ -615,13 +517,13 @@
         }
 
         @Override
-        public void onUnlockUser(int userHandle) {
-            mService.onUnlockUser(userHandle);
+        public void onUserUnlocking(@NonNull TargetUser user) {
+            mService.onUnlockUser(user.getUserIdentifier());
         }
 
         @Override
-        public void onStopUser(int userHandle) {
-            mService.onStopUser(userHandle);
+        public void onUserStopping(@NonNull TargetUser user) {
+            mService.onStopUser(user.getUserIdentifier());
         }
     }
 
diff --git a/services/core/java/com/android/server/storage/StorageSessionController.java b/services/core/java/com/android/server/storage/StorageSessionController.java
index 6dc1d692..0abeac8 100644
--- a/services/core/java/com/android/server/storage/StorageSessionController.java
+++ b/services/core/java/com/android/server/storage/StorageSessionController.java
@@ -336,11 +336,12 @@
     }
 
     /**
-     * Returns {@code true} if {@code vol} is an emulated or public volume,
+     * Returns {@code true} if {@code vol} is an emulated or visible public volume,
      * {@code false} otherwise
      **/
     public static boolean isEmulatedOrPublic(VolumeInfo vol) {
-        return vol.type == VolumeInfo.TYPE_EMULATED || vol.type == VolumeInfo.TYPE_PUBLIC;
+        return vol.type == VolumeInfo.TYPE_EMULATED
+                || (vol.type == VolumeInfo.TYPE_PUBLIC && vol.isVisible());
     }
 
     /** Exception thrown when communication with the {@link ExternalStorageService} fails. */
diff --git a/services/core/java/com/android/server/textclassifier/TextClassificationManagerService.java b/services/core/java/com/android/server/textclassifier/TextClassificationManagerService.java
index 1707d95..363e86d 100644
--- a/services/core/java/com/android/server/textclassifier/TextClassificationManagerService.java
+++ b/services/core/java/com/android/server/textclassifier/TextClassificationManagerService.java
@@ -66,6 +66,7 @@
 import com.android.internal.util.IndentingPrintWriter;
 import com.android.internal.util.Preconditions;
 import com.android.server.SystemService;
+import com.android.server.SystemService.TargetUser;
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
@@ -118,14 +119,14 @@
         }
 
         @Override
-        public void onStartUser(int userId) {
-            processAnyPendingWork(userId);
+        public void onUserStarting(@NonNull TargetUser user) {
+            processAnyPendingWork(user.getUserIdentifier());
         }
 
         @Override
-        public void onUnlockUser(int userId) {
+        public void onUserUnlocking(@NonNull TargetUser user) {
             // Rebind if we failed earlier due to locked encrypted user
-            processAnyPendingWork(userId);
+            processAnyPendingWork(user.getUserIdentifier());
         }
 
         private void processAnyPendingWork(int userId) {
@@ -135,7 +136,9 @@
         }
 
         @Override
-        public void onStopUser(int userId) {
+        public void onUserStopping(@NonNull TargetUser user) {
+            int userId = user.getUserIdentifier();
+
             synchronized (mManagerService.mLock) {
                 UserState userState = mManagerService.peekUserStateLocked(userId);
                 if (userState != null) {
diff --git a/services/core/java/com/android/server/textservices/TextServicesManagerService.java b/services/core/java/com/android/server/textservices/TextServicesManagerService.java
index e0bac93..f39067b 100644
--- a/services/core/java/com/android/server/textservices/TextServicesManagerService.java
+++ b/services/core/java/com/android/server/textservices/TextServicesManagerService.java
@@ -58,6 +58,7 @@
 import com.android.internal.util.DumpUtils;
 import com.android.server.LocalServices;
 import com.android.server.SystemService;
+import com.android.server.SystemService.TargetUser;
 
 import org.xmlpull.v1.XmlPullParserException;
 
@@ -287,21 +288,21 @@
         }
 
         @Override
-        public void onStopUser(@UserIdInt int userHandle) {
+        public void onUserStopping(@NonNull TargetUser user) {
             if (DBG) {
-                Slog.d(TAG, "onStopUser userId: " + userHandle);
+                Slog.d(TAG, "onStopUser user: " + user);
             }
-            mService.onStopUser(userHandle);
+            mService.onStopUser(user.getUserIdentifier());
         }
 
         @Override
-        public void onUnlockUser(@UserIdInt int userHandle) {
+        public void onUserUnlocking(@NonNull TargetUser user) {
             if(DBG) {
-                Slog.d(TAG, "onUnlockUser userId: " + userHandle);
+                Slog.d(TAG, "onUnlockUser userId: " + user);
             }
             // Called on the system server's main looper thread.
             // TODO: Dispatch this to a worker thread as needed.
-            mService.onUnlockUser(userHandle);
+            mService.onUnlockUser(user.getUserIdentifier());
         }
     }
 
diff --git a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorCallbackImpl.java b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorCallbackImpl.java
index c2b0d106..0ca36e0 100644
--- a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorCallbackImpl.java
+++ b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorCallbackImpl.java
@@ -72,6 +72,10 @@
             builder.setConfigureAutoDetectionEnabled(CAPABILITY_POSSESSED);
         }
 
+        // TODO(b/149014708) Replace this with real logic when the settings storage is fully
+        // implemented.
+        builder.setConfigureGeoDetectionEnabled(CAPABILITY_NOT_SUPPORTED);
+
         // The ability to make manual time zone suggestions can also be restricted by policy. With
         // the current logic above, this could lead to a situation where a device hardware does not
         // support auto detection, the device has been forced into "auto" mode by an admin and the
@@ -90,6 +94,7 @@
     public TimeZoneConfiguration getConfiguration(@UserIdInt int userId) {
         return new TimeZoneConfiguration.Builder()
                 .setAutoDetectionEnabled(isAutoDetectionEnabled())
+                .setGeoDetectionEnabled(isGeoDetectionEnabled())
                 .build();
     }
 
@@ -105,8 +110,11 @@
         // detection: if we wrote it down then we'd set the default explicitly. That might influence
         // what happens on later releases that do support auto detection on the same hardware.
         if (isAutoDetectionSupported()) {
-            final int value = configuration.isAutoDetectionEnabled() ? 1 : 0;
-            Settings.Global.putInt(mCr, Settings.Global.AUTO_TIME_ZONE, value);
+            final int autoEnabledValue = configuration.isAutoDetectionEnabled() ? 1 : 0;
+            Settings.Global.putInt(mCr, Settings.Global.AUTO_TIME_ZONE, autoEnabledValue);
+
+            final boolean geoTzDetectionEnabledValue = configuration.isGeoDetectionEnabled();
+            // TODO(b/149014708) Write this down to user-scoped settings once implemented.
         }
     }
 
@@ -126,6 +134,14 @@
     }
 
     @Override
+    public boolean isGeoDetectionEnabled() {
+        // TODO(b/149014708) Read this from user-scoped settings once implemented. The user's
+        //  location toggle will act as an override for this setting, i.e. so that the setting will
+        //  return false if the location toggle is disabled.
+        return false;
+    }
+
+    @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
diff --git a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorInternal.java b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorInternal.java
index 3d9ec64..fb7a73d 100644
--- a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorInternal.java
+++ b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorInternal.java
@@ -22,6 +22,7 @@
  * The internal (in-process) system server API for the {@link
  * com.android.server.timezonedetector.TimeZoneDetectorService}.
  *
+ * <p>The methods on this class can be called from any thread.
  * @hide
  */
 public interface TimeZoneDetectorInternal extends Dumpable.Container {
@@ -29,7 +30,7 @@
     /**
      * Suggests the current time zone, determined using geolocation, to the detector. The
      * detector may ignore the signal based on system settings, whether better information is
-     * available, and so on.
+     * available, and so on. This method may be implemented asynchronously.
      */
     void suggestGeolocationTimeZone(@NonNull GeolocationTimeZoneSuggestion timeZoneSuggestion);
 }
diff --git a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorInternalImpl.java b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorInternalImpl.java
index 4464f7d..15412a0 100644
--- a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorInternalImpl.java
+++ b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorInternalImpl.java
@@ -58,6 +58,7 @@
             @NonNull GeolocationTimeZoneSuggestion timeZoneSuggestion) {
         Objects.requireNonNull(timeZoneSuggestion);
 
+        // All strategy calls must take place on the mHandler thread.
         mHandler.post(
                 () -> mTimeZoneDetectorStrategy.suggestGeolocationTimeZone(timeZoneSuggestion));
     }
diff --git a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorService.java b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorService.java
index 3ec61fd..d81f949 100644
--- a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorService.java
+++ b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorService.java
@@ -18,7 +18,6 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
-import android.annotation.UserIdInt;
 import android.app.timezonedetector.ITimeZoneConfigurationListener;
 import android.app.timezonedetector.ITimeZoneDetectorService;
 import android.app.timezonedetector.ManualTimeZoneSuggestion;
@@ -38,6 +37,7 @@
 import android.provider.Settings;
 import android.util.IndentingPrintWriter;
 import android.util.Slog;
+import android.util.SparseArray;
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
@@ -60,7 +60,8 @@
  * and making calls async, leaving the (consequently more testable) {@link TimeZoneDetectorStrategy}
  * implementation to deal with the logic around time zone detection.
  */
-public final class TimeZoneDetectorService extends ITimeZoneDetectorService.Stub {
+public final class TimeZoneDetectorService extends ITimeZoneDetectorService.Stub
+        implements IBinder.DeathRecipient {
 
     private static final String TAG = "TimeZoneDetectorService";
 
@@ -104,9 +105,15 @@
     @NonNull
     private final TimeZoneDetectorStrategy mTimeZoneDetectorStrategy;
 
+    /**
+     * This sparse array acts as a map from userId to listeners running as that userId. User scoped
+     * as time zone detection configuration is partially user-specific, so different users can
+     * get different configuration.
+     */
     @GuardedBy("mConfigurationListeners")
     @NonNull
-    private final ArrayList<ConfigListenerInfo> mConfigurationListeners = new ArrayList<>();
+    private final SparseArray<ArrayList<ITimeZoneConfigurationListener>> mConfigurationListeners =
+            new SparseArray<>();
 
     private static TimeZoneDetectorService create(
             @NonNull Context context, @NonNull Handler handler,
@@ -123,6 +130,10 @@
                         service.handleAutoTimeZoneConfigChanged();
                     }
                 });
+        // TODO(b/149014708) Listen for changes to geolocation time zone detection enabled config.
+        //  This should also include listening to the current user and the current user's location
+        //  toggle since the config is user-scoped and the location toggle overrides the geolocation
+        //  time zone enabled setting.
         return service;
     }
 
@@ -188,18 +199,23 @@
         Objects.requireNonNull(listener);
         int userId = UserHandle.getCallingUserId();
 
-        ConfigListenerInfo listenerInfo = new ConfigListenerInfo(userId, listener);
-
         synchronized (mConfigurationListeners) {
-            if (mConfigurationListeners.contains(listenerInfo)) {
+            ArrayList<ITimeZoneConfigurationListener> listeners =
+                    mConfigurationListeners.get(userId);
+            if (listeners != null && listeners.contains(listener)) {
                 return;
             }
             try {
-                // Ensure the reference to the listener is removed if the client process dies.
-                listenerInfo.linkToDeath();
+                if (listeners == null) {
+                    listeners = new ArrayList<>(1);
+                    mConfigurationListeners.put(userId, listeners);
+                }
+
+                // Ensure the reference to the listener will be removed if the client process dies.
+                listener.asBinder().linkToDeath(this, 0 /* flags */);
 
                 // Only add the listener if we can linkToDeath().
-                mConfigurationListeners.add(listenerInfo);
+                listeners.add(listener);
             } catch (RemoteException e) {
                 Slog.e(TAG, "Unable to linkToDeath() for listener=" + listener, e);
             }
@@ -213,27 +229,62 @@
         int userId = UserHandle.getCallingUserId();
 
         synchronized (mConfigurationListeners) {
-            ConfigListenerInfo toRemove = new ConfigListenerInfo(userId, listener);
-            Iterator<ConfigListenerInfo> listenerIterator = mConfigurationListeners.iterator();
-            while (listenerIterator.hasNext()) {
-                ConfigListenerInfo currentListenerInfo = listenerIterator.next();
-                if (currentListenerInfo.equals(toRemove)) {
-                    listenerIterator.remove();
+            boolean removedListener = false;
+            ArrayList<ITimeZoneConfigurationListener> userListeners =
+                    mConfigurationListeners.get(userId);
+            if (userListeners.remove(listener)) {
+                // Stop listening for the client process to die.
+                listener.asBinder().unlinkToDeath(this, 0 /* flags */);
+                removedListener = true;
+            }
+            if (!removedListener) {
+                Slog.w(TAG, "Client asked to remove listenener=" + listener
+                        + ", but no listeners were removed."
+                        + " mConfigurationListeners=" + mConfigurationListeners);
+            }
+        }
+    }
 
-                    // Stop listening for the client process to die.
-                    try {
-                        currentListenerInfo.unlinkToDeath();
-                    } catch (RemoteException e) {
-                        Slog.e(TAG, "Unable to unlinkToDeath() for listener=" + listener, e);
+    @Override
+    public void binderDied() {
+        // Should not be used as binderDied(IBinder who) is overridden.
+        Slog.wtf(TAG, "binderDied() called unexpectedly.");
+    }
+
+    /**
+     * Called when one of the ITimeZoneConfigurationListener processes dies before calling
+     * {@link #removeConfigurationListener(ITimeZoneConfigurationListener)}.
+     */
+    @Override
+    public void binderDied(IBinder who) {
+        synchronized (mConfigurationListeners) {
+            boolean removedListener = false;
+            final int userCount = mConfigurationListeners.size();
+            for (int i = 0; i < userCount; i++) {
+                ArrayList<ITimeZoneConfigurationListener> userListeners =
+                        mConfigurationListeners.valueAt(i);
+                Iterator<ITimeZoneConfigurationListener> userListenerIterator =
+                        userListeners.iterator();
+                while (userListenerIterator.hasNext()) {
+                    ITimeZoneConfigurationListener userListener = userListenerIterator.next();
+                    if (userListener.asBinder().equals(who)) {
+                        userListenerIterator.remove();
+                        removedListener = true;
+                        break;
                     }
                 }
             }
+            if (!removedListener) {
+                Slog.w(TAG, "Notified of binder death for who=" + who
+                        + ", but did not remove any listeners."
+                        + " mConfigurationListeners=" + mConfigurationListeners);
+            }
         }
     }
 
     void handleConfigurationChanged() {
         // Note: we could trigger an async time zone detection operation here via a call to
-        // handleAutoTimeZoneDetectionChanged(), but that is triggered in response to the underlying
+        // handleAutoTimeZoneConfigChanged(), but that is triggered in response to the underlying
         // setting value changing so it is currently unnecessary. If we get to a point where all
         // configuration changes are guaranteed to happen in response to an updateConfiguration()
         // call, then we can remove that path and call it here instead.
@@ -241,16 +292,25 @@
         // Configuration has changed, but each user may have a different view of the configuration.
         // It's possible that this will cause unnecessary notifications but that shouldn't be a
         // problem.
-
         synchronized (mConfigurationListeners) {
-            for (ConfigListenerInfo listenerInfo : mConfigurationListeners) {
+            final int userCount = mConfigurationListeners.size();
+            for (int userIndex = 0; userIndex < userCount; userIndex++) {
+                int userId = mConfigurationListeners.keyAt(userIndex);
                 TimeZoneConfiguration configuration =
-                        mTimeZoneDetectorStrategy.getConfiguration(listenerInfo.getUserId());
-                try {
-                    listenerInfo.getListener().onChange(configuration);
-                } catch (RemoteException e) {
-                    Slog.w(TAG, "Unable to notify listener="
-                            + listenerInfo + " of updated configuration=" + configuration, e);
+                        mTimeZoneDetectorStrategy.getConfiguration(userId);
+
+                ArrayList<ITimeZoneConfigurationListener> listeners =
+                        mConfigurationListeners.valueAt(userIndex);
+                final int listenerCount = listeners.size();
+                for (int listenerIndex = 0; listenerIndex < listenerCount; listenerIndex++) {
+                    ITimeZoneConfigurationListener listener = listeners.get(listenerIndex);
+                    try {
+                        listener.onChange(configuration);
+                    } catch (RemoteException e) {
+                        Slog.w(TAG, "Unable to notify listener=" + listener
+                                + " for userId=" + userId
+                                + " of updated configuration=" + configuration, e);
+                    }
                 }
             }
         }
@@ -338,66 +398,5 @@
         (new TimeZoneDetectorShellCommand(this)).exec(
                 this, in, out, err, args, callback, resultReceiver);
     }
-
-    private class ConfigListenerInfo implements IBinder.DeathRecipient {
-        private final @UserIdInt int mUserId;
-        private final ITimeZoneConfigurationListener mListener;
-
-        ConfigListenerInfo(
-                @UserIdInt int userId, @NonNull ITimeZoneConfigurationListener listener) {
-            this.mUserId = userId;
-            this.mListener = Objects.requireNonNull(listener);
-        }
-
-        @UserIdInt int getUserId() {
-            return mUserId;
-        }
-
-        ITimeZoneConfigurationListener getListener() {
-            return mListener;
-        }
-
-        void linkToDeath() throws RemoteException {
-            mListener.asBinder().linkToDeath(this, 0 /* flags */);
-        }
-
-        void unlinkToDeath() throws RemoteException {
-            mListener.asBinder().unlinkToDeath(this, 0 /* flags */);
-        }
-
-        @Override
-        public void binderDied() {
-            synchronized (mConfigurationListeners) {
-                Slog.i(TAG, "Configuration listener client died: " + this);
-                mConfigurationListeners.remove(this);
-            }
-        }
-
-        @Override
-        public boolean equals(Object o) {
-            if (this == o) {
-                return true;
-            }
-            if (o == null || getClass() != o.getClass()) {
-                return false;
-            }
-            ConfigListenerInfo that = (ConfigListenerInfo) o;
-            return mUserId == that.mUserId
-                    && mListener.equals(that.mListener);
-        }
-
-        @Override
-        public int hashCode() {
-            return Objects.hash(mUserId, mListener);
-        }
-
-        @Override
-        public String toString() {
-            return "ConfigListenerInfo{"
-                    + "mUserId=" + mUserId
-                    + ", mListener=" + mListener
-                    + '}';
-        }
-    }
 }
 
diff --git a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorStrategy.java b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorStrategy.java
index f947a655..c5b7e39 100644
--- a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorStrategy.java
+++ b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorStrategy.java
@@ -30,9 +30,9 @@
  * <p>The strategy uses suggestions to decide whether to modify the device's time zone setting
  * and what to set it to.
  *
- * <p>Most calls will be handled by a single thread but that is not true for all calls. For example
- * {@link #dump(IndentingPrintWriter, String[])}) may be called on a different thread so
- * implementations mustvhandle thread safety.
+ * <p>Most calls will be handled by a single thread, but that is not true for all calls. For example
+ * {@link #dump(IndentingPrintWriter, String[])}) may be called on a different thread concurrently
+ * with other operations so implementations must still handle thread safety.
  *
  * @hide
  */
diff --git a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorStrategyImpl.java b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorStrategyImpl.java
index 9c36c39..d1369a2 100644
--- a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorStrategyImpl.java
+++ b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorStrategyImpl.java
@@ -23,6 +23,7 @@
 import static android.app.timezonedetector.TimeZoneCapabilities.CAPABILITY_NOT_APPLICABLE;
 import static android.app.timezonedetector.TimeZoneCapabilities.CAPABILITY_POSSESSED;
 import static android.app.timezonedetector.TimeZoneConfiguration.PROPERTY_AUTO_DETECTION_ENABLED;
+import static android.app.timezonedetector.TimeZoneConfiguration.PROPERTY_GEO_DETECTION_ENABLED;
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -98,6 +99,12 @@
         boolean isAutoDetectionEnabled();
 
         /**
+         * Returns whether geolocation can be used for time zone detection when {@link
+         * #isAutoDetectionEnabled()} returns {@code true}.
+         */
+        boolean isGeoDetectionEnabled();
+
+        /**
          * Returns true if the device has had an explicit time zone set.
          */
         boolean isDeviceTimeZoneInitialized();
@@ -200,7 +207,15 @@
      */
     @GuardedBy("this")
     private ArrayMapWithHistory<Integer, QualifiedTelephonyTimeZoneSuggestion>
-            mSuggestionBySlotIndex = new ArrayMapWithHistory<>(KEEP_SUGGESTION_HISTORY_SIZE);
+            mTelephonySuggestionsBySlotIndex =
+            new ArrayMapWithHistory<>(KEEP_SUGGESTION_HISTORY_SIZE);
+
+    /**
+     * The latest geolocation suggestion received.
+     */
+    @GuardedBy("this")
+    private ReferenceWithHistory<GeolocationTimeZoneSuggestion> mLatestGeoLocationSuggestion =
+            new ReferenceWithHistory<>(KEEP_SUGGESTION_HISTORY_SIZE);
 
     @GuardedBy("this")
     private final List<Dumpable> mDumpables = new ArrayList<>();
@@ -284,17 +299,26 @@
 
     private static boolean containsAutoTimeDetectionProperties(
             @NonNull TimeZoneConfiguration configuration) {
-        return configuration.hasProperty(PROPERTY_AUTO_DETECTION_ENABLED);
+        return configuration.hasProperty(PROPERTY_AUTO_DETECTION_ENABLED)
+                || configuration.hasProperty(PROPERTY_GEO_DETECTION_ENABLED);
     }
 
     @Override
     public synchronized void suggestGeolocationTimeZone(
             @NonNull GeolocationTimeZoneSuggestion suggestion) {
-        Objects.requireNonNull(suggestion);
+        if (DBG) {
+            Slog.d(LOG_TAG, "Geolocation suggestion received. newSuggestion=" + suggestion);
+        }
 
-        // TODO Implement this.
-        throw new UnsupportedOperationException(
-                "Geo-location time zone detection is not currently implemented");
+        Objects.requireNonNull(suggestion);
+        mLatestGeoLocationSuggestion.set(suggestion);
+
+        // Now perform auto time zone detection. The new suggestion may be used to modify the time
+        // zone setting.
+        if (mCallback.isGeoDetectionEnabled()) {
+            String reason = "New geolocation time zone suggested. suggestion=" + suggestion;
+            doAutoTimeZoneDetection(reason);
+        }
     }
 
     @Override
@@ -332,12 +356,14 @@
                 new QualifiedTelephonyTimeZoneSuggestion(suggestion, score);
 
         // Store the suggestion against the correct slotIndex.
-        mSuggestionBySlotIndex.put(suggestion.getSlotIndex(), scoredSuggestion);
+        mTelephonySuggestionsBySlotIndex.put(suggestion.getSlotIndex(), scoredSuggestion);
 
         // Now perform auto time zone detection. The new suggestion may be used to modify the time
         // zone setting.
-        String reason = "New telephony time suggested. suggestion=" + suggestion;
-        doAutoTimeZoneDetection(reason);
+        if (!mCallback.isGeoDetectionEnabled()) {
+            String reason = "New telephony time zone suggested. suggestion=" + suggestion;
+            doAutoTimeZoneDetection(reason);
+        }
     }
 
     private static int scoreTelephonySuggestion(@NonNull TelephonyTimeZoneSuggestion suggestion) {
@@ -363,9 +389,7 @@
     }
 
     /**
-     * Finds the best available time zone suggestion from all slotIndexes. If it is high-enough
-     * quality and automatic time zone detection is enabled then it will be set on the device. The
-     * outcome can be that this strategy becomes / remains un-opinionated and nothing is set.
+     * Performs automatic time zone detection.
      */
     @GuardedBy("this")
     private void doAutoTimeZoneDetection(@NonNull String detectionReason) {
@@ -374,6 +398,62 @@
             return;
         }
 
+        // Use the right suggestions based on the current configuration. This check is potentially
+        // race-prone until this value is set via a call to TimeZoneDetectorStrategy.
+        if (mCallback.isGeoDetectionEnabled()) {
+            doGeolocationTimeZoneDetection(detectionReason);
+        } else  {
+            doTelephonyTimeZoneDetection(detectionReason);
+        }
+    }
+
+    /**
+     * Detects the time zone using the latest available geolocation time zone suggestion, if one is
+     * available. The outcome can be that this strategy becomes / remains un-opinionated and nothing
+     * is set.
+     */
+    @GuardedBy("this")
+    private void doGeolocationTimeZoneDetection(@NonNull String detectionReason) {
+        GeolocationTimeZoneSuggestion latestGeolocationSuggestion =
+                mLatestGeoLocationSuggestion.get();
+        if (latestGeolocationSuggestion == null) {
+            return;
+        }
+
+        List<String> zoneIds = latestGeolocationSuggestion.getZoneIds();
+        if (zoneIds == null || zoneIds.isEmpty()) {
+            // This means the client has become uncertain about the time zone or it is certain there
+            // is no known zone. In either case we must leave the existing time zone setting as it
+            // is.
+            return;
+        }
+
+        // GeolocationTimeZoneSuggestion has no measure of quality. We assume all suggestions are
+        // reliable.
+        String zoneId;
+
+        // Introduce bias towards the device's current zone when there are multiple zone suggested.
+        String deviceTimeZone = mCallback.getDeviceTimeZone();
+        if (zoneIds.contains(deviceTimeZone)) {
+            if (DBG) {
+                Slog.d(LOG_TAG,
+                        "Geo tz suggestion contains current device time zone. Applying bias.");
+            }
+            zoneId = deviceTimeZone;
+        } else {
+            zoneId = zoneIds.get(0);
+        }
+        setDeviceTimeZoneIfRequired(zoneId, detectionReason);
+    }
+
+    /**
+     * Detects the time zone using the latest available telephony time zone suggestions.
+     * Finds the best available time zone suggestion from all slotIndexes. If it is high-enough
+     * quality and automatic time zone detection is enabled then it will be set on the device. The
+     * outcome can be that this strategy becomes / remains un-opinionated and nothing is set.
+     */
+    @GuardedBy("this")
+    private void doTelephonyTimeZoneDetection(@NonNull String detectionReason) {
         QualifiedTelephonyTimeZoneSuggestion bestTelephonySuggestion =
                 findBestTelephonySuggestion();
 
@@ -468,9 +548,9 @@
         // slotIndex and find the best. Note that we deliberately do not look at age: the caller can
         // rate-limit so age is not a strong indicator of confidence. Instead, the callers are
         // expected to withdraw suggestions they no longer have confidence in.
-        for (int i = 0; i < mSuggestionBySlotIndex.size(); i++) {
+        for (int i = 0; i < mTelephonySuggestionsBySlotIndex.size(); i++) {
             QualifiedTelephonyTimeZoneSuggestion candidateSuggestion =
-                    mSuggestionBySlotIndex.valueAt(i);
+                    mTelephonySuggestionsBySlotIndex.valueAt(i);
             if (candidateSuggestion == null) {
                 // Unexpected
                 continue;
@@ -505,14 +585,10 @@
     @Override
     public synchronized void handleAutoTimeZoneConfigChanged() {
         if (DBG) {
-            Slog.d(LOG_TAG, "handleTimeZoneDetectionChange() called");
+            Slog.d(LOG_TAG, "handleAutoTimeZoneConfigChanged()");
         }
-        if (mCallback.isAutoDetectionEnabled()) {
-            // When the user enabled time zone detection, run the time zone detection and change the
-            // device time zone if possible.
-            String reason = "Auto time zone detection setting enabled.";
-            doAutoTimeZoneDetection(reason);
-        }
+
+        doAutoTimeZoneDetection("handleAutoTimeZoneConfigChanged()");
     }
 
     @Override
@@ -532,15 +608,21 @@
         ipw.println("mCallback.isDeviceTimeZoneInitialized()="
                 + mCallback.isDeviceTimeZoneInitialized());
         ipw.println("mCallback.getDeviceTimeZone()=" + mCallback.getDeviceTimeZone());
+        ipw.println("mCallback.isGeoDetectionEnabled()=" + mCallback.isGeoDetectionEnabled());
 
         ipw.println("Time zone change log:");
         ipw.increaseIndent(); // level 2
         mTimeZoneChangesLog.dump(ipw);
         ipw.decreaseIndent(); // level 2
 
+        ipw.println("Geolocation suggestion history:");
+        ipw.increaseIndent(); // level 2
+        mLatestGeoLocationSuggestion.dump(ipw);
+        ipw.decreaseIndent(); // level 2
+
         ipw.println("Telephony suggestion history:");
         ipw.increaseIndent(); // level 2
-        mSuggestionBySlotIndex.dump(ipw);
+        mTelephonySuggestionsBySlotIndex.dump(ipw);
         ipw.decreaseIndent(); // level 2
         ipw.decreaseIndent(); // level 1
 
@@ -555,7 +637,15 @@
     @VisibleForTesting
     public synchronized QualifiedTelephonyTimeZoneSuggestion getLatestTelephonySuggestion(
             int slotIndex) {
-        return mSuggestionBySlotIndex.get(slotIndex);
+        return mTelephonySuggestionsBySlotIndex.get(slotIndex);
+    }
+
+    /**
+     * A method used to inspect strategy state during tests. Not intended for general use.
+     */
+    @VisibleForTesting
+    public GeolocationTimeZoneSuggestion getLatestGeolocationSuggestion() {
+        return mLatestGeoLocationSuggestion.get();
     }
 
     /**
diff --git a/services/core/java/com/android/server/trust/TrustManagerService.java b/services/core/java/com/android/server/trust/TrustManagerService.java
index fd3c1f9..0c85387 100644
--- a/services/core/java/com/android/server/trust/TrustManagerService.java
+++ b/services/core/java/com/android/server/trust/TrustManagerService.java
@@ -17,6 +17,8 @@
 package com.android.server.trust;
 
 import android.Manifest;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.annotation.UserIdInt;
 import android.app.ActivityManager;
 import android.app.AlarmManager;
@@ -71,6 +73,7 @@
 import com.android.internal.util.DumpUtils;
 import com.android.internal.widget.LockPatternUtils;
 import com.android.server.SystemService;
+import com.android.server.SystemService.TargetUser;
 
 import org.xmlpull.v1.XmlPullParser;
 import org.xmlpull.v1.XmlPullParserException;
@@ -1042,28 +1045,28 @@
     // User lifecycle
 
     @Override
-    public void onStartUser(int userId) {
-        mHandler.obtainMessage(MSG_START_USER, userId, 0, null).sendToTarget();
+    public void onUserStarting(@NonNull TargetUser user) {
+        mHandler.obtainMessage(MSG_START_USER, user.getUserIdentifier(), 0, null).sendToTarget();
     }
 
     @Override
-    public void onCleanupUser(int userId) {
-        mHandler.obtainMessage(MSG_CLEANUP_USER, userId, 0, null).sendToTarget();
+    public void onUserStopped(@NonNull TargetUser user) {
+        mHandler.obtainMessage(MSG_CLEANUP_USER, user.getUserIdentifier(), 0, null).sendToTarget();
     }
 
     @Override
-    public void onSwitchUser(int userId) {
-        mHandler.obtainMessage(MSG_SWITCH_USER, userId, 0, null).sendToTarget();
+    public void onUserSwitching(@Nullable TargetUser from, @NonNull TargetUser to) {
+        mHandler.obtainMessage(MSG_SWITCH_USER, to.getUserIdentifier(), 0, null).sendToTarget();
     }
 
     @Override
-    public void onUnlockUser(int userId) {
-        mHandler.obtainMessage(MSG_UNLOCK_USER, userId, 0, null).sendToTarget();
+    public void onUserUnlocking(@NonNull TargetUser user) {
+        mHandler.obtainMessage(MSG_UNLOCK_USER, user.getUserIdentifier(), 0, null).sendToTarget();
     }
 
     @Override
-    public void onStopUser(@UserIdInt int userId) {
-        mHandler.obtainMessage(MSG_STOP_USER, userId, 0, null).sendToTarget();
+    public void onUserStopping(@NonNull TargetUser user) {
+        mHandler.obtainMessage(MSG_STOP_USER, user.getUserIdentifier(), 0, null).sendToTarget();
     }
 
     // Plumbing
@@ -1153,7 +1156,7 @@
         }
 
         private void enforceListenerPermission() {
-            mContext.enforceCallingPermission(Manifest.permission.TRUST_LISTENER,
+            mContext.enforceCallingOrSelfPermission(Manifest.permission.TRUST_LISTENER,
                     "register trust listener");
         }
 
diff --git a/services/core/java/com/android/server/tv/TvInputManagerService.java b/services/core/java/com/android/server/tv/TvInputManagerService.java
index 323ac7b..8ccbbdd 100755
--- a/services/core/java/com/android/server/tv/TvInputManagerService.java
+++ b/services/core/java/com/android/server/tv/TvInputManagerService.java
@@ -20,6 +20,7 @@
 import static android.media.tv.TvInputManager.INPUT_STATE_CONNECTED;
 import static android.media.tv.TvInputManager.INPUT_STATE_CONNECTED_STANDBY;
 
+import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.app.ActivityManager;
 import android.content.BroadcastReceiver;
@@ -83,6 +84,7 @@
 import com.android.internal.util.IndentingPrintWriter;
 import com.android.server.IoThread;
 import com.android.server.SystemService;
+import com.android.server.SystemService.TargetUser;
 
 import java.io.File;
 import java.io.FileDescriptor;
@@ -97,6 +99,7 @@
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
+import java.util.UUID;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
 
@@ -165,10 +168,10 @@
     }
 
     @Override
-    public void onUnlockUser(int userHandle) {
-        if (DEBUG) Slog.d(TAG, "onUnlockUser(userHandle=" + userHandle + ")");
+    public void onUserUnlocking(@NonNull TargetUser user) {
+        if (DEBUG) Slog.d(TAG, "onUnlockUser(user=" + user + ")");
         synchronized (mLock) {
-            if (mCurrentUserId != userHandle) {
+            if (mCurrentUserId != user.getUserIdentifier()) {
                 return;
             }
             buildTvInputListLocked(mCurrentUserId, null);
@@ -1175,7 +1178,8 @@
             final int resolvedUserId = resolveCallingUserId(callingPid, callingUid,
                     userId, "createSession");
             final long identity = Binder.clearCallingIdentity();
-            StringBuilder sessionId = new StringBuilder();
+            // Generate a unique session id with a random UUID.
+            String uniqueSessionId = UUID.randomUUID().toString();
             try {
                 synchronized (mLock) {
                     if (userId != mCurrentUserId && !isRecordingSession) {
@@ -1204,20 +1208,17 @@
                         return;
                     }
 
-                    // Create a unique session id with pid, uid and resolved user id
-                    sessionId.append(callingUid).append(callingPid).append(resolvedUserId);
-
                     // Create a new session token and a session state.
                     IBinder sessionToken = new Binder();
                     SessionState sessionState = new SessionState(sessionToken, info.getId(),
                             info.getComponent(), isRecordingSession, client, seq, callingUid,
-                            callingPid, resolvedUserId, sessionId.toString());
+                            callingPid, resolvedUserId, uniqueSessionId);
 
                     // Add them to the global session state map of the current user.
                     userState.sessionStateMap.put(sessionToken, sessionState);
 
                     // Map the session id to the sessionStateMap in the user state
-                    mSessionIdToSessionStateMap.put(sessionId.toString(), sessionState);
+                    mSessionIdToSessionStateMap.put(uniqueSessionId, sessionState);
 
                     // Also, add them to the session state map of the current service.
                     serviceState.sessionTokens.add(sessionToken);
diff --git a/services/core/java/com/android/server/tv/tunerresourcemanager/ClientProfile.java b/services/core/java/com/android/server/tv/tunerresourcemanager/ClientProfile.java
index 2b0fe8a..2fc17fe 100644
--- a/services/core/java/com/android/server/tv/tunerresourcemanager/ClientProfile.java
+++ b/services/core/java/com/android/server/tv/tunerresourcemanager/ClientProfile.java
@@ -68,6 +68,11 @@
     private Set<Integer> mUsingFrontendIds = new HashSet<>();
 
     /**
+     * List of the client ids that share frontend with the current client.
+     */
+    private Set<Integer> mShareFeClientIds = new HashSet<>();
+
+    /**
      * List of the Lnb ids that are used by the current client.
      */
     private Set<Integer> mUsingLnbIds = new HashSet<>();
@@ -113,11 +118,7 @@
     }
 
     public int getPriority() {
-        return mPriority;
-    }
-
-    public int getNiceValue() {
-        return mNiceValue;
+        return mPriority - mNiceValue;
     }
 
     public void setGroupId(int groupId) {
@@ -141,17 +142,38 @@
         mUsingFrontendIds.add(frontendId);
     }
 
+    /**
+     * Update the set of client that share frontend with the current client.
+     *
+     * @param clientId the client to share the fe with the current client.
+     */
+    public void shareFrontend(int clientId) {
+        mShareFeClientIds.add(clientId);
+    }
+
+    /**
+     * Remove the given client id from the share frontend client id set.
+     *
+     * @param clientId the client to stop sharing the fe with the current client.
+     */
+    public void stopSharingFrontend(int clientId) {
+        mShareFeClientIds.remove(clientId);
+    }
+
     public Set<Integer> getInUseFrontendIds() {
         return mUsingFrontendIds;
     }
 
+    public Set<Integer> getShareFeClientIds() {
+        return mShareFeClientIds;
+    }
+
     /**
      * Called when the client released a frontend.
-     *
-     * @param frontendId being released.
      */
-    public void releaseFrontend(int frontendId) {
-        mUsingFrontendIds.remove(frontendId);
+    public void releaseFrontend() {
+        mUsingFrontendIds.clear();
+        mShareFeClientIds.clear();
     }
 
     /**
@@ -201,6 +223,7 @@
      */
     public void reclaimAllResources() {
         mUsingFrontendIds.clear();
+        mShareFeClientIds.clear();
         mUsingLnbIds.clear();
         mUsingCasSystemId = INVALID_RESOURCE_ID;
     }
diff --git a/services/core/java/com/android/server/tv/tunerresourcemanager/TunerResourceManagerService.java b/services/core/java/com/android/server/tv/tunerresourcemanager/TunerResourceManagerService.java
index 7cb59dc..fb2347e8 100644
--- a/services/core/java/com/android/server/tv/tunerresourcemanager/TunerResourceManagerService.java
+++ b/services/core/java/com/android/server/tv/tunerresourcemanager/TunerResourceManagerService.java
@@ -210,19 +210,36 @@
             }
             synchronized (mLock) {
                 if (!checkClientExists(request.getClientId())) {
-                    throw new RemoteException("Request frontend from unregistered client:"
+                    throw new RemoteException("Request frontend from unregistered client: "
                             + request.getClientId());
                 }
+                // If the request client is holding or sharing a frontend, throw an exception.
+                if (!getClientProfile(request.getClientId()).getInUseFrontendIds().isEmpty()) {
+                    throw new RemoteException("Release frontend before requesting another one. "
+                            + "Client id: " + request.getClientId());
+                }
                 return requestFrontendInternal(request, frontendHandle);
             }
         }
 
         @Override
-        public void shareFrontend(int selfClientId, int targetClientId) {
+        public void shareFrontend(int selfClientId, int targetClientId) throws RemoteException {
             enforceTunerAccessPermission("shareFrontend");
             enforceTrmAccessPermission("shareFrontend");
-            if (DEBUG) {
-                Slog.d(TAG, "shareFrontend from " + selfClientId + " with " + targetClientId);
+            synchronized (mLock) {
+                if (!checkClientExists(selfClientId)) {
+                    throw new RemoteException("Share frontend request from an unregistered client:"
+                            + selfClientId);
+                }
+                if (!checkClientExists(targetClientId)) {
+                    throw new RemoteException("Request to share frontend with an unregistered "
+                            + "client:" + targetClientId);
+                }
+                if (getClientProfile(targetClientId).getInUseFrontendIds().isEmpty()) {
+                    throw new RemoteException("Request to share frontend with a client that has no "
+                            + "frontend resources. Target client id:" + targetClientId);
+                }
+                shareFrontendInternal(selfClientId, targetClientId);
             }
         }
 
@@ -315,7 +332,7 @@
                     throw new RemoteException(
                             "Client is not the current owner of the releasing fe.");
                 }
-                releaseFrontendInternal(fe);
+                releaseFrontendInternal(fe, clientId);
             }
         }
 
@@ -649,6 +666,17 @@
     }
 
     @VisibleForTesting
+    protected void shareFrontendInternal(int selfClientId, int targetClientId) {
+        if (DEBUG) {
+            Slog.d(TAG, "shareFrontend from " + selfClientId + " with " + targetClientId);
+        }
+        for (int feId : getClientProfile(targetClientId).getInUseFrontendIds()) {
+            getClientProfile(selfClientId).useFrontend(feId);
+        }
+        getClientProfile(targetClientId).shareFrontend(selfClientId);
+    }
+
+    @VisibleForTesting
     protected boolean requestLnbInternal(TunerLnbRequest request, int[] lnbHandle) {
         if (DEBUG) {
             Slog.d(TAG, "requestLnb(request=" + request + ")");
@@ -777,11 +805,17 @@
     }
 
     @VisibleForTesting
-    protected void releaseFrontendInternal(FrontendResource fe) {
+    protected void releaseFrontendInternal(FrontendResource fe, int clientId) {
         if (DEBUG) {
-            Slog.d(TAG, "releaseFrontend(id=" + fe.getId() + ")");
+            Slog.d(TAG, "releaseFrontend(id=" + fe.getId() + ", clientId=" + clientId + " )");
         }
-        updateFrontendClientMappingOnRelease(fe);
+        if (clientId == fe.getOwnerClientId()) {
+            ClientProfile ownerClient = getClientProfile(fe.getOwnerClientId());
+            for (int shareOwnerId : ownerClient.getShareFeClientIds()) {
+                clearFrontendAndClientMapping(getClientProfile(shareOwnerId));
+            }
+        }
+        clearFrontendAndClientMapping(getClientProfile(clientId));
     }
 
     @VisibleForTesting
@@ -882,8 +916,21 @@
             Slog.e(TAG, "Failed to reclaim resources on client " + reclaimingClientId, e);
             return false;
         }
+
+        // Reclaim all the resources of the share owners of the frontend that is used by the current
+        // resource reclaimed client.
         ClientProfile profile = getClientProfile(reclaimingClientId);
-        reclaimingResourcesFromClient(profile);
+        Set<Integer> shareFeClientIds = profile.getShareFeClientIds();
+        for (int clientId : shareFeClientIds) {
+            try {
+                mListeners.get(clientId).getListener().onReclaimResources();
+            } catch (RemoteException e) {
+                Slog.e(TAG, "Failed to reclaim resources on client " + clientId, e);
+                return false;
+            }
+            clearAllResourcesAndClientMapping(getClientProfile(clientId));
+        }
+        clearAllResourcesAndClientMapping(profile);
         return true;
     }
 
@@ -929,16 +976,6 @@
         }
     }
 
-    private void updateFrontendClientMappingOnRelease(@NonNull FrontendResource releasingFrontend) {
-        ClientProfile ownerProfile = getClientProfile(releasingFrontend.getOwnerClientId());
-        releasingFrontend.removeOwner();
-        ownerProfile.releaseFrontend(releasingFrontend.getId());
-        for (int exclusiveGroupMember : releasingFrontend.getExclusiveGroupMemberFeIds()) {
-            getFrontendResource(exclusiveGroupMember).removeOwner();
-            ownerProfile.releaseFrontend(exclusiveGroupMember);
-        }
-    }
-
     private void updateLnbClientMappingOnNewGrant(int grantingId, int ownerClientId) {
         LnbResource grantingLnb = getLnbResource(grantingId);
         ClientProfile ownerProfile = getClientProfile(ownerClientId);
@@ -967,10 +1004,10 @@
     }
 
     /**
-     * Get the owner client's priority from the resource id.
+     * Get the owner client's priority.
      *
      * @param clientId the owner client id.
-     * @return the priority of the owner client of the resource.
+     * @return the priority of the owner client.
      */
     private int getOwnerClientPriority(int clientId) {
         return getClientProfile(clientId).getPriority();
@@ -1011,7 +1048,11 @@
             return;
         }
         if (fe.isInUse()) {
-            releaseFrontendInternal(fe);
+            ClientProfile ownerClient = getClientProfile(fe.getOwnerClientId());
+            for (int shareOwnerId : ownerClient.getShareFeClientIds()) {
+                clearFrontendAndClientMapping(getClientProfile(shareOwnerId));
+            }
+            clearFrontendAndClientMapping(ownerClient);
         }
         for (int excGroupmemberFeId : fe.getExclusiveGroupMemberFeIds()) {
             getFrontendResource(excGroupmemberFeId)
@@ -1093,21 +1134,37 @@
     }
 
     private void removeClientProfile(int clientId) {
-        reclaimingResourcesFromClient(getClientProfile(clientId));
+        for (int shareOwnerId : getClientProfile(clientId).getShareFeClientIds()) {
+            clearFrontendAndClientMapping(getClientProfile(shareOwnerId));
+        }
+        clearAllResourcesAndClientMapping(getClientProfile(clientId));
         mClientProfiles.remove(clientId);
         mListeners.remove(clientId);
     }
 
-    private void reclaimingResourcesFromClient(ClientProfile profile) {
+    private void clearFrontendAndClientMapping(ClientProfile profile) {
         for (Integer feId : profile.getInUseFrontendIds()) {
-            getFrontendResource(feId).removeOwner();
+            FrontendResource fe = getFrontendResource(feId);
+            if (fe.getOwnerClientId() == profile.getId()) {
+                fe.removeOwner();
+                continue;
+            }
+            getClientProfile(fe.getOwnerClientId()).stopSharingFrontend(profile.getId());
         }
+        profile.releaseFrontend();
+    }
+
+    private void clearAllResourcesAndClientMapping(ClientProfile profile) {
+        // Clear Lnb
         for (Integer lnbId : profile.getInUseLnbIds()) {
             getLnbResource(lnbId).removeOwner();
         }
+        // Clear Cas
         if (profile.getInUseCasSystemId() != ClientProfile.INVALID_RESOURCE_ID) {
             getCasResource(profile.getInUseCasSystemId()).removeOwner(profile.getId());
         }
+        // Clear Frontend
+        clearFrontendAndClientMapping(profile);
         profile.reclaimAllResources();
     }
 
diff --git a/services/core/java/com/android/server/uri/UriGrantsManagerInternal.java b/services/core/java/com/android/server/uri/UriGrantsManagerInternal.java
index cdb6199..5772dea 100644
--- a/services/core/java/com/android/server/uri/UriGrantsManagerInternal.java
+++ b/services/core/java/com/android/server/uri/UriGrantsManagerInternal.java
@@ -75,10 +75,31 @@
     void removeUriPermissionsForPackage(
             String packageName, int userHandle, boolean persistable, boolean targetOnly);
     /**
-     * @param uri This uri must NOT contain an embedded userId.
+     * Remove any {@link UriPermission} associated with the owner whose values match the given
+     * filtering parameters.
+     *
+     * @param token An opaque owner token as returned by {@link #newUriPermissionOwner(String)}.
+     * @param uri This uri must NOT contain an embedded userId. {@code null} to apply to all Uris.
+     * @param mode The modes (as a bitmask) to revoke.
      * @param userId The userId in which the uri is to be resolved.
      */
     void revokeUriPermissionFromOwner(IBinder token, Uri uri, int mode, int userId);
+
+    /**
+     * Remove any {@link UriPermission} associated with the owner whose values match the given
+     * filtering parameters.
+     *
+     * @param token An opaque owner token as returned by {@link #newUriPermissionOwner(String)}.
+     * @param uri This uri must NOT contain an embedded userId. {@code null} to apply to all Uris.
+     * @param mode The modes (as a bitmask) to revoke.
+     * @param userId The userId in which the uri is to be resolved.
+     * @param targetPkg Calling package name to match, or {@code null} to apply to all packages.
+     * @param targetUserId Calling user to match, or {@link UserHandle#USER_ALL} to apply to all
+     *                     users.
+     */
+    void revokeUriPermissionFromOwner(IBinder token, Uri uri, int mode, int userId,
+            String targetPkg, int targetUserId);
+
     boolean checkAuthorityGrants(
             int callingUid, ProviderInfo cpi, int userId, boolean checkUser);
     void dump(PrintWriter pw, boolean dumpAll, String dumpPackage);
diff --git a/services/core/java/com/android/server/uri/UriGrantsManagerService.java b/services/core/java/com/android/server/uri/UriGrantsManagerService.java
index b62121a..0b0bb70 100644
--- a/services/core/java/com/android/server/uri/UriGrantsManagerService.java
+++ b/services/core/java/com/android/server/uri/UriGrantsManagerService.java
@@ -51,7 +51,6 @@
 import android.app.GrantedUriPermission;
 import android.app.IUriGrantsManager;
 import android.content.ClipData;
-import android.content.ComponentName;
 import android.content.ContentProvider;
 import android.content.ContentResolver;
 import android.content.Context;
@@ -88,11 +87,11 @@
 import com.android.server.SystemService;
 import com.android.server.SystemServiceManager;
 
-import libcore.io.IoUtils;
-
 import com.google.android.collect.Lists;
 import com.google.android.collect.Maps;
 
+import libcore.io.IoUtils;
+
 import org.xmlpull.v1.XmlPullParser;
 import org.xmlpull.v1.XmlPullParserException;
 import org.xmlpull.v1.XmlSerializer;
@@ -1431,16 +1430,18 @@
 
         @Override
         public void revokeUriPermissionFromOwner(IBinder token, Uri uri, int mode, int userId) {
+            revokeUriPermissionFromOwner(token, uri, mode, userId, null, UserHandle.USER_ALL);
+        }
+
+        @Override
+        public void revokeUriPermissionFromOwner(IBinder token, Uri uri, int mode, int userId,
+                String targetPkg, int targetUserId) {
             final UriPermissionOwner owner = UriPermissionOwner.fromExternalToken(token);
             if (owner == null) {
                 throw new IllegalArgumentException("Unknown owner: " + token);
             }
-
-            if (uri == null) {
-                owner.removeUriPermissions(mode);
-            } else {
-                owner.removeUriPermission(new GrantUri(userId, uri, mode), mode);
-            }
+            GrantUri grantUri = uri == null ? null : new GrantUri(userId, uri, mode);
+            owner.removeUriPermission(grantUri, mode, targetPkg, targetUserId);
         }
 
         @Override
diff --git a/services/core/java/com/android/server/uri/UriPermissionOwner.java b/services/core/java/com/android/server/uri/UriPermissionOwner.java
index 2b404a4..0c26399 100644
--- a/services/core/java/com/android/server/uri/UriPermissionOwner.java
+++ b/services/core/java/com/android/server/uri/UriPermissionOwner.java
@@ -21,6 +21,7 @@
 
 import android.os.Binder;
 import android.os.IBinder;
+import android.os.UserHandle;
 import android.util.ArraySet;
 import android.util.proto.ProtoOutputStream;
 
@@ -74,30 +75,47 @@
     }
 
     void removeUriPermission(GrantUri grantUri, int mode) {
+        removeUriPermission(grantUri, mode, null, UserHandle.USER_ALL);
+    }
+
+    void removeUriPermission(GrantUri grantUri, int mode, String targetPgk, int targetUserId) {
         if ((mode & FLAG_GRANT_READ_URI_PERMISSION) != 0 && mReadPerms != null) {
             Iterator<UriPermission> it = mReadPerms.iterator();
             while (it.hasNext()) {
                 UriPermission perm = it.next();
-                if (grantUri == null || grantUri.equals(perm.uri)) {
-                    perm.removeReadOwner(this);
-                    mService.removeUriPermissionIfNeeded(perm);
-                    it.remove();
+                if (grantUri != null && !grantUri.equals(perm.uri)) {
+                    continue;
                 }
+                if (targetPgk != null && !targetPgk.equals(perm.targetPkg)) {
+                    continue;
+                }
+                if (targetUserId != UserHandle.USER_ALL && targetUserId != perm.targetUserId) {
+                    continue;
+                }
+                perm.removeReadOwner(this);
+                mService.removeUriPermissionIfNeeded(perm);
+                it.remove();
             }
             if (mReadPerms.isEmpty()) {
                 mReadPerms = null;
             }
         }
-        if ((mode & FLAG_GRANT_WRITE_URI_PERMISSION) != 0
-                && mWritePerms != null) {
+        if ((mode & FLAG_GRANT_WRITE_URI_PERMISSION) != 0 && mWritePerms != null) {
             Iterator<UriPermission> it = mWritePerms.iterator();
             while (it.hasNext()) {
                 UriPermission perm = it.next();
-                if (grantUri == null || grantUri.equals(perm.uri)) {
-                    perm.removeWriteOwner(this);
-                    mService.removeUriPermissionIfNeeded(perm);
-                    it.remove();
+                if (grantUri != null && !grantUri.equals(perm.uri)) {
+                    continue;
                 }
+                if (targetPgk != null && !targetPgk.equals(perm.targetPkg)) {
+                    continue;
+                }
+                if (targetUserId != UserHandle.USER_ALL && targetUserId != perm.targetUserId) {
+                    continue;
+                }
+                perm.removeWriteOwner(this);
+                mService.removeUriPermissionIfNeeded(perm);
+                it.remove();
             }
             if (mWritePerms.isEmpty()) {
                 mWritePerms = null;
diff --git a/services/core/java/com/android/server/vr/VrManagerService.java b/services/core/java/com/android/server/vr/VrManagerService.java
index 45689ce..ae873e2 100644
--- a/services/core/java/com/android/server/vr/VrManagerService.java
+++ b/services/core/java/com/android/server/vr/VrManagerService.java
@@ -19,6 +19,7 @@
 
 import android.Manifest;
 import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.app.ActivityManager;
 import android.app.ActivityManagerInternal;
 import android.app.AppOpsManager;
@@ -65,6 +66,7 @@
 import com.android.server.LocalServices;
 import com.android.server.SystemConfig;
 import com.android.server.SystemService;
+import com.android.server.SystemService.TargetUser;
 import com.android.server.utils.ManagedApplicationService;
 import com.android.server.utils.ManagedApplicationService.BinderChecker;
 import com.android.server.utils.ManagedApplicationService.LogEvent;
@@ -817,14 +819,14 @@
     }
 
     @Override
-    public void onStartUser(int userHandle) {
+    public void onUserStarting(@NonNull TargetUser user) {
         synchronized (mLock) {
             mComponentObserver.onUsersChanged();
         }
     }
 
     @Override
-    public void onSwitchUser(int userHandle) {
+    public void onUserSwitching(@Nullable TargetUser from, @NonNull TargetUser to) {
         FgThread.getHandler().post(() -> {
             synchronized (mLock) {
                 mComponentObserver.onUsersChanged();
@@ -834,7 +836,7 @@
     }
 
     @Override
-    public void onStopUser(int userHandle) {
+    public void onUserStopping(@NonNull TargetUser user) {
         synchronized (mLock) {
             mComponentObserver.onUsersChanged();
         }
@@ -842,7 +844,7 @@
     }
 
     @Override
-    public void onCleanupUser(int userHandle) {
+    public void onUserStopped(@NonNull TargetUser user) {
         synchronized (mLock) {
             mComponentObserver.onUsersChanged();
         }
diff --git a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
index 90f87b1..202a3dc 100644
--- a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
+++ b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
@@ -105,6 +105,7 @@
 import com.android.server.FgThread;
 import com.android.server.LocalServices;
 import com.android.server.SystemService;
+import com.android.server.SystemService.TargetUser;
 import com.android.server.utils.TimingsTraceAndSlog;
 import com.android.server.wm.WindowManagerInternal;
 
@@ -166,9 +167,9 @@
         }
 
         @Override
-        public void onUnlockUser(int userHandle) {
+        public void onUserUnlocking(@NonNull TargetUser user) {
             if (mService != null) {
-                mService.onUnlockUser(userHandle);
+                mService.onUnlockUser(user.getUserIdentifier());
             }
         }
     }
@@ -3069,7 +3070,7 @@
                     mLockWallpaperMap.put(userId, wallpaper);
                     ensureSaneWallpaperData(wallpaper);
                 } else {
-                    // sanity fallback: we're in bad shape, but establishing a known
+                    // rationality fallback: we're in bad shape, but establishing a known
                     // valid system+lock WallpaperData will keep us from dying.
                     Slog.wtf(TAG, "Didn't find wallpaper in non-lock case!");
                     wallpaper = new WallpaperData(userId, getWallpaperDir(userId),
diff --git a/services/core/java/com/android/server/webkit/WebViewUpdateService.java b/services/core/java/com/android/server/webkit/WebViewUpdateService.java
index 9bbeb72..90a153b 100644
--- a/services/core/java/com/android/server/webkit/WebViewUpdateService.java
+++ b/services/core/java/com/android/server/webkit/WebViewUpdateService.java
@@ -209,8 +209,8 @@
         private void grantVisibilityToCaller(String webViewPackageName, int callingUid) {
             final PackageManagerInternal pmInternal = LocalServices.getService(
                     PackageManagerInternal.class);
-            final int webviewUid = pmInternal.getPackageUidInternal(
-                    webViewPackageName, 0, UserHandle.getUserId(callingUid));
+            final int webviewUid = pmInternal.getPackageUid(
+                    webViewPackageName, 0 /* flags */, UserHandle.getUserId(callingUid));
             pmInternal.grantImplicitAccess(UserHandle.getUserId(callingUid), null,
                     UserHandle.getAppId(callingUid), webviewUid,
                     true /*direct*/);
diff --git a/services/core/java/com/android/server/wm/AccessibilityController.java b/services/core/java/com/android/server/wm/AccessibilityController.java
index ecba3f9..1536473 100644
--- a/services/core/java/com/android/server/wm/AccessibilityController.java
+++ b/services/core/java/com/android/server/wm/AccessibilityController.java
@@ -177,7 +177,7 @@
 
     public void performComputeChangedWindowsNotLocked(int displayId, boolean forceSend) {
         WindowsForAccessibilityObserver observer = null;
-        synchronized (mService) {
+        synchronized (mService.mGlobalLock) {
             final WindowsForAccessibilityObserver windowsForA11yObserver =
                     mWindowsForAccessibilityObserver.get(displayId);
             if (windowsForA11yObserver != null) {
@@ -266,7 +266,7 @@
         // Not relevant for the display magnifier.
 
         WindowsForAccessibilityObserver observer = null;
-        synchronized (mService) {
+        synchronized (mService.mGlobalLock) {
             final WindowsForAccessibilityObserver windowsForA11yObserver =
                     mWindowsForAccessibilityObserver.get(displayId);
             if (windowsForA11yObserver != null) {
diff --git a/services/core/java/com/android/server/wm/ActivityMetricsLogger.java b/services/core/java/com/android/server/wm/ActivityMetricsLogger.java
index 7565d8f..6e9526a 100644
--- a/services/core/java/com/android/server/wm/ActivityMetricsLogger.java
+++ b/services/core/java/com/android/server/wm/ActivityMetricsLogger.java
@@ -542,7 +542,7 @@
                     + " processSwitch=" + processSwitch + " info=" + info);
         }
 
-        if (launchedActivity.mDrawn) {
+        if (launchedActivity.isReportedDrawn()) {
             // Launched activity is already visible. We cannot measure windows drawn delay.
             abort(info, "launched activity already visible");
             return;
@@ -681,7 +681,7 @@
 
     /** @return {@code true} if the given task has an activity will be drawn. */
     private static boolean hasActivityToBeDrawn(Task t) {
-        return t.forAllActivities((r) -> r.mVisibleRequested && !r.mDrawn && !r.finishing);
+        return t.forAllActivities(r -> r.mVisibleRequested && !r.isReportedDrawn() && !r.finishing);
     }
 
     private void checkVisibility(Task t, ActivityRecord r) {
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index 024ef87..9316c46 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -70,7 +70,7 @@
 import static android.content.pm.ActivityInfo.LAUNCH_SINGLE_TOP;
 import static android.content.pm.ActivityInfo.LOCK_TASK_LAUNCH_MODE_ALWAYS;
 import static android.content.pm.ActivityInfo.LOCK_TASK_LAUNCH_MODE_DEFAULT;
-import static android.content.pm.ActivityInfo.LOCK_TASK_LAUNCH_MODE_IF_WHITELISTED;
+import static android.content.pm.ActivityInfo.LOCK_TASK_LAUNCH_MODE_IF_ALLOWLISTED;
 import static android.content.pm.ActivityInfo.LOCK_TASK_LAUNCH_MODE_NEVER;
 import static android.content.pm.ActivityInfo.PERSIST_ACROSS_REBOOTS;
 import static android.content.pm.ActivityInfo.PERSIST_ROOT_ONLY;
@@ -107,6 +107,12 @@
 import static android.view.WindowManager.TRANSIT_TASK_OPEN_BEHIND;
 import static android.view.WindowManager.TRANSIT_UNSET;
 
+import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_ADD_REMOVE;
+import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_APP_TRANSITIONS;
+import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_APP_TRANSITIONS_ANIM;
+import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_FOCUS_LIGHT;
+import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_ORIENTATION;
+import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_STARTING_WINDOW;
 import static com.android.server.policy.WindowManagerPolicy.FINISH_LAYOUT_REDO_ANIM;
 import static com.android.server.policy.WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER;
 import static com.android.server.wm.ActivityRecordProto.ALL_DRAWN;
@@ -168,16 +174,10 @@
 import static com.android.server.wm.ActivityTaskManagerService.RELAUNCH_REASON_FREE_RESIZE;
 import static com.android.server.wm.ActivityTaskManagerService.RELAUNCH_REASON_NONE;
 import static com.android.server.wm.ActivityTaskManagerService.RELAUNCH_REASON_WINDOWING_MODE_RESIZE;
-import static com.android.server.wm.ActivityTaskManagerService.getInputDispatchingTimeoutLocked;
+import static com.android.server.wm.ActivityTaskManagerService.getInputDispatchingTimeoutMillisLocked;
 import static com.android.server.wm.IdentifierProto.HASH_CODE;
 import static com.android.server.wm.IdentifierProto.TITLE;
 import static com.android.server.wm.IdentifierProto.USER_ID;
-import static com.android.server.wm.ProtoLogGroup.WM_DEBUG_ADD_REMOVE;
-import static com.android.server.wm.ProtoLogGroup.WM_DEBUG_APP_TRANSITIONS;
-import static com.android.server.wm.ProtoLogGroup.WM_DEBUG_APP_TRANSITIONS_ANIM;
-import static com.android.server.wm.ProtoLogGroup.WM_DEBUG_FOCUS_LIGHT;
-import static com.android.server.wm.ProtoLogGroup.WM_DEBUG_ORIENTATION;
-import static com.android.server.wm.ProtoLogGroup.WM_DEBUG_STARTING_WINDOW;
 import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_APP_TRANSITION;
 import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_WINDOW_ANIMATION;
 import static com.android.server.wm.Task.ActivityState.DESTROYED;
@@ -294,6 +294,7 @@
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.app.ResolverActivity;
 import com.android.internal.content.ReferrerIntent;
+import com.android.internal.protolog.common.ProtoLog;
 import com.android.internal.util.ToBooleanFunction;
 import com.android.internal.util.XmlUtils;
 import com.android.internal.util.function.pooled.PooledConsumer;
@@ -305,7 +306,6 @@
 import com.android.server.am.PendingIntentRecord;
 import com.android.server.display.color.ColorDisplayService;
 import com.android.server.policy.WindowManagerPolicy;
-import com.android.server.protolog.common.ProtoLog;
 import com.android.server.uri.NeededUriGrants;
 import com.android.server.uri.UriPermissionOwner;
 import com.android.server.wm.ActivityMetricsLogger.TransitionInfoSnapshot;
@@ -499,7 +499,6 @@
                                            // and reporting to the client that it is hidden.
     private boolean mSetToSleep; // have we told the activity to sleep?
     boolean nowVisible;     // is this activity's window visible?
-    boolean mDrawn;          // is this activity's window drawn?
     boolean mClientVisibilityDeferred;// was the visibility change message to client deferred?
     boolean idle;           // has the activity gone idle?
     boolean hasBeenLaunched;// has this activity ever been launched?
@@ -564,8 +563,8 @@
     private boolean mClientVisible;
 
     boolean firstWindowDrawn;
-    // Last drawn state we reported to the app token.
-    private boolean reportedDrawn;
+    /** Whether the visible window(s) of this activity is drawn. */
+    private boolean mReportedDrawn;
     private final WindowState.UpdateReportedVisibilityResults mReportedVisibilityResults =
             new WindowState.UpdateReportedVisibilityResults();
 
@@ -587,8 +586,8 @@
      */
     private boolean mOccludesParent;
 
-    // The input dispatching timeout for this application token in nanoseconds.
-    long mInputDispatchingTimeoutNanos;
+    // The input dispatching timeout for this application token in milliseconds.
+    long mInputDispatchingTimeoutMillis;
 
     private boolean mShowWhenLocked;
     private boolean mInheritShownWhenLocked;
@@ -927,7 +926,7 @@
         pw.println(prefix + "mVisibleRequested=" + mVisibleRequested
                 + " mVisible=" + mVisible + " mClientVisible=" + mClientVisible
                 + ((mDeferHidingClient) ? " mDeferHidingClient=" + mDeferHidingClient : "")
-                + " reportedDrawn=" + reportedDrawn + " reportedVisible=" + reportedVisible);
+                + " reportedDrawn=" + mReportedDrawn + " reportedVisible=" + reportedVisible);
         if (paused) {
             pw.print(prefix); pw.print("paused="); pw.println(paused);
         }
@@ -1213,14 +1212,6 @@
         return task;
     }
 
-    /**
-     * Sets the Task on this activity for the purposes of re-use during launch where we will
-     * re-use another activity instead of this one for the launch.
-     */
-    void setTaskForReuse(Task task) {
-        this.task = task;
-    }
-
     Task getStack() {
         return task != null ? task.getRootTask() : null;
     }
@@ -1245,7 +1236,7 @@
         if (oldParent == null && newParent != null) {
             // First time we are adding the activity to the system.
             mVoiceInteraction = newTask.voiceSession != null;
-            mInputDispatchingTimeoutNanos = getInputDispatchingTimeoutLocked(this) * 1000000L;
+            mInputDispatchingTimeoutMillis = getInputDispatchingTimeoutMillisLocked(this);
 
             // TODO(b/36505427): Maybe this call should be moved inside
             // updateOverrideConfiguration()
@@ -1591,7 +1582,6 @@
         keysPaused = false;
         inHistory = false;
         nowVisible = false;
-        mDrawn = false;
         mClientVisible = true;
         idle = false;
         hasBeenLaunched = false;
@@ -1675,7 +1665,8 @@
 
     static int getLockTaskLaunchMode(ActivityInfo aInfo, @Nullable ActivityOptions options) {
         int lockTaskLaunchMode = aInfo.lockTaskLaunchMode;
-        if (aInfo.applicationInfo.isPrivilegedApp()
+        // Non-priv apps are not allowed to use always or never, fall back to default
+        if (!aInfo.applicationInfo.isPrivilegedApp()
                 && (lockTaskLaunchMode == LOCK_TASK_LAUNCH_MODE_ALWAYS
                 || lockTaskLaunchMode == LOCK_TASK_LAUNCH_MODE_NEVER)) {
             lockTaskLaunchMode = LOCK_TASK_LAUNCH_MODE_DEFAULT;
@@ -1683,7 +1674,7 @@
         if (options != null) {
             final boolean useLockTask = options.getLockTaskMode();
             if (useLockTask && lockTaskLaunchMode == LOCK_TASK_LAUNCH_MODE_DEFAULT) {
-                lockTaskLaunchMode = LOCK_TASK_LAUNCH_MODE_IF_WHITELISTED;
+                lockTaskLaunchMode = LOCK_TASK_LAUNCH_MODE_IF_ALLOWLISTED;
             }
         }
         return lockTaskLaunchMode;
@@ -2548,7 +2539,9 @@
 
         final Task stack = getRootTask();
         final boolean mayAdjustTop = (isState(RESUMED) || stack.mResumedActivity == null)
-                && stack.isFocusedStackOnDisplay();
+                && stack.isFocusedStackOnDisplay()
+                // Do not adjust focus task because the task will be reused to launch new activity.
+                && !task.isClearingToReuseTask();
         final boolean shouldAdjustGlobalFocus = mayAdjustTop
                 // It must be checked before {@link #makeFinishingLocked} is called, because a stack
                 // is not visible if it only contains finishing activities.
@@ -2865,7 +2858,7 @@
 
         if (hasProcess()) {
             if (removeFromApp) {
-                app.removeActivity(this);
+                app.removeActivity(this, true /* keepAssociation */);
                 if (!app.hasActivities()) {
                     mAtmService.clearHeavyWeightProcessIfEquals(app);
                     // Update any services we are bound to that might care about whether
@@ -2914,7 +2907,7 @@
                 setState(DESTROYED,
                         "destroyActivityLocked. not finishing or skipping destroy");
                 if (DEBUG_APP) Slog.v(TAG_APP, "Clearing app during destroy for activity " + this);
-                app = null;
+                detachFromProcess();
             }
         } else {
             // Remove this record from the history.
@@ -2963,13 +2956,20 @@
         }
         setState(DESTROYED, "removeFromHistory");
         if (DEBUG_APP) Slog.v(TAG_APP, "Clearing app during remove for activity " + this);
-        app = null;
+        detachFromProcess();
         removeAppTokenFromDisplay();
 
         cleanUpActivityServices();
         removeUriPermissionsLocked();
     }
 
+    void detachFromProcess() {
+        if (app != null) {
+            app.removeActivity(this, false /* keepAssociation */);
+        }
+        app = null;
+    }
+
     void makeFinishingLocked() {
         if (finishing) {
             return;
@@ -3019,7 +3019,7 @@
         if (setState) {
             setState(DESTROYED, "cleanUp");
             if (DEBUG_APP) Slog.v(TAG_APP, "Clearing app during cleanUp for activity " + this);
-            app = null;
+            detachFromProcess();
         }
 
         // Inform supervisor the activity has been removed.
@@ -3160,6 +3160,64 @@
         mServiceConnectionsHolder = null;
     }
 
+    /**
+     * Detach this activity from process and clear the references to it. If the activity is
+     * finishing or has no saved state or crashed many times, it will also be removed from history.
+     */
+    void handleAppDied() {
+        final boolean remove;
+        if ((mRelaunchReason == RELAUNCH_REASON_WINDOWING_MODE_RESIZE
+                || mRelaunchReason == RELAUNCH_REASON_FREE_RESIZE)
+                && launchCount < 3 && !finishing) {
+            // If the process crashed during a resize, always try to relaunch it, unless it has
+            // failed more than twice. Skip activities that's already finishing cleanly by itself.
+            remove = false;
+        } else if ((!mHaveState && !stateNotNeeded
+                && !isState(ActivityState.RESTARTING_PROCESS)) || finishing) {
+            // Don't currently have state for the activity, or it is finishing -- always remove it.
+            remove = true;
+        } else if (!mVisibleRequested && launchCount > 2
+                && lastLaunchTime > (SystemClock.uptimeMillis() - 60000)) {
+            // We have launched this activity too many times since it was able to run, so give up
+            // and remove it. (Note if the activity is visible, we don't remove the record. We leave
+            // the dead window on the screen but the process will not be restarted unless user
+            // explicitly tap on it.)
+            remove = true;
+        } else {
+            // The process may be gone, but the activity lives on!
+            remove = false;
+        }
+        if (remove) {
+            if (ActivityTaskManagerDebugConfig.DEBUG_ADD_REMOVE || DEBUG_CLEANUP) {
+                Slog.i(TAG_ADD_REMOVE, "Removing activity " + this
+                        + " hasSavedState=" + mHaveState + " stateNotNeeded=" + stateNotNeeded
+                        + " finishing=" + finishing + " state=" + mState
+                        + " callers=" + Debug.getCallers(5));
+            }
+            if (!finishing || (app != null && app.isRemoved())) {
+                Slog.w(TAG, "Force removing " + this + ": app died, no saved state");
+                EventLogTags.writeWmFinishActivity(mUserId, System.identityHashCode(this),
+                        task != null ? task.mTaskId : -1, shortComponentName,
+                        "proc died without state saved");
+            }
+        } else {
+            // We have the current state for this activity, so it can be restarted later
+            // when needed.
+            if (DEBUG_APP) {
+                Slog.v(TAG_APP, "Keeping entry during removeHistory for activity " + this);
+            }
+            // Set nowVisible to previous visible state. If the app was visible while it died, we
+            // leave the dead window on screen so it's basically visible. This is needed when user
+            // later tap on the dead window, we need to stop other apps when user transfers focus
+            // to the restarted activity.
+            nowVisible = mVisibleRequested;
+        }
+        cleanUp(true /* cleanServices */, true /* setState */);
+        if (remove) {
+            removeFromHistory("appDied");
+        }
+    }
+
     @Override
     void removeImmediately() {
         onRemovedFromDisplay();
@@ -4114,6 +4172,12 @@
             // Now that the app is going invisible, we can remove it. It will be restarted
             // if made visible again.
             removeDeadWindows();
+            // If this activity is about to finish/stopped and now becomes invisible, remove it
+            // from the unknownApp list in case the activity does not want to draw anything, which
+            // keep the user waiting for the next transition to start.
+            if (finishing || isState(STOPPED)) {
+                displayContent.mUnknownAppVisibilityController.appRemovedOrHidden(this);
+            }
         } else {
             if (!appTransition.isTransitionSet()
                     && appTransition.isReady()) {
@@ -4600,7 +4664,7 @@
         // case where this is the top activity in a pinned stack.
         final boolean isTop = this == stack.getTopNonFinishingActivity();
         final boolean isTopNotPinnedStack = stack.isAttached()
-                && stack.getDisplayArea().isTopNotPinnedStack(stack);
+                && stack.getDisplayArea().isTopNotFinishNotPinnedStack(stack);
         final boolean visibleIgnoringDisplayStatus = stack.checkKeyguardVisibility(this,
                 visibleIgnoringKeyguard, isTop && isTopNotPinnedStack);
 
@@ -4742,6 +4806,15 @@
                 Slog.v(TAG_VISIBILITY, "Start visible activity, " + this);
             }
             setState(STARTED, "makeActiveIfNeeded");
+
+            // Update process info while making an activity from invisible to visible, to make
+            // sure the process state is updated to foreground.
+            if (app != null) {
+                app.updateProcessInfo(false /* updateServiceConnectionActivities */,
+                        true /* activityChange */, true /* updateOomAdj */,
+                        true /* addPendingTopUid */);
+            }
+
             try {
                 mAtmService.getLifecycleManager().scheduleTransaction(app.getThread(), appToken,
                         StartActivityItem.obtain());
@@ -5330,11 +5403,7 @@
     }
 
     /** Called when the windows associated app window container are drawn. */
-    void onWindowsDrawn(boolean drawn, long timestampNs) {
-        mDrawn = drawn;
-        if (!drawn) {
-            return;
-        }
+    private void onWindowsDrawn(long timestampNs) {
         final TransitionInfoSnapshot info = mStackSupervisor
                 .getActivityMetricsLogger().notifyWindowsDrawn(this, timestampNs);
         final boolean validInfo = info != null;
@@ -5440,7 +5509,7 @@
         if (!nowGone) {
             // If the app is not yet gone, then it can only become visible/drawn.
             if (!nowDrawn) {
-                nowDrawn = reportedDrawn;
+                nowDrawn = mReportedDrawn;
             }
             if (!nowVisible) {
                 nowVisible = reportedVisible;
@@ -5448,9 +5517,11 @@
         }
         if (DEBUG_VISIBILITY) Slog.v(TAG, "VIS " + this + ": interesting="
                 + numInteresting + " visible=" + numVisible);
-        if (nowDrawn != reportedDrawn) {
-            onWindowsDrawn(nowDrawn, SystemClock.elapsedRealtimeNanos());
-            reportedDrawn = nowDrawn;
+        if (nowDrawn != mReportedDrawn) {
+            if (nowDrawn) {
+                onWindowsDrawn(SystemClock.elapsedRealtimeNanos());
+            }
+            mReportedDrawn = nowDrawn;
         }
         if (nowVisible != reportedVisible) {
             if (DEBUG_VISIBILITY) Slog.v(TAG,
@@ -5464,6 +5535,10 @@
         }
     }
 
+    boolean isReportedDrawn() {
+        return mReportedDrawn;
+    }
+
     boolean isClientVisible() {
         return mClientVisible;
     }
@@ -5580,8 +5655,9 @@
         } else {
             // In this case another process added windows using this activity token. So, we call the
             // generic service input dispatch timed out method so that the right process is blamed.
-            return mAtmService.mAmInternal.inputDispatchingTimedOut(
-                    windowPid, false /* aboveSystem */, reason) < 0;
+            long timeoutMillis = mAtmService.mAmInternal.inputDispatchingTimedOut(
+                    windowPid, false /* aboveSystem */, reason);
+            return timeoutMillis <= 0;
         }
     }
 
@@ -7561,6 +7637,11 @@
     }
 
     @Override
+    boolean canCustomizeAppTransition() {
+        return true;
+    }
+
+    @Override
     public String toString() {
         if (stringName != null) {
             return stringName + " t" + (task == null ? INVALID_TASK_ID : task.mTaskId) +
@@ -7597,7 +7678,7 @@
         proto.write(VISIBLE_REQUESTED, mVisibleRequested);
         proto.write(CLIENT_VISIBLE, mClientVisible);
         proto.write(DEFER_HIDING_CLIENT, mDeferHidingClient);
-        proto.write(REPORTED_DRAWN, reportedDrawn);
+        proto.write(REPORTED_DRAWN, mReportedDrawn);
         proto.write(REPORTED_VISIBLE, reportedVisible);
         proto.write(NUM_INTERESTING_WINDOWS, mNumInterestingWindows);
         proto.write(NUM_DRAWN_WINDOWS, mNumDrawnWindows);
diff --git a/services/core/java/com/android/server/wm/ActivityStackSupervisor.java b/services/core/java/com/android/server/wm/ActivityStackSupervisor.java
index 04b1edc..2c475e0 100644
--- a/services/core/java/com/android/server/wm/ActivityStackSupervisor.java
+++ b/services/core/java/com/android/server/wm/ActivityStackSupervisor.java
@@ -74,9 +74,9 @@
 import static com.android.server.wm.Task.ActivityState.PAUSED;
 import static com.android.server.wm.Task.ActivityState.PAUSING;
 import static com.android.server.wm.Task.FLAG_FORCE_HIDDEN_FOR_PINNED_TASK;
+import static com.android.server.wm.Task.LOCK_TASK_AUTH_ALLOWLISTED;
 import static com.android.server.wm.Task.LOCK_TASK_AUTH_LAUNCHABLE;
 import static com.android.server.wm.Task.LOCK_TASK_AUTH_LAUNCHABLE_PRIV;
-import static com.android.server.wm.Task.LOCK_TASK_AUTH_WHITELISTED;
 import static com.android.server.wm.Task.REPARENT_KEEP_STACK_AT_FRONT;
 import static com.android.server.wm.Task.TAG_CLEANUP;
 import static com.android.server.wm.WindowContainer.AnimationFlags.PARENTS;
@@ -130,6 +130,7 @@
 import android.util.Slog;
 import android.util.SparseArray;
 import android.util.SparseIntArray;
+import android.view.Display;
 
 import com.android.internal.R;
 import com.android.internal.annotations.GuardedBy;
@@ -788,7 +789,7 @@
             final LockTaskController lockTaskController = mService.getLockTaskController();
             if (task.mLockTaskAuth == LOCK_TASK_AUTH_LAUNCHABLE
                     || task.mLockTaskAuth == LOCK_TASK_AUTH_LAUNCHABLE_PRIV
-                    || (task.mLockTaskAuth == LOCK_TASK_AUTH_WHITELISTED
+                    || (task.mLockTaskAuth == LOCK_TASK_AUTH_ALLOWLISTED
                             && lockTaskController.getLockTaskModeState()
                                     == LOCK_TASK_MODE_LOCKED)) {
                 lockTaskController.startLockTaskMode(task, false, 0 /* blank UID */);
@@ -891,7 +892,7 @@
                 // This is the first time we failed -- restart process and
                 // retry.
                 r.launchFailed = true;
-                proc.removeActivity(r);
+                proc.removeActivity(r, true /* keepAssociation */);
                 throw e;
             }
         } finally {
@@ -1090,9 +1091,9 @@
         // Check if caller is already present on display
         final boolean uidPresentOnDisplay = displayContent.isUidPresent(callingUid);
 
-        final int displayOwnerUid = displayContent.mDisplay.getOwnerUid();
-        if (displayContent.mDisplay.getType() == TYPE_VIRTUAL && displayOwnerUid != SYSTEM_UID) {
-            // Limit launching on virtual displays, because their contents can be read from Surface
+        final Display display = displayContent.mDisplay;
+        if (!display.isTrusted()) {
+            // Limit launching on untrusted displays because their contents can be read from Surface
             // by apps that created them.
             if ((aInfo.flags & ActivityInfo.FLAG_ALLOW_EMBEDDED) == 0) {
                 if (DEBUG_TASKS) Slog.d(TAG, "Launch on display check:"
@@ -1116,7 +1117,7 @@
         }
 
         // Check if the caller is the owner of the display.
-        if (displayOwnerUid == callingUid) {
+        if (display.getOwnerUid() == callingUid) {
             if (DEBUG_TASKS) Slog.d(TAG, "Launch on display check:"
                     + " allow launch for owner of the display");
             return true;
diff --git a/services/core/java/com/android/server/wm/ActivityStarter.java b/services/core/java/com/android/server/wm/ActivityStarter.java
index f7cb014..f615838 100644
--- a/services/core/java/com/android/server/wm/ActivityStarter.java
+++ b/services/core/java/com/android/server/wm/ActivityStarter.java
@@ -1766,8 +1766,8 @@
         } else if (mInTask != null) {
             return mInTask;
         } else {
-            final Task stack = getLaunchStack(mStartActivity, mLaunchFlags,
-                    null /* task */, mOptions);
+            final Task stack = getLaunchStack(mStartActivity, mLaunchFlags, null /* task */,
+                    mOptions);
             final ActivityRecord top = stack.getTopNonFinishingActivity();
             if (top != null) {
                 return top.getTask();
@@ -1870,13 +1870,7 @@
             return START_SUCCESS;
         }
 
-        boolean clearTaskForReuse = false;
         if (reusedTask != null) {
-            if (mStartActivity.getTask() == null) {
-                mStartActivity.setTaskForReuse(reusedTask);
-                clearTaskForReuse = true;
-            }
-
             if (targetTask.intent == null) {
                 // This task was started because of movement of the activity based on
                 // affinity...
@@ -1923,13 +1917,6 @@
         complyActivityFlags(targetTask,
                 reusedTask != null ? reusedTask.getTopNonFinishingActivity() : null, intentGrants);
 
-        if (clearTaskForReuse) {
-            // Clear task for re-use so later code to methods
-            // {@link #setTaskFromReuseOrCreateNewTask}, {@link #setTaskFromSourceRecord}, or
-            // {@link #setTaskFromInTask} can parent it to the task.
-            mStartActivity.setTaskForReuse(null);
-        }
-
         if (mAddingToTask) {
             return START_SUCCESS;
         }
@@ -2023,8 +2010,6 @@
             // of history or if it is finished immediately), thus disassociating the task. Also note
             // that mReuseTask is reset as a result of {@link Task#performClearTaskLocked}
             // launching another activity.
-            // TODO(b/36119896):  We shouldn't trigger activity launches in this path since we are
-            // already launching one.
             targetTask.performClearTaskLocked();
             targetTask.setIntent(mStartActivity);
             mAddingToTask = true;
@@ -2517,8 +2502,8 @@
                     intentActivity.setTaskToAffiliateWith(mSourceRecord.getTask());
                 }
 
-                final Task launchStack =
-                        getLaunchStack(mStartActivity, mLaunchFlags, intentTask, mOptions);
+                final Task launchStack = getLaunchStack(mStartActivity, mLaunchFlags, intentTask,
+                        mOptions);
                 if (launchStack == null || launchStack == mTargetStack) {
                     // Do not set mMovedToFront to true below for split-screen-top stack, or
                     // START_TASK_TO_FRONT will be returned and trigger unexpected animations when a
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java b/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java
index 777ddda..2dc22ec 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java
@@ -528,8 +528,8 @@
     public abstract void onActiveUidsCleared();
     public abstract void onUidProcStateChanged(int uid, int procState);
 
-    public abstract void onUidAddedToPendingTempWhitelist(int uid, String tag);
-    public abstract void onUidRemovedFromPendingTempWhitelist(int uid);
+    public abstract void onUidAddedToPendingTempAllowlist(int uid, String tag);
+    public abstract void onUidRemovedFromPendingTempAllowlist(int uid);
 
     /** Handle app crash event in {@link android.app.IActivityController} if there is one. */
     public abstract boolean handleAppCrashInActivityController(String processName, int pid,
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
index c4af3e2..403f225 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
@@ -50,6 +50,7 @@
 import static android.os.FactoryTest.FACTORY_TEST_HIGH_LEVEL;
 import static android.os.FactoryTest.FACTORY_TEST_LOW_LEVEL;
 import static android.os.FactoryTest.FACTORY_TEST_OFF;
+import static android.os.IInputConstants.DEFAULT_DISPATCHING_TIMEOUT_MILLIS;
 import static android.os.Process.FIRST_APPLICATION_UID;
 import static android.os.Process.SYSTEM_UID;
 import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER;
@@ -182,7 +183,6 @@
 import android.graphics.Bitmap;
 import android.graphics.Point;
 import android.graphics.Rect;
-import android.metrics.LogMaker;
 import android.net.Uri;
 import android.os.Binder;
 import android.os.Build;
@@ -237,12 +237,9 @@
 import com.android.internal.app.AssistUtils;
 import com.android.internal.app.IVoiceInteractor;
 import com.android.internal.app.ProcessMap;
-import com.android.internal.logging.MetricsLogger;
-import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
 import com.android.internal.messages.nano.SystemMessageProto.SystemMessage;
 import com.android.internal.notification.SystemNotificationChannels;
 import com.android.internal.os.TransferPipe;
-import com.android.internal.os.logging.MetricsLoggerWrapper;
 import com.android.internal.policy.IKeyguardDismissCallback;
 import com.android.internal.policy.KeyguardDismissCallback;
 import com.android.internal.util.ArrayUtils;
@@ -254,6 +251,7 @@
 import com.android.server.AttributeCache;
 import com.android.server.LocalServices;
 import com.android.server.SystemService;
+import com.android.server.SystemService.TargetUser;
 import com.android.server.SystemServiceManager;
 import com.android.server.UiThread;
 import com.android.server.Watchdog;
@@ -313,10 +311,8 @@
     private static final String TAG_LOCKTASK = TAG + POSTFIX_LOCKTASK;
     private static final String TAG_CONFIGURATION = TAG + POSTFIX_CONFIGURATION;
 
-    // How long we wait until we timeout on key dispatching.
-    public static final int KEY_DISPATCHING_TIMEOUT_MS = 5 * 1000;
     // How long we wait until we timeout on key dispatching during instrumentation.
-    static final int INSTRUMENTATION_KEY_DISPATCHING_TIMEOUT_MS = 60 * 1000;
+    static final long INSTRUMENTATION_KEY_DISPATCHING_TIMEOUT_MILLIS = 60 * 1000;
     // How long we permit background activity starts after an activity in the process
     // started or finished.
     static final long ACTIVITY_BG_START_GRACE_PERIOD_MS = 10 * 1000;
@@ -384,7 +380,7 @@
     private AppOpsManager mAppOpsManager;
     /** All active uids in the system. */
     private final MirrorActiveUids mActiveUids = new MirrorActiveUids();
-    private final SparseArray<String> mPendingTempWhitelist = new SparseArray<>();
+    private final SparseArray<String> mPendingTempAllowlist = new SparseArray<>();
     /** All processes currently running that might have a window organized by name. */
     final ProcessMap<WindowProcessController> mProcessNames = new ProcessMap<>();
     /** All processes we currently have running mapped by pid and uid */
@@ -1027,16 +1023,17 @@
         }
 
         @Override
-        public void onUnlockUser(int userId) {
+        public void onUserUnlocking(@NonNull TargetUser user) {
             synchronized (mService.getGlobalLock()) {
-                mService.mStackSupervisor.onUserUnlocked(userId);
+                mService.mStackSupervisor.onUserUnlocked(user.getUserIdentifier());
             }
         }
 
         @Override
-        public void onCleanupUser(int userId) {
+        public void onUserStopped(@NonNull TargetUser user) {
             synchronized (mService.getGlobalLock()) {
-                mService.mStackSupervisor.mLaunchParamsPersister.onCleanupUser(userId);
+                mService.mStackSupervisor.mLaunchParamsPersister
+                        .onCleanupUser(user.getUserIdentifier());
             }
         }
 
@@ -1109,7 +1106,7 @@
 
     @Override
     public int startActivityIntentSender(IApplicationThread caller, IIntentSender target,
-            IBinder whitelistToken, Intent fillInIntent, String resolvedType, IBinder resultTo,
+            IBinder allowlistToken, Intent fillInIntent, String resolvedType, IBinder resultTo,
             String resultWho, int requestCode, int flagsMask, int flagsValues, Bundle bOptions) {
         enforceNotIsolatedCaller("startActivityIntentSender");
         // Refuse possible leaked file descriptors
@@ -1132,7 +1129,7 @@
                 mAppSwitchesAllowedTime = 0;
             }
         }
-        return pir.sendInner(0, fillInIntent, resolvedType, whitelistToken, null, null,
+        return pir.sendInner(0, fillInIntent, resolvedType, allowlistToken, null, null,
                 resultTo, resultWho, requestCode, flagsMask, flagsValues, bOptions);
     }
 
@@ -3002,13 +2999,7 @@
 
     @Override
     public void stopLockTaskModeByToken(IBinder token) {
-        synchronized (mGlobalLock) {
-            final ActivityRecord r = ActivityRecord.forTokenLocked(token);
-            if (r == null) {
-                return;
-            }
-            stopLockTaskModeInternal(r.getTask(), false /* isSystemCaller */);
-        }
+        stopLockTaskModeInternal(token, false /* isSystemCaller */);
     }
 
     /**
@@ -3036,7 +3027,7 @@
         // system or a specific app.
         // * System-initiated requests will only start the pinned mode (screen pinning)
         // * App-initiated requests
-        //   - will put the device in fully locked mode (LockTask), if the app is whitelisted
+        //   - will put the device in fully locked mode (LockTask), if the app is allowlisted
         //   - will start the pinned mode, otherwise
         final int callingUid = Binder.getCallingUid();
         long ident = Binder.clearCallingIdentity();
@@ -3050,11 +3041,19 @@
         }
     }
 
-    private void stopLockTaskModeInternal(@Nullable Task task, boolean isSystemCaller) {
+    private void stopLockTaskModeInternal(@Nullable IBinder token, boolean isSystemCaller) {
         final int callingUid = Binder.getCallingUid();
         long ident = Binder.clearCallingIdentity();
         try {
             synchronized (mGlobalLock) {
+                Task task = null;
+                if (token != null) {
+                    final ActivityRecord r = ActivityRecord.forTokenLocked(token);
+                    if (r == null) {
+                        return;
+                    }
+                    task = r.getTask();
+                }
                 getLockTaskController().stopLockTaskMode(task, isSystemCaller, callingUid);
             }
             // Launch in-call UI if a call is ongoing. This is necessary to allow stopping the lock
@@ -3076,7 +3075,7 @@
                     "updateLockTaskPackages()");
         }
         synchronized (mGlobalLock) {
-            if (DEBUG_LOCKTASK) Slog.w(TAG_LOCKTASK, "Whitelisting " + userId + ":"
+            if (DEBUG_LOCKTASK) Slog.w(TAG_LOCKTASK, "Allowlisting " + userId + ":"
                     + Arrays.toString(packages));
             getLockTaskController().updateLockTaskPackages(userId, packages);
         }
@@ -4094,10 +4093,6 @@
                         final Task stack = r.getRootTask();
                         stack.setPictureInPictureAspectRatio(aspectRatio);
                         stack.setPictureInPictureActions(actions);
-                        MetricsLoggerWrapper.logPictureInPictureEnter(mContext,
-                                r.info.applicationInfo.uid, r.shortComponentName,
-                                r.supportsEnterPipOnTaskSwitch);
-                        logPictureInPictureArgs(params);
                     }
                 };
 
@@ -4141,7 +4136,6 @@
                             r.pictureInPictureArgs.getAspectRatio());
                     stack.setPictureInPictureActions(r.pictureInPictureArgs.getActions());
                 }
-                logPictureInPictureArgs(params);
             }
         } finally {
             Binder.restoreCallingIdentity(origId);
@@ -4155,18 +4149,6 @@
         return 3;
     }
 
-    private void logPictureInPictureArgs(PictureInPictureParams params) {
-        if (params.hasSetActions()) {
-            MetricsLogger.histogram(mContext, "tron_varz_picture_in_picture_actions_count",
-                    params.getActions().size());
-        }
-        if (params.hasSetAspectRatio()) {
-            LogMaker lm = new LogMaker(MetricsEvent.ACTION_PICTURE_IN_PICTURE_ASPECT_RATIO_CHANGED);
-            lm.addTaggedData(MetricsEvent.PICTURE_IN_PICTURE_ASPECT_RATIO, params.getAspectRatio());
-            MetricsLogger.action(lm);
-        }
-    }
-
     /**
      * Checks the state of the system and the activity associated with the given {@param token} to
      * verify that picture-in-picture is supported for that activity.
@@ -5365,24 +5347,18 @@
         return mAmInternal.isBackgroundActivityStartsEnabled();
     }
 
-    void enableScreenAfterBoot(boolean booted) {
-        writeBootProgressEnableScreen(SystemClock.uptimeMillis());
-        mWindowManager.enableScreenAfterBoot();
-
-        synchronized (mGlobalLock) {
-            updateEventDispatchingLocked(booted);
-        }
-    }
-
-    static long getInputDispatchingTimeoutLocked(ActivityRecord r) {
+    static long getInputDispatchingTimeoutMillisLocked(ActivityRecord r) {
         if (r == null || !r.hasProcess()) {
-            return KEY_DISPATCHING_TIMEOUT_MS;
+            return DEFAULT_DISPATCHING_TIMEOUT_MILLIS;
         }
-        return getInputDispatchingTimeoutLocked(r.app);
+        return getInputDispatchingTimeoutMillisLocked(r.app);
     }
 
-    private static long getInputDispatchingTimeoutLocked(WindowProcessController r) {
-        return r != null ? r.getInputDispatchingTimeout() : KEY_DISPATCHING_TIMEOUT_MS;
+    private static long getInputDispatchingTimeoutMillisLocked(WindowProcessController r) {
+        if (r == null) {
+            return DEFAULT_DISPATCHING_TIMEOUT_MILLIS;
+        }
+        return r.getInputDispatchingTimeoutMillis();
     }
 
     /**
@@ -5975,11 +5951,11 @@
     }
 
     /**
-     * @return whitelist tag for a uid from mPendingTempWhitelist, null if not currently on
-     * the whitelist
+     * @return allowlist tag for a uid from mPendingTempAllowlist, null if not currently on
+     * the allowlist
      */
-    String getPendingTempWhitelistTagForUidLocked(int uid) {
-        return mPendingTempWhitelist.get(uid);
+    String getPendingTempAllowlistTagForUidLocked(int uid) {
+        return mPendingTempAllowlist.get(uid);
     }
 
     void logAppTooSlow(WindowProcessController app, long startTime, String msg) {
@@ -6464,9 +6440,9 @@
 
         @Override
         public void enableScreenAfterBoot(boolean booted) {
+            writeBootProgressEnableScreen(SystemClock.uptimeMillis());
+            mWindowManager.enableScreenAfterBoot();
             synchronized (mGlobalLock) {
-                writeBootProgressEnableScreen(SystemClock.uptimeMillis());
-                mWindowManager.enableScreenAfterBoot();
                 updateEventDispatchingLocked(booted);
             }
         }
@@ -6789,11 +6765,14 @@
         public void handleAppDied(WindowProcessController wpc, boolean restarting,
                 Runnable finishInstrumentationCallback) {
             synchronized (mGlobalLockWithoutBoost) {
-                // Remove this application's activities from active lists.
-                boolean hasVisibleActivities = mRootWindowContainer.handleAppDied(wpc);
-
-                wpc.clearRecentTasks();
-                wpc.clearActivities();
+                mStackSupervisor.beginDeferResume();
+                final boolean hasVisibleActivities;
+                try {
+                    // Remove this application's activities from active lists.
+                    hasVisibleActivities = wpc.handleAppDied();
+                } finally {
+                    mStackSupervisor.endDeferResume();
+                }
 
                 if (wpc.isInstrumenting()) {
                     finishInstrumentationCallback.run();
@@ -7318,16 +7297,16 @@
         }
 
         @Override
-        public void onUidAddedToPendingTempWhitelist(int uid, String tag) {
+        public void onUidAddedToPendingTempAllowlist(int uid, String tag) {
             synchronized (mGlobalLockWithoutBoost) {
-                mPendingTempWhitelist.put(uid, tag);
+                mPendingTempAllowlist.put(uid, tag);
             }
         }
 
         @Override
-        public void onUidRemovedFromPendingTempWhitelist(int uid) {
+        public void onUidRemovedFromPendingTempAllowlist(int uid) {
             synchronized (mGlobalLockWithoutBoost) {
-                mPendingTempWhitelist.remove(uid);
+                mPendingTempAllowlist.remove(uid);
             }
         }
 
diff --git a/services/core/java/com/android/server/wm/AppTransition.java b/services/core/java/com/android/server/wm/AppTransition.java
index 3abc54f..f76108f 100644
--- a/services/core/java/com/android/server/wm/AppTransition.java
+++ b/services/core/java/com/android/server/wm/AppTransition.java
@@ -67,10 +67,10 @@
 import static com.android.internal.R.styleable.WindowAnimation_wallpaperIntraOpenExitAnimation;
 import static com.android.internal.R.styleable.WindowAnimation_wallpaperOpenEnterAnimation;
 import static com.android.internal.R.styleable.WindowAnimation_wallpaperOpenExitAnimation;
+import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_APP_TRANSITIONS;
+import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_APP_TRANSITIONS_ANIM;
 import static com.android.server.wm.AppTransitionProto.APP_TRANSITION_STATE;
 import static com.android.server.wm.AppTransitionProto.LAST_USED_APP_TRANSITION;
-import static com.android.server.wm.ProtoLogGroup.WM_DEBUG_APP_TRANSITIONS;
-import static com.android.server.wm.ProtoLogGroup.WM_DEBUG_APP_TRANSITIONS_ANIM;
 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_ANIM;
 import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
 import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
@@ -127,11 +127,11 @@
 
 import com.android.internal.R;
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.protolog.common.ProtoLog;
 import com.android.internal.util.DumpUtils.Dump;
 import com.android.internal.util.function.pooled.PooledLambda;
 import com.android.internal.util.function.pooled.PooledPredicate;
 import com.android.server.AttributeCache;
-import com.android.server.protolog.common.ProtoLog;
 import com.android.server.wm.animation.ClipRectLRAnimation;
 import com.android.server.wm.animation.ClipRectTBAnimation;
 import com.android.server.wm.animation.CurvedTranslateAnimation;
@@ -203,6 +203,7 @@
     private static final int NEXT_TRANSIT_TYPE_REMOTE = 10;
 
     private int mNextAppTransitionType = NEXT_TRANSIT_TYPE_NONE;
+    private boolean mNextAppTransitionOverrideRequested;
 
     // These are the possible states for the enter/exit activities during a thumbnail transition
     private static final int THUMBNAIL_TRANSITION_ENTER_SCALE_UP = 0;
@@ -337,6 +338,11 @@
         mNextAppTransitionFlags |= flags;
         setLastAppTransition(TRANSIT_UNSET, null, null, null);
         updateBooster();
+        if (isTransitionSet()) {
+            removeAppTransitionTimeoutCallbacks();
+            mHandler.postDelayed(mHandleAppTransitionTimeoutRunnable,
+                    APP_TRANSITION_TIMEOUT_MS);
+        }
     }
 
     void setLastAppTransition(int transit, ActivityRecord openingApp, ActivityRecord closingApp,
@@ -454,6 +460,7 @@
 
     void clear() {
         mNextAppTransitionType = NEXT_TRANSIT_TYPE_NONE;
+        mNextAppTransitionOverrideRequested = false;
         mNextAppTransitionPackage = null;
         mNextAppTransitionAnimationsSpecs.clear();
         mRemoteAnimationController = null;
@@ -1574,6 +1581,7 @@
      */
     boolean canSkipFirstFrame() {
         return mNextAppTransitionType != NEXT_TRANSIT_TYPE_CUSTOM
+                && !mNextAppTransitionOverrideRequested
                 && mNextAppTransitionType != NEXT_TRANSIT_TYPE_CUSTOM_IN_PLACE
                 && mNextAppTransitionType != NEXT_TRANSIT_TYPE_CLIP_REVEAL
                 && mNextAppTransition != TRANSIT_KEYGUARD_GOING_AWAY;
@@ -1608,6 +1616,11 @@
             int orientation, Rect frame, Rect displayFrame, Rect insets,
             @Nullable Rect surfaceInsets, @Nullable Rect stableInsets, boolean isVoiceInteraction,
             boolean freeform, WindowContainer container) {
+
+        if (mNextAppTransitionOverrideRequested && container.canCustomizeAppTransition()) {
+            mNextAppTransitionType = NEXT_TRANSIT_TYPE_CUSTOM;
+        }
+
         Animation a;
         if (isKeyguardGoingAwayTransit(transit) && enter) {
             a = loadKeyguardExitAnimation(transit);
@@ -1648,7 +1661,6 @@
                     "applyAnimation: anim=%s nextAppTransition=ANIM_CUSTOM transit=%s "
                             + "isEntrance=%b Callers=%s",
                     a, appTransitionToString(transit), enter, Debug.getCallers(3));
-            setAppTransitionFinishedCallbackIfNeeded(a);
         } else if (mNextAppTransitionType == NEXT_TRANSIT_TYPE_CUSTOM_IN_PLACE) {
             a = loadAnimationRes(mNextAppTransitionPackage, mNextAppTransitionInPlace);
             ProtoLog.v(WM_DEBUG_APP_TRANSITIONS_ANIM,
@@ -1775,6 +1787,7 @@
                     a, animAttr, appTransitionToString(transit), enter,
                     Debug.getCallers(3));
         }
+        setAppTransitionFinishedCallbackIfNeeded(a);
         return a;
     }
 
@@ -1816,7 +1829,7 @@
             IRemoteCallback startedCallback, IRemoteCallback endedCallback) {
         if (canOverridePendingAppTransition()) {
             clear();
-            mNextAppTransitionType = NEXT_TRANSIT_TYPE_CUSTOM;
+            mNextAppTransitionOverrideRequested = true;
             mNextAppTransitionPackage = packageName;
             mNextAppTransitionEnter = enterAnim;
             mNextAppTransitionExit = exitAnim;
@@ -2134,15 +2147,16 @@
             pw.print(prefix); pw.print("mNextAppTransitionType=");
                     pw.println(transitTypeToString());
         }
+        if (mNextAppTransitionOverrideRequested
+                || mNextAppTransitionType == NEXT_TRANSIT_TYPE_CUSTOM) {
+            pw.print(prefix); pw.print("mNextAppTransitionPackage=");
+            pw.println(mNextAppTransitionPackage);
+            pw.print(prefix); pw.print("mNextAppTransitionEnter=0x");
+            pw.print(Integer.toHexString(mNextAppTransitionEnter));
+            pw.print(" mNextAppTransitionExit=0x");
+            pw.println(Integer.toHexString(mNextAppTransitionExit));
+        }
         switch (mNextAppTransitionType) {
-            case NEXT_TRANSIT_TYPE_CUSTOM:
-                pw.print(prefix); pw.print("mNextAppTransitionPackage=");
-                        pw.println(mNextAppTransitionPackage);
-                pw.print(prefix); pw.print("mNextAppTransitionEnter=0x");
-                        pw.print(Integer.toHexString(mNextAppTransitionEnter));
-                        pw.print(" mNextAppTransitionExit=0x");
-                        pw.println(Integer.toHexString(mNextAppTransitionExit));
-                break;
             case NEXT_TRANSIT_TYPE_CUSTOM_IN_PLACE:
                 pw.print(prefix); pw.print("mNextAppTransitionPackage=");
                         pw.println(mNextAppTransitionPackage);
@@ -2229,12 +2243,7 @@
                 setAppTransition(transit, flags);
             }
         }
-        boolean prepared = prepare();
-        if (isTransitionSet()) {
-            removeAppTransitionTimeoutCallbacks();
-            mHandler.postDelayed(mHandleAppTransitionTimeoutRunnable, APP_TRANSITION_TIMEOUT_MS);
-        }
-        return prepared;
+        return prepare();
     }
 
     /**
diff --git a/services/core/java/com/android/server/wm/AppTransitionController.java b/services/core/java/com/android/server/wm/AppTransitionController.java
index 5720e9b..57d51c5 100644
--- a/services/core/java/com/android/server/wm/AppTransitionController.java
+++ b/services/core/java/com/android/server/wm/AppTransitionController.java
@@ -41,14 +41,14 @@
 import static android.view.WindowManager.TRANSIT_WALLPAPER_INTRA_OPEN;
 import static android.view.WindowManager.TRANSIT_WALLPAPER_OPEN;
 
+import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_APP_TRANSITIONS;
+import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_APP_TRANSITIONS_ANIM;
 import static com.android.server.policy.WindowManagerPolicy.FINISH_LAYOUT_REDO_CONFIG;
 import static com.android.server.policy.WindowManagerPolicy.FINISH_LAYOUT_REDO_LAYOUT;
 import static com.android.server.wm.ActivityTaskManagerInternal.APP_TRANSITION_SNAPSHOT;
 import static com.android.server.wm.ActivityTaskManagerInternal.APP_TRANSITION_SPLASH_SCREEN;
 import static com.android.server.wm.ActivityTaskManagerInternal.APP_TRANSITION_WINDOWS_DRAWN;
 import static com.android.server.wm.AppTransition.isKeyguardGoingAwayTransit;
-import static com.android.server.wm.ProtoLogGroup.WM_DEBUG_APP_TRANSITIONS;
-import static com.android.server.wm.ProtoLogGroup.WM_DEBUG_APP_TRANSITIONS_ANIM;
 import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_APP_TRANSITION;
 import static com.android.server.wm.WindowContainer.AnimationFlags.PARENTS;
 import static com.android.server.wm.WindowManagerDebugConfig.SHOW_LIGHT_TRANSACTIONS;
@@ -69,7 +69,7 @@
 import android.view.animation.Animation;
 
 import com.android.internal.annotations.VisibleForTesting;
-import com.android.server.protolog.common.ProtoLog;
+import com.android.internal.protolog.common.ProtoLog;
 
 import java.util.ArrayList;
 import java.util.LinkedList;
diff --git a/services/core/java/com/android/server/wm/BlackFrame.java b/services/core/java/com/android/server/wm/BlackFrame.java
index f563e57..0b2c851 100644
--- a/services/core/java/com/android/server/wm/BlackFrame.java
+++ b/services/core/java/com/android/server/wm/BlackFrame.java
@@ -16,13 +16,13 @@
 
 package com.android.server.wm;
 
-import static com.android.server.wm.ProtoLogGroup.WM_SHOW_SURFACE_ALLOC;
+import static com.android.internal.protolog.ProtoLogGroup.WM_SHOW_SURFACE_ALLOC;
 
 import android.graphics.Rect;
 import android.view.Surface.OutOfResourcesException;
 import android.view.SurfaceControl;
 
-import com.android.server.protolog.common.ProtoLog;
+import com.android.internal.protolog.common.ProtoLog;
 
 import java.io.PrintWriter;
 import java.util.function.Supplier;
diff --git a/services/core/java/com/android/server/wm/DisplayArea.java b/services/core/java/com/android/server/wm/DisplayArea.java
index 6ffb482..8bd42f0 100644
--- a/services/core/java/com/android/server/wm/DisplayArea.java
+++ b/services/core/java/com/android/server/wm/DisplayArea.java
@@ -24,11 +24,11 @@
 import static android.window.DisplayAreaOrganizer.FEATURE_UNDEFINED;
 import static android.window.DisplayAreaOrganizer.FEATURE_WINDOW_TOKENS;
 
+import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_ORIENTATION;
 import static com.android.internal.util.Preconditions.checkState;
 import static com.android.server.wm.DisplayAreaProto.IS_TASK_DISPLAY_AREA;
 import static com.android.server.wm.DisplayAreaProto.NAME;
 import static com.android.server.wm.DisplayAreaProto.WINDOW_CONTAINER;
-import static com.android.server.wm.ProtoLogGroup.WM_DEBUG_ORIENTATION;
 import static com.android.server.wm.WindowContainerChildProto.DISPLAY_AREA;
 
 import android.annotation.Nullable;
@@ -38,8 +38,8 @@
 import android.window.DisplayAreaInfo;
 import android.window.IDisplayAreaOrganizer;
 
+import com.android.internal.protolog.common.ProtoLog;
 import com.android.server.policy.WindowManagerPolicy;
-import com.android.server.protolog.common.ProtoLog;
 
 import java.util.Comparator;
 import java.util.function.BiFunction;
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index fc17053..0215ead 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -74,6 +74,14 @@
 import static android.window.DisplayAreaOrganizer.FEATURE_ROOT;
 import static android.window.DisplayAreaOrganizer.FEATURE_WINDOWED_MAGNIFICATION;
 
+import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_APP_TRANSITIONS;
+import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_BOOT;
+import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_FOCUS;
+import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_FOCUS_LIGHT;
+import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_IME;
+import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_ORIENTATION;
+import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_SCREEN_ON;
+import static com.android.internal.protolog.ProtoLogGroup.WM_SHOW_TRANSACTIONS;
 import static com.android.server.policy.WindowManagerPolicy.FINISH_LAYOUT_REDO_ANIM;
 import static com.android.server.policy.WindowManagerPolicy.FINISH_LAYOUT_REDO_CONFIG;
 import static com.android.server.policy.WindowManagerPolicy.FINISH_LAYOUT_REDO_LAYOUT;
@@ -94,14 +102,6 @@
 import static com.android.server.wm.DisplayContentProto.ROTATION;
 import static com.android.server.wm.DisplayContentProto.SCREEN_ROTATION_ANIMATION;
 import static com.android.server.wm.DisplayContentProto.SINGLE_TASK_INSTANCE;
-import static com.android.server.wm.ProtoLogGroup.WM_DEBUG_APP_TRANSITIONS;
-import static com.android.server.wm.ProtoLogGroup.WM_DEBUG_BOOT;
-import static com.android.server.wm.ProtoLogGroup.WM_DEBUG_FOCUS;
-import static com.android.server.wm.ProtoLogGroup.WM_DEBUG_FOCUS_LIGHT;
-import static com.android.server.wm.ProtoLogGroup.WM_DEBUG_IME;
-import static com.android.server.wm.ProtoLogGroup.WM_DEBUG_ORIENTATION;
-import static com.android.server.wm.ProtoLogGroup.WM_DEBUG_SCREEN_ON;
-import static com.android.server.wm.ProtoLogGroup.WM_SHOW_TRANSACTIONS;
 import static com.android.server.wm.Task.ActivityState.RESUMED;
 import static com.android.server.wm.WindowContainer.AnimationFlags.PARENTS;
 import static com.android.server.wm.WindowContainer.AnimationFlags.TRANSITION;
@@ -117,7 +117,6 @@
 import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
 import static com.android.server.wm.WindowManagerService.H.REPORT_FOCUS_CHANGE;
 import static com.android.server.wm.WindowManagerService.H.REPORT_HARD_KEYBOARD_STATUS_CHANGE;
-import static com.android.server.wm.WindowManagerService.H.REPORT_LOSING_FOCUS;
 import static com.android.server.wm.WindowManagerService.H.UPDATE_MULTI_WINDOW_STACKS;
 import static com.android.server.wm.WindowManagerService.H.WINDOW_HIDE_TIMEOUT;
 import static com.android.server.wm.WindowManagerService.LAYOUT_REPEAT_THRESHOLD;
@@ -203,6 +202,7 @@
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.logging.MetricsLogger;
 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
+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;
@@ -211,7 +211,6 @@
 import com.android.internal.util.function.pooled.PooledPredicate;
 import com.android.server.inputmethod.InputMethodManagerInternal;
 import com.android.server.policy.WindowManagerPolicy;
-import com.android.server.protolog.common.ProtoLog;
 import com.android.server.wm.utils.DisplayRotationUtil;
 import com.android.server.wm.utils.RotationCache;
 import com.android.server.wm.utils.WmDisplayCutout;
@@ -469,12 +468,6 @@
     WindowState mLastFocus = null;
 
     /**
-     * Windows that have lost input focus and are waiting for the new focus window to be displayed
-     * before they are told about this.
-     */
-    ArrayList<WindowState> mLosingFocus = new ArrayList<>();
-
-    /**
      * The foreground app of this display. Windows below this app cannot be the focused window. If
      * the user taps on the area outside of the task of the focused app, we will notify AM about the
      * new task the user wants to interact with.
@@ -899,10 +892,6 @@
             }
         }
 
-        if (!mLosingFocus.isEmpty() && w.isFocused() && w.isDisplayedLw()) {
-            mWmService.mH.obtainMessage(REPORT_LOSING_FOCUS, this).sendToTarget();
-        }
-
         w.updateResizingWindowIfNeeded();
     };
 
@@ -1557,7 +1546,12 @@
             // the heavy operations. This also benefits that the states of multiple activities
             // are handled together.
             r.linkFixedRotationTransform(prevRotatedLaunchingApp);
-            setFixedRotationLaunchingAppUnchecked(r, rotation);
+            if (r != mFixedRotationTransitionListener.mAnimatingRecents) {
+                // Only update the record for normal activity so the display orientation can be
+                // updated when the transition is done if it becomes the top. And the case of
+                // recents can be handled when the recents animation is finished.
+                setFixedRotationLaunchingAppUnchecked(r, rotation);
+            }
             return;
         }
 
@@ -2919,21 +2913,6 @@
         if (mLastFocus != mCurrentFocus) {
             pw.print("  mLastFocus="); pw.println(mLastFocus);
         }
-        if (mLosingFocus.size() > 0) {
-            pw.println();
-            pw.println("  Windows losing focus:");
-            for (int i = mLosingFocus.size() - 1; i >= 0; i--) {
-                final WindowState w = mLosingFocus.get(i);
-                pw.print("  Losing #"); pw.print(i); pw.print(' ');
-                pw.print(w);
-                if (dumpAll) {
-                    pw.println(":");
-                    w.dump(pw, "    ", true);
-                } else {
-                    pw.println();
-                }
-            }
-        }
         pw.print("  mFocusedApp="); pw.println(mFocusedApp);
         if (mLastStatusBarVisibility != 0) {
             pw.print("  mLastStatusBarVisibility=0x");
@@ -3152,7 +3131,6 @@
                 mCurrentFocus, newFocus, getDisplayId(), Debug.getCallers(4));
         final WindowState oldFocus = mCurrentFocus;
         mCurrentFocus = newFocus;
-        mLosingFocus.remove(newFocus);
 
         if (newFocus != null) {
             mWinAddedSinceNullFocus.clear();
@@ -4004,9 +3982,6 @@
 
         mDisplayFrames.onDisplayInfoUpdated(mDisplayInfo,
                 calculateDisplayCutoutForRotation(mDisplayInfo.rotation));
-        // TODO: Not sure if we really need to set the rotation here since we are updating from
-        // the display info above...
-        mDisplayFrames.mRotation = getRotation();
         mDisplayPolicy.beginLayoutLw(mDisplayFrames, getConfiguration().uiMode);
 
         int seq = mLayoutSeq + 1;
@@ -4063,9 +4038,14 @@
         if (DEBUG_SCREENSHOT && inRotation) Slog.v(TAG_WM, "Taking screenshot while rotating");
 
         // Send invalid rect and no width and height since it will screenshot the entire display.
-        Rect frame = new Rect(0, 0, -1, -1);
-        final Bitmap bitmap = SurfaceControl.screenshot(frame, 0, 0, inRotation,
-                mDisplay.getRotation());
+        final IBinder displayToken = SurfaceControl.getInternalDisplayToken();
+        final SurfaceControl.DisplayCaptureArgs captureArgs =
+                new SurfaceControl.DisplayCaptureArgs.Builder(displayToken)
+                        .setUseIdentityTransform(inRotation)
+                        .build();
+        final SurfaceControl.ScreenshotHardwareBuffer screenshotBuffer =
+                SurfaceControl.captureDisplay(captureArgs);
+        final Bitmap bitmap = screenshotBuffer == null ? null : screenshotBuffer.asBitmap();
         if (bitmap == null) {
             Slog.w(TAG_WM, "Failed to take screenshot");
             return null;
diff --git a/services/core/java/com/android/server/wm/DisplayPolicy.java b/services/core/java/com/android/server/wm/DisplayPolicy.java
index 4f6f75d..40fc25b 100644
--- a/services/core/java/com/android/server/wm/DisplayPolicy.java
+++ b/services/core/java/com/android/server/wm/DisplayPolicy.java
@@ -112,6 +112,7 @@
 import static android.view.WindowManagerPolicyConstants.NAV_BAR_LEFT;
 import static android.view.WindowManagerPolicyConstants.NAV_BAR_RIGHT;
 
+import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_SCREEN_ON;
 import static com.android.server.policy.PhoneWindowManager.TOAST_WINDOW_TIMEOUT;
 import static com.android.server.policy.WindowManagerPolicy.FINISH_LAYOUT_REDO_LAYOUT;
 import static com.android.server.policy.WindowManagerPolicy.TRANSIT_ENTER;
@@ -120,7 +121,6 @@
 import static com.android.server.policy.WindowManagerPolicy.TRANSIT_PREVIEW_DONE;
 import static com.android.server.policy.WindowManagerPolicy.TRANSIT_SHOW;
 import static com.android.server.policy.WindowManagerPolicy.WindowManagerFuncs.LID_ABSENT;
-import static com.android.server.wm.ProtoLogGroup.WM_DEBUG_SCREEN_ON;
 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_ANIM;
 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_LAYOUT;
 import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
@@ -187,6 +187,7 @@
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.policy.GestureNavigationSettingsObserver;
 import com.android.internal.policy.ScreenDecorationsUtils;
+import com.android.internal.protolog.common.ProtoLog;
 import com.android.internal.util.ScreenshotHelper;
 import com.android.internal.util.function.TriConsumer;
 import com.android.internal.view.AppearanceRegion;
@@ -199,7 +200,6 @@
 import com.android.server.policy.WindowManagerPolicy.ScreenOnListener;
 import com.android.server.policy.WindowManagerPolicy.WindowManagerFuncs;
 import com.android.server.policy.WindowOrientationListener;
-import com.android.server.protolog.common.ProtoLog;
 import com.android.server.statusbar.StatusBarManagerInternal;
 import com.android.server.wallpaper.WallpaperManagerInternal;
 import com.android.server.wm.utils.InsetUtils;
@@ -246,6 +246,9 @@
                     | View.STATUS_BAR_TRANSPARENT
                     | View.NAVIGATION_BAR_TRANSPARENT;
 
+    private static final int[] SHOW_TYPES_FOR_SWIPE = {ITYPE_NAVIGATION_BAR, ITYPE_STATUS_BAR};
+    private static final int[] SHOW_TYPES_FOR_PANIC = {ITYPE_NAVIGATION_BAR};
+
     private final WindowManagerService mService;
     private final Context mContext;
     private final Context mUiContext;
@@ -3353,8 +3356,15 @@
                 return;
             }
 
+            final InsetsState requestedState = controlTarget.getRequestedInsetsState();
+            final @InsetsType int restorePositionTypes =
+                    (requestedState.getSourceOrDefaultVisibility(ITYPE_NAVIGATION_BAR)
+                            ? Type.navigationBars() : 0)
+                    | (requestedState.getSourceOrDefaultVisibility(ITYPE_STATUS_BAR)
+                            ? Type.statusBars() : 0);
+
             if (swipeTarget == mNavigationBar
-                    && !getInsetsPolicy().isHidden(ITYPE_NAVIGATION_BAR)) {
+                    && (restorePositionTypes & Type.navigationBars()) != 0) {
                 // Don't show status bar when swiping on already visible navigation bar.
                 // But restore the position of navigation bar if it has been moved by the control
                 // target.
@@ -3362,14 +3372,13 @@
                 return;
             }
 
-            int insetsTypesToShow = Type.systemBars();
-
             if (controlTarget.canShowTransient()) {
-                insetsTypesToShow &= ~mDisplayContent.getInsetsPolicy().showTransient(IntArray.wrap(
-                        new int[]{ITYPE_STATUS_BAR, ITYPE_NAVIGATION_BAR}));
-            }
-            if (insetsTypesToShow != 0) {
-                controlTarget.showInsets(insetsTypesToShow, false);
+                // Show transient bars if they are hidden; restore position if they are visible.
+                mDisplayContent.getInsetsPolicy().showTransient(SHOW_TYPES_FOR_SWIPE);
+                controlTarget.showInsets(restorePositionTypes, false);
+            } else {
+                // Restore visibilities and positions of system bars.
+                controlTarget.showInsets(Type.statusBars() | Type.navigationBars(), false);
             }
         } else {
             boolean sb = mStatusBarController.checkShowTransientBarLw();
@@ -3770,8 +3779,7 @@
             // we're no longer on the Keyguard and the screen is ready. We can now request the bars.
             mPendingPanicGestureUptime = 0;
             if (!isNavBarEmpty(vis)) {
-                mDisplayContent.getInsetsPolicy().showTransient(IntArray.wrap(
-                        new int[] {ITYPE_NAVIGATION_BAR}));
+                mDisplayContent.getInsetsPolicy().showTransient(SHOW_TYPES_FOR_PANIC);
             }
         }
 
diff --git a/services/core/java/com/android/server/wm/DisplayRotation.java b/services/core/java/com/android/server/wm/DisplayRotation.java
index c63128c..0206787 100644
--- a/services/core/java/com/android/server/wm/DisplayRotation.java
+++ b/services/core/java/com/android/server/wm/DisplayRotation.java
@@ -22,8 +22,8 @@
 import static android.view.WindowManager.LayoutParams.ROTATION_ANIMATION_ROTATE;
 import static android.view.WindowManager.LayoutParams.ROTATION_ANIMATION_SEAMLESS;
 
+import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_ORIENTATION;
 import static com.android.server.policy.WindowManagerPolicy.WindowManagerFuncs.LID_OPEN;
-import static com.android.server.wm.ProtoLogGroup.WM_DEBUG_ORIENTATION;
 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_ANIM;
 import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
 import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
@@ -58,12 +58,12 @@
 
 import com.android.internal.R;
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.protolog.common.ProtoLog;
 import com.android.internal.util.function.pooled.PooledLambda;
 import com.android.server.LocalServices;
 import com.android.server.UiThread;
 import com.android.server.policy.WindowManagerPolicy;
 import com.android.server.policy.WindowOrientationListener;
-import com.android.server.protolog.common.ProtoLog;
 import com.android.server.statusbar.StatusBarManagerInternal;
 
 import java.io.PrintWriter;
diff --git a/services/core/java/com/android/server/wm/DragState.java b/services/core/java/com/android/server/wm/DragState.java
index f840d92..c9f463b 100644
--- a/services/core/java/com/android/server/wm/DragState.java
+++ b/services/core/java/com/android/server/wm/DragState.java
@@ -16,11 +16,13 @@
 
 package com.android.server.wm;
 
+import static android.os.IInputConstants.DEFAULT_DISPATCHING_TIMEOUT_MILLIS;
+
+import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_ORIENTATION;
+import static com.android.internal.protolog.ProtoLogGroup.WM_SHOW_TRANSACTIONS;
 import static com.android.server.wm.DragDropController.MSG_ANIMATION_END;
 import static com.android.server.wm.DragDropController.MSG_DRAG_END_TIMEOUT;
 import static com.android.server.wm.DragDropController.MSG_TEAR_DOWN_DRAG_AND_DROP_INPUT;
-import static com.android.server.wm.ProtoLogGroup.WM_DEBUG_ORIENTATION;
-import static com.android.server.wm.ProtoLogGroup.WM_SHOW_TRANSACTIONS;
 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_DRAG;
 import static com.android.server.wm.WindowManagerDebugConfig.SHOW_LIGHT_TRANSACTIONS;
 import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
@@ -56,9 +58,9 @@
 import android.view.animation.DecelerateInterpolator;
 import android.view.animation.Interpolator;
 
+import com.android.internal.protolog.common.ProtoLog;
 import com.android.internal.view.IDragAndDropPermissions;
 import com.android.server.LocalServices;
-import com.android.server.protolog.common.ProtoLog;
 
 import java.util.ArrayList;
 
@@ -271,8 +273,7 @@
 
             mDragApplicationHandle = new InputApplicationHandle(new Binder());
             mDragApplicationHandle.name = "drag";
-            mDragApplicationHandle.dispatchingTimeoutNanos =
-                    WindowManagerService.DEFAULT_INPUT_DISPATCHING_TIMEOUT_NANOS;
+            mDragApplicationHandle.dispatchingTimeoutMillis = DEFAULT_DISPATCHING_TIMEOUT_MILLIS;
 
             mDragWindowHandle = new InputWindowHandle(mDragApplicationHandle,
                     display.getDisplayId());
@@ -280,11 +281,11 @@
             mDragWindowHandle.token = mServerChannel.getToken();
             mDragWindowHandle.layoutParamsFlags = 0;
             mDragWindowHandle.layoutParamsType = WindowManager.LayoutParams.TYPE_DRAG;
-            mDragWindowHandle.dispatchingTimeoutNanos =
-                    WindowManagerService.DEFAULT_INPUT_DISPATCHING_TIMEOUT_NANOS;
+            mDragWindowHandle.dispatchingTimeoutMillis = DEFAULT_DISPATCHING_TIMEOUT_MILLIS;
             mDragWindowHandle.visible = true;
-            mDragWindowHandle.canReceiveKeys = false;
-            mDragWindowHandle.hasFocus = true;
+            // Allows the system to consume keys when dragging is active. This can also be used to
+            // modify the drag state on key press. Example, cancel drag on escape key.
+            mDragWindowHandle.focusable = true;
             mDragWindowHandle.hasWallpaper = false;
             mDragWindowHandle.paused = false;
             mDragWindowHandle.ownerPid = Process.myPid();
diff --git a/services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java b/services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java
index e7fbc334..86e2698 100644
--- a/services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java
+++ b/services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java
@@ -16,13 +16,13 @@
 
 package com.android.server.wm;
 
-import static com.android.server.wm.ProtoLogGroup.WM_DEBUG_IME;
+import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_IME;
 
 import android.view.InsetsSource;
 import android.view.WindowInsets;
 
 import com.android.internal.annotations.VisibleForTesting;
-import com.android.server.protolog.common.ProtoLog;
+import com.android.internal.protolog.common.ProtoLog;
 
 import java.io.PrintWriter;
 
diff --git a/services/core/java/com/android/server/wm/InputConsumerImpl.java b/services/core/java/com/android/server/wm/InputConsumerImpl.java
index 852b3672..a79d3bb 100644
--- a/services/core/java/com/android/server/wm/InputConsumerImpl.java
+++ b/services/core/java/com/android/server/wm/InputConsumerImpl.java
@@ -16,6 +16,8 @@
 
 package com.android.server.wm;
 
+import static android.os.IInputConstants.DEFAULT_DISPATCHING_TIMEOUT_MILLIS;
+
 import android.graphics.Point;
 import android.graphics.Rect;
 import android.os.Binder;
@@ -69,19 +71,16 @@
 
         mApplicationHandle = new InputApplicationHandle(new Binder());
         mApplicationHandle.name = name;
-        mApplicationHandle.dispatchingTimeoutNanos =
-                WindowManagerService.DEFAULT_INPUT_DISPATCHING_TIMEOUT_NANOS;
+        mApplicationHandle.dispatchingTimeoutMillis = DEFAULT_DISPATCHING_TIMEOUT_MILLIS;
 
         mWindowHandle = new InputWindowHandle(mApplicationHandle, displayId);
         mWindowHandle.name = name;
         mWindowHandle.token = mServerChannel.getToken();
         mWindowHandle.layoutParamsType = WindowManager.LayoutParams.TYPE_INPUT_CONSUMER;
         mWindowHandle.layoutParamsFlags = 0;
-        mWindowHandle.dispatchingTimeoutNanos =
-                WindowManagerService.DEFAULT_INPUT_DISPATCHING_TIMEOUT_NANOS;
+        mWindowHandle.dispatchingTimeoutMillis = DEFAULT_DISPATCHING_TIMEOUT_MILLIS;
         mWindowHandle.visible = true;
-        mWindowHandle.canReceiveKeys = false;
-        mWindowHandle.hasFocus = false;
+        mWindowHandle.focusable = false;
         mWindowHandle.hasWallpaper = false;
         mWindowHandle.paused = false;
         mWindowHandle.ownerPid = Process.myPid();
diff --git a/services/core/java/com/android/server/wm/InputManagerCallback.java b/services/core/java/com/android/server/wm/InputManagerCallback.java
index 9c4ac89..e166bfc 100644
--- a/services/core/java/com/android/server/wm/InputManagerCallback.java
+++ b/services/core/java/com/android/server/wm/InputManagerCallback.java
@@ -10,6 +10,7 @@
 import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
 import static com.android.server.wm.WindowManagerService.H.ON_POINTER_DOWN_OUTSIDE_FOCUS;
 
+import android.annotation.Nullable;
 import android.os.Build;
 import android.os.Debug;
 import android.os.IBinder;
@@ -173,23 +174,23 @@
      */
     @Override
     public long notifyANR(InputApplicationHandle inputApplicationHandle, IBinder token,
-            String reason) {
+            @Nullable Integer pid, String reason) {
         final long startTime = SystemClock.uptimeMillis();
         try {
-            return notifyANRInner(inputApplicationHandle, token, reason);
+            return notifyANRInner(inputApplicationHandle, token, pid, reason);
         } finally {
             // Log the time because the method is called from InputDispatcher thread. It shouldn't
-            // take too long that may affect input response time.
+            // take too long because it blocks input while executing.
             Slog.d(TAG_WM, "notifyANR took " + (SystemClock.uptimeMillis() - startTime) + "ms");
         }
     }
 
     private long notifyANRInner(InputApplicationHandle inputApplicationHandle, IBinder token,
-            String reason) {
+            @Nullable Integer pid, String reason) {
         ActivityRecord activity = null;
         WindowState windowState = null;
         boolean aboveSystem = false;
-        int windowPid = INVALID_PID;
+        int windowPid = pid != null ? pid : INVALID_PID;
 
         preDumpIfLockTooSlow();
 
@@ -258,18 +259,14 @@
             if (!abort) {
                 // The activity manager declined to abort dispatching.
                 // Wait a bit longer and timeout again later.
-                return activity.mInputDispatchingTimeoutNanos;
+                return TimeUnit.MILLISECONDS.toNanos(activity.mInputDispatchingTimeoutMillis);
             }
         } else if (windowState != null || windowPid != INVALID_PID) {
             // Notify the activity manager about the timeout and let it decide whether
             // to abort dispatching or keep waiting.
-            long timeout = mService.mAmInternal.inputDispatchingTimedOut(windowPid, aboveSystem,
-                    reason);
-            if (timeout >= 0) {
-                // The activity manager declined to abort dispatching.
-                // Wait a bit longer and timeout again later.
-                return timeout * 1000000L; // nanoseconds
-            }
+            long timeoutMillis =
+                    mService.mAmInternal.inputDispatchingTimedOut(windowPid, aboveSystem, reason);
+            return TimeUnit.MILLISECONDS.toNanos(timeoutMillis);
         }
         return 0; // abort dispatching
     }
diff --git a/services/core/java/com/android/server/wm/InputMonitor.java b/services/core/java/com/android/server/wm/InputMonitor.java
index 20f1b9f..16c4942 100644
--- a/services/core/java/com/android/server/wm/InputMonitor.java
+++ b/services/core/java/com/android/server/wm/InputMonitor.java
@@ -41,7 +41,7 @@
 import static android.view.WindowManager.LayoutParams.TYPE_STATUS_BAR;
 import static android.view.WindowManager.LayoutParams.TYPE_WALLPAPER;
 
-import static com.android.server.wm.ProtoLogGroup.WM_DEBUG_FOCUS_LIGHT;
+import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_FOCUS_LIGHT;
 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_INPUT;
 import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
 
@@ -60,8 +60,8 @@
 import android.view.InputWindowHandle;
 import android.view.SurfaceControl;
 
+import com.android.internal.protolog.common.ProtoLog;
 import com.android.server.policy.WindowManagerPolicy;
-import com.android.server.protolog.common.ProtoLog;
 
 import java.io.PrintWriter;
 import java.util.Set;
@@ -227,6 +227,11 @@
 
     WindowManagerPolicy.InputConsumer createInputConsumer(Looper looper, String name,
             InputEventReceiver.Factory inputEventReceiverFactory) {
+        if (!name.contentEquals(INPUT_CONSUMER_NAVIGATION)) {
+            throw new IllegalArgumentException("Illegal input consumer : " + name
+                    + ", display: " + mDisplayId);
+        }
+
         if (mInputConsumers.containsKey(name)) {
             throw new IllegalStateException("Existing input consumer found with name: " + name
                     + ", display: " + mDisplayId);
@@ -256,6 +261,11 @@
                 // stack, and we need FLAG_NOT_TOUCH_MODAL to ensure other events fall through
                 consumer.mWindowHandle.layoutParamsFlags |= FLAG_NOT_TOUCH_MODAL;
                 break;
+            case INPUT_CONSUMER_RECENTS_ANIMATION:
+                break;
+            default:
+                throw new IllegalArgumentException("Illegal input consumer : " + name
+                        + ", display: " + mDisplayId);
         }
         addInputConsumer(name, consumer);
     }
@@ -269,10 +279,9 @@
         flags = child.getSurfaceTouchableRegion(inputWindowHandle, flags);
         inputWindowHandle.layoutParamsFlags = flags;
         inputWindowHandle.layoutParamsType = type;
-        inputWindowHandle.dispatchingTimeoutNanos = child.getInputDispatchingTimeoutNanos();
+        inputWindowHandle.dispatchingTimeoutMillis = child.getInputDispatchingTimeoutMillis();
         inputWindowHandle.visible = isVisible;
-        inputWindowHandle.canReceiveKeys = child.canReceiveKeys();
-        inputWindowHandle.hasFocus = hasFocus;
+        inputWindowHandle.focusable = hasFocus;
         inputWindowHandle.hasWallpaper = hasWallpaper;
         inputWindowHandle.paused = child.mActivityRecord != null ? child.mActivityRecord.paused : false;
         inputWindowHandle.ownerPid = child.mSession.mPid;
@@ -385,7 +394,7 @@
         } else {
             final InputApplicationHandle handle = newApp.mInputApplicationHandle;
             handle.name = newApp.toString();
-            handle.dispatchingTimeoutNanos = newApp.mInputDispatchingTimeoutNanos;
+            handle.dispatchingTimeoutMillis = newApp.mInputDispatchingTimeoutMillis;
 
             mService.mInputManager.setFocusedApplication(mDisplayId, handle);
         }
@@ -463,9 +472,6 @@
             mDisplayContent.forAllWindows(this,
                     true /* traverseTopToBottom */);
 
-            if (mAddWallpaperInputConsumerHandle) {
-                mWallpaperInputConsumer.show(mInputTransaction, 0);
-            }
             if (!mUpdateInputWindowsImmediately) {
                 mDisplayContent.getPendingTransaction().merge(mInputTransaction);
                 mDisplayContent.scheduleAnimation();
@@ -570,11 +576,9 @@
             final String name, final int type, final boolean isVisible) {
         inputWindowHandle.name = name;
         inputWindowHandle.layoutParamsType = type;
-        inputWindowHandle.dispatchingTimeoutNanos =
-                WindowManagerService.DEFAULT_INPUT_DISPATCHING_TIMEOUT_NANOS;
+        inputWindowHandle.dispatchingTimeoutMillis = 0; // it should never receive input
         inputWindowHandle.visible = isVisible;
-        inputWindowHandle.canReceiveKeys = false;
-        inputWindowHandle.hasFocus = false;
+        inputWindowHandle.focusable = false;
         inputWindowHandle.inputFeatures = INPUT_FEATURE_NO_INPUT_CHANNEL;
         inputWindowHandle.scaleFactor = 1;
         inputWindowHandle.layoutParamsFlags =
diff --git a/services/core/java/com/android/server/wm/InsetsControlTarget.java b/services/core/java/com/android/server/wm/InsetsControlTarget.java
index 3ffc26a..5e7ed3f 100644
--- a/services/core/java/com/android/server/wm/InsetsControlTarget.java
+++ b/services/core/java/com/android/server/wm/InsetsControlTarget.java
@@ -17,6 +17,7 @@
 package com.android.server.wm;
 
 import android.inputmethodservice.InputMethodService;
+import android.view.InsetsState;
 import android.view.WindowInsets.Type.InsetsType;
 
 /**
@@ -38,6 +39,13 @@
     }
 
     /**
+     * @return The requested {@link InsetsState} of this target.
+     */
+    default InsetsState getRequestedInsetsState() {
+        return InsetsState.EMPTY;
+    }
+
+    /**
      * Instructs the control target to show inset sources.
      *
      * @param types to specify which types of insets source window should be shown.
diff --git a/services/core/java/com/android/server/wm/InsetsPolicy.java b/services/core/java/com/android/server/wm/InsetsPolicy.java
index b7287e7..18a2503 100644
--- a/services/core/java/com/android/server/wm/InsetsPolicy.java
+++ b/services/core/java/com/android/server/wm/InsetsPolicy.java
@@ -42,7 +42,6 @@
 import android.view.SurfaceControl;
 import android.view.SyncRtSurfaceTransactionApplier;
 import android.view.ViewRootImpl;
-import android.view.WindowInsets.Type.InsetsType;
 import android.view.WindowInsetsAnimation;
 import android.view.WindowInsetsAnimation.Bounds;
 import android.view.WindowInsetsAnimationControlListener;
@@ -153,15 +152,13 @@
         return provider != null && provider.hasWindow() && !provider.getSource().isVisible();
     }
 
-    @InsetsType int showTransient(IntArray types) {
-        @InsetsType int showingTransientTypes = 0;
+    void showTransient(@InternalInsetsType int[] types) {
         boolean changed = false;
-        for (int i = types.size() - 1; i >= 0; i--) {
-            final int type = types.get(i);
+        for (int i = types.length - 1; i >= 0; i--) {
+            final @InternalInsetsType int type = types[i];
             if (!isHidden(type)) {
                 continue;
             }
-            showingTransientTypes |= InsetsState.toPublicType(type);
             if (mShowingTransientTypes.indexOf(type) != -1) {
                 continue;
             }
@@ -189,7 +186,6 @@
                 }
             });
         }
-        return showingTransientTypes;
     }
 
     void hideTransient() {
diff --git a/services/core/java/com/android/server/wm/InsetsSourceProvider.java b/services/core/java/com/android/server/wm/InsetsSourceProvider.java
index f64149c..d1eb795 100644
--- a/services/core/java/com/android/server/wm/InsetsSourceProvider.java
+++ b/services/core/java/com/android/server/wm/InsetsSourceProvider.java
@@ -24,7 +24,7 @@
 import static android.view.ViewRootImpl.NEW_INSETS_MODE_NONE;
 import static android.view.ViewRootImpl.sNewInsetsMode;
 
-import static com.android.server.wm.ProtoLogGroup.WM_DEBUG_IME;
+import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_IME;
 import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_INSETS_CONTROL;
 import static com.android.server.wm.WindowManagerService.H.LAYOUT_AND_ASSIGN_WINDOW_LAYERS_IF_NEEDED;
 
@@ -40,8 +40,8 @@
 import android.view.SurfaceControl.Transaction;
 
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.protolog.common.ProtoLog;
 import com.android.internal.util.function.TriConsumer;
-import com.android.server.protolog.common.ProtoLog;
 import com.android.server.wm.SurfaceAnimator.AnimationType;
 import com.android.server.wm.SurfaceAnimator.OnAnimationFinishedCallback;
 
diff --git a/services/core/java/com/android/server/wm/InsetsStateController.java b/services/core/java/com/android/server/wm/InsetsStateController.java
index 9c978fd..ab1074e 100644
--- a/services/core/java/com/android/server/wm/InsetsStateController.java
+++ b/services/core/java/com/android/server/wm/InsetsStateController.java
@@ -30,7 +30,7 @@
 import static android.view.WindowManager.LayoutParams.TYPE_NAVIGATION_BAR;
 import static android.view.WindowManager.LayoutParams.TYPE_STATUS_BAR;
 
-import static com.android.server.wm.ProtoLogGroup.WM_DEBUG_IME;
+import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_IME;
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -45,8 +45,8 @@
 import android.view.InsetsState.InternalInsetsType;
 import android.view.WindowManager;
 
+import com.android.internal.protolog.common.ProtoLog;
 import com.android.server.inputmethod.InputMethodManagerInternal;
-import com.android.server.protolog.common.ProtoLog;
 
 import java.io.PrintWriter;
 import java.util.ArrayList;
@@ -105,6 +105,10 @@
      * @return The state stripped of the necessary information.
      */
     InsetsState getInsetsForDispatch(@NonNull WindowState target) {
+        final InsetsState rotatedState = target.mToken.getFixedRotationTransformInsetsState();
+        if (rotatedState != null) {
+            return rotatedState;
+        }
         final InsetsSourceProvider provider = target.getControllableInsetProvider();
         final @InternalInsetsType int type = provider != null
                 ? provider.getSource().getType() : ITYPE_INVALID;
diff --git a/services/core/java/com/android/server/wm/Letterbox.java b/services/core/java/com/android/server/wm/Letterbox.java
index 28dcbcd..dccd3a6 100644
--- a/services/core/java/com/android/server/wm/Letterbox.java
+++ b/services/core/java/com/android/server/wm/Letterbox.java
@@ -16,6 +16,7 @@
 
 package com.android.server.wm;
 
+import static android.os.IInputConstants.DEFAULT_DISPATCHING_TIMEOUT_MILLIS;
 import static android.view.SurfaceControl.HIDDEN;
 
 import android.graphics.Point;
@@ -217,8 +218,7 @@
                     | WindowManager.LayoutParams.FLAG_SPLIT_TOUCH
                     | WindowManager.LayoutParams.FLAG_SLIPPERY;
             mWindowHandle.layoutParamsType = WindowManager.LayoutParams.TYPE_INPUT_CONSUMER;
-            mWindowHandle.dispatchingTimeoutNanos =
-                    WindowManagerService.DEFAULT_INPUT_DISPATCHING_TIMEOUT_NANOS;
+            mWindowHandle.dispatchingTimeoutMillis = DEFAULT_DISPATCHING_TIMEOUT_MILLIS;
             mWindowHandle.visible = true;
             mWindowHandle.ownerPid = Process.myPid();
             mWindowHandle.ownerUid = Process.myUid();
diff --git a/services/core/java/com/android/server/wm/LockTaskController.java b/services/core/java/com/android/server/wm/LockTaskController.java
index c7a438d..8ef57f7 100644
--- a/services/core/java/com/android/server/wm/LockTaskController.java
+++ b/services/core/java/com/android/server/wm/LockTaskController.java
@@ -33,11 +33,11 @@
 import static com.android.server.wm.ActivityTaskManagerDebugConfig.POSTFIX_LOCKTASK;
 import static com.android.server.wm.ActivityTaskManagerDebugConfig.TAG_ATM;
 import static com.android.server.wm.ActivityTaskManagerDebugConfig.TAG_WITH_CLASS_NAME;
+import static com.android.server.wm.Task.LOCK_TASK_AUTH_ALLOWLISTED;
 import static com.android.server.wm.Task.LOCK_TASK_AUTH_DONT_LOCK;
 import static com.android.server.wm.Task.LOCK_TASK_AUTH_LAUNCHABLE;
 import static com.android.server.wm.Task.LOCK_TASK_AUTH_LAUNCHABLE_PRIV;
 import static com.android.server.wm.Task.LOCK_TASK_AUTH_PINNABLE;
-import static com.android.server.wm.Task.LOCK_TASK_AUTH_WHITELISTED;
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -264,12 +264,12 @@
     }
 
     /**
-     * @return whether the requested task is allowed to be locked (either whitelisted, or declares
+     * @return whether the requested task is allowed to be locked (either allowlisted, or declares
      * lockTaskMode="always" in the manifest).
      */
-    boolean isTaskWhitelisted(Task task) {
+    boolean isTaskAllowlisted(Task task) {
         switch(task.mLockTaskAuth) {
-            case LOCK_TASK_AUTH_WHITELISTED:
+            case LOCK_TASK_AUTH_ALLOWLISTED:
             case LOCK_TASK_AUTH_LAUNCHABLE:
             case LOCK_TASK_AUTH_LAUNCHABLE_PRIV:
                 return true;
@@ -311,7 +311,7 @@
 
     private boolean isLockTaskModeViolationInternal(Task task, boolean isNewClearTask) {
         // TODO: Double check what's going on here. If the task is already in lock task mode, it's
-        // likely whitelisted, so will return false below.
+        // likely allowlisted, so will return false below.
         if (isTaskLocked(task) && !isNewClearTask) {
             // If the task is already at the top and won't be cleared, then allow the operation
             return false;
@@ -327,7 +327,7 @@
             return false;
         }
 
-        return !(isTaskWhitelisted(task) || mLockTaskModeTasks.isEmpty());
+        return !(isTaskAllowlisted(task) || mLockTaskModeTasks.isEmpty());
     }
 
     private boolean isRecentsAllowed(int userId) {
@@ -356,7 +356,7 @@
                 return false;
             default:
         }
-        return isPackageWhitelisted(userId, packageName);
+        return isPackageAllowlisted(userId, packageName);
     }
 
     private boolean isEmergencyCallTask(Task task) {
@@ -556,7 +556,7 @@
         if (!isSystemCaller) {
             task.mLockTaskUid = callingUid;
             if (task.mLockTaskAuth == LOCK_TASK_AUTH_PINNABLE) {
-                // startLockTask() called by app, but app is not part of lock task whitelist. Show
+                // startLockTask() called by app, but app is not part of lock task allowlist. Show
                 // app pinning request. We will come back here with isSystemCaller true.
                 if (DEBUG_LOCKTASK) Slog.w(TAG_LOCKTASK, "Mode default, asking user");
                 StatusBarManagerInternal statusBarManager = LocalServices.getService(
@@ -649,8 +649,8 @@
 
     /**
      * Update packages that are allowed to be launched in lock task mode.
-     * @param userId Which user this whitelist is associated with
-     * @param packages The whitelist of packages allowed in lock task mode
+     * @param userId Which user this allowlist is associated with
+     * @param packages The allowlist of packages allowed in lock task mode
      * @see #mLockTaskPackages
      */
     void updateLockTaskPackages(int userId, String[] packages) {
@@ -659,19 +659,19 @@
         boolean taskChanged = false;
         for (int taskNdx = mLockTaskModeTasks.size() - 1; taskNdx >= 0; --taskNdx) {
             final Task lockedTask = mLockTaskModeTasks.get(taskNdx);
-            final boolean wasWhitelisted = lockedTask.mLockTaskAuth == LOCK_TASK_AUTH_LAUNCHABLE
-                    || lockedTask.mLockTaskAuth == LOCK_TASK_AUTH_WHITELISTED;
+            final boolean wasAllowlisted = lockedTask.mLockTaskAuth == LOCK_TASK_AUTH_LAUNCHABLE
+                    || lockedTask.mLockTaskAuth == LOCK_TASK_AUTH_ALLOWLISTED;
             lockedTask.setLockTaskAuth();
-            final boolean isWhitelisted = lockedTask.mLockTaskAuth == LOCK_TASK_AUTH_LAUNCHABLE
-                    || lockedTask.mLockTaskAuth == LOCK_TASK_AUTH_WHITELISTED;
+            final boolean isAllowlisted = lockedTask.mLockTaskAuth == LOCK_TASK_AUTH_LAUNCHABLE
+                    || lockedTask.mLockTaskAuth == LOCK_TASK_AUTH_ALLOWLISTED;
 
             if (mLockTaskModeState != LOCK_TASK_MODE_LOCKED
                     || lockedTask.mUserId != userId
-                    || !wasWhitelisted || isWhitelisted) {
+                    || !wasAllowlisted || isAllowlisted) {
                 continue;
             }
 
-            // Terminate locked tasks that have recently lost whitelist authorization.
+            // Terminate locked tasks that have recently lost allowlist authorization.
             if (DEBUG_LOCKTASK) Slog.d(TAG_LOCKTASK, "onLockTaskPackagesUpdated: removing " +
                     lockedTask + " mLockTaskAuth()=" + lockedTask.lockTaskAuthToString());
             removeLockedTask(lockedTask);
@@ -697,17 +697,17 @@
         }
     }
 
-    boolean isPackageWhitelisted(int userId, String pkg) {
+    boolean isPackageAllowlisted(int userId, String pkg) {
         if (pkg == null) {
             return false;
         }
-        String[] whitelist;
-        whitelist = mLockTaskPackages.get(userId);
-        if (whitelist == null) {
+        String[] allowlist;
+        allowlist = mLockTaskPackages.get(userId);
+        if (allowlist == null) {
             return false;
         }
-        for (String whitelistedPkg : whitelist) {
-            if (pkg.equals(whitelistedPkg)) {
+        for (String allowlistedPkg : allowlist) {
+            if (pkg.equals(allowlistedPkg)) {
                 return true;
             }
         }
diff --git a/services/core/java/com/android/server/wm/PolicyControl.java b/services/core/java/com/android/server/wm/PolicyControl.java
index 0f92bc8..61b6e0b 100644
--- a/services/core/java/com/android/server/wm/PolicyControl.java
+++ b/services/core/java/com/android/server/wm/PolicyControl.java
@@ -196,40 +196,40 @@
         private static final String ALL = "*";
         private static final String APPS = "apps";
 
-        private final ArraySet<String> mWhitelist;
-        private final ArraySet<String> mBlacklist;
+        private final ArraySet<String> mAllowlist;
+        private final ArraySet<String> mDenylist;
 
-        private Filter(ArraySet<String> whitelist, ArraySet<String> blacklist) {
-            mWhitelist = whitelist;
-            mBlacklist = blacklist;
+        private Filter(ArraySet<String> allowlist, ArraySet<String> denylist) {
+            mAllowlist = allowlist;
+            mDenylist = denylist;
         }
 
         boolean matches(LayoutParams attrs) {
             if (attrs == null) return false;
             boolean isApp = attrs.type >= WindowManager.LayoutParams.FIRST_APPLICATION_WINDOW
                     && attrs.type <= WindowManager.LayoutParams.LAST_APPLICATION_WINDOW;
-            if (isApp && mBlacklist.contains(APPS)) return false;
-            if (onBlacklist(attrs.packageName)) return false;
-            if (isApp && mWhitelist.contains(APPS)) return true;
-            return onWhitelist(attrs.packageName);
+            if (isApp && mDenylist.contains(APPS)) return false;
+            if (onDenylist(attrs.packageName)) return false;
+            if (isApp && mAllowlist.contains(APPS)) return true;
+            return onAllowlist(attrs.packageName);
         }
 
         boolean matches(String packageName) {
-            return !onBlacklist(packageName) && onWhitelist(packageName);
+            return !onDenylist(packageName) && onAllowlist(packageName);
         }
 
-        private boolean onBlacklist(String packageName) {
-            return mBlacklist.contains(packageName) || mBlacklist.contains(ALL);
+        private boolean onDenylist(String packageName) {
+            return mDenylist.contains(packageName) || mDenylist.contains(ALL);
         }
 
-        private boolean onWhitelist(String packageName) {
-            return mWhitelist.contains(ALL) || mWhitelist.contains(packageName);
+        private boolean onAllowlist(String packageName) {
+            return mAllowlist.contains(ALL) || mAllowlist.contains(packageName);
         }
 
         void dump(PrintWriter pw) {
             pw.print("Filter[");
-            dump("whitelist", mWhitelist, pw); pw.print(',');
-            dump("blacklist", mBlacklist, pw); pw.print(']');
+            dump("allowlist", mAllowlist, pw); pw.print(',');
+            dump("denylist", mDenylist, pw); pw.print(']');
         }
 
         private void dump(String name, ArraySet<String> set, PrintWriter pw) {
@@ -253,18 +253,18 @@
         // e.g. "com.package1", or "apps, com.android.keyguard" or "*"
         static Filter parse(String value) {
             if (value == null) return null;
-            ArraySet<String> whitelist = new ArraySet<String>();
-            ArraySet<String> blacklist = new ArraySet<String>();
+            ArraySet<String> allowlist = new ArraySet<String>();
+            ArraySet<String> denylist = new ArraySet<String>();
             for (String token : value.split(",")) {
                 token = token.trim();
                 if (token.startsWith("-") && token.length() > 1) {
                     token = token.substring(1);
-                    blacklist.add(token);
+                    denylist.add(token);
                 } else {
-                    whitelist.add(token);
+                    allowlist.add(token);
                 }
             }
-            return new Filter(whitelist, blacklist);
+            return new Filter(allowlist, denylist);
         }
     }
 }
diff --git a/services/core/java/com/android/server/wm/RecentTasks.java b/services/core/java/com/android/server/wm/RecentTasks.java
index ba2c0b6..3c64ffb 100644
--- a/services/core/java/com/android/server/wm/RecentTasks.java
+++ b/services/core/java/com/android/server/wm/RecentTasks.java
@@ -655,7 +655,8 @@
         }
         for (int i = mTasks.size() - 1; i >= 0; --i) {
             final Task task = mTasks.get(i);
-            if (task.mUserId == userId && !mService.getLockTaskController().isTaskWhitelisted(task)) {
+            if (task.mUserId == userId
+                    && !mService.getLockTaskController().isTaskAllowlisted(task)) {
                 remove(task);
             }
         }
@@ -1626,8 +1627,8 @@
         }
         if (DEBUG_RECENTS) Slog.d(TAG_RECENTS, "addRecent: adding affilliates starting at "
                 + topIndex + " from intial " + taskIndex);
-        // Find the end of the chain, doing a sanity check along the way.
-        boolean sane = top.mAffiliatedTaskId == task.mAffiliatedTaskId;
+        // Find the end of the chain, doing a validity check along the way.
+        boolean isValid = top.mAffiliatedTaskId == task.mAffiliatedTaskId;
         int endIndex = topIndex;
         Task prev = top;
         while (endIndex < recentsCount) {
@@ -1639,7 +1640,7 @@
                 if (cur.mNextAffiliate != null || cur.mNextAffiliateTaskId != INVALID_TASK_ID) {
                     Slog.wtf(TAG, "Bad chain @" + endIndex
                             + ": first task has next affiliate: " + prev);
-                    sane = false;
+                    isValid = false;
                     break;
                 }
             } else {
@@ -1651,7 +1652,7 @@
                             + " has bad next affiliate "
                             + cur.mNextAffiliate + " id " + cur.mNextAffiliateTaskId
                             + ", expected " + prev);
-                    sane = false;
+                    isValid = false;
                     break;
                 }
             }
@@ -1661,7 +1662,7 @@
                     Slog.wtf(TAG, "Bad chain @" + endIndex
                             + ": last task " + cur + " has previous affiliate "
                             + cur.mPrevAffiliate);
-                    sane = false;
+                    isValid = false;
                 }
                 if (DEBUG_RECENTS) Slog.d(TAG_RECENTS, "addRecent: end of chain @" + endIndex);
                 break;
@@ -1672,7 +1673,7 @@
                             + ": task " + cur + " has previous affiliate "
                             + cur.mPrevAffiliate + " but should be id "
                             + cur.mPrevAffiliate);
-                    sane = false;
+                    isValid = false;
                     break;
                 }
             }
@@ -1681,7 +1682,7 @@
                         + ": task " + cur + " has affiliated id "
                         + cur.mAffiliatedTaskId + " but should be "
                         + task.mAffiliatedTaskId);
-                sane = false;
+                isValid = false;
                 break;
             }
             prev = cur;
@@ -1689,18 +1690,18 @@
             if (endIndex >= recentsCount) {
                 Slog.wtf(TAG, "Bad chain ran off index " + endIndex
                         + ": last task " + prev);
-                sane = false;
+                isValid = false;
                 break;
             }
         }
-        if (sane) {
+        if (isValid) {
             if (endIndex < taskIndex) {
                 Slog.wtf(TAG, "Bad chain @" + endIndex
                         + ": did not extend to task " + task + " @" + taskIndex);
-                sane = false;
+                isValid = false;
             }
         }
-        if (sane) {
+        if (isValid) {
             // All looks good, we can just move all of the affiliated tasks
             // to the top.
             for (int i=topIndex; i<=endIndex; i++) {
diff --git a/services/core/java/com/android/server/wm/RecentsAnimation.java b/services/core/java/com/android/server/wm/RecentsAnimation.java
index d7b43bc..6c41683 100644
--- a/services/core/java/com/android/server/wm/RecentsAnimation.java
+++ b/services/core/java/com/android/server/wm/RecentsAnimation.java
@@ -25,8 +25,8 @@
 import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER;
 import static android.view.WindowManager.TRANSIT_NONE;
 
+import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_RECENTS_ANIMATIONS;
 import static com.android.server.wm.ActivityStackSupervisor.PRESERVE_WINDOWS;
-import static com.android.server.wm.ProtoLogGroup.WM_DEBUG_RECENTS_ANIMATIONS;
 import static com.android.server.wm.RecentsAnimationController.REORDER_KEEP_IN_PLACE;
 import static com.android.server.wm.RecentsAnimationController.REORDER_MOVE_TO_ORIGINAL_POSITION;
 import static com.android.server.wm.RecentsAnimationController.REORDER_MOVE_TO_TOP;
@@ -41,9 +41,9 @@
 import android.util.Slog;
 import android.view.IRecentsAnimationRunner;
 
+import com.android.internal.protolog.common.ProtoLog;
 import com.android.internal.util.function.pooled.PooledLambda;
 import com.android.internal.util.function.pooled.PooledPredicate;
-import com.android.server.protolog.common.ProtoLog;
 import com.android.server.wm.ActivityMetricsLogger.LaunchingState;
 import com.android.server.wm.RecentsAnimationController.RecentsAnimationCallbacks;
 
diff --git a/services/core/java/com/android/server/wm/RecentsAnimationController.java b/services/core/java/com/android/server/wm/RecentsAnimationController.java
index 6b3a5d6..6882dc4 100644
--- a/services/core/java/com/android/server/wm/RecentsAnimationController.java
+++ b/services/core/java/com/android/server/wm/RecentsAnimationController.java
@@ -23,10 +23,10 @@
 import static android.view.WindowManager.INPUT_CONSUMER_RECENTS_ANIMATION;
 import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
 
+import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_RECENTS_ANIMATIONS;
 import static com.android.server.policy.WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER;
 import static com.android.server.wm.ActivityTaskManagerInternal.APP_TRANSITION_RECENTS_ANIM;
 import static com.android.server.wm.AnimationAdapterProto.REMOTE;
-import static com.android.server.wm.ProtoLogGroup.WM_DEBUG_RECENTS_ANIMATIONS;
 import static com.android.server.wm.RemoteAnimationAdapterWrapperProto.TARGET;
 import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_RECENTS;
 import static com.android.server.wm.WindowManagerInternal.AppTransitionListener;
@@ -56,12 +56,12 @@
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.inputmethod.SoftInputShowHideReason;
+import com.android.internal.protolog.common.ProtoLog;
 import com.android.internal.util.function.pooled.PooledConsumer;
 import com.android.internal.util.function.pooled.PooledFunction;
 import com.android.internal.util.function.pooled.PooledLambda;
 import com.android.server.LocalServices;
 import com.android.server.inputmethod.InputMethodManagerInternal;
-import com.android.server.protolog.common.ProtoLog;
 import com.android.server.statusbar.StatusBarManagerInternal;
 import com.android.server.wm.SurfaceAnimator.AnimationType;
 import com.android.server.wm.SurfaceAnimator.OnAnimationFinishedCallback;
@@ -821,7 +821,7 @@
                 : null;
         if (targetAppMainWindow != null) {
             targetAppMainWindow.getBounds(mTmpRect);
-            inputWindowHandle.hasFocus = hasFocus;
+            inputWindowHandle.focusable = hasFocus;
             inputWindowHandle.touchableRegion.set(mTmpRect);
             return true;
         }
diff --git a/services/core/java/com/android/server/wm/RemoteAnimationController.java b/services/core/java/com/android/server/wm/RemoteAnimationController.java
index c255a18..e7461e7 100644
--- a/services/core/java/com/android/server/wm/RemoteAnimationController.java
+++ b/services/core/java/com/android/server/wm/RemoteAnimationController.java
@@ -16,8 +16,8 @@
 
 package com.android.server.wm;
 
+import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_REMOTE_ANIMATIONS;
 import static com.android.server.wm.AnimationAdapterProto.REMOTE;
-import static com.android.server.wm.ProtoLogGroup.WM_DEBUG_REMOTE_ANIMATIONS;
 import static com.android.server.wm.RemoteAnimationAdapterWrapperProto.TARGET;
 import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
 import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
@@ -37,9 +37,9 @@
 import android.view.SurfaceControl;
 import android.view.SurfaceControl.Transaction;
 
+import com.android.internal.protolog.ProtoLogImpl;
+import com.android.internal.protolog.common.ProtoLog;
 import com.android.internal.util.FastPrintWriter;
-import com.android.server.protolog.ProtoLogImpl;
-import com.android.server.protolog.common.ProtoLog;
 import com.android.server.wm.SurfaceAnimator.AnimationType;
 import com.android.server.wm.SurfaceAnimator.OnAnimationFinishedCallback;
 
diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java
index 06dec7c..aeaffd9 100644
--- a/services/core/java/com/android/server/wm/RootWindowContainer.java
+++ b/services/core/java/com/android/server/wm/RootWindowContainer.java
@@ -41,6 +41,11 @@
 import static android.view.WindowManager.TRANSIT_SHOW_SINGLE_TASK_DISPLAY;
 import static android.view.WindowManager.TRANSIT_TASK_TO_BACK;
 
+import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_FOCUS_LIGHT;
+import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_KEEP_SCREEN_ON;
+import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_ORIENTATION;
+import static com.android.internal.protolog.ProtoLogGroup.WM_SHOW_SURFACE_ALLOC;
+import static com.android.internal.protolog.ProtoLogGroup.WM_SHOW_TRANSACTIONS;
 import static com.android.server.policy.PhoneWindowManager.SYSTEM_DIALOG_REASON_ASSIST;
 import static com.android.server.policy.WindowManagerPolicy.FINISH_LAYOUT_REDO_LAYOUT;
 import static com.android.server.policy.WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER;
@@ -59,11 +64,6 @@
 import static com.android.server.wm.ActivityTaskManagerDebugConfig.POSTFIX_TASKS;
 import static com.android.server.wm.ActivityTaskManagerService.ANIMATE;
 import static com.android.server.wm.ActivityTaskManagerService.TAG_SWITCH;
-import static com.android.server.wm.ProtoLogGroup.WM_DEBUG_FOCUS_LIGHT;
-import static com.android.server.wm.ProtoLogGroup.WM_DEBUG_KEEP_SCREEN_ON;
-import static com.android.server.wm.ProtoLogGroup.WM_DEBUG_ORIENTATION;
-import static com.android.server.wm.ProtoLogGroup.WM_SHOW_SURFACE_ALLOC;
-import static com.android.server.wm.ProtoLogGroup.WM_SHOW_TRANSACTIONS;
 import static com.android.server.wm.RecentsAnimationController.REORDER_KEEP_IN_PLACE;
 import static com.android.server.wm.RootWindowContainerProto.IS_HOME_RECENTS_COMPONENT;
 import static com.android.server.wm.RootWindowContainerProto.KEYGUARD_CONTROLLER;
@@ -146,6 +146,7 @@
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.app.ResolverActivity;
+import com.android.internal.protolog.common.ProtoLog;
 import com.android.internal.util.function.pooled.PooledConsumer;
 import com.android.internal.util.function.pooled.PooledFunction;
 import com.android.internal.util.function.pooled.PooledLambda;
@@ -155,7 +156,6 @@
 import com.android.server.am.AppTimeTracker;
 import com.android.server.am.UserState;
 import com.android.server.policy.WindowManagerPolicy;
-import com.android.server.protolog.common.ProtoLog;
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
@@ -737,7 +737,7 @@
                 winAnimator.mSession.mPid, operation);
         final long callingIdentity = Binder.clearCallingIdentity();
         try {
-            // There was some problem...first, do a sanity check of the window list to make sure
+            // There was some problem...first, do a validity check of the window list to make sure
             // we haven't left any dangling surfaces around.
 
             Slog.i(TAG_WM, "Out of memory for surface!  Looking for leaks...");
@@ -811,7 +811,7 @@
     }
 
     // "Something has changed!  Let's make it correct now."
-    // TODO: Super crazy long method that should be broken down...
+    // TODO: Super long method that should be broken down...
     void performSurfacePlacementNoTrace() {
         if (DEBUG_WINDOW_TRACE) Slog.v(TAG, "performSurfacePlacementInner: entry. Called by "
                 + Debug.getCallers(3));
@@ -2155,6 +2155,7 @@
                 // non-fullscreen bounds. Then when this new PIP task exits PIP, it can restore
                 // to its previous freeform bounds.
                 stack.setLastNonFullscreenBounds(task.mLastNonFullscreenBounds);
+                stack.setBounds(task.getBounds());
 
                 // There are multiple activities in the task and moving the top activity should
                 // reveal/leave the other activities in their original task.
@@ -2570,7 +2571,7 @@
         mDisplayAccessUIDs.clear();
         for (int displayNdx = getChildCount() - 1; displayNdx >= 0; --displayNdx) {
             final DisplayContent displayContent = getChildAt(displayNdx);
-            // Only bother calculating the whitelist for private displays
+            // Only bother calculating the allowlist for private displays
             if (displayContent.isPrivate()) {
                 mDisplayAccessUIDs.append(
                         displayContent.mDisplayId, displayContent.getPresentUIDs());
@@ -2751,7 +2752,7 @@
         if (r.app != app) return;
         Slog.w(TAG, "  Force finishing activity "
                 + r.intent.getComponent().flattenToShortString());
-        r.app = null;
+        r.detachFromProcess();
         r.getDisplay().mDisplayContent.prepareAppTransition(
                 TRANSIT_CRASHING_ACTIVITY_CLOSE, false /* alwaysKeepCurrent */);
         r.destroyIfPossible("handleAppCrashed");
@@ -2815,7 +2816,6 @@
      * @param launchParams The resolved launch params to use.
      * @param realCallingPid The pid from {@link ActivityStarter#setRealCallingPid}
      * @param realCallingUid The uid from {@link ActivityStarter#setRealCallingUid}
-     *
      * @return The stack to use for the launch or INVALID_STACK_ID.
      */
     Task getLaunchStack(@Nullable ActivityRecord r,
@@ -2964,10 +2964,8 @@
         // If {@code r} is already in target display area and its task is the same as the candidate
         // task, the intention should be getting a launch stack for the reusable activity, so we can
         // use the existing stack.
-        if (candidateTask != null && (r.getTask() == null || r.getTask() == candidateTask)) {
-            // TODO(b/153920825): Fix incorrect evaluation of attached state
-            final TaskDisplayArea attachedTaskDisplayArea = r.getTask() != null
-                    ? r.getTask().getDisplayArea() : r.getDisplayArea();
+        if (candidateTask != null) {
+            final TaskDisplayArea attachedTaskDisplayArea = candidateTask.getDisplayArea();
             if (attachedTaskDisplayArea == null || attachedTaskDisplayArea == taskDisplayArea) {
                 return candidateTask.getRootTask();
             }
@@ -3104,22 +3102,6 @@
         return null;
     }
 
-    boolean handleAppDied(WindowProcessController app) {
-        if (app.isRemoved()) {
-            // The package of the died process should be force-stopped, so make its activities as
-            // finishing to prevent the process from being started again if the next top (or being
-            // visible) activity also resides in the same process.
-            app.makeFinishingForProcessRemoved();
-        }
-        return reduceOnAllTaskDisplayAreas((taskDisplayArea, result) -> {
-            for (int sNdx = taskDisplayArea.getStackCount() - 1; sNdx >= 0; --sNdx) {
-                final Task stack = taskDisplayArea.getStackAt(sNdx);
-                result |= stack.handleAppDied(app);
-            }
-            return result;
-        }, false /* initValue */);
-    }
-
     void closeSystemDialogActivities(String reason) {
         forAllActivities((r) -> {
             if ((r.info.flags & ActivityInfo.FLAG_FINISH_ON_CLOSE_SYSTEM_DIALOGS) != 0
diff --git a/services/core/java/com/android/server/wm/SafeActivityOptions.java b/services/core/java/com/android/server/wm/SafeActivityOptions.java
index b71ecbb..ede6708 100644
--- a/services/core/java/com/android/server/wm/SafeActivityOptions.java
+++ b/services/core/java/com/android/server/wm/SafeActivityOptions.java
@@ -233,10 +233,10 @@
             Slog.w(TAG, msg);
             throw new SecurityException(msg);
         }
-        // Check if someone tries to launch an unwhitelisted activity into LockTask mode.
+        // Check if someone tries to launch an unallowlisted activity into LockTask mode.
         final boolean lockTaskMode = options.getLockTaskMode();
         if (aInfo != null && lockTaskMode
-                && !supervisor.mService.getLockTaskController().isPackageWhitelisted(
+                && !supervisor.mService.getLockTaskController().isPackageAllowlisted(
                         UserHandle.getUserId(callingUid), aInfo.packageName)) {
             final String msg = "Permission Denial: starting " + getIntentString(intent)
                     + " from " + callerApp + " (pid=" + callingPid
diff --git a/services/core/java/com/android/server/wm/ScreenRotationAnimation.java b/services/core/java/com/android/server/wm/ScreenRotationAnimation.java
index d7b8fb0..25732e7 100644
--- a/services/core/java/com/android/server/wm/ScreenRotationAnimation.java
+++ b/services/core/java/com/android/server/wm/ScreenRotationAnimation.java
@@ -18,9 +18,9 @@
 
 import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER;
 
+import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_ORIENTATION;
+import static com.android.internal.protolog.ProtoLogGroup.WM_SHOW_SURFACE_ALLOC;
 import static com.android.server.wm.AnimationSpecProto.ROTATE;
-import static com.android.server.wm.ProtoLogGroup.WM_DEBUG_ORIENTATION;
-import static com.android.server.wm.ProtoLogGroup.WM_SHOW_SURFACE_ALLOC;
 import static com.android.server.wm.RotationAnimationSpecProto.DURATION_MS;
 import static com.android.server.wm.RotationAnimationSpecProto.END_LUMA;
 import static com.android.server.wm.RotationAnimationSpecProto.START_LUMA;
@@ -50,7 +50,7 @@
 import android.view.animation.Transformation;
 
 import com.android.internal.R;
-import com.android.server.protolog.common.ProtoLog;
+import com.android.internal.protolog.common.ProtoLog;
 import com.android.server.wm.SurfaceAnimator.AnimationType;
 import com.android.server.wm.SurfaceAnimator.OnAnimationFinishedCallback;
 import com.android.server.wm.utils.RotationAnimationUtils;
@@ -101,7 +101,6 @@
     private final Transformation mRotateExitTransformation = new Transformation();
     private final Transformation mRotateEnterTransformation = new Transformation();
     // Complete transformations being applied.
-    private final Transformation mEnterTransformation = new Transformation();
     private final Matrix mSnapshotInitialMatrix = new Matrix();
     private final WindowManagerService mService;
     /** Only used for custom animations and not screen rotation. */
@@ -309,8 +308,6 @@
         pw.print(" "); mRotateExitTransformation.printShortString(pw); pw.println();
         pw.print(prefix); pw.print("mRotateEnterAnimation="); pw.print(mRotateEnterAnimation);
         pw.print(" "); mRotateEnterTransformation.printShortString(pw); pw.println();
-        pw.print(prefix); pw.print("mEnterTransformation=");
-        mEnterTransformation.printShortString(pw); pw.println();
         pw.print(prefix); pw.print("mSnapshotInitialMatrix=");
         mSnapshotInitialMatrix.dump(pw); pw.println();
         pw.print(prefix); pw.print("mForceDefaultOrientation="); pw.print(mForceDefaultOrientation);
@@ -508,10 +505,6 @@
         return mCurRotation != mOriginalRotation;
     }
 
-    public Transformation getEnterTransformation() {
-        return mEnterTransformation;
-    }
-
     /**
      * Utility class that runs a {@link ScreenRotationAnimation} on the {@link
      * SurfaceAnimationRunner}.
diff --git a/services/core/java/com/android/server/wm/Session.java b/services/core/java/com/android/server/wm/Session.java
index 86cbf3e..3b32a9d 100644
--- a/services/core/java/com/android/server/wm/Session.java
+++ b/services/core/java/com/android/server/wm/Session.java
@@ -24,7 +24,7 @@
 import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
 import static android.view.WindowManager.LayoutParams.isSystemAlertWindowType;
 
-import static com.android.server.wm.ProtoLogGroup.WM_SHOW_TRANSACTIONS;
+import static com.android.internal.protolog.ProtoLogGroup.WM_SHOW_TRANSACTIONS;
 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG;
 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_TASK_POSITIONING;
 import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
@@ -58,7 +58,7 @@
 import android.view.WindowManager;
 
 import com.android.internal.os.logging.MetricsLoggerWrapper;
-import com.android.server.protolog.common.ProtoLog;
+import com.android.internal.protolog.common.ProtoLog;
 import com.android.server.wm.WindowManagerService.H;
 
 import java.io.PrintWriter;
diff --git a/services/core/java/com/android/server/wm/SurfaceFreezer.java b/services/core/java/com/android/server/wm/SurfaceFreezer.java
index 5cea786..d9365c5 100644
--- a/services/core/java/com/android/server/wm/SurfaceFreezer.java
+++ b/services/core/java/com/android/server/wm/SurfaceFreezer.java
@@ -16,20 +16,19 @@
 
 package com.android.server.wm;
 
-import static com.android.server.wm.ProtoLogGroup.WM_SHOW_TRANSACTIONS;
+import static com.android.internal.protolog.ProtoLogGroup.WM_SHOW_TRANSACTIONS;
 import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_APP_TRANSITION;
 import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_SCREEN_ROTATION;
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
-import android.graphics.ColorSpace;
 import android.graphics.PixelFormat;
 import android.graphics.Rect;
 import android.hardware.HardwareBuffer;
 import android.view.Surface;
 import android.view.SurfaceControl;
 
-import com.android.server.protolog.common.ProtoLog;
+import com.android.internal.protolog.common.ProtoLog;
 
 import java.util.function.Supplier;
 
@@ -89,8 +88,7 @@
             if (buffer == null || buffer.getWidth() <= 1 || buffer.getHeight() <= 1) {
                 return;
             }
-            mSnapshot = new Snapshot(mWmService.mSurfaceFactory, t, buffer,
-                    screenshotBuffer.getColorSpace(), mLeash);
+            mSnapshot = new Snapshot(mWmService.mSurfaceFactory, t, screenshotBuffer, mLeash);
         }
     }
 
@@ -135,8 +133,12 @@
             cropBounds = new Rect(bounds);
             cropBounds.offsetTo(0, 0);
         }
-        return SurfaceControl.captureLayers(target, cropBounds, 1.f /* frameScale */,
-                PixelFormat.RGBA_8888);
+        SurfaceControl.LayerCaptureArgs captureArgs =
+                new SurfaceControl.LayerCaptureArgs.Builder(target)
+                        .setSourceCrop(cropBounds)
+                        .setCaptureSecureLayers(true)
+                        .build();
+        return SurfaceControl.captureLayers(captureArgs);
     }
 
     class Snapshot {
@@ -146,21 +148,23 @@
 
         /**
          * @param t Transaction to create the thumbnail in.
-         * @param thumbnailHeader A thumbnail or placeholder for thumbnail to initialize with.
+         * @param screenshotBuffer A thumbnail or placeholder for thumbnail to initialize with.
          */
         Snapshot(Supplier<Surface> surfaceFactory, SurfaceControl.Transaction t,
-                HardwareBuffer thumbnailHeader, ColorSpace colorSpace, SurfaceControl parent) {
+                SurfaceControl.ScreenshotHardwareBuffer screenshotBuffer, SurfaceControl parent) {
             Surface drawSurface = surfaceFactory.get();
             // We can't use a delegating constructor since we need to
             // reference this::onAnimationFinished
-            final int width = thumbnailHeader.getWidth();
-            final int height = thumbnailHeader.getHeight();
+            HardwareBuffer hardwareBuffer = screenshotBuffer.getHardwareBuffer();
+            final int width = hardwareBuffer.getWidth();
+            final int height = hardwareBuffer.getHeight();
 
             mSurfaceControl = mAnimatable.makeAnimationLeash()
                     .setName("snapshot anim: " + mAnimatable.toString())
                     .setBufferSize(width, height)
                     .setFormat(PixelFormat.TRANSLUCENT)
                     .setParent(parent)
+                    .setSecure(screenshotBuffer.containsSecureLayers())
                     .setCallsite("SurfaceFreezer.Snapshot")
                     .build();
 
@@ -168,7 +172,8 @@
 
             // Transfer the thumbnail to the surface
             drawSurface.copyFrom(mSurfaceControl);
-            drawSurface.attachAndQueueBufferWithColorSpace(thumbnailHeader, colorSpace);
+            drawSurface.attachAndQueueBufferWithColorSpace(hardwareBuffer,
+                    screenshotBuffer.getColorSpace());
             drawSurface.release();
             t.show(mSurfaceControl);
 
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index 897b680..4dbbbc6 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -45,7 +45,7 @@
 import static android.content.pm.ActivityInfo.FLAG_SHOW_FOR_ALL_USERS;
 import static android.content.pm.ActivityInfo.LOCK_TASK_LAUNCH_MODE_ALWAYS;
 import static android.content.pm.ActivityInfo.LOCK_TASK_LAUNCH_MODE_DEFAULT;
-import static android.content.pm.ActivityInfo.LOCK_TASK_LAUNCH_MODE_IF_WHITELISTED;
+import static android.content.pm.ActivityInfo.LOCK_TASK_LAUNCH_MODE_IF_ALLOWLISTED;
 import static android.content.pm.ActivityInfo.LOCK_TASK_LAUNCH_MODE_NEVER;
 import static android.content.pm.ActivityInfo.RESIZE_MODE_FORCE_RESIZABLE_LANDSCAPE_ONLY;
 import static android.content.pm.ActivityInfo.RESIZE_MODE_FORCE_RESIZABLE_PORTRAIT_ONLY;
@@ -78,6 +78,8 @@
 
 import static com.android.internal.policy.DecorView.DECOR_SHADOW_FOCUSED_HEIGHT_IN_DIP;
 import static com.android.internal.policy.DecorView.DECOR_SHADOW_UNFOCUSED_HEIGHT_IN_DIP;
+import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_ADD_REMOVE;
+import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_RECENTS_ANIMATIONS;
 import static com.android.server.wm.ActivityRecord.STARTING_WINDOW_SHOWN;
 import static com.android.server.wm.ActivityStackSupervisor.DEFER_RESUME;
 import static com.android.server.wm.ActivityStackSupervisor.ON_TOP;
@@ -86,8 +88,6 @@
 import static com.android.server.wm.ActivityStackSupervisor.dumpHistoryList;
 import static com.android.server.wm.ActivityStackSupervisor.printThisActivity;
 import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_ADD_REMOVE;
-import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_ALL;
-import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_APP;
 import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_CLEANUP;
 import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_LOCKTASK;
 import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_PAUSE;
@@ -115,13 +115,9 @@
 import static com.android.server.wm.ActivityTaskManagerDebugConfig.TAG_ATM;
 import static com.android.server.wm.ActivityTaskManagerDebugConfig.TAG_WITH_CLASS_NAME;
 import static com.android.server.wm.ActivityTaskManagerService.H.FIRST_ACTIVITY_STACK_MSG;
-import static com.android.server.wm.ActivityTaskManagerService.RELAUNCH_REASON_FREE_RESIZE;
-import static com.android.server.wm.ActivityTaskManagerService.RELAUNCH_REASON_WINDOWING_MODE_RESIZE;
 import static com.android.server.wm.IdentifierProto.HASH_CODE;
 import static com.android.server.wm.IdentifierProto.TITLE;
 import static com.android.server.wm.IdentifierProto.USER_ID;
-import static com.android.server.wm.ProtoLogGroup.WM_DEBUG_ADD_REMOVE;
-import static com.android.server.wm.ProtoLogGroup.WM_DEBUG_RECENTS_ANIMATIONS;
 import static com.android.server.wm.Task.ActivityState.PAUSED;
 import static com.android.server.wm.Task.ActivityState.PAUSING;
 import static com.android.server.wm.Task.ActivityState.RESUMED;
@@ -214,7 +210,7 @@
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.app.IVoiceInteractor;
-import com.android.internal.os.logging.MetricsLoggerWrapper;
+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.PooledFunction;
@@ -223,7 +219,6 @@
 import com.android.server.Watchdog;
 import com.android.server.am.ActivityManagerService;
 import com.android.server.am.AppTimeTracker;
-import com.android.server.protolog.common.ProtoLog;
 import com.android.server.uri.NeededUriGrants;
 
 import org.xmlpull.v1.XmlPullParser;
@@ -415,7 +410,7 @@
     /** Starts in LOCK_TASK_MODE_LOCKED automatically. Can start over existing lockTask task. */
     final static int LOCK_TASK_AUTH_LAUNCHABLE = 2;
     /** Can enter lockTask without user approval. Can start over existing lockTask task. */
-    final static int LOCK_TASK_AUTH_WHITELISTED = 3;
+    final static int LOCK_TASK_AUTH_ALLOWLISTED = 3;
     /** Priv-app that starts in LOCK_TASK_MODE_LOCKED automatically. Can start over existing
      * lockTask task. */
     final static int LOCK_TASK_AUTH_LAUNCHABLE_PRIV = 4;
@@ -746,111 +741,6 @@
         }
     }
 
-    // TODO: Can we just loop through WindowProcessController#mActivities instead of doing this?
-    private final RemoveHistoryRecordsForApp mRemoveHistoryRecordsForApp =
-            new RemoveHistoryRecordsForApp();
-    private class RemoveHistoryRecordsForApp {
-        private boolean mHasVisibleActivities;
-        private boolean mIsProcessRemoved;
-        private WindowProcessController mApp;
-        private ArrayList<ActivityRecord> mToRemove = new ArrayList<>();
-
-        boolean process(WindowProcessController app) {
-            mToRemove.clear();
-            mHasVisibleActivities = false;
-            mApp = app;
-            mIsProcessRemoved = app.isRemoved();
-
-            final PooledConsumer c = PooledLambda.obtainConsumer(
-                    RemoveHistoryRecordsForApp::addActivityToRemove, this,
-                    PooledLambda.__(ActivityRecord.class));
-            forAllActivities(c);
-            c.recycle();
-
-            while (!mToRemove.isEmpty()) {
-                processActivity(mToRemove.remove(0));
-            }
-
-            mApp = null;
-            return mHasVisibleActivities;
-        }
-
-        private void addActivityToRemove(ActivityRecord r) {
-            if (r.app == mApp) {
-                mToRemove.add(r);
-            }
-        }
-
-        private void processActivity(ActivityRecord r) {
-            if (DEBUG_CLEANUP) Slog.v(TAG_CLEANUP, "Record " + r + ": app=" + r.app);
-
-            if (r.app != mApp) {
-                return;
-            }
-            if (r.isVisible() || r.mVisibleRequested) {
-                // While an activity launches a new activity, it's possible that the old
-                // activity is already requested to be hidden (mVisibleRequested=false), but
-                // this visibility is not yet committed, so isVisible()=true.
-                mHasVisibleActivities = true;
-            }
-            final boolean remove;
-            if ((r.mRelaunchReason == RELAUNCH_REASON_WINDOWING_MODE_RESIZE
-                    || r.mRelaunchReason == RELAUNCH_REASON_FREE_RESIZE)
-                    && r.launchCount < 3 && !r.finishing) {
-                // If the process crashed during a resize, always try to relaunch it, unless
-                // it has failed more than twice. Skip activities that's already finishing
-                // cleanly by itself.
-                remove = false;
-            } else if ((!r.hasSavedState() && !r.stateNotNeeded
-                    && !r.isState(ActivityState.RESTARTING_PROCESS)) || r.finishing) {
-                // Don't currently have state for the activity, or
-                // it is finishing -- always remove it.
-                remove = true;
-            } else if (!r.mVisibleRequested && r.launchCount > 2
-                    && r.lastLaunchTime > (SystemClock.uptimeMillis() - 60000)) {
-                // We have launched this activity too many times since it was
-                // able to run, so give up and remove it.
-                // (Note if the activity is visible, we don't remove the record.
-                // We leave the dead window on the screen but the process will
-                // not be restarted unless user explicitly tap on it.)
-                remove = true;
-            } else {
-                // The process may be gone, but the activity lives on!
-                remove = false;
-            }
-            if (remove) {
-                if (DEBUG_ADD_REMOVE || DEBUG_CLEANUP) Slog.i(TAG_ADD_REMOVE,
-                        "Removing activity " + r + " from stack "
-                                + ": hasSavedState=" + r.hasSavedState()
-                                + " stateNotNeeded=" + r.stateNotNeeded
-                                + " finishing=" + r.finishing
-                                + " state=" + r.getState() + " callers=" + Debug.getCallers(5));
-                if (!r.finishing || mIsProcessRemoved) {
-                    Slog.w(TAG, "Force removing " + r + ": app died, no saved state");
-                    EventLogTags.writeWmFinishActivity(r.mUserId,
-                            System.identityHashCode(r), r.getTask().mTaskId,
-                            r.shortComponentName, "proc died without state saved");
-                }
-            } else {
-                // We have the current state for this activity, so
-                // it can be restarted later when needed.
-                if (DEBUG_ALL) Slog.v(TAG, "Keeping entry, setting app to null");
-                if (DEBUG_APP) Slog.v(TAG_APP,
-                        "Clearing app during removeHistory for activity " + r);
-                r.app = null;
-                // Set nowVisible to previous visible state. If the app was visible while
-                // it died, we leave the dead window on screen so it's basically visible.
-                // This is needed when user later tap on the dead window, we need to stop
-                // other apps when user transfers focus to the restarted activity.
-                r.nowVisible = r.mVisibleRequested;
-            }
-            r.cleanUp(true /* cleanServices */, true /* setState */);
-            if (remove) {
-                r.removeFromHistory("appDied");
-            }
-        }
-    }
-
     private final FindRootHelper mFindRootHelper = new FindRootHelper();
     private class FindRootHelper {
         private ActivityRecord mRoot;
@@ -893,7 +783,7 @@
      * taskAppeared callback, and emit a taskRemoved callback when the Task is vanished.
      */
     ITaskOrganizer mTaskOrganizer;
-    private int mLastTaskOrganizerWindowingMode = -1;
+
     /**
      * Prevent duplicate calls to onTaskAppeared.
      */
@@ -908,6 +798,7 @@
      *     organizer for ordering purposes.</li>
      * </ul>
      */
+    @VisibleForTesting
     boolean mCreatedByOrganizer;
 
     /**
@@ -1795,7 +1686,7 @@
             getDisplayArea().addStackReferenceIfNeeded((Task) child);
         }
 
-        // Make sure the list of display UID whitelists is updated
+        // Make sure the list of display UID allowlists is updated
         // now that this record is in a new task.
         mRootWindowContainer.updateUIDsPresentOnDisplay();
 
@@ -1946,14 +1837,25 @@
      */
     void performClearTaskLocked() {
         mReuseTask = true;
-        performClearTask("clear-task-all");
-        mReuseTask = false;
+        mStackSupervisor.beginDeferResume();
+        try {
+            performClearTask("clear-task-all");
+        } finally {
+            mStackSupervisor.endDeferResume();
+            mReuseTask = false;
+        }
     }
 
     ActivityRecord performClearTaskForReuseLocked(ActivityRecord newR, int launchFlags) {
         mReuseTask = true;
-        final ActivityRecord result = performClearTaskLocked(newR, launchFlags);
-        mReuseTask = false;
+        mStackSupervisor.beginDeferResume();
+        final ActivityRecord result;
+        try {
+            result = performClearTaskLocked(newR, launchFlags);
+        } finally {
+            mStackSupervisor.endDeferResume();
+            mReuseTask = false;
+        }
         return result;
     }
 
@@ -2012,7 +1914,7 @@
             case LOCK_TASK_AUTH_DONT_LOCK: return "LOCK_TASK_AUTH_DONT_LOCK";
             case LOCK_TASK_AUTH_PINNABLE: return "LOCK_TASK_AUTH_PINNABLE";
             case LOCK_TASK_AUTH_LAUNCHABLE: return "LOCK_TASK_AUTH_LAUNCHABLE";
-            case LOCK_TASK_AUTH_WHITELISTED: return "LOCK_TASK_AUTH_WHITELISTED";
+            case LOCK_TASK_AUTH_ALLOWLISTED: return "LOCK_TASK_AUTH_ALLOWLISTED";
             case LOCK_TASK_AUTH_LAUNCHABLE_PRIV: return "LOCK_TASK_AUTH_LAUNCHABLE_PRIV";
             default: return "unknown=" + mLockTaskAuth;
         }
@@ -2032,8 +1934,8 @@
         final LockTaskController lockTaskController = mAtmService.getLockTaskController();
         switch (r.lockTaskLaunchMode) {
             case LOCK_TASK_LAUNCH_MODE_DEFAULT:
-                mLockTaskAuth = lockTaskController.isPackageWhitelisted(mUserId, pkg)
-                        ? LOCK_TASK_AUTH_WHITELISTED : LOCK_TASK_AUTH_PINNABLE;
+                mLockTaskAuth = lockTaskController.isPackageAllowlisted(mUserId, pkg)
+                        ? LOCK_TASK_AUTH_ALLOWLISTED : LOCK_TASK_AUTH_PINNABLE;
                 break;
 
             case LOCK_TASK_LAUNCH_MODE_NEVER:
@@ -2044,8 +1946,8 @@
                 mLockTaskAuth = LOCK_TASK_AUTH_LAUNCHABLE_PRIV;
                 break;
 
-            case LOCK_TASK_LAUNCH_MODE_IF_WHITELISTED:
-                mLockTaskAuth = lockTaskController.isPackageWhitelisted(mUserId, pkg)
+            case LOCK_TASK_LAUNCH_MODE_IF_ALLOWLISTED:
+                mLockTaskAuth = lockTaskController.isPackageAllowlisted(mUserId, pkg)
                         ? LOCK_TASK_AUTH_LAUNCHABLE : LOCK_TASK_AUTH_PINNABLE;
                 break;
         }
@@ -2475,7 +2377,6 @@
     private boolean shouldStartChangeTransition(int prevWinMode, int newWinMode) {
         if (mWmService.mDisableTransitionAnimation
                 || !isVisible()
-                || getDisplayContent().mAppTransition.isTransitionSet()
                 || getSurfaceControl() == null
                 || !isLeafTask()) {
             return false;
@@ -2888,8 +2789,16 @@
             // For calculating screen layout, we need to use the non-decor inset screen area for the
             // calculation for compatibility reasons, i.e. screen area without system bars that
             // could never go away in Honeycomb.
-            final int compatScreenWidthDp = (int) (mTmpNonDecorBounds.width() / density);
-            final int compatScreenHeightDp = (int) (mTmpNonDecorBounds.height() / density);
+            int compatScreenWidthDp = (int) (mTmpNonDecorBounds.width() / density);
+            int compatScreenHeightDp = (int) (mTmpNonDecorBounds.height() / density);
+            // Use overrides if provided. If both overrides are provided, mTmpNonDecorBounds is
+            // undefined so it can't be used.
+            if (inOutConfig.screenWidthDp != Configuration.SCREEN_WIDTH_DP_UNDEFINED) {
+                compatScreenWidthDp = inOutConfig.screenWidthDp;
+            }
+            if (inOutConfig.screenHeightDp != Configuration.SCREEN_HEIGHT_DP_UNDEFINED) {
+                compatScreenHeightDp = inOutConfig.screenHeightDp;
+            }
             // Reducing the screen layout starting from its parent config.
             inOutConfig.screenLayout = computeScreenLayoutOverride(parentConfig.screenLayout,
                     compatScreenWidthDp, compatScreenHeightDp);
@@ -3920,7 +3829,10 @@
 
     @Override
     boolean fillsParent() {
-        return matchParentBounds();
+        // From the perspective of policy, we still want to report that this task fills parent
+        // in fullscreen windowing mode even it doesn't match parent bounds because there will be
+        // letterbox around its real content.
+        return getWindowingMode() == WINDOWING_MODE_FULLSCREEN || matchParentBounds();
     }
 
     @Override
@@ -4920,19 +4832,18 @@
             return false;
         }
 
-        ITaskOrganizer previousOrganizer = mTaskOrganizer;
+        ITaskOrganizer prevOrganizer = mTaskOrganizer;
         // Update the new task organizer before calling sendTaskVanished since it could result in
         // a new SurfaceControl getting created that would notify the old organizer about it.
         mTaskOrganizer = organizer;
         // Let the old organizer know it has lost control.
-        sendTaskVanished(previousOrganizer);
+        sendTaskVanished(prevOrganizer);
 
         if (mTaskOrganizer != null) {
             sendTaskAppeared();
         } else {
             // No longer managed by any organizer.
             mTaskAppearedSent = false;
-            mLastTaskOrganizerWindowingMode = -1;
             setForceHidden(FLAG_FORCE_HIDDEN_FOR_TASK_ORG, false /* set */);
             if (mCreatedByOrganizer) {
                 removeImmediately();
@@ -4953,29 +4864,16 @@
      */
     boolean updateTaskOrganizerState(boolean forceUpdate) {
         if (!isRootTask()) {
-            final boolean result = setTaskOrganizer(null);
-            mLastTaskOrganizerWindowingMode = -1;
-            return result;
+            return setTaskOrganizer(null);
         }
 
         final int windowingMode = getWindowingMode();
-        if (!forceUpdate && windowingMode == mLastTaskOrganizerWindowingMode) {
-            // If our windowing mode hasn't actually changed, then just stick
-            // with our old organizer. This lets us implement the semantic
-            // where SysUI can continue to manage it's old tasks
-            // while CTS temporarily takes over the registration.
+        final TaskOrganizerController controller = mWmService.mAtmService.mTaskOrganizerController;
+        final ITaskOrganizer organizer = controller.getTaskOrganizer(windowingMode);
+        if (!forceUpdate && mTaskOrganizer == organizer) {
             return false;
         }
-        /*
-         * Different windowing modes may be managed by different task organizers. If
-         * getTaskOrganizer returns null, we still call setTaskOrganizer to
-         * make sure we clear it.
-         */
-        final ITaskOrganizer org =
-                mWmService.mAtmService.mTaskOrganizerController.getTaskOrganizer(windowingMode);
-        final boolean result = setTaskOrganizer(org);
-        mLastTaskOrganizerWindowingMode = org != null ? windowingMode : -1;
-        return result;
+        return setTaskOrganizer(organizer);
     }
 
     @Override
@@ -7167,21 +7065,21 @@
     /**
      * Reset local parameters because an app's activity died.
      * @param app The app of the activity that died.
-     * @return result from removeHistoryRecordsForAppLocked.
+     * @return {@code true} if the process of the pausing activity is died.
      */
     boolean handleAppDied(WindowProcessController app) {
+        boolean isPausingDied = false;
         if (mPausingActivity != null && mPausingActivity.app == app) {
             if (DEBUG_PAUSE || DEBUG_CLEANUP) Slog.v(TAG_PAUSE,
                     "App died while pausing: " + mPausingActivity);
             mPausingActivity = null;
+            isPausingDied = true;
         }
         if (mLastPausedActivity != null && mLastPausedActivity.app == app) {
             mLastPausedActivity = null;
             mLastNoHistoryActivity = null;
         }
-
-        mStackSupervisor.removeHistoryRecords(app);
-        return mRemoveHistoryRecordsForApp.process(app);
+        return isPausingDied;
     }
 
     boolean dump(FileDescriptor fd, PrintWriter pw, boolean dumpAll, boolean dumpClient,
@@ -7485,8 +7383,6 @@
             getDisplayArea().positionChildAt(POSITION_TOP, this, false /* includingParents */);
 
             mStackSupervisor.scheduleUpdatePictureInPictureModeIfNeeded(task, this);
-            MetricsLoggerWrapper.logPictureInPictureFullScreen(mAtmService.mContext,
-                    task.effectiveUid, task.realActivity.flattenToString());
         });
     }
 
diff --git a/services/core/java/com/android/server/wm/TaskDisplayArea.java b/services/core/java/com/android/server/wm/TaskDisplayArea.java
index a847744..6550167 100644
--- a/services/core/java/com/android/server/wm/TaskDisplayArea.java
+++ b/services/core/java/com/android/server/wm/TaskDisplayArea.java
@@ -32,13 +32,13 @@
 import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSET;
 import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
 
+import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_ADD_REMOVE;
+import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_ORIENTATION;
 import static com.android.server.wm.ActivityStackSupervisor.TAG_TASKS;
 import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_STATES;
 import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_TASKS;
 import static com.android.server.wm.ActivityTaskManagerService.TAG_STACK;
 import static com.android.server.wm.DisplayContent.alwaysCreateStack;
-import static com.android.server.wm.ProtoLogGroup.WM_DEBUG_ADD_REMOVE;
-import static com.android.server.wm.ProtoLogGroup.WM_DEBUG_ORIENTATION;
 import static com.android.server.wm.RootWindowContainer.TAG_STATES;
 import static com.android.server.wm.Task.ActivityState.RESUMED;
 import static com.android.server.wm.Task.STACK_VISIBILITY_VISIBLE;
@@ -58,10 +58,10 @@
 import android.window.WindowContainerTransaction;
 
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.protolog.common.ProtoLog;
 import com.android.internal.util.ToBooleanFunction;
 import com.android.internal.util.function.pooled.PooledLambda;
 import com.android.internal.util.function.pooled.PooledPredicate;
-import com.android.server.protolog.common.ProtoLog;
 
 import java.io.PrintWriter;
 import java.util.ArrayList;
@@ -1489,9 +1489,13 @@
         return stack == getTopStack();
     }
 
-    boolean isTopNotPinnedStack(Task stack) {
+    boolean isTopNotFinishNotPinnedStack(Task stack) {
         for (int i = getStackCount() - 1; i >= 0; --i) {
             final Task current = getStackAt(i);
+            final ActivityRecord topAct = current.getTopNonFinishingActivity();
+            if (topAct == null) {
+                continue;
+            }
             if (!current.inPinnedWindowingMode()) {
                 return current == stack;
             }
diff --git a/services/core/java/com/android/server/wm/TaskOrganizerController.java b/services/core/java/com/android/server/wm/TaskOrganizerController.java
index 04d134c..f8465dd 100644
--- a/services/core/java/com/android/server/wm/TaskOrganizerController.java
+++ b/services/core/java/com/android/server/wm/TaskOrganizerController.java
@@ -20,6 +20,8 @@
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
 import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
 import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
+import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY;
+import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_SECONDARY;
 
 import static com.android.server.wm.WindowOrganizerController.CONTROLLABLE_CONFIGS;
 import static com.android.server.wm.WindowOrganizerController.CONTROLLABLE_WINDOW_CONFIGS;
@@ -35,7 +37,6 @@
 import android.os.IBinder;
 import android.os.RemoteException;
 import android.util.Slog;
-import android.util.SparseArray;
 import android.view.SurfaceControl;
 import android.window.ITaskOrganizer;
 import android.window.ITaskOrganizerController;
@@ -46,9 +47,12 @@
 
 import java.io.PrintWriter;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.HashMap;
+import java.util.LinkedHashMap;
 import java.util.LinkedList;
 import java.util.List;
+import java.util.Set;
 import java.util.WeakHashMap;
 import java.util.function.Consumer;
 
@@ -58,7 +62,6 @@
  */
 class TaskOrganizerController extends ITaskOrganizerController.Stub {
     private static final String TAG = "TaskOrganizerController";
-    private static final LinkedList<IBinder> EMPTY_LIST = new LinkedList<>();
 
     /**
      * Masks specifying which configurations are important to report back to an organizer when
@@ -67,6 +70,16 @@
     private static final int REPORT_CONFIGS = CONTROLLABLE_CONFIGS;
     private static final int REPORT_WINDOW_CONFIGS = CONTROLLABLE_WINDOW_CONFIGS;
 
+    // The set of modes that are currently supports
+    // TODO: Remove once the task organizer can support all modes
+    @VisibleForTesting
+    static final int[] SUPPORTED_WINDOWING_MODES = {
+            WINDOWING_MODE_PINNED,
+            WINDOWING_MODE_SPLIT_SCREEN_PRIMARY,
+            WINDOWING_MODE_SPLIT_SCREEN_SECONDARY,
+            WINDOWING_MODE_MULTI_WINDOW,
+    };
+
     private final WindowManagerGlobalLock mGlobalLock;
 
     private class DeathRecipient implements IBinder.DeathRecipient {
@@ -233,9 +246,7 @@
 
         void dispose() {
             // Move organizer from managing specific windowing modes
-            for (int i = mTaskOrganizersForWindowingMode.size() - 1; i >= 0; --i) {
-                mTaskOrganizersForWindowingMode.valueAt(i).remove(mOrganizer.getBinder());
-            }
+            mTaskOrganizers.remove(mOrganizer.mTaskOrganizer);
 
             // Update tasks currently managed by this organizer to the next one available if
             // possible.
@@ -257,8 +268,8 @@
         }
     }
 
-    private final SparseArray<LinkedList<IBinder>> mTaskOrganizersForWindowingMode =
-            new SparseArray<>();
+    // List of task organizers by priority
+    private final LinkedList<ITaskOrganizer> mTaskOrganizers = new LinkedList<>();
     private final HashMap<IBinder, TaskOrganizerState> mTaskOrganizerStates = new HashMap<>();
     private final WeakHashMap<Task, RunningTaskInfo> mLastSentTaskInfos = new WeakHashMap<>();
     private final ArrayList<Task> mPendingTaskInfoChanges = new ArrayList<>();
@@ -285,59 +296,28 @@
     public void setDeferTaskOrgCallbacksConsumer(Consumer<Runnable> consumer) {
         mDeferTaskOrgCallbacksConsumer = consumer;
     }
-
     /**
-     * Register a TaskOrganizer to manage tasks as they enter the given windowing mode.
-     * If there was already a TaskOrganizer for this windowing mode it will be evicted
-     * but will continue to organize it's existing tasks.
+     * Register a TaskOrganizer to manage tasks as they enter the a supported windowing mode.
      */
     @Override
-    public void registerTaskOrganizer(ITaskOrganizer organizer, int windowingMode) {
-        if (windowingMode == WINDOWING_MODE_PINNED) {
-            if (!mService.mSupportsPictureInPicture) {
-                throw new UnsupportedOperationException("Picture in picture is not supported on "
-                        + "this device");
-            }
-        } else if (WindowConfiguration.isSplitScreenWindowingMode(windowingMode)) {
-            if (!mService.mSupportsSplitScreenMultiWindow) {
-                throw new UnsupportedOperationException("Split-screen is not supported on this "
-                        + "device");
-            }
-        } else if (windowingMode == WINDOWING_MODE_MULTI_WINDOW) {
-            if (!mService.mSupportsMultiWindow) {
-                throw new UnsupportedOperationException("Multi-window is not supported on this "
-                        + "device");
-            }
-        } else {
-            throw new UnsupportedOperationException("As of now only Pinned/Split/Multiwindow"
-                    + " windowing modes are supported for registerTaskOrganizer");
-        }
+    public void registerTaskOrganizer(ITaskOrganizer organizer) {
         enforceStackPermission("registerTaskOrganizer()");
         final int uid = Binder.getCallingUid();
         final long origId = Binder.clearCallingIdentity();
         try {
             synchronized (mGlobalLock) {
-                if (getTaskOrganizer(windowingMode) != null) {
-                    Slog.w(TAG, "Task organizer already exists for windowing mode: "
-                            + windowingMode);
-                }
-
-                LinkedList<IBinder> orgs = mTaskOrganizersForWindowingMode.get(windowingMode);
-                if (orgs == null) {
-                    orgs = new LinkedList<>();
-                    mTaskOrganizersForWindowingMode.put(windowingMode, orgs);
-                }
-                orgs.add(organizer.asBinder());
-                if (!mTaskOrganizerStates.containsKey(organizer.asBinder())) {
-                    mTaskOrganizerStates.put(organizer.asBinder(),
-                            new TaskOrganizerState(organizer, uid));
-                }
-
-                mService.mRootWindowContainer.forAllTasks((task) -> {
-                    if (task.getWindowingMode() == windowingMode) {
-                        task.updateTaskOrganizerState(true /* forceUpdate */);
+                for (int winMode : SUPPORTED_WINDOWING_MODES) {
+                    if (!mTaskOrganizerStates.containsKey(organizer.asBinder())) {
+                        mTaskOrganizers.add(organizer);
+                        mTaskOrganizerStates.put(organizer.asBinder(),
+                                new TaskOrganizerState(organizer, uid));
                     }
-                });
+                    mService.mRootWindowContainer.forAllTasks((task) -> {
+                        if (task.getWindowingMode() == winMode) {
+                            task.updateTaskOrganizerState(true /* forceUpdate */);
+                        }
+                    });
+                }
             }
         } finally {
             Binder.restoreCallingIdentity(origId);
@@ -362,17 +342,22 @@
         }
     }
 
+    /**
+     * @return the task organizer key for a given windowing mode.
+     */
     ITaskOrganizer getTaskOrganizer(int windowingMode) {
-        final IBinder organizer =
-                mTaskOrganizersForWindowingMode.get(windowingMode, EMPTY_LIST).peekLast();
-        if (organizer == null) {
-            return null;
+        return isSupportedWindowingMode(windowingMode)
+                ? mTaskOrganizers.peekLast()
+                : null;
+    }
+
+    private boolean isSupportedWindowingMode(int winMode) {
+        for (int i = 0; i < SUPPORTED_WINDOWING_MODES.length; i++) {
+            if (SUPPORTED_WINDOWING_MODES[i] == winMode) {
+                return true;
+            }
         }
-        final TaskOrganizerState state = mTaskOrganizerStates.get(organizer);
-        if (state == null) {
-            return null;
-        }
-        return state.mOrganizer.mTaskOrganizer;
+        return false;
     }
 
     void onTaskAppeared(ITaskOrganizer organizer, Task task) {
@@ -458,6 +443,8 @@
                 || mTmpTaskInfo.topActivityType != lastInfo.topActivityType
                 || mTmpTaskInfo.isResizeable != lastInfo.isResizeable
                 || mTmpTaskInfo.pictureInPictureParams != lastInfo.pictureInPictureParams
+                || mTmpTaskInfo.getConfiguration().windowConfiguration.getWindowingMode()
+                        != lastInfo.getConfiguration().windowConfiguration.getWindowingMode()
                 || !TaskDescription.equals(mTmpTaskInfo.taskDescription, lastInfo.taskDescription);
         if (!changed) {
             int cfgChanges = mTmpTaskInfo.configuration.diff(lastInfo.configuration);
@@ -655,18 +642,19 @@
         final String innerPrefix = prefix + "  ";
         pw.print(prefix); pw.println("TaskOrganizerController:");
         pw.print(innerPrefix); pw.println("Per windowing mode:");
-        for (int i = 0; i < mTaskOrganizersForWindowingMode.size(); i++) {
-            final int windowingMode = mTaskOrganizersForWindowingMode.keyAt(i);
-            final List<IBinder> taskOrgs = mTaskOrganizersForWindowingMode.valueAt(i);
+        for (int i = 0; i < SUPPORTED_WINDOWING_MODES.length; i++) {
+            final int windowingMode = SUPPORTED_WINDOWING_MODES[i];
             pw.println(innerPrefix + "  "
                     + WindowConfiguration.windowingModeToString(windowingMode) + ":");
-            for (int j = 0; j < taskOrgs.size(); j++) {
-                final TaskOrganizerState state =  mTaskOrganizerStates.get(taskOrgs.get(j));
+            for (final TaskOrganizerState state : mTaskOrganizerStates.values()) {
                 final ArrayList<Task> tasks = state.mOrganizedTasks;
                 pw.print(innerPrefix + "    ");
                 pw.println(state.mOrganizer.mTaskOrganizer + " uid=" + state.mUid + ":");
                 for (int k = 0; k < tasks.size(); k++) {
-                    pw.println(innerPrefix + "      " + tasks.get(k));
+                    final Task task = tasks.get(k);
+                    if (windowingMode == task.getWindowingMode()) {
+                        pw.println(innerPrefix + "      " + task);
+                    }
                 }
             }
 
diff --git a/services/core/java/com/android/server/wm/TaskPositioner.java b/services/core/java/com/android/server/wm/TaskPositioner.java
index c68b660..f32781a 100644
--- a/services/core/java/com/android/server/wm/TaskPositioner.java
+++ b/services/core/java/com/android/server/wm/TaskPositioner.java
@@ -18,6 +18,7 @@
 
 import static android.app.ActivityTaskManager.RESIZE_MODE_USER;
 import static android.app.ActivityTaskManager.RESIZE_MODE_USER_FORCED;
+import static android.os.IInputConstants.DEFAULT_DISPATCHING_TIMEOUT_MILLIS;
 import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER;
 
 import static com.android.internal.policy.TaskResizingAlgorithm.CTRL_BOTTOM;
@@ -25,8 +26,8 @@
 import static com.android.internal.policy.TaskResizingAlgorithm.CTRL_NONE;
 import static com.android.internal.policy.TaskResizingAlgorithm.CTRL_RIGHT;
 import static com.android.internal.policy.TaskResizingAlgorithm.CTRL_TOP;
+import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_ORIENTATION;
 import static com.android.server.wm.DragResizeMode.DRAG_RESIZE_MODE_FREEFORM;
-import static com.android.server.wm.ProtoLogGroup.WM_DEBUG_ORIENTATION;
 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_TASK_POSITIONING;
 import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
 import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
@@ -58,7 +59,7 @@
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.policy.TaskResizingAlgorithm;
 import com.android.internal.policy.TaskResizingAlgorithm.CtrlType;
-import com.android.server.protolog.common.ProtoLog;
+import com.android.internal.protolog.common.ProtoLog;
 
 class TaskPositioner implements IBinder.DeathRecipient {
     private static final boolean DEBUG_ORIENTATION_VIOLATIONS = false;
@@ -230,8 +231,8 @@
 
         mDragApplicationHandle = new InputApplicationHandle(new Binder());
         mDragApplicationHandle.name = TAG;
-        mDragApplicationHandle.dispatchingTimeoutNanos =
-                WindowManagerService.DEFAULT_INPUT_DISPATCHING_TIMEOUT_NANOS;
+        mDragApplicationHandle.dispatchingTimeoutMillis = DEFAULT_DISPATCHING_TIMEOUT_MILLIS;
+
 
         mDragWindowHandle = new InputWindowHandle(mDragApplicationHandle,
                 displayContent.getDisplayId());
@@ -239,11 +240,10 @@
         mDragWindowHandle.token = mServerChannel.getToken();
         mDragWindowHandle.layoutParamsFlags = 0;
         mDragWindowHandle.layoutParamsType = WindowManager.LayoutParams.TYPE_DRAG;
-        mDragWindowHandle.dispatchingTimeoutNanos =
-                WindowManagerService.DEFAULT_INPUT_DISPATCHING_TIMEOUT_NANOS;
+        mDragWindowHandle.dispatchingTimeoutMillis = DEFAULT_DISPATCHING_TIMEOUT_MILLIS;
         mDragWindowHandle.visible = true;
-        mDragWindowHandle.canReceiveKeys = false;
-        mDragWindowHandle.hasFocus = true;
+        // When dragging the window around, we do not want to steal focus for the window.
+        mDragWindowHandle.focusable = false;
         mDragWindowHandle.hasWallpaper = false;
         mDragWindowHandle.paused = false;
         mDragWindowHandle.ownerPid = Process.myPid();
diff --git a/services/core/java/com/android/server/wm/TaskScreenshotAnimatable.java b/services/core/java/com/android/server/wm/TaskScreenshotAnimatable.java
index 1103bf1..3def091 100644
--- a/services/core/java/com/android/server/wm/TaskScreenshotAnimatable.java
+++ b/services/core/java/com/android/server/wm/TaskScreenshotAnimatable.java
@@ -15,14 +15,14 @@
  */
 package com.android.server.wm;
 
-import static com.android.server.wm.ProtoLogGroup.WM_DEBUG_RECENTS_ANIMATIONS;
+import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_RECENTS_ANIMATIONS;
 
 import android.hardware.HardwareBuffer;
 import android.view.Surface;
 import android.view.SurfaceControl;
 import android.view.SurfaceSession;
 
-import com.android.server.protolog.common.ProtoLog;
+import com.android.internal.protolog.common.ProtoLog;
 
 import java.util.function.Function;
 
diff --git a/services/core/java/com/android/server/wm/TaskSnapshotController.java b/services/core/java/com/android/server/wm/TaskSnapshotController.java
index 68445f6..dbbb7ff 100644
--- a/services/core/java/com/android/server/wm/TaskSnapshotController.java
+++ b/services/core/java/com/android/server/wm/TaskSnapshotController.java
@@ -27,7 +27,6 @@
 import android.app.ActivityManager.TaskSnapshot;
 import android.content.pm.PackageManager;
 import android.graphics.Bitmap;
-import android.graphics.Insets;
 import android.graphics.PixelFormat;
 import android.graphics.Point;
 import android.graphics.RecordingCanvas;
@@ -82,7 +81,7 @@
 
     /**
      * Return value for {@link #getSnapshotMode}: We are not allowed to take a real screenshot but
-     * we should try to use the app theme to create a dummy representation of the app.
+     * we should try to use the app theme to create a fake representation of the app.
      */
     @VisibleForTesting
     static final int SNAPSHOT_MODE_APP_THEME = 1;
diff --git a/services/core/java/com/android/server/wm/TaskSnapshotSurface.java b/services/core/java/com/android/server/wm/TaskSnapshotSurface.java
index f3c7a5d..e9ada6b 100644
--- a/services/core/java/com/android/server/wm/TaskSnapshotSurface.java
+++ b/services/core/java/com/android/server/wm/TaskSnapshotSurface.java
@@ -16,6 +16,7 @@
 
 package com.android.server.wm;
 
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
 import static android.graphics.Color.WHITE;
 import static android.graphics.Color.alpha;
 import static android.view.View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR;
@@ -40,7 +41,7 @@
 import static com.android.internal.policy.DecorView.NAVIGATION_BAR_COLOR_VIEW_ATTRIBUTES;
 import static com.android.internal.policy.DecorView.STATUS_BAR_COLOR_VIEW_ATTRIBUTES;
 import static com.android.internal.policy.DecorView.getNavigationBarRect;
-import static com.android.server.wm.ProtoLogGroup.WM_DEBUG_STARTING_WINDOW;
+import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_STARTING_WINDOW;
 import static com.android.server.wm.TaskSnapshotController.getSystemBarInsets;
 import static com.android.server.wm.TaskSnapshotController.mergeInsetsSources;
 import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
@@ -82,9 +83,9 @@
 import com.android.internal.R;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.policy.DecorView;
+import com.android.internal.protolog.common.ProtoLog;
 import com.android.internal.view.BaseIWindow;
 import com.android.server.policy.WindowManagerPolicy.StartingSurface;
-import com.android.server.protolog.common.ProtoLog;
 
 /**
  * This class represents a starting window that shows a snapshot.
@@ -142,6 +143,7 @@
     private final Handler mHandler;
     private boolean mSizeMismatch;
     private final Paint mBackgroundPaint = new Paint();
+    private final int mActivityType;
     private final int mStatusBarColor;
     @VisibleForTesting final SystemBarBackgroundPainter mSystemBarBackgroundPainter;
     private final int mOrientationOnCreation;
@@ -173,6 +175,7 @@
         final int windowFlags;
         final int windowPrivateFlags;
         final int currentOrientation;
+        final int activityType;
         final InsetsState insetsState;
         synchronized (service.mGlobalLock) {
             final WindowState mainWindow = activity.findMainWindow();
@@ -241,6 +244,7 @@
             taskBounds = new Rect();
             task.getBounds(taskBounds);
             currentOrientation = topFullscreenOpaqueWindow.getConfiguration().orientation;
+            activityType = activity.getActivityType();
 
             final InsetsPolicy insetsPolicy = topFullscreenOpaqueWindow.getDisplayContent()
                     .getInsetsPolicy();
@@ -261,7 +265,8 @@
         }
         final TaskSnapshotSurface snapshotSurface = new TaskSnapshotSurface(service, window,
                 surfaceControl, snapshot, layoutParams.getTitle(), taskDescription, sysUiVis,
-                windowFlags, windowPrivateFlags, taskBounds, currentOrientation, insetsState);
+                windowFlags, windowPrivateFlags, taskBounds, currentOrientation, activityType,
+                insetsState);
         window.setOuter(snapshotSurface);
         try {
             session.relayout(window, window.mSeq, layoutParams, -1, -1, View.VISIBLE, 0, -1,
@@ -282,7 +287,7 @@
     TaskSnapshotSurface(WindowManagerService service, Window window, SurfaceControl surfaceControl,
             TaskSnapshot snapshot, CharSequence title, TaskDescription taskDescription,
             int sysUiVis, int windowFlags, int windowPrivateFlags, Rect taskBounds,
-            int currentOrientation, InsetsState insetsState) {
+            int currentOrientation, int activityType, InsetsState insetsState) {
         mService = service;
         mSurface = service.mSurfaceFactory.get();
         mHandler = new Handler(mService.mH.getLooper());
@@ -298,6 +303,7 @@
                 windowPrivateFlags, sysUiVis, taskDescription, 1f, insetsState);
         mStatusBarColor = taskDescription.getStatusBarColor();
         mOrientationOnCreation = currentOrientation;
+        mActivityType = activityType;
         mTransaction = mService.mTransactionFactory.get();
     }
 
@@ -305,7 +311,9 @@
     public void remove() {
         synchronized (mService.mGlobalLock) {
             final long now = SystemClock.uptimeMillis();
-            if (mSizeMismatch && now - mShownTime < SIZE_MISMATCH_MINIMUM_TIME_MS) {
+            if (mSizeMismatch && now - mShownTime < SIZE_MISMATCH_MINIMUM_TIME_MS
+                    // Show the latest content as soon as possible for unlocking to home.
+                    && mActivityType != ACTIVITY_TYPE_HOME) {
                 mHandler.postAtTime(this::remove, mShownTime + SIZE_MISMATCH_MINIMUM_TIME_MS);
                 ProtoLog.v(WM_DEBUG_STARTING_WINDOW,
                         "Defer removing snapshot surface in %dms", (now - mShownTime));
diff --git a/services/core/java/com/android/server/wm/UnknownAppVisibilityController.java b/services/core/java/com/android/server/wm/UnknownAppVisibilityController.java
index 61e9e50..5e81e40 100644
--- a/services/core/java/com/android/server/wm/UnknownAppVisibilityController.java
+++ b/services/core/java/com/android/server/wm/UnknownAppVisibilityController.java
@@ -24,8 +24,6 @@
 import android.util.ArrayMap;
 import android.util.Slog;
 
-import com.android.server.wm.WindowManagerService.H;
-
 import java.io.PrintWriter;
 
 /**
@@ -102,7 +100,13 @@
         if (DEBUG_UNKNOWN_APP_VISIBILITY) {
             Slog.d(TAG, "App launched activity=" + activity);
         }
-        mUnknownApps.put(activity, UNKNOWN_STATE_WAITING_RESUME);
+        // If the activity was started with launchTaskBehind, the lifecycle will goes to paused
+        // directly, and the process will pass onResume, so we don't need to waiting resume for it.
+        if (!activity.mLaunchTaskBehind) {
+            mUnknownApps.put(activity, UNKNOWN_STATE_WAITING_RESUME);
+        } else {
+            mUnknownApps.put(activity, UNKNOWN_STATE_WAITING_RELAYOUT);
+        }
     }
 
     /**
diff --git a/services/core/java/com/android/server/wm/WallpaperAnimationAdapter.java b/services/core/java/com/android/server/wm/WallpaperAnimationAdapter.java
index f467015..38bff9e 100644
--- a/services/core/java/com/android/server/wm/WallpaperAnimationAdapter.java
+++ b/services/core/java/com/android/server/wm/WallpaperAnimationAdapter.java
@@ -15,8 +15,8 @@
  */
 package com.android.server.wm;
 
+import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_REMOTE_ANIMATIONS;
 import static com.android.server.wm.AnimationAdapterProto.REMOTE;
-import static com.android.server.wm.ProtoLogGroup.WM_DEBUG_REMOTE_ANIMATIONS;
 import static com.android.server.wm.RemoteAnimationAdapterWrapperProto.TARGET;
 import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_WINDOW_ANIMATION;
 
@@ -26,7 +26,7 @@
 import android.view.RemoteAnimationTarget;
 import android.view.SurfaceControl;
 
-import com.android.server.protolog.common.ProtoLog;
+import com.android.internal.protolog.common.ProtoLog;
 import com.android.server.wm.SurfaceAnimator.AnimationType;
 
 import java.io.PrintWriter;
diff --git a/services/core/java/com/android/server/wm/WindowAnimator.java b/services/core/java/com/android/server/wm/WindowAnimator.java
index 6377a21..5c6266a 100644
--- a/services/core/java/com/android/server/wm/WindowAnimator.java
+++ b/services/core/java/com/android/server/wm/WindowAnimator.java
@@ -16,7 +16,7 @@
 
 package com.android.server.wm;
 
-import static com.android.server.wm.ProtoLogGroup.WM_SHOW_TRANSACTIONS;
+import static com.android.internal.protolog.ProtoLogGroup.WM_SHOW_TRANSACTIONS;
 import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_ALL;
 import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_APP_TRANSITION;
 import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_RECENTS;
@@ -36,8 +36,8 @@
 import android.view.Choreographer;
 import android.view.SurfaceControl;
 
+import com.android.internal.protolog.common.ProtoLog;
 import com.android.server.policy.WindowManagerPolicy;
-import com.android.server.protolog.common.ProtoLog;
 
 import java.io.PrintWriter;
 import java.util.ArrayList;
diff --git a/services/core/java/com/android/server/wm/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java
index a3a4e40..8a5e70f 100644
--- a/services/core/java/com/android/server/wm/WindowContainer.java
+++ b/services/core/java/com/android/server/wm/WindowContainer.java
@@ -28,14 +28,14 @@
 import static android.os.UserHandle.USER_NULL;
 import static android.view.SurfaceControl.Transaction;
 
+import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_APP_TRANSITIONS;
+import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_APP_TRANSITIONS_ANIM;
+import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_ORIENTATION;
 import static com.android.server.policy.WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER;
 import static com.android.server.wm.AppTransition.MAX_APP_TRANSITION_DURATION;
 import static com.android.server.wm.IdentifierProto.HASH_CODE;
 import static com.android.server.wm.IdentifierProto.TITLE;
 import static com.android.server.wm.IdentifierProto.USER_ID;
-import static com.android.server.wm.ProtoLogGroup.WM_DEBUG_APP_TRANSITIONS;
-import static com.android.server.wm.ProtoLogGroup.WM_DEBUG_APP_TRANSITIONS_ANIM;
-import static com.android.server.wm.ProtoLogGroup.WM_DEBUG_ORIENTATION;
 import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_ALL;
 import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_APP_TRANSITION;
 import static com.android.server.wm.WindowContainer.AnimationFlags.CHILDREN;
@@ -82,8 +82,8 @@
 import android.window.WindowContainerToken;
 
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.protolog.common.ProtoLog;
 import com.android.internal.util.ToBooleanFunction;
-import com.android.server.protolog.common.ProtoLog;
 import com.android.server.wm.SurfaceAnimator.Animatable;
 import com.android.server.wm.SurfaceAnimator.AnimationType;
 import com.android.server.wm.SurfaceAnimator.OnAnimationFinishedCallback;
@@ -857,6 +857,14 @@
     }
 
     /**
+     * @return {@code true} when an application can override an app transition animation on this
+     * container.
+     */
+    boolean canCustomizeAppTransition() {
+        return !WindowManagerService.sDisableCustomTaskAnimationProperty;
+    }
+
+    /**
      * @return {@code true} when this container or its related containers are running an
      * animation, {@code false} otherwise.
      *
diff --git a/services/core/java/com/android/server/wm/WindowContainerThumbnail.java b/services/core/java/com/android/server/wm/WindowContainerThumbnail.java
index b75f886..b9f67a5 100644
--- a/services/core/java/com/android/server/wm/WindowContainerThumbnail.java
+++ b/services/core/java/com/android/server/wm/WindowContainerThumbnail.java
@@ -19,7 +19,7 @@
 import static android.view.SurfaceControl.METADATA_OWNER_UID;
 import static android.view.SurfaceControl.METADATA_WINDOW_TYPE;
 
-import static com.android.server.wm.ProtoLogGroup.WM_SHOW_TRANSACTIONS;
+import static com.android.internal.protolog.ProtoLogGroup.WM_SHOW_TRANSACTIONS;
 import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_RECENTS;
 import static com.android.server.wm.WindowContainerThumbnailProto.HEIGHT;
 import static com.android.server.wm.WindowContainerThumbnailProto.SURFACE_ANIMATOR;
@@ -39,7 +39,7 @@
 import android.view.SurfaceControl.Transaction;
 import android.view.animation.Animation;
 
-import com.android.server.protolog.common.ProtoLog;
+import com.android.internal.protolog.common.ProtoLog;
 import com.android.server.wm.SurfaceAnimator.Animatable;
 import com.android.server.wm.SurfaceAnimator.AnimationType;
 
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 9acaa9e..2ce16b2 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -17,6 +17,7 @@
 package com.android.server.wm;
 
 import static android.Manifest.permission.CONTROL_REMOTE_APP_TRANSITION_ANIMATIONS;
+import static android.Manifest.permission.INPUT_CONSUMER;
 import static android.Manifest.permission.INTERNAL_SYSTEM_WINDOW;
 import static android.Manifest.permission.MANAGE_ACTIVITY_STACKS;
 import static android.Manifest.permission.MANAGE_APP_TOKENS;
@@ -35,6 +36,7 @@
 import static android.content.pm.PackageManager.FEATURE_FREEFORM_WINDOW_MANAGEMENT;
 import static android.content.pm.PackageManager.FEATURE_PC;
 import static android.content.pm.PackageManager.PERMISSION_GRANTED;
+import static android.os.IInputConstants.DEFAULT_DISPATCHING_TIMEOUT_MILLIS;
 import static android.os.Process.INVALID_UID;
 import static android.os.Process.SYSTEM_UID;
 import static android.os.Process.myPid;
@@ -83,22 +85,22 @@
 import static android.view.WindowManagerGlobal.RELAYOUT_RES_SURFACE_CHANGED;
 import static android.view.WindowManagerPolicyConstants.NAV_BAR_INVALID;
 
+import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_ADD_REMOVE;
+import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_BOOT;
+import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_FOCUS;
+import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_FOCUS_LIGHT;
+import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_IME;
+import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_KEEP_SCREEN_ON;
+import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_ORIENTATION;
+import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_SCREEN_ON;
+import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_STARTING_WINDOW;
+import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_WINDOW_MOVEMENT;
+import static com.android.internal.protolog.ProtoLogGroup.WM_ERROR;
+import static com.android.internal.protolog.ProtoLogGroup.WM_SHOW_TRANSACTIONS;
 import static com.android.internal.util.LatencyTracker.ACTION_ROTATE_SCREEN;
 import static com.android.server.LockGuard.INDEX_WINDOW;
 import static com.android.server.LockGuard.installLock;
 import static com.android.server.policy.WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER;
-import static com.android.server.wm.ProtoLogGroup.WM_DEBUG_ADD_REMOVE;
-import static com.android.server.wm.ProtoLogGroup.WM_DEBUG_BOOT;
-import static com.android.server.wm.ProtoLogGroup.WM_DEBUG_FOCUS;
-import static com.android.server.wm.ProtoLogGroup.WM_DEBUG_FOCUS_LIGHT;
-import static com.android.server.wm.ProtoLogGroup.WM_DEBUG_IME;
-import static com.android.server.wm.ProtoLogGroup.WM_DEBUG_KEEP_SCREEN_ON;
-import static com.android.server.wm.ProtoLogGroup.WM_DEBUG_ORIENTATION;
-import static com.android.server.wm.ProtoLogGroup.WM_DEBUG_SCREEN_ON;
-import static com.android.server.wm.ProtoLogGroup.WM_DEBUG_STARTING_WINDOW;
-import static com.android.server.wm.ProtoLogGroup.WM_DEBUG_WINDOW_MOVEMENT;
-import static com.android.server.wm.ProtoLogGroup.WM_ERROR;
-import static com.android.server.wm.ProtoLogGroup.WM_SHOW_TRANSACTIONS;
 import static com.android.server.wm.WindowContainer.AnimationFlags.CHILDREN;
 import static com.android.server.wm.WindowContainer.AnimationFlags.PARENTS;
 import static com.android.server.wm.WindowContainer.AnimationFlags.TRANSITION;
@@ -264,6 +266,8 @@
 import com.android.internal.policy.IKeyguardDismissCallback;
 import com.android.internal.policy.IShortcutService;
 import com.android.internal.policy.KeyInterceptionInfo;
+import com.android.internal.protolog.ProtoLogImpl;
+import com.android.internal.protolog.common.ProtoLog;
 import com.android.internal.util.DumpUtils;
 import com.android.internal.util.FastPrintWriter;
 import com.android.internal.util.LatencyTracker;
@@ -280,8 +284,6 @@
 import com.android.server.policy.WindowManagerPolicy;
 import com.android.server.policy.WindowManagerPolicy.ScreenOffListener;
 import com.android.server.power.ShutdownThread;
-import com.android.server.protolog.ProtoLogImpl;
-import com.android.server.protolog.common.ProtoLog;
 import com.android.server.utils.PriorityDump;
 import com.android.server.wm.utils.DeviceConfigInterface;
 
@@ -366,9 +368,6 @@
     // proceding with safe mode detection.
     private static final int INPUT_DEVICES_READY_FOR_SAFE_MODE_DETECTION_TIMEOUT_MILLIS = 1000;
 
-    // Default input dispatching timeout in nanoseconds.
-    static final long DEFAULT_INPUT_DISPATCHING_TIMEOUT_NANOS = 5000 * 1000000L;
-
     // Poll interval in milliseconds for watching boot animation finished.
     // TODO(b/159045990) Migrate to SystemService.waitForState with dedicated thread.
     private static final int BOOT_ANIMATION_POLL_INTERVAL = 50;
@@ -395,6 +394,21 @@
     // trying to apply a new one.
     private static final boolean ALWAYS_KEEP_CURRENT = true;
 
+    /**
+     * 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, false);
+
     private static final String DISABLE_TRIPLE_BUFFERING_PROPERTY =
             "ro.sf.disable_triple_buffer";
 
@@ -916,7 +930,7 @@
 
     private void setShadowRenderer() {
         mRenderShadowsInCompositor = Settings.Global.getInt(mContext.getContentResolver(),
-                DEVELOPMENT_RENDER_SHADOWS_IN_COMPOSITOR, 0) != 0;
+                DEVELOPMENT_RENDER_SHADOWS_IN_COMPOSITOR, 1) != 0;
     }
 
     PowerManager mPowerManager;
@@ -3599,7 +3613,7 @@
             }
 
             if (SHOW_VERBOSE_TRANSACTIONS) Slog.i(TAG_WM, ">>> showStrictModeViolation");
-            // TODO: Modify this to use the surface trace once it is not going crazy.
+            // TODO: Modify this to use the surface trace once it is not going baffling.
             // b/31532461
             // TODO(multi-display): support multiple displays
             if (mStrictModeFlash == null) {
@@ -4727,7 +4741,6 @@
 
     final class H extends android.os.Handler {
         public static final int REPORT_FOCUS_CHANGE = 2;
-        public static final int REPORT_LOSING_FOCUS = 3;
         public static final int WINDOW_FREEZE_TIMEOUT = 11;
 
         public static final int PERSIST_ANIMATION_SCALE = 14;
@@ -4802,11 +4815,6 @@
                         ProtoLog.i(WM_DEBUG_FOCUS_LIGHT, "Focus moving from %s"
                                         + " to %s displayId=%d", lastFocus, newFocus,
                                 displayContent.getDisplayId());
-                        if (newFocus != null && lastFocus != null && !newFocus.isDisplayedLw()) {
-                            ProtoLog.i(WM_DEBUG_FOCUS_LIGHT, "Delaying loss of focus...");
-                            displayContent.mLosingFocus.add(lastFocus);
-                            lastFocus = null;
-                        }
                     }
 
                     // First notify the accessibility manager for the change so it has
@@ -4829,24 +4837,6 @@
                     break;
                 }
 
-                case REPORT_LOSING_FOCUS: {
-                    final DisplayContent displayContent = (DisplayContent) msg.obj;
-                    ArrayList<WindowState> losers;
-
-                    synchronized (mGlobalLock) {
-                        losers = displayContent.mLosingFocus;
-                        displayContent.mLosingFocus = new ArrayList<>();
-                    }
-
-                    final int N = losers.size();
-                    for (int i = 0; i < N; i++) {
-                        ProtoLog.i(WM_DEBUG_FOCUS_LIGHT, "Losing delayed focus: %s",
-                                losers.get(i));
-                        losers.get(i).reportFocusChangedSerialized(false);
-                    }
-                    break;
-                }
-
                 case WINDOW_FREEZE_TIMEOUT: {
                     final DisplayContent displayContent = (DisplayContent) msg.obj;
                     synchronized (mGlobalLock) {
@@ -5872,6 +5862,11 @@
     @Override
     public void createInputConsumer(IBinder token, String name, int displayId,
             InputChannel inputChannel) {
+        if (!mAtmInternal.isCallerRecents(Binder.getCallingUid())
+                && mContext.checkCallingOrSelfPermission(INPUT_CONSUMER) != PERMISSION_GRANTED) {
+            throw new SecurityException("createInputConsumer requires INPUT_CONSUMER permission");
+        }
+
         synchronized (mGlobalLock) {
             DisplayContent display = mRoot.getDisplayContent(displayId);
             if (display != null) {
@@ -5883,6 +5878,11 @@
 
     @Override
     public boolean destroyInputConsumer(String name, int displayId) {
+        if (!mAtmInternal.isCallerRecents(Binder.getCallingUid())
+                && mContext.checkCallingOrSelfPermission(INPUT_CONSUMER) != PERMISSION_GRANTED) {
+            throw new SecurityException("destroyInputConsumer requires INPUT_CONSUMER permission");
+        }
+
         synchronized (mGlobalLock) {
             DisplayContent display = mRoot.getDisplayContent(displayId);
             if (display != null) {
@@ -8075,9 +8075,8 @@
                 | LayoutParams.FLAG_SLIPPERY);
         h.layoutParamsFlags = WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL | sanitizedFlags;
         h.layoutParamsType = type;
-        h.dispatchingTimeoutNanos = DEFAULT_INPUT_DISPATCHING_TIMEOUT_NANOS;
-        h.canReceiveKeys = false;
-        h.hasFocus = false;
+        h.dispatchingTimeoutMillis = DEFAULT_DISPATCHING_TIMEOUT_MILLIS;
+        h.focusable = false;
         h.hasWallpaper = false;
         h.paused = false;
 
diff --git a/services/core/java/com/android/server/wm/WindowManagerShellCommand.java b/services/core/java/com/android/server/wm/WindowManagerShellCommand.java
index bdecb8d..271d2b1 100644
--- a/services/core/java/com/android/server/wm/WindowManagerShellCommand.java
+++ b/services/core/java/com/android/server/wm/WindowManagerShellCommand.java
@@ -31,7 +31,7 @@
 import android.view.ViewDebug;
 
 import com.android.internal.os.ByteTransferPipe;
-import com.android.server.protolog.ProtoLogImpl;
+import com.android.internal.protolog.ProtoLogImpl;
 
 import java.io.IOException;
 import java.io.PrintWriter;
diff --git a/services/core/java/com/android/server/wm/WindowProcessController.java b/services/core/java/com/android/server/wm/WindowProcessController.java
index 6ba8769..c714eeb 100644
--- a/services/core/java/com/android/server/wm/WindowProcessController.java
+++ b/services/core/java/com/android/server/wm/WindowProcessController.java
@@ -19,6 +19,7 @@
 import static android.app.ActivityManager.PROCESS_STATE_NONEXISTENT;
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
 import static android.os.Build.VERSION_CODES.Q;
+import static android.os.IInputConstants.DEFAULT_DISPATCHING_TIMEOUT_MILLIS;
 import static android.view.Display.INVALID_DISPLAY;
 
 import static com.android.server.am.ActivityManagerService.MY_PID;
@@ -30,8 +31,7 @@
 import static com.android.server.wm.ActivityTaskManagerDebugConfig.TAG_ATM;
 import static com.android.server.wm.ActivityTaskManagerDebugConfig.TAG_WITH_CLASS_NAME;
 import static com.android.server.wm.ActivityTaskManagerService.ACTIVITY_BG_START_GRACE_PERIOD_MS;
-import static com.android.server.wm.ActivityTaskManagerService.INSTRUMENTATION_KEY_DISPATCHING_TIMEOUT_MS;
-import static com.android.server.wm.ActivityTaskManagerService.KEY_DISPATCHING_TIMEOUT_MS;
+import static com.android.server.wm.ActivityTaskManagerService.INSTRUMENTATION_KEY_DISPATCHING_TIMEOUT_MILLIS;
 import static com.android.server.wm.ActivityTaskManagerService.RELAUNCH_REASON_NONE;
 import static com.android.server.wm.Task.ActivityState.DESTROYED;
 import static com.android.server.wm.Task.ActivityState.DESTROYING;
@@ -41,6 +41,7 @@
 import static com.android.server.wm.Task.ActivityState.STARTED;
 import static com.android.server.wm.Task.ActivityState.STOPPING;
 
+
 import android.Manifest;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -177,8 +178,10 @@
     // Whether this process has ever started a service with the BIND_INPUT_METHOD permission.
     private volatile boolean mHasImeService;
 
-    // all activities running in the process
+    /** All activities running in the process (exclude destroying). */
     private final ArrayList<ActivityRecord> mActivities = new ArrayList<>();
+    /** The activities will be removed but still belong to this process. */
+    private ArrayList<ActivityRecord> mInactiveActivities;
     // any tasks this process had run root activities in
     private final ArrayList<Task> mRecentTasks = new ArrayList<>();
     // The most recent top-most activity that was resumed in the process for pre-Q app.
@@ -193,7 +196,6 @@
     private final Configuration mLastReportedConfiguration = new Configuration();
     // Configuration that is waiting to be dispatched to the process.
     private Configuration mPendingConfiguration;
-    private final Configuration mNewOverrideConfig = new Configuration();
     // Registered display id as a listener to override config change
     private int mDisplayId;
     private ActivityRecord mConfigActivityRecord;
@@ -635,21 +637,39 @@
             return;
         }
         mActivities.add(r);
+        if (mInactiveActivities != null) {
+            mInactiveActivities.remove(r);
+        }
         updateActivityConfigurationListener();
     }
 
-    void removeActivity(ActivityRecord r) {
+    /**
+     * Indicates that the given activity is no longer active in this process.
+     *
+     * @param r The running activity to be removed.
+     * @param keepAssociation {@code true} if the activity still belongs to this process but will
+     *                        be removed soon, e.g. destroying. From the perspective of process
+     *                        priority, the process is not important if it only contains activities
+     *                        that are being destroyed. But the association is still needed to
+     *                        ensure all activities are reachable from this process.
+     */
+    void removeActivity(ActivityRecord r, boolean keepAssociation) {
+        if (keepAssociation) {
+            if (mInactiveActivities == null) {
+                mInactiveActivities = new ArrayList<>();
+                mInactiveActivities.add(r);
+            } else if (!mInactiveActivities.contains(r)) {
+                mInactiveActivities.add(r);
+            }
+        } else if (mInactiveActivities != null) {
+            mInactiveActivities.remove(r);
+        }
         mActivities.remove(r);
         updateActivityConfigurationListener();
     }
 
-    void makeFinishingForProcessRemoved() {
-        for (int i = mActivities.size() - 1; i >= 0; --i) {
-            mActivities.get(i).makeFinishingLocked();
-        }
-    }
-
     void clearActivities() {
+        mInactiveActivities = null;
         mActivities.clear();
         updateActivityConfigurationListener();
     }
@@ -1044,10 +1064,16 @@
         return RELAUNCH_REASON_NONE;
     }
 
-    public long getInputDispatchingTimeout() {
+    /**
+     * Get the current dispatching timeout. If instrumentation is currently taking place, return
+     * a longer value. Shorter timeout is returned otherwise.
+     * @return The timeout in milliseconds
+     */
+    public long getInputDispatchingTimeoutMillis() {
         synchronized (mAtm.mGlobalLock) {
             return isInstrumenting() || isUsingWrapper()
-                    ? INSTRUMENTATION_KEY_DISPATCHING_TIMEOUT_MS : KEY_DISPATCHING_TIMEOUT_MS;
+                    ? INSTRUMENTATION_KEY_DISPATCHING_TIMEOUT_MILLIS :
+                    DEFAULT_DISPATCHING_TIMEOUT_MILLIS;
         }
     }
 
@@ -1142,6 +1168,51 @@
         mAtm.mH.sendMessage(m);
     }
 
+    /**
+     * Clean up the activities belonging to this process.
+     *
+     * @return {@code true} if the process has any visible activity.
+     */
+    boolean handleAppDied() {
+        mAtm.mStackSupervisor.removeHistoryRecords(this);
+
+        boolean hasVisibleActivities = false;
+        if (mInactiveActivities != null && !mInactiveActivities.isEmpty()) {
+            // Make sure that all activities in this process are handled.
+            mActivities.addAll(mInactiveActivities);
+        }
+        if (isRemoved()) {
+            // The package of the died process should be force-stopped, so make its activities as
+            // finishing to prevent the process from being started again if the next top (or being
+            // visible) activity also resides in the same process. This must be done before removal.
+            for (int i = mActivities.size() - 1; i >= 0; i--) {
+                mActivities.get(i).makeFinishingLocked();
+            }
+        }
+        for (int i = mActivities.size() - 1; i >= 0; i--) {
+            final ActivityRecord r = mActivities.get(i);
+            if (r.mVisibleRequested || r.isVisible()) {
+                // While an activity launches a new activity, it's possible that the old activity
+                // is already requested to be hidden (mVisibleRequested=false), but this visibility
+                // is not yet committed, so isVisible()=true.
+                hasVisibleActivities = true;
+            }
+
+            final Task rootTask = r.getRootTask();
+            if (rootTask != null) {
+                // There may be a pausing activity that hasn't shown any window and was requested
+                // to be hidden. But pausing is also a visible state, it should be regarded as
+                // visible, so the caller can know the next activity should be resumed.
+                hasVisibleActivities |= rootTask.handleAppDied(this);
+            }
+            r.handleAppDied();
+        }
+        clearRecentTasks();
+        clearActivities();
+
+        return hasVisibleActivities;
+    }
+
     void registerDisplayConfigurationListener(DisplayContent displayContent) {
         if (displayContent == null) {
             return;
@@ -1220,11 +1291,26 @@
     }
 
     @Override
+    public void onRequestedOverrideConfigurationChanged(Configuration overrideConfiguration) {
+        super.onRequestedOverrideConfigurationChanged(
+                sanitizeProcessConfiguration(overrideConfiguration));
+    }
+
+    @Override
     public void onMergedOverrideConfigurationChanged(Configuration mergedOverrideConfig) {
+        super.onRequestedOverrideConfigurationChanged(
+                sanitizeProcessConfiguration(mergedOverrideConfig));
+    }
+
+    private static Configuration sanitizeProcessConfiguration(Configuration config) {
         // Make sure that we don't accidentally override the activity type.
-        mNewOverrideConfig.setTo(mergedOverrideConfig);
-        mNewOverrideConfig.windowConfiguration.setActivityType(ACTIVITY_TYPE_UNDEFINED);
-        super.onRequestedOverrideConfigurationChanged(mNewOverrideConfig);
+        if (config.windowConfiguration.getActivityType() != ACTIVITY_TYPE_UNDEFINED) {
+            final Configuration sanitizedConfig = new Configuration(config);
+            sanitizedConfig.windowConfiguration.setActivityType(ACTIVITY_TYPE_UNDEFINED);
+            return sanitizedConfig;
+        }
+
+        return config;
     }
 
     private void updateConfiguration() {
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index ef78420..f262c5a 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -25,6 +25,7 @@
 import static android.app.WindowConfiguration.isSplitScreenWindowingMode;
 import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
 import static android.graphics.GraphicsProtos.dumpPointProto;
+import static android.os.IInputConstants.DEFAULT_DISPATCHING_TIMEOUT_MILLIS;
 import static android.os.PowerManager.DRAW_WAKE_LOCK;
 import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER;
 import static android.view.InsetsState.ITYPE_IME;
@@ -100,6 +101,14 @@
 import static android.view.WindowManagerGlobal.RELAYOUT_RES_FIRST_TIME;
 import static android.view.WindowManagerGlobal.RELAYOUT_RES_SURFACE_CHANGED;
 
+import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_ADD_REMOVE;
+import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_APP_TRANSITIONS;
+import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_FOCUS;
+import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_FOCUS_LIGHT;
+import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_IME;
+import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_ORIENTATION;
+import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_RESIZE;
+import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_STARTING_WINDOW;
 import static com.android.server.am.ActivityManagerService.MY_PID;
 import static com.android.server.policy.WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER;
 import static com.android.server.policy.WindowManagerPolicy.TRANSIT_ENTER;
@@ -115,14 +124,6 @@
 import static com.android.server.wm.MoveAnimationSpecProto.DURATION_MS;
 import static com.android.server.wm.MoveAnimationSpecProto.FROM;
 import static com.android.server.wm.MoveAnimationSpecProto.TO;
-import static com.android.server.wm.ProtoLogGroup.WM_DEBUG_ADD_REMOVE;
-import static com.android.server.wm.ProtoLogGroup.WM_DEBUG_APP_TRANSITIONS;
-import static com.android.server.wm.ProtoLogGroup.WM_DEBUG_FOCUS;
-import static com.android.server.wm.ProtoLogGroup.WM_DEBUG_FOCUS_LIGHT;
-import static com.android.server.wm.ProtoLogGroup.WM_DEBUG_IME;
-import static com.android.server.wm.ProtoLogGroup.WM_DEBUG_ORIENTATION;
-import static com.android.server.wm.ProtoLogGroup.WM_DEBUG_RESIZE;
-import static com.android.server.wm.ProtoLogGroup.WM_DEBUG_STARTING_WINDOW;
 import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_WINDOW_ANIMATION;
 import static com.android.server.wm.WindowContainer.AnimationFlags.PARENTS;
 import static com.android.server.wm.WindowContainer.AnimationFlags.TRANSITION;
@@ -235,10 +236,10 @@
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.policy.KeyInterceptionInfo;
+import com.android.internal.protolog.common.ProtoLog;
 import com.android.internal.util.FrameworkStatsLog;
 import com.android.internal.util.ToBooleanFunction;
 import com.android.server.policy.WindowManagerPolicy;
-import com.android.server.protolog.common.ProtoLog;
 import com.android.server.wm.LocalAnimationAdapter.AnimationSpec;
 import com.android.server.wm.SurfaceAnimator.AnimationType;
 import com.android.server.wm.utils.WmDisplayCutout;
@@ -727,7 +728,8 @@
      * @return The insets state as requested by the client, i.e. the dispatched insets state
      *         for which the visibilities are overridden with what the client requested.
      */
-    InsetsState getRequestedInsetsState() {
+    @Override
+    public InsetsState getRequestedInsetsState() {
         return mRequestedInsetsState;
     }
 
@@ -1534,10 +1536,6 @@
     }
 
     InsetsState getInsetsState() {
-        final InsetsState insetsState = mToken.getFixedRotationTransformInsetsState();
-        if (insetsState != null) {
-            return insetsState;
-        }
         return getDisplayContent().getInsetsPolicy().getInsetsForDispatch(this);
     }
 
@@ -1635,10 +1633,10 @@
         }
     }
 
-    public long getInputDispatchingTimeoutNanos() {
+    public long getInputDispatchingTimeoutMillis() {
         return mActivityRecord != null
-                ? mActivityRecord.mInputDispatchingTimeoutNanos
-                : WindowManagerService.DEFAULT_INPUT_DISPATCHING_TIMEOUT_NANOS;
+                ? mActivityRecord.mInputDispatchingTimeoutMillis
+                : DEFAULT_DISPATCHING_TIMEOUT_MILLIS;
     }
 
     @Override
@@ -2060,10 +2058,17 @@
         // animating... let's do something.
         final int left = mWindowFrames.mFrame.left;
         final int top = mWindowFrames.mFrame.top;
+
+        // During the transition from pip to fullscreen, the activity windowing mode is set to
+        // fullscreen at the beginning while the task is kept in pinned mode. Skip the move
+        // animation in such case since the transition is handled in SysUI.
+        final boolean hasMovementAnimation = getTask() == null
+                ? getWindowConfiguration().hasMovementAnimations()
+                : getTask().getWindowConfiguration().hasMovementAnimations();
         if (mToken.okToAnimate()
                 && (mAttrs.privateFlags & PRIVATE_FLAG_NO_MOVE_ANIMATION) == 0
                 && !isDragResizing()
-                && getWindowConfiguration().hasMovementAnimations()
+                && hasMovementAnimation
                 && !mWinAnimator.mLastHidden
                 && !mSeamlesslyRotated) {
             startMoveAnimation(left, top);
@@ -2459,9 +2464,7 @@
             finishInputEvent(event, true);
         }
     }
-    /**
-     *  Dummy event receiver for windows that died visible.
-     */
+    /** Fake event receiver for windows that died visible. */
     private DeadWindowEventReceiver mDeadWindowEventReceiver;
 
     void openInputChannel(InputChannel outInputChannel) {
@@ -2479,9 +2482,9 @@
             mClientChannel.dispose();
             mClientChannel = null;
         } else {
-            // If the window died visible, we setup a dummy input channel, so that taps
+            // If the window died visible, we setup a fake input channel, so that taps
             // can still detected by input monitor channel, and we can relaunch the app.
-            // Create dummy event receiver that simply reports all events as handled.
+            // Create fake event receiver that simply reports all events as handled.
             mDeadWindowEventReceiver = new DeadWindowEventReceiver(mClientChannel);
         }
         mWmService.mInputToWindowMap.put(mInputWindowHandle.token, this);
diff --git a/services/core/java/com/android/server/wm/WindowStateAnimator.java b/services/core/java/com/android/server/wm/WindowStateAnimator.java
index d0101ad..6f48342 100644
--- a/services/core/java/com/android/server/wm/WindowStateAnimator.java
+++ b/services/core/java/com/android/server/wm/WindowStateAnimator.java
@@ -31,13 +31,14 @@
 import static android.view.WindowManager.LayoutParams.TYPE_WALLPAPER;
 import static android.view.WindowManager.TRANSIT_NONE;
 
+import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_DRAW;
+import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_ORIENTATION;
+import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_STARTING_WINDOW;
+import static com.android.internal.protolog.ProtoLogGroup.WM_SHOW_SURFACE_ALLOC;
+import static com.android.internal.protolog.ProtoLogGroup.WM_SHOW_TRANSACTIONS;
 import static com.android.server.policy.WindowManagerPolicy.FINISH_LAYOUT_REDO_ANIM;
 import static com.android.server.policy.WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER;
-import static com.android.server.wm.ProtoLogGroup.WM_DEBUG_DRAW;
-import static com.android.server.wm.ProtoLogGroup.WM_DEBUG_ORIENTATION;
-import static com.android.server.wm.ProtoLogGroup.WM_DEBUG_STARTING_WINDOW;
-import static com.android.server.wm.ProtoLogGroup.WM_SHOW_SURFACE_ALLOC;
-import static com.android.server.wm.ProtoLogGroup.WM_SHOW_TRANSACTIONS;
+import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_WINDOW_ANIMATION;
 import static com.android.server.wm.WindowContainer.AnimationFlags.PARENTS;
 import static com.android.server.wm.WindowContainer.AnimationFlags.TRANSITION;
 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG;
@@ -76,8 +77,8 @@
 import android.view.animation.Animation;
 import android.view.animation.AnimationUtils;
 
+import com.android.internal.protolog.common.ProtoLog;
 import com.android.server.policy.WindowManagerPolicy;
-import com.android.server.protolog.common.ProtoLog;
 
 import java.io.PrintWriter;
 
@@ -560,9 +561,9 @@
             }
         }
 
-        // Something is wrong and SurfaceFlinger will not like this, try to revert to sane values.
-        // This doesn't necessarily mean that there is an error in the system. The sizes might be
-        // incorrect, because it is before the first layout or draw.
+        // Something is wrong and SurfaceFlinger will not like this, try to revert to reasonable
+        // values. This doesn't necessarily mean that there is an error in the system. The sizes
+        // might be incorrect, because it is before the first layout or draw.
         if (outSize.width() < 1) {
             outSize.right = 1;
         }
@@ -659,80 +660,7 @@
     }
 
     void computeShownFrameLocked() {
-        final ScreenRotationAnimation screenRotationAnimation =
-                mWin.getDisplayContent().getRotationAnimation();
-        final boolean windowParticipatesInScreenRotationAnimation =
-                !mWin.mForceSeamlesslyRotate;
-        final boolean screenAnimation = screenRotationAnimation != null
-                && screenRotationAnimation.isAnimating()
-                && windowParticipatesInScreenRotationAnimation;
-
-        if (screenAnimation) {
-            // cache often used attributes locally
-            final Rect frame = mWin.getFrameLw();
-            final float tmpFloats[] = mService.mTmpFloats;
-            final Matrix tmpMatrix = mWin.mTmpMatrix;
-
-            // Compute the desired transformation.
-            if (screenRotationAnimation.isRotating()) {
-                // If we are doing a screen animation, the global rotation
-                // applied to windows can result in windows that are carefully
-                // aligned with each other to slightly separate, allowing you
-                // to see what is behind them.  An unsightly mess.  This...
-                // thing...  magically makes it call good: scale each window
-                // slightly (two pixels larger in each dimension, from the
-                // window's center).
-                final float w = frame.width();
-                final float h = frame.height();
-                if (w>=1 && h>=1) {
-                    tmpMatrix.setScale(1 + 2/w, 1 + 2/h, w/2, h/2);
-                } else {
-                    tmpMatrix.reset();
-                }
-            } else {
-                tmpMatrix.reset();
-            }
-
-            tmpMatrix.postScale(mWin.mGlobalScale, mWin.mGlobalScale);
-
-            // WindowState.prepareSurfaces expands for surface insets (in order they don't get
-            // clipped by the WindowState surface), so we need to go into the other direction here.
-            tmpMatrix.postTranslate(mWin.mAttrs.surfaceInsets.left,
-                    mWin.mAttrs.surfaceInsets.top);
-
-
-            // "convert" it into SurfaceFlinger's format
-            // (a 2x2 matrix + an offset)
-            // Here we must not transform the position of the surface
-            // since it is already included in the transformation.
-            //Slog.i(TAG_WM, "Transform: " + matrix);
-
-            mHaveMatrix = true;
-            tmpMatrix.getValues(tmpFloats);
-            mDsDx = tmpFloats[Matrix.MSCALE_X];
-            mDtDx = tmpFloats[Matrix.MSKEW_Y];
-            mDtDy = tmpFloats[Matrix.MSKEW_X];
-            mDsDy = tmpFloats[Matrix.MSCALE_Y];
-
-            // Now set the alpha...  but because our current hardware
-            // can't do alpha transformation on a non-opaque surface,
-            // turn it off if we are running an animation that is also
-            // transforming since it is more important to have that
-            // animation be smooth.
-            mShownAlpha = mAlpha;
-            if (!mService.mLimitedAlphaCompositing
-                    || (!PixelFormat.formatHasAlpha(mWin.mAttrs.format)
-                    || (mWin.isIdentityMatrix(mDsDx, mDtDx, mDtDy, mDsDy)))) {
-                mShownAlpha *= screenRotationAnimation.getEnterTransformation().getAlpha();
-            }
-
-            if ((DEBUG_ANIM || DEBUG) && (mShownAlpha == 1.0 || mShownAlpha == 0.0)) {
-                Slog.v(TAG, "computeShownFrameLocked: Animating " + this + " mAlpha=" + mAlpha
-                                + " screen=" + (screenAnimation
-                        ? screenRotationAnimation.getEnterTransformation().getAlpha() : "null"));
-            }
-            return;
-        } else if (mIsWallpaper && mService.mRoot.mWallpaperActionPending) {
+        if (mIsWallpaper && mService.mRoot.mWallpaperActionPending) {
             return;
         } else if (mWin.isDragResizeChanged()) {
             // This window is awaiting a relayout because user just started (or ended)
@@ -1327,7 +1255,7 @@
             mWin.getDisplayContent().adjustForImeIfNeeded();
         }
 
-        return mWin.isAnimating(PARENTS);
+        return mWin.isAnimating(0 /* flags */, ANIMATION_TYPE_WINDOW_ANIMATION);
     }
 
     void dumpDebug(ProtoOutputStream proto, long fieldId) {
diff --git a/services/core/java/com/android/server/wm/WindowSurfaceController.java b/services/core/java/com/android/server/wm/WindowSurfaceController.java
index b89cdd3..9b40822 100644
--- a/services/core/java/com/android/server/wm/WindowSurfaceController.java
+++ b/services/core/java/com/android/server/wm/WindowSurfaceController.java
@@ -21,8 +21,8 @@
 import static android.view.SurfaceControl.METADATA_OWNER_UID;
 import static android.view.SurfaceControl.METADATA_WINDOW_TYPE;
 
-import static com.android.server.wm.ProtoLogGroup.WM_SHOW_SURFACE_ALLOC;
-import static com.android.server.wm.ProtoLogGroup.WM_SHOW_TRANSACTIONS;
+import static com.android.internal.protolog.ProtoLogGroup.WM_SHOW_SURFACE_ALLOC;
+import static com.android.internal.protolog.ProtoLogGroup.WM_SHOW_TRANSACTIONS;
 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_VISIBILITY;
 import static com.android.server.wm.WindowManagerDebugConfig.SHOW_LIGHT_TRANSACTIONS;
 import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
@@ -40,7 +40,7 @@
 import android.view.WindowContentFrameStats;
 import android.view.WindowManager;
 
-import com.android.server.protolog.common.ProtoLog;
+import com.android.internal.protolog.common.ProtoLog;
 
 import java.io.PrintWriter;
 
diff --git a/services/core/java/com/android/server/wm/WindowToken.java b/services/core/java/com/android/server/wm/WindowToken.java
index 2c1bb3e..bc4d9a9 100644
--- a/services/core/java/com/android/server/wm/WindowToken.java
+++ b/services/core/java/com/android/server/wm/WindowToken.java
@@ -23,10 +23,10 @@
 import static android.view.WindowManager.LayoutParams.TYPE_DOCK_DIVIDER;
 import static android.view.WindowManager.LayoutParams.TYPE_NAVIGATION_BAR;
 
-import static com.android.server.wm.ProtoLogGroup.WM_DEBUG_ADD_REMOVE;
-import static com.android.server.wm.ProtoLogGroup.WM_DEBUG_FOCUS;
-import static com.android.server.wm.ProtoLogGroup.WM_DEBUG_WINDOW_MOVEMENT;
-import static com.android.server.wm.ProtoLogGroup.WM_ERROR;
+import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_ADD_REMOVE;
+import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_FOCUS;
+import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_WINDOW_MOVEMENT;
+import static com.android.internal.protolog.ProtoLogGroup.WM_ERROR;
 import static com.android.server.wm.WindowContainer.AnimationFlags.CHILDREN;
 import static com.android.server.wm.WindowContainer.AnimationFlags.PARENTS;
 import static com.android.server.wm.WindowContainer.AnimationFlags.TRANSITION;
@@ -59,8 +59,8 @@
 import android.view.WindowManager;
 
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.protolog.common.ProtoLog;
 import com.android.server.policy.WindowManagerPolicy;
-import com.android.server.protolog.common.ProtoLog;
 
 import java.io.PrintWriter;
 import java.util.ArrayList;
@@ -548,7 +548,7 @@
     void applyFixedRotationTransform(DisplayInfo info, DisplayFrames displayFrames,
             Configuration config) {
         if (mFixedRotationTransformState != null) {
-            return;
+            cleanUpFixedRotationTransformState(true /* replacing */);
         }
         mFixedRotationTransformState = new FixedRotationTransformState(info, displayFrames,
                 new Configuration(config), mDisplayContent.getRotation());
@@ -565,12 +565,12 @@
      * one. This takes the same effect as {@link #applyFixedRotationTransform}.
      */
     void linkFixedRotationTransform(WindowToken other) {
-        if (mFixedRotationTransformState != null) {
+        final FixedRotationTransformState fixedRotationState = other.mFixedRotationTransformState;
+        if (fixedRotationState == null || mFixedRotationTransformState == fixedRotationState) {
             return;
         }
-        final FixedRotationTransformState fixedRotationState = other.mFixedRotationTransformState;
-        if (fixedRotationState == null) {
-            return;
+        if (mFixedRotationTransformState != null) {
+            cleanUpFixedRotationTransformState(true /* replacing */);
         }
         mFixedRotationTransformState = fixedRotationState;
         fixedRotationState.mAssociatedTokens.add(this);
@@ -626,11 +626,17 @@
         // The state is cleared at the end, because it is used to indicate that other windows can
         // use seamless rotation when applying rotation to display.
         for (int i = state.mAssociatedTokens.size() - 1; i >= 0; i--) {
-            state.mAssociatedTokens.get(i).cleanUpFixedRotationTransformState();
+            state.mAssociatedTokens.get(i).cleanUpFixedRotationTransformState(
+                    false /* replacing */);
         }
     }
 
-    private void cleanUpFixedRotationTransformState() {
+    private void cleanUpFixedRotationTransformState(boolean replacing) {
+        if (replacing && mFixedRotationTransformState.mAssociatedTokens.size() > 1) {
+            // The state is not only used by self. Make sure to leave the influence by others.
+            mFixedRotationTransformState.mAssociatedTokens.remove(this);
+            mFixedRotationTransformState.mRotatedContainers.remove(this);
+        }
         mFixedRotationTransformState = null;
         notifyFixedRotationTransform(false /* enabled */);
     }
diff --git a/services/core/java/com/android/server/wm/WindowTracing.java b/services/core/java/com/android/server/wm/WindowTracing.java
index ba3dc60..e8b8bfc 100644
--- a/services/core/java/com/android/server/wm/WindowTracing.java
+++ b/services/core/java/com/android/server/wm/WindowTracing.java
@@ -34,7 +34,7 @@
 import android.util.proto.ProtoOutputStream;
 import android.view.Choreographer;
 
-import com.android.server.protolog.ProtoLogImpl;
+import com.android.internal.protolog.ProtoLogImpl;
 import com.android.internal.util.TraceBuffer;
 
 import java.io.File;
diff --git a/services/core/jni/Android.bp b/services/core/jni/Android.bp
index 0e1b2f2..4b5f38c 100644
--- a/services/core/jni/Android.bp
+++ b/services/core/jni/Android.bp
@@ -99,6 +99,7 @@
         "libpowermanager",
         "libutils",
         "libui",
+        "libvibratorservice",
         "libinput",
         "libinputflinger",
         "libinputflinger_base",
diff --git a/services/core/jni/com_android_server_VibratorService.cpp b/services/core/jni/com_android_server_VibratorService.cpp
index 05aa359..b3f3a5e 100644
--- a/services/core/jni/com_android_server_VibratorService.cpp
+++ b/services/core/jni/com_android_server_VibratorService.cpp
@@ -17,9 +17,7 @@
 #define LOG_TAG "VibratorService"
 
 #include <android/hardware/vibrator/1.3/IVibrator.h>
-#include <android/hardware/vibrator/BnVibratorCallback.h>
 #include <android/hardware/vibrator/IVibrator.h>
-#include <binder/IServiceManager.h>
 
 #include "jni.h"
 #include <nativehelper/JNIHelp.h>
@@ -28,16 +26,10 @@
 
 #include <utils/misc.h>
 #include <utils/Log.h>
-#include <hardware/vibrator.h>
 
 #include <inttypes.h>
-#include <stdio.h>
 
-using android::hardware::Return;
-using android::hardware::Void;
-using android::hardware::vibrator::V1_0::EffectStrength;
-using android::hardware::vibrator::V1_0::Status;
-using android::hardware::vibrator::V1_1::Effect_1_1;
+#include <vibratorservice/VibratorHalController.h>
 
 namespace V1_0 = android::hardware::vibrator::V1_0;
 namespace V1_1 = android::hardware::vibrator::V1_1;
@@ -47,6 +39,8 @@
 
 namespace android {
 
+static JavaVM* sJvm = nullptr;
+
 static jmethodID sMethodIdOnComplete;
 
 static struct {
@@ -83,341 +77,13 @@
 static_assert(static_cast<uint8_t>(V1_3::Effect::TEXTURE_TICK) ==
                 static_cast<uint8_t>(aidl::Effect::TEXTURE_TICK));
 
-class VibratorCallback {
-    public:
-        VibratorCallback(JNIEnv *env, jobject vibration) :
-        mVibration(MakeGlobalRefOrDie(env, vibration)) {}
-
-        ~VibratorCallback() {
-            JNIEnv *env = AndroidRuntime::getJNIEnv();
-            env->DeleteGlobalRef(mVibration);
-        }
-
-        void onComplete() {
-            auto env = AndroidRuntime::getJNIEnv();
-            env->CallVoidMethod(mVibration, sMethodIdOnComplete);
-        }
-
-    private:
-        jobject mVibration;
-};
-
-class AidlVibratorCallback : public aidl::BnVibratorCallback {
-  public:
-    AidlVibratorCallback(JNIEnv *env, jobject vibration) :
-    mCb(env, vibration) {}
-
-    binder::Status onComplete() override {
-        mCb.onComplete();
-        return binder::Status::ok(); // oneway, local call
+static inline void callVibrationOnComplete(jobject vibration) {
+    if (vibration == nullptr) {
+        return;
     }
-
-  private:
-    VibratorCallback mCb;
-};
-
-static constexpr int NUM_TRIES = 2;
-
-template<class R>
-inline R NoneStatus() {
-    using ::android::hardware::Status;
-    return Status::fromExceptionCode(Status::EX_NONE);
-}
-
-template<>
-inline binder::Status NoneStatus() {
-    using binder::Status;
-    return Status::fromExceptionCode(Status::EX_NONE);
-}
-
-// Creates a Return<R> with STATUS::EX_NULL_POINTER.
-template<class R>
-inline R NullptrStatus() {
-    using ::android::hardware::Status;
-    return Status::fromExceptionCode(Status::EX_NULL_POINTER);
-}
-
-template<>
-inline binder::Status NullptrStatus() {
-    using binder::Status;
-    return Status::fromExceptionCode(Status::EX_NULL_POINTER);
-}
-
-template <typename I>
-sp<I> getService() {
-    return I::getService();
-}
-
-template <>
-sp<aidl::IVibrator> getService() {
-    return waitForVintfService<aidl::IVibrator>();
-}
-
-template <typename I>
-sp<I> tryGetService() {
-    return I::tryGetService();
-}
-
-template <>
-sp<aidl::IVibrator> tryGetService() {
-    return checkVintfService<aidl::IVibrator>();
-}
-
-template <typename I>
-class HalWrapper {
-  public:
-    static std::unique_ptr<HalWrapper> Create() {
-        // Assume that if getService returns a nullptr, HAL is not available on the
-        // device.
-        auto hal = getService<I>();
-        return hal ? std::unique_ptr<HalWrapper>(new HalWrapper(std::move(hal))) : nullptr;
-    }
-
-    // Helper used to transparently deal with the vibrator HAL becoming unavailable.
-    template<class R, class... Args0, class... Args1>
-    R call(R (I::* fn)(Args0...), Args1&&... args1) {
-        // Return<R> doesn't have a default constructor, so make a Return<R> with
-        // STATUS::EX_NONE.
-        R ret{NoneStatus<R>()};
-
-        // Note that ret is guaranteed to be changed after this loop.
-        for (int i = 0; i < NUM_TRIES; ++i) {
-            ret = (mHal == nullptr) ? NullptrStatus<R>()
-                    : (*mHal.*fn)(std::forward<Args1>(args1)...);
-
-            if (ret.isOk()) {
-                break;
-            }
-
-            ALOGE("Failed to issue command to vibrator HAL. Retrying.");
-
-            // Restoring connection to the HAL.
-            mHal = tryGetService<I>();
-        }
-        return ret;
-    }
-
-  private:
-    HalWrapper(sp<I> &&hal) : mHal(std::move(hal)) {}
-
-  private:
-    sp<I> mHal;
-};
-
-template <typename I>
-static auto getHal() {
-    static auto sHalWrapper = HalWrapper<I>::Create();
-    return sHalWrapper.get();
-}
-
-template<class R, class I, class... Args0, class... Args1>
-R halCall(R (I::* fn)(Args0...), Args1&&... args1) {
-    auto hal = getHal<I>();
-    return hal ? hal->call(fn, std::forward<Args1>(args1)...) : NullptrStatus<R>();
-}
-
-template<class R>
-bool isValidEffect(jlong effect) {
-    if (effect < 0) {
-        return false;
-    }
-    R val = static_cast<R>(effect);
-    auto iter = hardware::hidl_enum_range<R>();
-    return val >= *iter.begin() && val <= *std::prev(iter.end());
-}
-
-static void vibratorInit(JNIEnv *env, jclass clazz)
-{
-    if (auto hal = getHal<aidl::IVibrator>()) {
-        // IBinder::pingBinder isn't accessible as a pointer function
-        // but getCapabilities can serve the same purpose
-        int32_t cap;
-        hal->call(&aidl::IVibrator::getCapabilities, &cap).isOk();
-    } else {
-        halCall(&V1_0::IVibrator::ping).isOk();
-    }
-}
-
-static jboolean vibratorExists(JNIEnv* /* env */, jclass /* clazz */)
-{
-    bool ok;
-
-    if (auto hal = getHal<aidl::IVibrator>()) {
-        // IBinder::pingBinder isn't accessible as a pointer function
-        // but getCapabilities can serve the same purpose
-        int32_t cap;
-        ok = hal->call(&aidl::IVibrator::getCapabilities, &cap).isOk();
-    } else {
-        ok = halCall(&V1_0::IVibrator::ping).isOk();
-    }
-    return ok ? JNI_TRUE : JNI_FALSE;
-}
-
-static void vibratorOn(JNIEnv* /* env */, jclass /* clazz */, jlong timeout_ms)
-{
-    if (auto hal = getHal<aidl::IVibrator>()) {
-        auto status = hal->call(&aidl::IVibrator::on, timeout_ms, nullptr);
-        if (!status.isOk()) {
-            ALOGE("vibratorOn command failed: %s", status.toString8().string());
-        }
-    } else {
-        Status retStatus = halCall(&V1_0::IVibrator::on, timeout_ms).withDefault(Status::UNKNOWN_ERROR);
-        if (retStatus != Status::OK) {
-            ALOGE("vibratorOn command failed (%" PRIu32 ").", static_cast<uint32_t>(retStatus));
-        }
-    }
-}
-
-static void vibratorOff(JNIEnv* /* env */, jclass /* clazz */)
-{
-    if (auto hal = getHal<aidl::IVibrator>()) {
-        auto status = hal->call(&aidl::IVibrator::off);
-        if (!status.isOk()) {
-            ALOGE("vibratorOff command failed: %s", status.toString8().string());
-        }
-    } else {
-        Status retStatus = halCall(&V1_0::IVibrator::off).withDefault(Status::UNKNOWN_ERROR);
-        if (retStatus != Status::OK) {
-            ALOGE("vibratorOff command failed (%" PRIu32 ").", static_cast<uint32_t>(retStatus));
-        }
-    }
-}
-
-static jlong vibratorSupportsAmplitudeControl(JNIEnv*, jclass) {
-    if (auto hal = getHal<aidl::IVibrator>()) {
-        int32_t cap = 0;
-        if (!hal->call(&aidl::IVibrator::getCapabilities, &cap).isOk()) {
-            return false;
-        }
-        return (cap & aidl::IVibrator::CAP_AMPLITUDE_CONTROL) > 0;
-    } else {
-        return halCall(&V1_0::IVibrator::supportsAmplitudeControl).withDefault(false);
-    }
-}
-
-static void vibratorSetAmplitude(JNIEnv*, jclass, jint amplitude) {
-    if (auto hal = getHal<aidl::IVibrator>()) {
-        auto status = hal->call(&aidl::IVibrator::IVibrator::setAmplitude, static_cast<float>(amplitude) / UINT8_MAX);
-        if (!status.isOk()) {
-            ALOGE("Failed to set vibrator amplitude: %s", status.toString8().string());
-        }
-    } else {
-        Status status = halCall(&V1_0::IVibrator::setAmplitude, static_cast<uint32_t>(amplitude))
-            .withDefault(Status::UNKNOWN_ERROR);
-        if (status != Status::OK) {
-            ALOGE("Failed to set vibrator amplitude (%" PRIu32 ").",
-                  static_cast<uint32_t>(status));
-        }
-    }
-}
-
-static jboolean vibratorSupportsExternalControl(JNIEnv*, jclass) {
-    if (auto hal = getHal<aidl::IVibrator>()) {
-        int32_t cap = 0;
-        if (!hal->call(&aidl::IVibrator::getCapabilities, &cap).isOk()) {
-            return false;
-        }
-        return (cap & aidl::IVibrator::CAP_EXTERNAL_CONTROL) > 0;
-    } else {
-        return halCall(&V1_3::IVibrator::supportsExternalControl).withDefault(false);
-    }
-}
-
-static void vibratorSetExternalControl(JNIEnv*, jclass, jboolean enabled) {
-    if (auto hal = getHal<aidl::IVibrator>()) {
-        auto status = hal->call(&aidl::IVibrator::IVibrator::setExternalControl, enabled);
-        if (!status.isOk()) {
-            ALOGE("Failed to set vibrator external control: %s", status.toString8().string());
-        }
-    } else {
-        Status status = halCall(&V1_3::IVibrator::setExternalControl, static_cast<uint32_t>(enabled))
-            .withDefault(Status::UNKNOWN_ERROR);
-        if (status != Status::OK) {
-            ALOGE("Failed to set vibrator external control (%" PRIu32 ").",
-                static_cast<uint32_t>(status));
-        }
-    }
-}
-
-static jintArray vibratorGetSupportedEffects(JNIEnv *env, jclass) {
-    if (auto hal = getHal<aidl::IVibrator>()) {
-        std::vector<aidl::Effect> supportedEffects;
-        if (!hal->call(&aidl::IVibrator::getSupportedEffects, &supportedEffects).isOk()) {
-            return nullptr;
-        }
-        jintArray arr = env->NewIntArray(supportedEffects.size());
-        env->SetIntArrayRegion(arr, 0, supportedEffects.size(),
-                reinterpret_cast<jint*>(supportedEffects.data()));
-        return arr;
-    } else {
-        return nullptr;
-    }
-}
-
-static jlong vibratorPerformEffect(JNIEnv* env, jclass, jlong effect, jlong strength,
-                                   jobject vibration, jboolean withCallback) {
-    if (auto hal = getHal<aidl::IVibrator>()) {
-        int32_t lengthMs;
-        sp<AidlVibratorCallback> effectCallback =
-                (withCallback != JNI_FALSE ? new AidlVibratorCallback(env, vibration) : nullptr);
-        aidl::Effect effectType(static_cast<aidl::Effect>(effect));
-        aidl::EffectStrength effectStrength(static_cast<aidl::EffectStrength>(strength));
-
-        auto status = hal->call(&aidl::IVibrator::perform, effectType, effectStrength, effectCallback, &lengthMs);
-        if (!status.isOk()) {
-            if (status.exceptionCode() != binder::Status::EX_UNSUPPORTED_OPERATION) {
-                ALOGE("Failed to perform haptic effect: effect=%" PRId64 ", strength=%" PRId32
-                        ": %s", static_cast<int64_t>(effect), static_cast<int32_t>(strength), status.toString8().string());
-            }
-            return -1;
-        }
-        return lengthMs;
-    } else {
-        Status status;
-        uint32_t lengthMs;
-        auto callback = [&status, &lengthMs](Status retStatus, uint32_t retLengthMs) {
-            status = retStatus;
-            lengthMs = retLengthMs;
-        };
-        EffectStrength effectStrength(static_cast<EffectStrength>(strength));
-
-        Return<void> ret;
-        if (isValidEffect<V1_0::Effect>(effect)) {
-            ret = halCall(&V1_0::IVibrator::perform, static_cast<V1_0::Effect>(effect),
-                    effectStrength, callback);
-        } else if (isValidEffect<Effect_1_1>(effect)) {
-            ret = halCall(&V1_1::IVibrator::perform_1_1, static_cast<Effect_1_1>(effect),
-                            effectStrength, callback);
-        } else if (isValidEffect<V1_2::Effect>(effect)) {
-            ret = halCall(&V1_2::IVibrator::perform_1_2, static_cast<V1_2::Effect>(effect),
-                            effectStrength, callback);
-        } else if (isValidEffect<V1_3::Effect>(effect)) {
-            ret = halCall(&V1_3::IVibrator::perform_1_3, static_cast<V1_3::Effect>(effect),
-                            effectStrength, callback);
-        } else {
-            ALOGW("Unable to perform haptic effect, invalid effect ID (%" PRId32 ")",
-                    static_cast<int32_t>(effect));
-            return -1;
-        }
-
-        if (!ret.isOk()) {
-            ALOGW("Failed to perform effect (%" PRId32 ")", static_cast<int32_t>(effect));
-            return -1;
-        }
-
-        if (status == Status::OK) {
-            return lengthMs;
-        } else if (status != Status::UNSUPPORTED_OPERATION) {
-            // Don't warn on UNSUPPORTED_OPERATION, that's a normal event and just means the motor
-            // doesn't have a pre-defined waveform to perform for it, so we should just give the
-            // opportunity to fall back to the framework waveforms.
-            ALOGE("Failed to perform haptic effect: effect=%" PRId64 ", strength=%" PRId32
-                    ", error=%" PRIu32 ").", static_cast<int64_t>(effect),
-                    static_cast<int32_t>(strength), static_cast<uint32_t>(status));
-        }
-    }
-
-    return -1;
+    auto jniEnv = GetOrAttachJNIEnvironment(sJvm);
+    jniEnv->CallVoidMethod(vibration, sMethodIdOnComplete);
+    jniEnv->DeleteGlobalRef(vibration);
 }
 
 static aidl::CompositeEffect effectFromJavaPrimitive(JNIEnv* env, jobject primitive) {
@@ -425,14 +91,134 @@
     effect.primitive = static_cast<aidl::CompositePrimitive>(
             env->GetIntField(primitive, gPrimitiveClassInfo.id));
     effect.scale = static_cast<float>(env->GetFloatField(primitive, gPrimitiveClassInfo.scale));
-    effect.delayMs = static_cast<int>(env->GetIntField(primitive, gPrimitiveClassInfo.delay));
+    effect.delayMs = static_cast<int32_t>(env->GetIntField(primitive, gPrimitiveClassInfo.delay));
     return effect;
 }
 
-static void vibratorPerformComposedEffect(JNIEnv* env, jclass, jobjectArray composition,
-                                   jobject vibration) {
-    auto hal = getHal<aidl::IVibrator>();
-    if (!hal) {
+static void destroyVibratorController(void* rawVibratorController) {
+    vibrator::HalController* vibratorController =
+            reinterpret_cast<vibrator::HalController*>(rawVibratorController);
+    if (vibratorController) {
+        delete vibratorController;
+    }
+}
+
+static jlong vibratorInit(JNIEnv* /* env */, jclass /* clazz */) {
+    std::unique_ptr<vibrator::HalController> controller =
+            std::make_unique<vibrator::HalController>();
+    controller->init();
+    return reinterpret_cast<jlong>(controller.release());
+}
+
+static jlong vibratorGetFinalizer(JNIEnv* /* env */, jclass /* clazz */) {
+    return static_cast<jlong>(reinterpret_cast<uintptr_t>(&destroyVibratorController));
+}
+
+static jboolean vibratorExists(JNIEnv* env, jclass /* clazz */, jlong controllerPtr) {
+    vibrator::HalController* controller = reinterpret_cast<vibrator::HalController*>(controllerPtr);
+    if (controller == nullptr) {
+        ALOGE("vibratorExists failed because controller was not initialized");
+        return JNI_FALSE;
+    }
+    return controller->ping().isOk() ? JNI_TRUE : JNI_FALSE;
+}
+
+static void vibratorOn(JNIEnv* env, jclass /* clazz */, jlong controllerPtr, jlong timeoutMs,
+                       jobject vibration) {
+    vibrator::HalController* controller = reinterpret_cast<vibrator::HalController*>(controllerPtr);
+    if (controller == nullptr) {
+        ALOGE("vibratorOn failed because controller was not initialized");
+        return;
+    }
+    jobject vibrationRef = vibration == nullptr ? vibration : MakeGlobalRefOrDie(env, vibration);
+    auto callback = [vibrationRef]() { callVibrationOnComplete(vibrationRef); };
+    controller->on(std::chrono::milliseconds(timeoutMs), callback);
+}
+
+static void vibratorOff(JNIEnv* env, jclass /* clazz */, jlong controllerPtr) {
+    vibrator::HalController* controller = reinterpret_cast<vibrator::HalController*>(controllerPtr);
+    if (controller == nullptr) {
+        ALOGE("vibratorOff failed because controller was not initialized");
+        return;
+    }
+    controller->off();
+}
+
+static void vibratorSetAmplitude(JNIEnv* env, jclass /* clazz */, jlong controllerPtr,
+                                 jint amplitude) {
+    vibrator::HalController* controller = reinterpret_cast<vibrator::HalController*>(controllerPtr);
+    if (controller == nullptr) {
+        ALOGE("vibratorSetAmplitude failed because controller was not initialized");
+        return;
+    }
+    controller->setAmplitude(static_cast<int32_t>(amplitude));
+}
+
+static void vibratorSetExternalControl(JNIEnv* env, jclass /* clazz */, jlong controllerPtr,
+                                       jboolean enabled) {
+    vibrator::HalController* controller = reinterpret_cast<vibrator::HalController*>(controllerPtr);
+    if (controller == nullptr) {
+        ALOGE("vibratorSetExternalControl failed because controller was not initialized");
+        return;
+    }
+    controller->setExternalControl(enabled);
+}
+
+static jintArray vibratorGetSupportedEffects(JNIEnv* env, jclass /* clazz */, jlong controllerPtr) {
+    vibrator::HalController* controller = reinterpret_cast<vibrator::HalController*>(controllerPtr);
+    if (controller == nullptr) {
+        ALOGE("vibratorGetSupportedEffects failed because controller was not initialized");
+        return nullptr;
+    }
+    auto result = controller->getSupportedEffects();
+    if (!result.isOk()) {
+        return nullptr;
+    }
+    std::vector<aidl::Effect> supportedEffects = result.value();
+    jintArray effects = env->NewIntArray(supportedEffects.size());
+    env->SetIntArrayRegion(effects, 0, supportedEffects.size(),
+                           reinterpret_cast<jint*>(supportedEffects.data()));
+    return effects;
+}
+
+static jintArray vibratorGetSupportedPrimitives(JNIEnv* env, jclass /* clazz */,
+                                                jlong controllerPtr) {
+    vibrator::HalController* controller = reinterpret_cast<vibrator::HalController*>(controllerPtr);
+    if (controller == nullptr) {
+        ALOGE("vibratorGetSupportedPrimitives failed because controller was not initialized");
+        return nullptr;
+    }
+    auto result = controller->getSupportedPrimitives();
+    if (!result.isOk()) {
+        return nullptr;
+    }
+    std::vector<aidl::CompositePrimitive> supportedPrimitives = result.value();
+    jintArray primitives = env->NewIntArray(supportedPrimitives.size());
+    env->SetIntArrayRegion(primitives, 0, supportedPrimitives.size(),
+                           reinterpret_cast<jint*>(supportedPrimitives.data()));
+    return primitives;
+}
+
+static jlong vibratorPerformEffect(JNIEnv* env, jclass /* clazz */, jlong controllerPtr,
+                                   jlong effect, jlong strength, jobject vibration) {
+    vibrator::HalController* controller = reinterpret_cast<vibrator::HalController*>(controllerPtr);
+    if (controller == nullptr) {
+        ALOGE("vibratorPerformEffect failed because controller was not initialized");
+        return -1;
+    }
+    aidl::Effect effectType = static_cast<aidl::Effect>(effect);
+    aidl::EffectStrength effectStrength = static_cast<aidl::EffectStrength>(strength);
+    jobject vibrationRef = vibration == nullptr ? vibration : MakeGlobalRefOrDie(env, vibration);
+    auto callback = [vibrationRef]() { callVibrationOnComplete(vibrationRef); };
+    auto result = controller->performEffect(effectType, effectStrength, callback);
+    return result.isOk() ? result.value().count() : -1;
+}
+
+static void vibratorPerformComposedEffect(JNIEnv* env, jclass /* clazz */, jlong controllerPtr,
+                                          jobjectArray composition, jobject vibration) {
+    vibrator::HalController* controller = reinterpret_cast<vibrator::HalController*>(controllerPtr);
+    if (controller == nullptr) {
+        ALOGE("vibratorPerformComposedEffect failed because controller was not initialized");
         return;
     }
     size_t size = env->GetArrayLength(composition);
@@ -441,75 +227,78 @@
         jobject element = env->GetObjectArrayElement(composition, i);
         effects.push_back(effectFromJavaPrimitive(env, element));
     }
-    sp<AidlVibratorCallback> effectCallback = new AidlVibratorCallback(env, vibration);
-
-    auto status = hal->call(&aidl::IVibrator::compose, effects, effectCallback);
-    if (!status.isOk()) {
-        if (status.exceptionCode() != binder::Status::EX_UNSUPPORTED_OPERATION) {
-            ALOGE("Failed to play haptic effect composition");
-        }
-    }
+    jobject vibrationRef = vibration == nullptr ? vibration : MakeGlobalRefOrDie(env, vibration);
+    auto callback = [vibrationRef]() { callVibrationOnComplete(vibrationRef); };
+    controller->performComposedEffect(effects, callback);
 }
 
-static jlong vibratorGetCapabilities(JNIEnv*, jclass) {
-    if (auto hal = getHal<aidl::IVibrator>()) {
-        int32_t cap = 0;
-        if (!hal->call(&aidl::IVibrator::getCapabilities, &cap).isOk()) {
-            return 0;
-        }
-        return cap;
+static jlong vibratorGetCapabilities(JNIEnv* env, jclass /* clazz */, jlong controllerPtr) {
+    vibrator::HalController* controller = reinterpret_cast<vibrator::HalController*>(controllerPtr);
+    if (controller == nullptr) {
+        ALOGE("vibratorGetCapabilities failed because controller was not initialized");
+        return 0;
     }
-
-    return 0;
+    auto result = controller->getCapabilities();
+    return result.isOk() ? static_cast<jlong>(result.value()) : 0;
 }
 
-static void vibratorAlwaysOnEnable(JNIEnv* env, jclass, jlong id, jlong effect, jlong strength) {
-    auto status = halCall(&aidl::IVibrator::alwaysOnEnable, id,
-            static_cast<aidl::Effect>(effect), static_cast<aidl::EffectStrength>(strength));
-    if (!status.isOk()) {
-        ALOGE("vibratortAlwaysOnEnable command failed (%s).", status.toString8().string());
+static void vibratorAlwaysOnEnable(JNIEnv* env, jclass /* clazz */, jlong controllerPtr, jlong id,
+                                   jlong effect, jlong strength) {
+    vibrator::HalController* controller = reinterpret_cast<vibrator::HalController*>(controllerPtr);
+    if (controller == nullptr) {
+        ALOGE("vibratorAlwaysOnEnable failed because controller was not initialized");
+        return;
     }
+    controller->alwaysOnEnable(static_cast<int32_t>(id), static_cast<aidl::Effect>(effect),
+                               static_cast<aidl::EffectStrength>(strength));
 }
 
-static void vibratorAlwaysOnDisable(JNIEnv* env, jclass, jlong id) {
-    auto status = halCall(&aidl::IVibrator::alwaysOnDisable, id);
-    if (!status.isOk()) {
-        ALOGE("vibratorAlwaysOnDisable command failed (%s).", status.toString8().string());
+static void vibratorAlwaysOnDisable(JNIEnv* env, jclass /* clazz */, jlong controllerPtr,
+                                    jlong id) {
+    vibrator::HalController* controller = reinterpret_cast<vibrator::HalController*>(controllerPtr);
+    if (controller == nullptr) {
+        ALOGE("vibratorAlwaysOnDisable failed because controller was not initialized");
+        return;
     }
+    controller->alwaysOnDisable(static_cast<int32_t>(id));
 }
 
 static const JNINativeMethod method_table[] = {
-        {"vibratorExists", "()Z", (void*)vibratorExists},
-        {"vibratorInit", "()V", (void*)vibratorInit},
-        {"vibratorOn", "(J)V", (void*)vibratorOn},
-        {"vibratorOff", "()V", (void*)vibratorOff},
-        {"vibratorSupportsAmplitudeControl", "()Z", (void*)vibratorSupportsAmplitudeControl},
-        {"vibratorSetAmplitude", "(I)V", (void*)vibratorSetAmplitude},
-        {"vibratorPerformEffect", "(JJLcom/android/server/VibratorService$Vibration;Z)J",
+        {"vibratorInit", "()J", (void*)vibratorInit},
+        {"vibratorGetFinalizer", "()J", (void*)vibratorGetFinalizer},
+        {"vibratorExists", "(J)Z", (void*)vibratorExists},
+        {"vibratorOn", "(JJLcom/android/server/VibratorService$Vibration;)V", (void*)vibratorOn},
+        {"vibratorOff", "(J)V", (void*)vibratorOff},
+        {"vibratorSetAmplitude", "(JI)V", (void*)vibratorSetAmplitude},
+        {"vibratorPerformEffect", "(JJJLcom/android/server/VibratorService$Vibration;)J",
          (void*)vibratorPerformEffect},
         {"vibratorPerformComposedEffect",
-         "([Landroid/os/VibrationEffect$Composition$PrimitiveEffect;Lcom/android/server/"
+         "(J[Landroid/os/VibrationEffect$Composition$PrimitiveEffect;Lcom/android/server/"
          "VibratorService$Vibration;)V",
          (void*)vibratorPerformComposedEffect},
-        {"vibratorGetSupportedEffects", "()[I", (void*)vibratorGetSupportedEffects},
-        {"vibratorSupportsExternalControl", "()Z", (void*)vibratorSupportsExternalControl},
-        {"vibratorSetExternalControl", "(Z)V", (void*)vibratorSetExternalControl},
-        {"vibratorGetCapabilities", "()J", (void*)vibratorGetCapabilities},
-        {"vibratorAlwaysOnEnable", "(JJJ)V", (void*)vibratorAlwaysOnEnable},
-        {"vibratorAlwaysOnDisable", "(J)V", (void*)vibratorAlwaysOnDisable},
+        {"vibratorGetSupportedEffects", "(J)[I", (void*)vibratorGetSupportedEffects},
+        {"vibratorGetSupportedPrimitives", "(J)[I", (void*)vibratorGetSupportedPrimitives},
+        {"vibratorSetExternalControl", "(JZ)V", (void*)vibratorSetExternalControl},
+        {"vibratorGetCapabilities", "(J)J", (void*)vibratorGetCapabilities},
+        {"vibratorAlwaysOnEnable", "(JJJJ)V", (void*)vibratorAlwaysOnEnable},
+        {"vibratorAlwaysOnDisable", "(JJ)V", (void*)vibratorAlwaysOnDisable},
 };
 
-int register_android_server_VibratorService(JNIEnv *env) {
-    sMethodIdOnComplete = GetMethodIDOrDie(env,
-            FindClassOrDie(env, "com/android/server/VibratorService$Vibration"),
-            "onComplete", "()V");
-    jclass primitiveClass = FindClassOrDie(env,
-            "android/os/VibrationEffect$Composition$PrimitiveEffect");
+int register_android_server_VibratorService(JavaVM* vm, JNIEnv* env) {
+    sJvm = vm;
+    sMethodIdOnComplete =
+            GetMethodIDOrDie(env,
+                             FindClassOrDie(env, "com/android/server/VibratorService$Vibration"),
+                             "onComplete", "()V");
+
+    jclass primitiveClass =
+            FindClassOrDie(env, "android/os/VibrationEffect$Composition$PrimitiveEffect");
     gPrimitiveClassInfo.id = GetFieldIDOrDie(env, primitiveClass, "id", "I");
     gPrimitiveClassInfo.scale = GetFieldIDOrDie(env, primitiveClass, "scale", "F");
     gPrimitiveClassInfo.delay = GetFieldIDOrDie(env, primitiveClass, "delay", "I");
-    return jniRegisterNativeMethods(env, "com/android/server/VibratorService",
-            method_table, NELEM(method_table));
+
+    return jniRegisterNativeMethods(env, "com/android/server/VibratorService", method_table,
+                                    NELEM(method_table));
 }
 
-};
+}; // namespace android
diff --git a/services/core/jni/com_android_server_input_InputManagerService.cpp b/services/core/jni/com_android_server_input_InputManagerService.cpp
index 7843663..9751c46 100644
--- a/services/core/jni/com_android_server_input_InputManagerService.cpp
+++ b/services/core/jni/com_android_server_input_InputManagerService.cpp
@@ -75,6 +75,12 @@
 using android::base::ParseUint;
 using android::base::StringPrintf;
 
+// Maximum allowable delay value in a vibration pattern before
+// which the delay will be truncated.
+static constexpr std::chrono::duration MAX_VIBRATE_PATTERN_DELAY = 100s;
+static constexpr std::chrono::milliseconds MAX_VIBRATE_PATTERN_DELAY_MILLIS =
+        std::chrono::duration_cast<std::chrono::milliseconds>(MAX_VIBRATE_PATTERN_DELAY);
+
 namespace android {
 
 // The exponent used to calculate the pointer speed scaling factor.
@@ -415,6 +421,10 @@
         AutoMutex _l(mLock);
         mLocked.viewports = viewports;
         mLocked.pointerDisplayId = pointerDisplayId;
+        std::shared_ptr<PointerController> controller = mLocked.pointerController.lock();
+        if (controller != nullptr) {
+            controller->onDisplayViewportsUpdated(mLocked.viewports);
+        }
     } // release lock
 
     mInputManager->getReader()->requestRefreshConfiguration(
@@ -710,9 +720,8 @@
     jobject tokenObj = javaObjectForIBinder(env, token);
     jstring reasonObj = env->NewStringUTF(reason.c_str());
 
-    jlong newTimeout = env->CallLongMethod(mServiceObj,
-            gServiceClassInfo.notifyANR, inputApplicationHandleObj, tokenObj,
-                 reasonObj);
+    jlong newTimeout = env->CallLongMethod(mServiceObj, gServiceClassInfo.notifyANR,
+                                           inputApplicationHandleObj, tokenObj, reasonObj);
     if (checkAndClearExceptionFromCallback(env, "notifyANR")) {
         newTimeout = 0; // abort dispatch
     } else {
@@ -813,8 +822,8 @@
     }
 
     bool lightsOut = mLocked.systemUiVisibility & ASYSTEM_UI_VISIBILITY_STATUS_BAR_HIDDEN;
-    controller->setInactivityTimeout(lightsOut ? PointerController::InactivityTimeout::SHORT
-                                               : PointerController::InactivityTimeout::NORMAL);
+    controller->setInactivityTimeout(lightsOut ? InactivityTimeout::SHORT
+                                               : InactivityTimeout::NORMAL);
 }
 
 void NativeInputManager::setPointerSpeed(int32_t speed) {
@@ -1637,17 +1646,19 @@
             patternObj, nullptr));
     jint* amplitudes = static_cast<jint*>(env->GetPrimitiveArrayCritical(amplitudesObj, nullptr));
 
-    std::vector<VibrationElement> pattern(patternSize);
+    std::vector<VibrationElement> elements(patternSize);
     for (size_t i = 0; i < patternSize; i++) {
-        jlong duration =
-                max(min(patternMillis[i], (jlong)MAX_VIBRATE_PATTERN_DELAY_MSECS), (jlong)0);
-        pattern[i].duration = std::chrono::milliseconds(duration);
-        pattern[i].channels = {amplitudes[i]};
+        // VibrationEffect.validate guarantees duration > 0.
+        std::chrono::milliseconds duration(patternMillis[i]);
+        elements[i].duration = std::min(duration, MAX_VIBRATE_PATTERN_DELAY_MILLIS);
+        // TODO: (b/161629089) apply channel specific amplitudes from development API.
+        elements[i].channels = {static_cast<uint8_t>(amplitudes[i]),
+                                static_cast<uint8_t>(amplitudes[i])};
     }
     env->ReleasePrimitiveArrayCritical(patternObj, patternMillis, JNI_ABORT);
     env->ReleasePrimitiveArrayCritical(amplitudesObj, amplitudes, JNI_ABORT);
 
-    im->getInputManager()->getReader()->vibrate(deviceId, pattern, repeat, token);
+    im->getInputManager()->getReader()->vibrate(deviceId, elements, repeat, token);
 }
 
 static void nativeCancelVibrate(JNIEnv* /* env */,
diff --git a/services/core/jni/com_android_server_pm_PackageManagerShellCommandDataLoader.cpp b/services/core/jni/com_android_server_pm_PackageManagerShellCommandDataLoader.cpp
index e904645..9e2bb45 100644
--- a/services/core/jni/com_android_server_pm_PackageManagerShellCommandDataLoader.cpp
+++ b/services/core/jni/com_android_server_pm_PackageManagerShellCommandDataLoader.cpp
@@ -314,31 +314,6 @@
     return result;
 }
 
-static inline JNIEnv* GetJNIEnvironment(JavaVM* vm) {
-    JNIEnv* env;
-    if (vm->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION_1_6) != JNI_OK) {
-        return 0;
-    }
-    return env;
-}
-
-static inline JNIEnv* GetOrAttachJNIEnvironment(JavaVM* jvm) {
-    JNIEnv* env = GetJNIEnvironment(jvm);
-    if (!env) {
-        int result = jvm->AttachCurrentThread(&env, nullptr);
-        CHECK_EQ(result, JNI_OK) << "thread attach failed";
-        struct VmDetacher {
-            VmDetacher(JavaVM* vm) : mVm(vm) {}
-            ~VmDetacher() { mVm->DetachCurrentThread(); }
-
-        private:
-            JavaVM* const mVm;
-        };
-        static thread_local VmDetacher detacher(jvm);
-    }
-    return env;
-}
-
 class PMSCDataLoader;
 
 struct OnTraceChanged {
@@ -415,7 +390,7 @@
     bool onPrepareImage(dataloader::DataLoaderInstallationFiles addedFiles) final {
         ALOGE("onPrepareImage: start.");
 
-        JNIEnv* env = GetOrAttachJNIEnvironment(mJvm);
+        JNIEnv* env = GetOrAttachJNIEnvironment(mJvm, JNI_VERSION_1_6);
         const auto& jni = jniIds(env);
 
         jobject shellCommand = env->CallStaticObjectMethod(jni.packageManagerShellCommandDataLoader,
diff --git a/services/core/jni/onload.cpp b/services/core/jni/onload.cpp
index 6f24e3b..5df1ada 100644
--- a/services/core/jni/onload.cpp
+++ b/services/core/jni/onload.cpp
@@ -37,7 +37,7 @@
 int register_android_server_UsbMidiDevice(JNIEnv* env);
 int register_android_server_UsbHostManager(JNIEnv* env);
 int register_android_server_vr_VrManagerService(JNIEnv* env);
-int register_android_server_VibratorService(JNIEnv* env);
+int register_android_server_VibratorService(JavaVM* vm, JNIEnv* env);
 int register_android_server_location_GnssLocationProvider(JNIEnv* env);
 int register_android_server_connectivity_Vpn(JNIEnv* env);
 int register_android_server_TestNetworkService(JNIEnv* env);
@@ -90,7 +90,7 @@
     register_android_server_UsbAlsaJackDetector(env);
     register_android_server_UsbHostManager(env);
     register_android_server_vr_VrManagerService(env);
-    register_android_server_VibratorService(env);
+    register_android_server_VibratorService(vm, env);
     register_android_server_SystemServer(env);
     register_android_server_location_GnssLocationProvider(env);
     register_android_server_connectivity_Vpn(env);
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/ActiveAdmin.java b/services/devicepolicy/java/com/android/server/devicepolicy/ActiveAdmin.java
new file mode 100644
index 0000000..6fea2aa
--- /dev/null
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/ActiveAdmin.java
@@ -0,0 +1,1116 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.devicepolicy;
+
+import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED;
+
+import static org.xmlpull.v1.XmlPullParser.END_DOCUMENT;
+import static org.xmlpull.v1.XmlPullParser.END_TAG;
+import static org.xmlpull.v1.XmlPullParser.TEXT;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.app.admin.DeviceAdminInfo;
+import android.app.admin.DevicePolicyManager;
+import android.app.admin.FactoryResetProtectionPolicy;
+import android.app.admin.PasswordPolicy;
+import android.graphics.Color;
+import android.os.Bundle;
+import android.os.PersistableBundle;
+import android.os.UserHandle;
+import android.os.UserManager;
+import android.text.TextUtils;
+import android.util.ArrayMap;
+import android.util.ArraySet;
+import android.util.Log;
+import android.util.Slog;
+
+import com.android.internal.util.IndentingPrintWriter;
+import com.android.internal.util.Preconditions;
+import com.android.internal.util.XmlUtils;
+import com.android.server.pm.UserRestrictionsUtils;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+import org.xmlpull.v1.XmlSerializer;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.function.Predicate;
+
+class ActiveAdmin {
+    private static final String TAG_DISABLE_KEYGUARD_FEATURES = "disable-keyguard-features";
+    private static final String TAG_TEST_ONLY_ADMIN = "test-only-admin";
+    private static final String TAG_DISABLE_CAMERA = "disable-camera";
+    private static final String TAG_DISABLE_CALLER_ID = "disable-caller-id";
+    private static final String TAG_DISABLE_CONTACTS_SEARCH = "disable-contacts-search";
+    private static final String TAG_DISABLE_BLUETOOTH_CONTACT_SHARING =
+            "disable-bt-contacts-sharing";
+    private static final String TAG_DISABLE_SCREEN_CAPTURE = "disable-screen-capture";
+    private static final String TAG_DISABLE_ACCOUNT_MANAGEMENT = "disable-account-management";
+    private static final String TAG_REQUIRE_AUTO_TIME = "require_auto_time";
+    private static final String TAG_FORCE_EPHEMERAL_USERS = "force_ephemeral_users";
+    private static final String TAG_IS_NETWORK_LOGGING_ENABLED = "is_network_logging_enabled";
+    private static final String TAG_ACCOUNT_TYPE = "account-type";
+    private static final String TAG_PERMITTED_ACCESSIBILITY_SERVICES =
+            "permitted-accessiblity-services";
+    private static final String TAG_ENCRYPTION_REQUESTED = "encryption-requested";
+    private static final String TAG_MANAGE_TRUST_AGENT_FEATURES = "manage-trust-agent-features";
+    private static final String TAG_TRUST_AGENT_COMPONENT_OPTIONS = "trust-agent-component-options";
+    private static final String TAG_TRUST_AGENT_COMPONENT = "component";
+    private static final String TAG_PASSWORD_EXPIRATION_DATE = "password-expiration-date";
+    private static final String TAG_PASSWORD_EXPIRATION_TIMEOUT = "password-expiration-timeout";
+    private static final String TAG_GLOBAL_PROXY_EXCLUSION_LIST = "global-proxy-exclusion-list";
+    private static final String TAG_GLOBAL_PROXY_SPEC = "global-proxy-spec";
+    private static final String TAG_SPECIFIES_GLOBAL_PROXY = "specifies-global-proxy";
+    private static final String TAG_PERMITTED_IMES = "permitted-imes";
+    private static final String TAG_PERMITTED_NOTIFICATION_LISTENERS =
+            "permitted-notification-listeners";
+    private static final String TAG_MAX_FAILED_PASSWORD_WIPE = "max-failed-password-wipe";
+    private static final String TAG_MAX_TIME_TO_UNLOCK = "max-time-to-unlock";
+    private static final String TAG_STRONG_AUTH_UNLOCK_TIMEOUT = "strong-auth-unlock-timeout";
+    private static final String TAG_MIN_PASSWORD_NONLETTER = "min-password-nonletter";
+    private static final String TAG_MIN_PASSWORD_SYMBOLS = "min-password-symbols";
+    private static final String TAG_MIN_PASSWORD_NUMERIC = "min-password-numeric";
+    private static final String TAG_MIN_PASSWORD_LETTERS = "min-password-letters";
+    private static final String TAG_MIN_PASSWORD_LOWERCASE = "min-password-lowercase";
+    private static final String TAG_MIN_PASSWORD_UPPERCASE = "min-password-uppercase";
+    private static final String TAG_PASSWORD_HISTORY_LENGTH = "password-history-length";
+    private static final String TAG_MIN_PASSWORD_LENGTH = "min-password-length";
+    private static final String TAG_PASSWORD_QUALITY = "password-quality";
+    private static final String TAG_POLICIES = "policies";
+    private static final String TAG_CROSS_PROFILE_WIDGET_PROVIDERS =
+            "cross-profile-widget-providers";
+    private static final String TAG_PROVIDER = "provider";
+    private static final String TAG_PACKAGE_LIST_ITEM  = "item";
+    private static final String TAG_KEEP_UNINSTALLED_PACKAGES  = "keep-uninstalled-packages";
+    private static final String TAG_USER_RESTRICTIONS = "user-restrictions";
+    private static final String TAG_DEFAULT_ENABLED_USER_RESTRICTIONS =
+            "default-enabled-user-restrictions";
+    private static final String TAG_RESTRICTION = "restriction";
+    private static final String TAG_SHORT_SUPPORT_MESSAGE = "short-support-message";
+    private static final String TAG_LONG_SUPPORT_MESSAGE = "long-support-message";
+    private static final String TAG_PARENT_ADMIN = "parent-admin";
+    private static final String TAG_ORGANIZATION_COLOR = "organization-color";
+    private static final String TAG_ORGANIZATION_NAME = "organization-name";
+    private static final String TAG_IS_LOGOUT_ENABLED = "is_logout_enabled";
+    private static final String TAG_START_USER_SESSION_MESSAGE = "start_user_session_message";
+    private static final String TAG_END_USER_SESSION_MESSAGE = "end_user_session_message";
+    private static final String TAG_METERED_DATA_DISABLED_PACKAGES =
+            "metered_data_disabled_packages";
+    private static final String TAG_CROSS_PROFILE_CALENDAR_PACKAGES =
+            "cross-profile-calendar-packages";
+    private static final String TAG_CROSS_PROFILE_CALENDAR_PACKAGES_NULL =
+            "cross-profile-calendar-packages-null";
+    private static final String TAG_CROSS_PROFILE_PACKAGES = "cross-profile-packages";
+    private static final String TAG_FACTORY_RESET_PROTECTION_POLICY =
+            "factory_reset_protection_policy";
+    private static final String TAG_SUSPEND_PERSONAL_APPS = "suspend-personal-apps";
+    private static final String TAG_PROFILE_MAXIMUM_TIME_OFF = "profile-max-time-off";
+    private static final String TAG_PROFILE_OFF_DEADLINE = "profile-off-deadline";
+    private static final String TAG_ALWAYS_ON_VPN_PACKAGE = "vpn-package";
+    private static final String TAG_ALWAYS_ON_VPN_LOCKDOWN = "vpn-lockdown";
+    private static final String TAG_COMMON_CRITERIA_MODE = "common-criteria-mode";
+    private static final String ATTR_VALUE = "value";
+    private static final String ATTR_LAST_NETWORK_LOGGING_NOTIFICATION = "last-notification";
+    private static final String ATTR_NUM_NETWORK_LOGGING_NOTIFICATIONS = "num-notifications";
+
+    DeviceAdminInfo info;
+
+    static final int DEF_PASSWORD_HISTORY_LENGTH = 0;
+    int passwordHistoryLength = DEF_PASSWORD_HISTORY_LENGTH;
+
+    @NonNull
+    PasswordPolicy mPasswordPolicy = new PasswordPolicy();
+
+    @Nullable
+    FactoryResetProtectionPolicy mFactoryResetProtectionPolicy = null;
+
+    static final long DEF_MAXIMUM_TIME_TO_UNLOCK = 0;
+    long maximumTimeToUnlock = DEF_MAXIMUM_TIME_TO_UNLOCK;
+
+    long strongAuthUnlockTimeout = 0; // admin doesn't participate by default
+
+    static final int DEF_MAXIMUM_FAILED_PASSWORDS_FOR_WIPE = 0;
+    int maximumFailedPasswordsForWipe = DEF_MAXIMUM_FAILED_PASSWORDS_FOR_WIPE;
+
+    static final long DEF_PASSWORD_EXPIRATION_TIMEOUT = 0;
+    long passwordExpirationTimeout = DEF_PASSWORD_EXPIRATION_TIMEOUT;
+
+    static final long DEF_PASSWORD_EXPIRATION_DATE = 0;
+    long passwordExpirationDate = DEF_PASSWORD_EXPIRATION_DATE;
+
+    static final int DEF_KEYGUARD_FEATURES_DISABLED = 0; // none
+
+    int disabledKeyguardFeatures = DEF_KEYGUARD_FEATURES_DISABLED;
+
+    boolean encryptionRequested = false;
+    boolean testOnlyAdmin = false;
+    boolean disableCamera = false;
+    boolean disableCallerId = false;
+    boolean disableContactsSearch = false;
+    boolean disableBluetoothContactSharing = true;
+    boolean disableScreenCapture = false;
+    boolean requireAutoTime = false;
+    boolean forceEphemeralUsers = false;
+    boolean isNetworkLoggingEnabled = false;
+    boolean isLogoutEnabled = false;
+
+    // one notification after enabling + one more after reboots
+    static final int DEF_MAXIMUM_NETWORK_LOGGING_NOTIFICATIONS_SHOWN = 2;
+    int numNetworkLoggingNotifications = 0;
+    long lastNetworkLoggingNotificationTimeMs = 0; // Time in milliseconds since epoch
+
+    ActiveAdmin parentAdmin;
+    final boolean isParent;
+
+    static class TrustAgentInfo {
+        public PersistableBundle options;
+        TrustAgentInfo(PersistableBundle bundle) {
+            options = bundle;
+        }
+    }
+
+    // The list of packages which are not allowed to use metered data.
+    List<String> meteredDisabledPackages;
+
+    final Set<String> accountTypesWithManagementDisabled = new ArraySet<>();
+
+    // The list of permitted accessibility services package namesas set by a profile
+    // or device owner. Null means all accessibility services are allowed, empty means
+    // none except system services are allowed.
+    List<String> permittedAccessiblityServices;
+
+    // The list of permitted input methods package names as set by a profile or device owner.
+    // Null means all input methods are allowed, empty means none except system imes are
+    // allowed.
+    List<String> permittedInputMethods;
+
+    // The list of packages allowed to use a NotificationListenerService to receive events for
+    // notifications from this user. Null means that all packages are allowed. Empty list means
+    // that only packages from the system are allowed.
+    List<String> permittedNotificationListeners;
+
+    // List of package names to keep cached.
+    List<String> keepUninstalledPackages;
+
+    // TODO: review implementation decisions with frameworks team
+    boolean specifiesGlobalProxy = false;
+    String globalProxySpec = null;
+    String globalProxyExclusionList = null;
+
+    @NonNull
+    ArrayMap<String, TrustAgentInfo> trustAgentInfos = new ArrayMap<>();
+
+    List<String> crossProfileWidgetProviders;
+
+    Bundle userRestrictions;
+
+    // User restrictions that have already been enabled by default for this admin (either when
+    // setting the device or profile owner, or during a system update if one of those "enabled
+    // by default" restrictions is newly added).
+    final Set<String> defaultEnabledRestrictionsAlreadySet = new ArraySet<>();
+
+    // Support text provided by the admin to display to the user.
+    CharSequence shortSupportMessage = null;
+    CharSequence longSupportMessage = null;
+
+    // Background color of confirm credentials screen. Default: teal.
+    static final int DEF_ORGANIZATION_COLOR = Color.parseColor("#00796B");
+    int organizationColor = DEF_ORGANIZATION_COLOR;
+
+    // Default title of confirm credentials screen
+    String organizationName = null;
+
+    // Message for user switcher
+    String startUserSessionMessage = null;
+    String endUserSessionMessage = null;
+
+    // The allow list of packages that can access cross profile calendar APIs.
+    // This allow list should be in default an empty list, which indicates that no package
+    // is allow listed.
+    List<String> mCrossProfileCalendarPackages = Collections.emptyList();
+
+    // The allow list of packages that the admin has enabled to be able to request consent from
+    // the user to communicate cross-profile. By default, no packages are allowed, which is
+    // represented as an empty list.
+    List<String> mCrossProfilePackages = Collections.emptyList();
+
+    // Whether the admin explicitly requires personal apps to be suspended
+    boolean mSuspendPersonalApps = false;
+    // Maximum time the profile owned by this admin can be off.
+    long mProfileMaximumTimeOffMillis = 0;
+    // Time by which the profile should be turned on according to System.currentTimeMillis().
+    long mProfileOffDeadline = 0;
+
+    public String mAlwaysOnVpnPackage;
+    public boolean mAlwaysOnVpnLockdown;
+    boolean mCommonCriteriaMode;
+
+    ActiveAdmin(DeviceAdminInfo info, boolean isParent) {
+        this.info = info;
+        this.isParent = isParent;
+    }
+
+    ActiveAdmin getParentActiveAdmin() {
+        Preconditions.checkState(!isParent);
+
+        if (parentAdmin == null) {
+            parentAdmin = new ActiveAdmin(info, /* parent */ true);
+        }
+        return parentAdmin;
+    }
+
+    boolean hasParentActiveAdmin() {
+        return parentAdmin != null;
+    }
+
+    int getUid() {
+        return info.getActivityInfo().applicationInfo.uid;
+    }
+
+    public UserHandle getUserHandle() {
+        return UserHandle.of(UserHandle.getUserId(info.getActivityInfo().applicationInfo.uid));
+    }
+
+    void writeToXml(XmlSerializer out)
+            throws IllegalArgumentException, IllegalStateException, IOException {
+        out.startTag(null, TAG_POLICIES);
+        info.writePoliciesToXml(out);
+        out.endTag(null, TAG_POLICIES);
+        if (mPasswordPolicy.quality != PASSWORD_QUALITY_UNSPECIFIED) {
+            writeAttributeValueToXml(
+                    out, TAG_PASSWORD_QUALITY, mPasswordPolicy.quality);
+            if (mPasswordPolicy.length != PasswordPolicy.DEF_MINIMUM_LENGTH) {
+                writeAttributeValueToXml(
+                        out, TAG_MIN_PASSWORD_LENGTH, mPasswordPolicy.length);
+            }
+            if (mPasswordPolicy.upperCase != PasswordPolicy.DEF_MINIMUM_UPPER_CASE) {
+                writeAttributeValueToXml(
+                        out, TAG_MIN_PASSWORD_UPPERCASE, mPasswordPolicy.upperCase);
+            }
+            if (mPasswordPolicy.lowerCase != PasswordPolicy.DEF_MINIMUM_LOWER_CASE) {
+                writeAttributeValueToXml(
+                        out, TAG_MIN_PASSWORD_LOWERCASE, mPasswordPolicy.lowerCase);
+            }
+            if (mPasswordPolicy.letters != PasswordPolicy.DEF_MINIMUM_LETTERS) {
+                writeAttributeValueToXml(
+                        out, TAG_MIN_PASSWORD_LETTERS, mPasswordPolicy.letters);
+            }
+            if (mPasswordPolicy.numeric != PasswordPolicy.DEF_MINIMUM_NUMERIC) {
+                writeAttributeValueToXml(
+                        out, TAG_MIN_PASSWORD_NUMERIC, mPasswordPolicy.numeric);
+            }
+            if (mPasswordPolicy.symbols != PasswordPolicy.DEF_MINIMUM_SYMBOLS) {
+                writeAttributeValueToXml(
+                        out, TAG_MIN_PASSWORD_SYMBOLS, mPasswordPolicy.symbols);
+            }
+            if (mPasswordPolicy.nonLetter > PasswordPolicy.DEF_MINIMUM_NON_LETTER) {
+                writeAttributeValueToXml(
+                        out, TAG_MIN_PASSWORD_NONLETTER, mPasswordPolicy.nonLetter);
+            }
+        }
+        if (passwordHistoryLength != DEF_PASSWORD_HISTORY_LENGTH) {
+            writeAttributeValueToXml(
+                    out, TAG_PASSWORD_HISTORY_LENGTH, passwordHistoryLength);
+        }
+        if (maximumTimeToUnlock != DEF_MAXIMUM_TIME_TO_UNLOCK) {
+            writeAttributeValueToXml(
+                    out, TAG_MAX_TIME_TO_UNLOCK, maximumTimeToUnlock);
+        }
+        if (strongAuthUnlockTimeout != DevicePolicyManager.DEFAULT_STRONG_AUTH_TIMEOUT_MS) {
+            writeAttributeValueToXml(
+                    out, TAG_STRONG_AUTH_UNLOCK_TIMEOUT, strongAuthUnlockTimeout);
+        }
+        if (maximumFailedPasswordsForWipe != DEF_MAXIMUM_FAILED_PASSWORDS_FOR_WIPE) {
+            writeAttributeValueToXml(
+                    out, TAG_MAX_FAILED_PASSWORD_WIPE, maximumFailedPasswordsForWipe);
+        }
+        if (specifiesGlobalProxy) {
+            writeAttributeValueToXml(
+                    out, TAG_SPECIFIES_GLOBAL_PROXY, specifiesGlobalProxy);
+            if (globalProxySpec != null) {
+                writeAttributeValueToXml(out, TAG_GLOBAL_PROXY_SPEC, globalProxySpec);
+            }
+            if (globalProxyExclusionList != null) {
+                writeAttributeValueToXml(
+                        out, TAG_GLOBAL_PROXY_EXCLUSION_LIST, globalProxyExclusionList);
+            }
+        }
+        if (passwordExpirationTimeout != DEF_PASSWORD_EXPIRATION_TIMEOUT) {
+            writeAttributeValueToXml(
+                    out, TAG_PASSWORD_EXPIRATION_TIMEOUT, passwordExpirationTimeout);
+        }
+        if (passwordExpirationDate != DEF_PASSWORD_EXPIRATION_DATE) {
+            writeAttributeValueToXml(
+                    out, TAG_PASSWORD_EXPIRATION_DATE, passwordExpirationDate);
+        }
+        if (encryptionRequested) {
+            writeAttributeValueToXml(
+                    out, TAG_ENCRYPTION_REQUESTED, encryptionRequested);
+        }
+        if (testOnlyAdmin) {
+            writeAttributeValueToXml(
+                    out, TAG_TEST_ONLY_ADMIN, testOnlyAdmin);
+        }
+        if (disableCamera) {
+            writeAttributeValueToXml(
+                    out, TAG_DISABLE_CAMERA, disableCamera);
+        }
+        if (disableCallerId) {
+            writeAttributeValueToXml(
+                    out, TAG_DISABLE_CALLER_ID, disableCallerId);
+        }
+        if (disableContactsSearch) {
+            writeAttributeValueToXml(
+                    out, TAG_DISABLE_CONTACTS_SEARCH, disableContactsSearch);
+        }
+        if (!disableBluetoothContactSharing) {
+            writeAttributeValueToXml(
+                    out, TAG_DISABLE_BLUETOOTH_CONTACT_SHARING, disableBluetoothContactSharing);
+        }
+        if (disableScreenCapture) {
+            writeAttributeValueToXml(
+                    out, TAG_DISABLE_SCREEN_CAPTURE, disableScreenCapture);
+        }
+        if (requireAutoTime) {
+            writeAttributeValueToXml(
+                    out, TAG_REQUIRE_AUTO_TIME, requireAutoTime);
+        }
+        if (forceEphemeralUsers) {
+            writeAttributeValueToXml(
+                    out, TAG_FORCE_EPHEMERAL_USERS, forceEphemeralUsers);
+        }
+        if (isNetworkLoggingEnabled) {
+            out.startTag(null, TAG_IS_NETWORK_LOGGING_ENABLED);
+            out.attribute(null, ATTR_VALUE, Boolean.toString(isNetworkLoggingEnabled));
+            out.attribute(null, ATTR_NUM_NETWORK_LOGGING_NOTIFICATIONS,
+                    Integer.toString(numNetworkLoggingNotifications));
+            out.attribute(null, ATTR_LAST_NETWORK_LOGGING_NOTIFICATION,
+                    Long.toString(lastNetworkLoggingNotificationTimeMs));
+            out.endTag(null, TAG_IS_NETWORK_LOGGING_ENABLED);
+        }
+        if (disabledKeyguardFeatures != DEF_KEYGUARD_FEATURES_DISABLED) {
+            writeAttributeValueToXml(
+                    out, TAG_DISABLE_KEYGUARD_FEATURES, disabledKeyguardFeatures);
+        }
+        if (!accountTypesWithManagementDisabled.isEmpty()) {
+            writeAttributeValuesToXml(
+                    out, TAG_DISABLE_ACCOUNT_MANAGEMENT, TAG_ACCOUNT_TYPE,
+                    accountTypesWithManagementDisabled);
+        }
+        if (!trustAgentInfos.isEmpty()) {
+            Set<Map.Entry<String, TrustAgentInfo>> set = trustAgentInfos.entrySet();
+            out.startTag(null, TAG_MANAGE_TRUST_AGENT_FEATURES);
+            for (Map.Entry<String, TrustAgentInfo> entry : set) {
+                TrustAgentInfo trustAgentInfo = entry.getValue();
+                out.startTag(null, TAG_TRUST_AGENT_COMPONENT);
+                out.attribute(null, ATTR_VALUE, entry.getKey());
+                if (trustAgentInfo.options != null) {
+                    out.startTag(null, TAG_TRUST_AGENT_COMPONENT_OPTIONS);
+                    try {
+                        trustAgentInfo.options.saveToXml(out);
+                    } catch (XmlPullParserException e) {
+                        Log.e(DevicePolicyManagerService.LOG_TAG,
+                                "Failed to save TrustAgent options", e);
+                    }
+                    out.endTag(null, TAG_TRUST_AGENT_COMPONENT_OPTIONS);
+                }
+                out.endTag(null, TAG_TRUST_AGENT_COMPONENT);
+            }
+            out.endTag(null, TAG_MANAGE_TRUST_AGENT_FEATURES);
+        }
+        if (crossProfileWidgetProviders != null && !crossProfileWidgetProviders.isEmpty()) {
+            writeAttributeValuesToXml(
+                    out, TAG_CROSS_PROFILE_WIDGET_PROVIDERS, TAG_PROVIDER,
+                    crossProfileWidgetProviders);
+        }
+        writePackageListToXml(out, TAG_PERMITTED_ACCESSIBILITY_SERVICES,
+                permittedAccessiblityServices);
+        writePackageListToXml(out, TAG_PERMITTED_IMES, permittedInputMethods);
+        writePackageListToXml(out, TAG_PERMITTED_NOTIFICATION_LISTENERS,
+                permittedNotificationListeners);
+        writePackageListToXml(out, TAG_KEEP_UNINSTALLED_PACKAGES, keepUninstalledPackages);
+        writePackageListToXml(out, TAG_METERED_DATA_DISABLED_PACKAGES, meteredDisabledPackages);
+        if (hasUserRestrictions()) {
+            UserRestrictionsUtils.writeRestrictions(
+                    out, userRestrictions, TAG_USER_RESTRICTIONS);
+        }
+        if (!defaultEnabledRestrictionsAlreadySet.isEmpty()) {
+            writeAttributeValuesToXml(out, TAG_DEFAULT_ENABLED_USER_RESTRICTIONS,
+                    TAG_RESTRICTION,
+                    defaultEnabledRestrictionsAlreadySet);
+        }
+        if (!TextUtils.isEmpty(shortSupportMessage)) {
+            writeTextToXml(out, TAG_SHORT_SUPPORT_MESSAGE, shortSupportMessage.toString());
+        }
+        if (!TextUtils.isEmpty(longSupportMessage)) {
+            writeTextToXml(out, TAG_LONG_SUPPORT_MESSAGE, longSupportMessage.toString());
+        }
+        if (parentAdmin != null) {
+            out.startTag(null, TAG_PARENT_ADMIN);
+            parentAdmin.writeToXml(out);
+            out.endTag(null, TAG_PARENT_ADMIN);
+        }
+        if (organizationColor != DEF_ORGANIZATION_COLOR) {
+            writeAttributeValueToXml(out, TAG_ORGANIZATION_COLOR, organizationColor);
+        }
+        if (organizationName != null) {
+            writeTextToXml(out, TAG_ORGANIZATION_NAME, organizationName);
+        }
+        if (isLogoutEnabled) {
+            writeAttributeValueToXml(out, TAG_IS_LOGOUT_ENABLED, isLogoutEnabled);
+        }
+        if (startUserSessionMessage != null) {
+            writeTextToXml(out, TAG_START_USER_SESSION_MESSAGE, startUserSessionMessage);
+        }
+        if (endUserSessionMessage != null) {
+            writeTextToXml(out, TAG_END_USER_SESSION_MESSAGE, endUserSessionMessage);
+        }
+        if (mCrossProfileCalendarPackages == null) {
+            out.startTag(null, TAG_CROSS_PROFILE_CALENDAR_PACKAGES_NULL);
+            out.endTag(null, TAG_CROSS_PROFILE_CALENDAR_PACKAGES_NULL);
+        } else {
+            writePackageListToXml(out, TAG_CROSS_PROFILE_CALENDAR_PACKAGES,
+                    mCrossProfileCalendarPackages);
+        }
+        writePackageListToXml(out, TAG_CROSS_PROFILE_PACKAGES, mCrossProfilePackages);
+        if (mFactoryResetProtectionPolicy != null) {
+            out.startTag(null, TAG_FACTORY_RESET_PROTECTION_POLICY);
+            mFactoryResetProtectionPolicy.writeToXml(out);
+            out.endTag(null, TAG_FACTORY_RESET_PROTECTION_POLICY);
+        }
+        if (mSuspendPersonalApps) {
+            writeAttributeValueToXml(out, TAG_SUSPEND_PERSONAL_APPS, mSuspendPersonalApps);
+        }
+        if (mProfileMaximumTimeOffMillis != 0) {
+            writeAttributeValueToXml(out, TAG_PROFILE_MAXIMUM_TIME_OFF,
+                    mProfileMaximumTimeOffMillis);
+        }
+        if (mProfileMaximumTimeOffMillis != 0) {
+            writeAttributeValueToXml(out, TAG_PROFILE_OFF_DEADLINE, mProfileOffDeadline);
+        }
+        if (!TextUtils.isEmpty(mAlwaysOnVpnPackage)) {
+            writeAttributeValueToXml(out, TAG_ALWAYS_ON_VPN_PACKAGE, mAlwaysOnVpnPackage);
+        }
+        if (mAlwaysOnVpnLockdown) {
+            writeAttributeValueToXml(out, TAG_ALWAYS_ON_VPN_LOCKDOWN, mAlwaysOnVpnLockdown);
+        }
+        if (mCommonCriteriaMode) {
+            writeAttributeValueToXml(out, TAG_COMMON_CRITERIA_MODE, mCommonCriteriaMode);
+        }
+    }
+
+    void writeTextToXml(XmlSerializer out, String tag, String text) throws IOException {
+        out.startTag(null, tag);
+        out.text(text);
+        out.endTag(null, tag);
+    }
+
+    void writePackageListToXml(XmlSerializer out, String outerTag,
+            List<String> packageList)
+            throws IllegalArgumentException, IllegalStateException, IOException {
+        if (packageList == null) {
+            return;
+        }
+        writeAttributeValuesToXml(out, outerTag, TAG_PACKAGE_LIST_ITEM, packageList);
+    }
+
+    void writeAttributeValueToXml(XmlSerializer out, String tag, String value)
+            throws IOException {
+        out.startTag(null, tag);
+        out.attribute(null, ATTR_VALUE, value);
+        out.endTag(null, tag);
+    }
+
+    void writeAttributeValueToXml(XmlSerializer out, String tag, int value)
+            throws IOException {
+        out.startTag(null, tag);
+        out.attribute(null, ATTR_VALUE, Integer.toString(value));
+        out.endTag(null, tag);
+    }
+
+    void writeAttributeValueToXml(XmlSerializer out, String tag, long value)
+            throws IOException {
+        out.startTag(null, tag);
+        out.attribute(null, ATTR_VALUE, Long.toString(value));
+        out.endTag(null, tag);
+    }
+
+    void writeAttributeValueToXml(XmlSerializer out, String tag, boolean value)
+            throws IOException {
+        out.startTag(null, tag);
+        out.attribute(null, ATTR_VALUE, Boolean.toString(value));
+        out.endTag(null, tag);
+    }
+
+    void writeAttributeValuesToXml(XmlSerializer out, String outerTag, String innerTag,
+            @NonNull Collection<String> values) throws IOException {
+        out.startTag(null, outerTag);
+        for (String value : values) {
+            out.startTag(null, innerTag);
+            out.attribute(null, ATTR_VALUE, value);
+            out.endTag(null, innerTag);
+        }
+        out.endTag(null, outerTag);
+    }
+
+    void readFromXml(XmlPullParser parser, boolean shouldOverridePolicies)
+            throws XmlPullParserException, IOException {
+        int outerDepth = parser.getDepth();
+        int type;
+        while ((type = parser.next()) != END_DOCUMENT
+               && (type != END_TAG || parser.getDepth() > outerDepth)) {
+            if (type == END_TAG || type == TEXT) {
+                continue;
+            }
+            String tag = parser.getName();
+            if (TAG_POLICIES.equals(tag)) {
+                if (shouldOverridePolicies) {
+                    Log.d(DevicePolicyManagerService.LOG_TAG,
+                            "Overriding device admin policies from XML.");
+                    info.readPoliciesFromXml(parser);
+                }
+            } else if (TAG_PASSWORD_QUALITY.equals(tag)) {
+                mPasswordPolicy.quality = Integer.parseInt(
+                        parser.getAttributeValue(null, ATTR_VALUE));
+            } else if (TAG_MIN_PASSWORD_LENGTH.equals(tag)) {
+                mPasswordPolicy.length = Integer.parseInt(
+                        parser.getAttributeValue(null, ATTR_VALUE));
+            } else if (TAG_PASSWORD_HISTORY_LENGTH.equals(tag)) {
+                passwordHistoryLength = Integer.parseInt(
+                        parser.getAttributeValue(null, ATTR_VALUE));
+            } else if (TAG_MIN_PASSWORD_UPPERCASE.equals(tag)) {
+                mPasswordPolicy.upperCase = Integer.parseInt(
+                        parser.getAttributeValue(null, ATTR_VALUE));
+            } else if (TAG_MIN_PASSWORD_LOWERCASE.equals(tag)) {
+                mPasswordPolicy.lowerCase = Integer.parseInt(
+                        parser.getAttributeValue(null, ATTR_VALUE));
+            } else if (TAG_MIN_PASSWORD_LETTERS.equals(tag)) {
+                mPasswordPolicy.letters = Integer.parseInt(
+                        parser.getAttributeValue(null, ATTR_VALUE));
+            } else if (TAG_MIN_PASSWORD_NUMERIC.equals(tag)) {
+                mPasswordPolicy.numeric = Integer.parseInt(
+                        parser.getAttributeValue(null, ATTR_VALUE));
+            } else if (TAG_MIN_PASSWORD_SYMBOLS.equals(tag)) {
+                mPasswordPolicy.symbols = Integer.parseInt(
+                        parser.getAttributeValue(null, ATTR_VALUE));
+            } else if (TAG_MIN_PASSWORD_NONLETTER.equals(tag)) {
+                mPasswordPolicy.nonLetter = Integer.parseInt(
+                        parser.getAttributeValue(null, ATTR_VALUE));
+            } else if (TAG_MAX_TIME_TO_UNLOCK.equals(tag)) {
+                maximumTimeToUnlock = Long.parseLong(
+                        parser.getAttributeValue(null, ATTR_VALUE));
+            } else if (TAG_STRONG_AUTH_UNLOCK_TIMEOUT.equals(tag)) {
+                strongAuthUnlockTimeout = Long.parseLong(
+                        parser.getAttributeValue(null, ATTR_VALUE));
+            } else if (TAG_MAX_FAILED_PASSWORD_WIPE.equals(tag)) {
+                maximumFailedPasswordsForWipe = Integer.parseInt(
+                        parser.getAttributeValue(null, ATTR_VALUE));
+            } else if (TAG_SPECIFIES_GLOBAL_PROXY.equals(tag)) {
+                specifiesGlobalProxy = Boolean.parseBoolean(
+                        parser.getAttributeValue(null, ATTR_VALUE));
+            } else if (TAG_GLOBAL_PROXY_SPEC.equals(tag)) {
+                globalProxySpec =
+                    parser.getAttributeValue(null, ATTR_VALUE);
+            } else if (TAG_GLOBAL_PROXY_EXCLUSION_LIST.equals(tag)) {
+                globalProxyExclusionList =
+                    parser.getAttributeValue(null, ATTR_VALUE);
+            } else if (TAG_PASSWORD_EXPIRATION_TIMEOUT.equals(tag)) {
+                passwordExpirationTimeout = Long.parseLong(
+                        parser.getAttributeValue(null, ATTR_VALUE));
+            } else if (TAG_PASSWORD_EXPIRATION_DATE.equals(tag)) {
+                passwordExpirationDate = Long.parseLong(
+                        parser.getAttributeValue(null, ATTR_VALUE));
+            } else if (TAG_ENCRYPTION_REQUESTED.equals(tag)) {
+                encryptionRequested = Boolean.parseBoolean(
+                        parser.getAttributeValue(null, ATTR_VALUE));
+            } else if (TAG_TEST_ONLY_ADMIN.equals(tag)) {
+                testOnlyAdmin = Boolean.parseBoolean(
+                        parser.getAttributeValue(null, ATTR_VALUE));
+            } else if (TAG_DISABLE_CAMERA.equals(tag)) {
+                disableCamera = Boolean.parseBoolean(
+                        parser.getAttributeValue(null, ATTR_VALUE));
+            } else if (TAG_DISABLE_CALLER_ID.equals(tag)) {
+                disableCallerId = Boolean.parseBoolean(
+                        parser.getAttributeValue(null, ATTR_VALUE));
+            } else if (TAG_DISABLE_CONTACTS_SEARCH.equals(tag)) {
+                disableContactsSearch = Boolean.parseBoolean(
+                        parser.getAttributeValue(null, ATTR_VALUE));
+            } else if (TAG_DISABLE_BLUETOOTH_CONTACT_SHARING.equals(tag)) {
+                disableBluetoothContactSharing = Boolean.parseBoolean(parser
+                        .getAttributeValue(null, ATTR_VALUE));
+            } else if (TAG_DISABLE_SCREEN_CAPTURE.equals(tag)) {
+                disableScreenCapture = Boolean.parseBoolean(
+                        parser.getAttributeValue(null, ATTR_VALUE));
+            } else if (TAG_REQUIRE_AUTO_TIME.equals(tag)) {
+                requireAutoTime = Boolean.parseBoolean(
+                        parser.getAttributeValue(null, ATTR_VALUE));
+            } else if (TAG_FORCE_EPHEMERAL_USERS.equals(tag)) {
+                forceEphemeralUsers = Boolean.parseBoolean(
+                        parser.getAttributeValue(null, ATTR_VALUE));
+            } else if (TAG_IS_NETWORK_LOGGING_ENABLED.equals(tag)) {
+                isNetworkLoggingEnabled = Boolean.parseBoolean(
+                        parser.getAttributeValue(null, ATTR_VALUE));
+                lastNetworkLoggingNotificationTimeMs = Long.parseLong(
+                        parser.getAttributeValue(null, ATTR_LAST_NETWORK_LOGGING_NOTIFICATION));
+                numNetworkLoggingNotifications = Integer.parseInt(
+                        parser.getAttributeValue(null, ATTR_NUM_NETWORK_LOGGING_NOTIFICATIONS));
+            } else if (TAG_DISABLE_KEYGUARD_FEATURES.equals(tag)) {
+                disabledKeyguardFeatures = Integer.parseInt(
+                        parser.getAttributeValue(null, ATTR_VALUE));
+            } else if (TAG_DISABLE_ACCOUNT_MANAGEMENT.equals(tag)) {
+                readAttributeValues(
+                        parser, TAG_ACCOUNT_TYPE, accountTypesWithManagementDisabled);
+            } else if (TAG_MANAGE_TRUST_AGENT_FEATURES.equals(tag)) {
+                trustAgentInfos = getAllTrustAgentInfos(parser, tag);
+            } else if (TAG_CROSS_PROFILE_WIDGET_PROVIDERS.equals(tag)) {
+                crossProfileWidgetProviders = new ArrayList<>();
+                readAttributeValues(parser, TAG_PROVIDER, crossProfileWidgetProviders);
+            } else if (TAG_PERMITTED_ACCESSIBILITY_SERVICES.equals(tag)) {
+                permittedAccessiblityServices = readPackageList(parser, tag);
+            } else if (TAG_PERMITTED_IMES.equals(tag)) {
+                permittedInputMethods = readPackageList(parser, tag);
+            } else if (TAG_PERMITTED_NOTIFICATION_LISTENERS.equals(tag)) {
+                permittedNotificationListeners = readPackageList(parser, tag);
+            } else if (TAG_KEEP_UNINSTALLED_PACKAGES.equals(tag)) {
+                keepUninstalledPackages = readPackageList(parser, tag);
+            } else if (TAG_METERED_DATA_DISABLED_PACKAGES.equals(tag)) {
+                meteredDisabledPackages = readPackageList(parser, tag);
+            } else if (TAG_USER_RESTRICTIONS.equals(tag)) {
+                userRestrictions = UserRestrictionsUtils.readRestrictions(parser);
+            } else if (TAG_DEFAULT_ENABLED_USER_RESTRICTIONS.equals(tag)) {
+                readAttributeValues(
+                        parser, TAG_RESTRICTION, defaultEnabledRestrictionsAlreadySet);
+            } else if (TAG_SHORT_SUPPORT_MESSAGE.equals(tag)) {
+                type = parser.next();
+                if (type == XmlPullParser.TEXT) {
+                    shortSupportMessage = parser.getText();
+                } else {
+                    Log.w(DevicePolicyManagerService.LOG_TAG,
+                            "Missing text when loading short support message");
+                }
+            } else if (TAG_LONG_SUPPORT_MESSAGE.equals(tag)) {
+                type = parser.next();
+                if (type == XmlPullParser.TEXT) {
+                    longSupportMessage = parser.getText();
+                } else {
+                    Log.w(DevicePolicyManagerService.LOG_TAG,
+                            "Missing text when loading long support message");
+                }
+            } else if (TAG_PARENT_ADMIN.equals(tag)) {
+                Preconditions.checkState(!isParent);
+                parentAdmin = new ActiveAdmin(info, /* parent */ true);
+                parentAdmin.readFromXml(parser, shouldOverridePolicies);
+            } else if (TAG_ORGANIZATION_COLOR.equals(tag)) {
+                organizationColor = Integer.parseInt(
+                        parser.getAttributeValue(null, ATTR_VALUE));
+            } else if (TAG_ORGANIZATION_NAME.equals(tag)) {
+                type = parser.next();
+                if (type == XmlPullParser.TEXT) {
+                    organizationName = parser.getText();
+                } else {
+                    Log.w(DevicePolicyManagerService.LOG_TAG,
+                            "Missing text when loading organization name");
+                }
+            } else if (TAG_IS_LOGOUT_ENABLED.equals(tag)) {
+                isLogoutEnabled = Boolean.parseBoolean(
+                        parser.getAttributeValue(null, ATTR_VALUE));
+            } else if (TAG_START_USER_SESSION_MESSAGE.equals(tag)) {
+                type = parser.next();
+                if (type == XmlPullParser.TEXT) {
+                    startUserSessionMessage = parser.getText();
+                } else {
+                    Log.w(DevicePolicyManagerService.LOG_TAG,
+                            "Missing text when loading start session message");
+                }
+            } else if (TAG_END_USER_SESSION_MESSAGE.equals(tag)) {
+                type = parser.next();
+                if (type == XmlPullParser.TEXT) {
+                    endUserSessionMessage = parser.getText();
+                } else {
+                    Log.w(DevicePolicyManagerService.LOG_TAG,
+                            "Missing text when loading end session message");
+                }
+            } else if (TAG_CROSS_PROFILE_CALENDAR_PACKAGES.equals(tag)) {
+                mCrossProfileCalendarPackages = readPackageList(parser, tag);
+            } else if (TAG_CROSS_PROFILE_CALENDAR_PACKAGES_NULL.equals(tag)) {
+                mCrossProfileCalendarPackages = null;
+            } else if (TAG_CROSS_PROFILE_PACKAGES.equals(tag)) {
+                mCrossProfilePackages = readPackageList(parser, tag);
+            } else if (TAG_FACTORY_RESET_PROTECTION_POLICY.equals(tag)) {
+                mFactoryResetProtectionPolicy = FactoryResetProtectionPolicy.readFromXml(
+                            parser);
+            } else if (TAG_SUSPEND_PERSONAL_APPS.equals(tag)) {
+                mSuspendPersonalApps = Boolean.parseBoolean(
+                        parser.getAttributeValue(null, ATTR_VALUE));
+            } else if (TAG_PROFILE_MAXIMUM_TIME_OFF.equals(tag)) {
+                mProfileMaximumTimeOffMillis =
+                        Long.parseLong(parser.getAttributeValue(null, ATTR_VALUE));
+            } else if (TAG_PROFILE_OFF_DEADLINE.equals(tag)) {
+                mProfileOffDeadline =
+                        Long.parseLong(parser.getAttributeValue(null, ATTR_VALUE));
+            } else if (TAG_ALWAYS_ON_VPN_PACKAGE.equals(tag)) {
+                mAlwaysOnVpnPackage = parser.getAttributeValue(null, ATTR_VALUE);
+            } else if (TAG_ALWAYS_ON_VPN_LOCKDOWN.equals(tag)) {
+                mAlwaysOnVpnLockdown = Boolean.parseBoolean(
+                        parser.getAttributeValue(null, ATTR_VALUE));
+            } else if (TAG_COMMON_CRITERIA_MODE.equals(tag)) {
+                mCommonCriteriaMode = Boolean.parseBoolean(
+                        parser.getAttributeValue(null, ATTR_VALUE));
+            } else {
+                Slog.w(DevicePolicyManagerService.LOG_TAG, "Unknown admin tag: " + tag);
+                XmlUtils.skipCurrentTag(parser);
+            }
+        }
+    }
+
+    private List<String> readPackageList(XmlPullParser parser,
+            String tag) throws XmlPullParserException, IOException {
+        List<String> result = new ArrayList<String>();
+        int outerDepth = parser.getDepth();
+        int outerType;
+        while ((outerType = parser.next()) != XmlPullParser.END_DOCUMENT
+                && (outerType != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
+            if (outerType == XmlPullParser.END_TAG || outerType == XmlPullParser.TEXT) {
+                continue;
+            }
+            String outerTag = parser.getName();
+            if (TAG_PACKAGE_LIST_ITEM.equals(outerTag)) {
+                String packageName = parser.getAttributeValue(null, ATTR_VALUE);
+                if (packageName != null) {
+                    result.add(packageName);
+                } else {
+                    Slog.w(DevicePolicyManagerService.LOG_TAG,
+                            "Package name missing under " + outerTag);
+                }
+            } else {
+                Slog.w(DevicePolicyManagerService.LOG_TAG,
+                        "Unknown tag under " + tag +  ": " + outerTag);
+            }
+        }
+        return result;
+    }
+
+    private void readAttributeValues(
+            XmlPullParser parser, String tag, Collection<String> result)
+            throws XmlPullParserException, IOException {
+        result.clear();
+        int outerDepthDAM = parser.getDepth();
+        int typeDAM;
+        while ((typeDAM = parser.next()) != END_DOCUMENT
+                && (typeDAM != END_TAG || parser.getDepth() > outerDepthDAM)) {
+            if (typeDAM == END_TAG || typeDAM == TEXT) {
+                continue;
+            }
+            String tagDAM = parser.getName();
+            if (tag.equals(tagDAM)) {
+                result.add(parser.getAttributeValue(null, ATTR_VALUE));
+            } else {
+                Slog.e(DevicePolicyManagerService.LOG_TAG,
+                        "Expected tag " + tag +  " but found " + tagDAM);
+            }
+        }
+    }
+
+    @NonNull
+    private ArrayMap<String, TrustAgentInfo> getAllTrustAgentInfos(
+            XmlPullParser parser, String tag) throws XmlPullParserException, IOException {
+        int outerDepthDAM = parser.getDepth();
+        int typeDAM;
+        final ArrayMap<String, TrustAgentInfo> result = new ArrayMap<>();
+        while ((typeDAM = parser.next()) != END_DOCUMENT
+                && (typeDAM != END_TAG || parser.getDepth() > outerDepthDAM)) {
+            if (typeDAM == END_TAG || typeDAM == TEXT) {
+                continue;
+            }
+            String tagDAM = parser.getName();
+            if (TAG_TRUST_AGENT_COMPONENT.equals(tagDAM)) {
+                final String component = parser.getAttributeValue(null, ATTR_VALUE);
+                final TrustAgentInfo trustAgentInfo = getTrustAgentInfo(parser, tag);
+                result.put(component, trustAgentInfo);
+            } else {
+                Slog.w(DevicePolicyManagerService.LOG_TAG,
+                        "Unknown tag under " + tag +  ": " + tagDAM);
+            }
+        }
+        return result;
+    }
+
+    private TrustAgentInfo getTrustAgentInfo(XmlPullParser parser, String tag)
+            throws XmlPullParserException, IOException  {
+        int outerDepthDAM = parser.getDepth();
+        int typeDAM;
+        TrustAgentInfo result = new TrustAgentInfo(null);
+        while ((typeDAM = parser.next()) != END_DOCUMENT
+                && (typeDAM != END_TAG || parser.getDepth() > outerDepthDAM)) {
+            if (typeDAM == END_TAG || typeDAM == TEXT) {
+                continue;
+            }
+            String tagDAM = parser.getName();
+            if (TAG_TRUST_AGENT_COMPONENT_OPTIONS.equals(tagDAM)) {
+                result.options = PersistableBundle.restoreFromXml(parser);
+            } else {
+                Slog.w(DevicePolicyManagerService.LOG_TAG,
+                        "Unknown tag under " + tag +  ": " + tagDAM);
+            }
+        }
+        return result;
+    }
+
+    boolean hasUserRestrictions() {
+        return userRestrictions != null && userRestrictions.size() > 0;
+    }
+
+    Bundle ensureUserRestrictions() {
+        if (userRestrictions == null) {
+            userRestrictions = new Bundle();
+        }
+        return userRestrictions;
+    }
+
+    public void transfer(DeviceAdminInfo deviceAdminInfo) {
+        if (hasParentActiveAdmin()) {
+            parentAdmin.info = deviceAdminInfo;
+        }
+        info = deviceAdminInfo;
+    }
+
+    Bundle addSyntheticRestrictions(Bundle restrictions) {
+        if (disableCamera) {
+            restrictions.putBoolean(UserManager.DISALLOW_CAMERA, true);
+        }
+        if (requireAutoTime) {
+            restrictions.putBoolean(UserManager.DISALLOW_CONFIG_DATE_TIME, true);
+        }
+        return restrictions;
+    }
+
+    static Bundle removeDeprecatedRestrictions(Bundle restrictions) {
+        for (String deprecatedRestriction: UserRestrictionsUtils.DEPRECATED_USER_RESTRICTIONS) {
+            restrictions.remove(deprecatedRestriction);
+        }
+        return restrictions;
+    }
+
+    static Bundle filterRestrictions(Bundle restrictions, Predicate<String> filter) {
+        Bundle result = new Bundle();
+        for (String key : restrictions.keySet()) {
+            if (!restrictions.getBoolean(key)) {
+                continue;
+            }
+            if (filter.test(key)) {
+                result.putBoolean(key, true);
+            }
+        }
+        return result;
+    }
+
+    Bundle getEffectiveRestrictions() {
+        return addSyntheticRestrictions(
+                removeDeprecatedRestrictions(new Bundle(ensureUserRestrictions())));
+    }
+
+    Bundle getLocalUserRestrictions(int adminType) {
+        return filterRestrictions(getEffectiveRestrictions(),
+                key -> UserRestrictionsUtils.isLocal(adminType, key));
+    }
+
+    Bundle getGlobalUserRestrictions(int adminType) {
+        return filterRestrictions(getEffectiveRestrictions(),
+                key -> UserRestrictionsUtils.isGlobal(adminType, key));
+    }
+
+    void dump(IndentingPrintWriter pw) {
+        pw.print("uid=");
+        pw.println(getUid());
+
+        pw.print("testOnlyAdmin=");
+        pw.println(testOnlyAdmin);
+
+        pw.println("policies:");
+        ArrayList<DeviceAdminInfo.PolicyInfo> pols = info.getUsedPolicies();
+        if (pols != null) {
+            pw.increaseIndent();
+            for (int i = 0; i < pols.size(); i++) {
+                pw.println(pols.get(i).tag);
+            }
+            pw.decreaseIndent();
+        }
+
+        pw.print("passwordQuality=0x");
+        pw.println(Integer.toHexString(mPasswordPolicy.quality));
+
+        pw.print("minimumPasswordLength=");
+        pw.println(mPasswordPolicy.length);
+
+        pw.print("passwordHistoryLength=");
+        pw.println(passwordHistoryLength);
+
+        pw.print("minimumPasswordUpperCase=");
+        pw.println(mPasswordPolicy.upperCase);
+
+        pw.print("minimumPasswordLowerCase=");
+        pw.println(mPasswordPolicy.lowerCase);
+
+        pw.print("minimumPasswordLetters=");
+        pw.println(mPasswordPolicy.letters);
+
+        pw.print("minimumPasswordNumeric=");
+        pw.println(mPasswordPolicy.numeric);
+
+        pw.print("minimumPasswordSymbols=");
+        pw.println(mPasswordPolicy.symbols);
+
+        pw.print("minimumPasswordNonLetter=");
+        pw.println(mPasswordPolicy.nonLetter);
+
+        pw.print("maximumTimeToUnlock=");
+        pw.println(maximumTimeToUnlock);
+
+        pw.print("strongAuthUnlockTimeout=");
+        pw.println(strongAuthUnlockTimeout);
+
+        pw.print("maximumFailedPasswordsForWipe=");
+        pw.println(maximumFailedPasswordsForWipe);
+
+        pw.print("specifiesGlobalProxy=");
+        pw.println(specifiesGlobalProxy);
+
+        pw.print("passwordExpirationTimeout=");
+        pw.println(passwordExpirationTimeout);
+
+        pw.print("passwordExpirationDate=");
+        pw.println(passwordExpirationDate);
+
+        if (globalProxySpec != null) {
+            pw.print("globalProxySpec=");
+            pw.println(globalProxySpec);
+        }
+        if (globalProxyExclusionList != null) {
+            pw.print("globalProxyEclusionList=");
+            pw.println(globalProxyExclusionList);
+        }
+        pw.print("encryptionRequested=");
+        pw.println(encryptionRequested);
+
+        pw.print("disableCamera=");
+        pw.println(disableCamera);
+
+        pw.print("disableCallerId=");
+        pw.println(disableCallerId);
+
+        pw.print("disableContactsSearch=");
+        pw.println(disableContactsSearch);
+
+        pw.print("disableBluetoothContactSharing=");
+        pw.println(disableBluetoothContactSharing);
+
+        pw.print("disableScreenCapture=");
+        pw.println(disableScreenCapture);
+
+        pw.print("requireAutoTime=");
+        pw.println(requireAutoTime);
+
+        pw.print("forceEphemeralUsers=");
+        pw.println(forceEphemeralUsers);
+
+        pw.print("isNetworkLoggingEnabled=");
+        pw.println(isNetworkLoggingEnabled);
+
+        pw.print("disabledKeyguardFeatures=");
+        pw.println(disabledKeyguardFeatures);
+
+        pw.print("crossProfileWidgetProviders=");
+        pw.println(crossProfileWidgetProviders);
+
+        if (permittedAccessiblityServices != null) {
+            pw.print("permittedAccessibilityServices=");
+            pw.println(permittedAccessiblityServices);
+        }
+
+        if (permittedInputMethods != null) {
+            pw.print("permittedInputMethods=");
+            pw.println(permittedInputMethods);
+        }
+
+        if (permittedNotificationListeners != null) {
+            pw.print("permittedNotificationListeners=");
+            pw.println(permittedNotificationListeners);
+        }
+
+        if (keepUninstalledPackages != null) {
+            pw.print("keepUninstalledPackages=");
+            pw.println(keepUninstalledPackages);
+        }
+
+        pw.print("organizationColor=");
+        pw.println(organizationColor);
+
+        if (organizationName != null) {
+            pw.print("organizationName=");
+            pw.println(organizationName);
+        }
+
+        pw.println("userRestrictions:");
+        UserRestrictionsUtils.dumpRestrictions(pw, "  ", userRestrictions);
+
+        pw.print("defaultEnabledRestrictionsAlreadySet=");
+        pw.println(defaultEnabledRestrictionsAlreadySet);
+
+        pw.print("isParent=");
+        pw.println(isParent);
+
+        if (parentAdmin != null) {
+            pw.println("parentAdmin:");
+            pw.increaseIndent();
+            parentAdmin.dump(pw);
+            pw.decreaseIndent();
+        }
+
+        if (mCrossProfileCalendarPackages != null) {
+            pw.print("mCrossProfileCalendarPackages=");
+            pw.println(mCrossProfileCalendarPackages);
+        }
+
+        pw.print("mCrossProfilePackages=");
+        pw.println(mCrossProfilePackages);
+
+        pw.print("mSuspendPersonalApps=");
+        pw.println(mSuspendPersonalApps);
+
+        pw.print("mProfileMaximumTimeOffMillis=");
+        pw.println(mProfileMaximumTimeOffMillis);
+
+        pw.print("mProfileOffDeadline=");
+        pw.println(mProfileOffDeadline);
+
+        pw.print("mAlwaysOnVpnPackage=");
+        pw.println(mAlwaysOnVpnPackage);
+
+        pw.print("mAlwaysOnVpnLockdown=");
+        pw.println(mAlwaysOnVpnLockdown);
+
+        pw.print("mCommonCriteriaMode=");
+        pw.println(mCommonCriteriaMode);
+    }
+}
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/CallerIdentity.java b/services/devicepolicy/java/com/android/server/devicepolicy/CallerIdentity.java
new file mode 100644
index 0000000..5193fa8
--- /dev/null
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/CallerIdentity.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.devicepolicy;
+
+import android.annotation.Nullable;
+import android.content.ComponentName;
+import android.os.UserHandle;
+
+/**
+ * Caller identity containing the caller's UID, package name and component name.
+ * All parameters are verified on object creation unless the component name is null and the
+ * caller is a delegate.
+ */
+class CallerIdentity {
+
+    private final int mUid;
+    @Nullable
+    private final String mPackageName;
+    @Nullable
+    private final ComponentName mComponentName;
+
+    CallerIdentity(int uid, @Nullable String packageName, @Nullable ComponentName componentName) {
+        mUid = uid;
+        mPackageName = packageName;
+        mComponentName = componentName;
+    }
+
+    public int getUid() {
+        return mUid;
+    }
+
+    public int getUserId() {
+        return UserHandle.getUserId(mUid);
+    }
+
+    public UserHandle getUserHandle() {
+        return UserHandle.getUserHandleForUid(mUid);
+    }
+
+    @Nullable  public String getPackageName() {
+        return mPackageName;
+    }
+
+    @Nullable public ComponentName getComponentName() {
+        return mComponentName;
+    }
+}
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyData.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyData.java
new file mode 100644
index 0000000..130cfd5
--- /dev/null
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyData.java
@@ -0,0 +1,566 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.devicepolicy;
+
+import android.app.admin.DeviceAdminInfo;
+import android.app.admin.DevicePolicyManager;
+import android.content.ComponentName;
+import android.os.FileUtils;
+import android.os.PersistableBundle;
+import android.text.TextUtils;
+import android.util.ArrayMap;
+import android.util.ArraySet;
+import android.util.Slog;
+import android.util.Xml;
+
+import com.android.internal.util.FastXmlSerializer;
+import com.android.internal.util.JournaledFile;
+import com.android.internal.util.XmlUtils;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+import org.xmlpull.v1.XmlSerializer;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Set;
+import java.util.function.Function;
+
+class DevicePolicyData {
+    private static final String TAG_ACCEPTED_CA_CERTIFICATES = "accepted-ca-certificate";
+    private static final String TAG_LOCK_TASK_COMPONENTS = "lock-task-component";
+    private static final String TAG_LOCK_TASK_FEATURES = "lock-task-features";
+    private static final String TAG_STATUS_BAR = "statusbar";
+    private static final String TAG_APPS_SUSPENDED = "apps-suspended";
+    private static final String TAG_SECONDARY_LOCK_SCREEN = "secondary-lock-screen";
+    private static final String TAG_DO_NOT_ASK_CREDENTIALS_ON_BOOT =
+            "do-not-ask-credentials-on-boot";
+    private static final String TAG_AFFILIATION_ID = "affiliation-id";
+    private static final String TAG_LAST_SECURITY_LOG_RETRIEVAL = "last-security-log-retrieval";
+    private static final String TAG_LAST_BUG_REPORT_REQUEST = "last-bug-report-request";
+    private static final String TAG_LAST_NETWORK_LOG_RETRIEVAL = "last-network-log-retrieval";
+    private static final String TAG_ADMIN_BROADCAST_PENDING = "admin-broadcast-pending";
+    private static final String TAG_CURRENT_INPUT_METHOD_SET = "current-ime-set";
+    private static final String TAG_OWNER_INSTALLED_CA_CERT = "owner-installed-ca-cert";
+    private static final String TAG_INITIALIZATION_BUNDLE = "initialization-bundle";
+    private static final String TAG_PASSWORD_VALIDITY = "password-validity";
+    private static final String TAG_PASSWORD_TOKEN_HANDLE = "password-token";
+    private static final String TAG_PROTECTED_PACKAGES = "protected-packages";
+    private static final String ATTR_VALUE = "value";
+    private static final String ATTR_ALIAS = "alias";
+    private static final String ATTR_ID = "id";
+    private static final String ATTR_PERMISSION_PROVIDER = "permission-provider";
+    private static final String ATTR_NAME = "name";
+    private static final String ATTR_DISABLED = "disabled";
+    private static final String ATTR_SETUP_COMPLETE = "setup-complete";
+    private static final String ATTR_PROVISIONING_STATE = "provisioning-state";
+    private static final String ATTR_PERMISSION_POLICY = "permission-policy";
+    private static final String ATTR_DEVICE_PROVISIONING_CONFIG_APPLIED =
+            "device-provisioning-config-applied";
+    private static final String ATTR_DEVICE_PAIRED = "device-paired";
+
+    int mFailedPasswordAttempts = 0;
+    boolean mPasswordValidAtLastCheckpoint = true;
+
+    int mUserHandle;
+    int mPasswordOwner = -1;
+    long mLastMaximumTimeToLock = -1;
+    boolean mUserSetupComplete = false;
+    boolean mPaired = false;
+    int mUserProvisioningState;
+    int mPermissionPolicy;
+
+    boolean mDeviceProvisioningConfigApplied = false;
+
+    final ArrayMap<ComponentName, ActiveAdmin> mAdminMap = new ArrayMap<>();
+    final ArrayList<ActiveAdmin> mAdminList = new ArrayList<>();
+    final ArrayList<ComponentName> mRemovingAdmins = new ArrayList<>();
+
+    // TODO(b/35385311): Keep track of metadata in TrustedCertificateStore instead.
+    final ArraySet<String> mAcceptedCaCertificates = new ArraySet<>();
+
+    // This is the list of component allowed to start lock task mode.
+    List<String> mLockTaskPackages = new ArrayList<>();
+
+    // List of packages protected by device owner
+    List<String> mUserControlDisabledPackages = new ArrayList<>();
+
+    // Bitfield of feature flags to be enabled during LockTask mode.
+    // We default on the power button menu, in order to be consistent with pre-P behaviour.
+    int mLockTaskFeatures = DevicePolicyManager.LOCK_TASK_FEATURE_GLOBAL_ACTIONS;
+
+    boolean mStatusBarDisabled = false;
+
+    ComponentName mRestrictionsProvider;
+
+    // Map of delegate package to delegation scopes
+    final ArrayMap<String, List<String>> mDelegationMap = new ArrayMap<>();
+
+    boolean mDoNotAskCredentialsOnBoot = false;
+
+    Set<String> mAffiliationIds = new ArraySet<>();
+
+    long mLastSecurityLogRetrievalTime = -1;
+
+    long mLastBugReportRequestTime = -1;
+
+    long mLastNetworkLogsRetrievalTime = -1;
+
+    boolean mCurrentInputMethodSet = false;
+
+    boolean mSecondaryLockscreenEnabled = false;
+
+    // TODO(b/35385311): Keep track of metadata in TrustedCertificateStore instead.
+    Set<String> mOwnerInstalledCaCerts = new ArraySet<>();
+
+    // Used for initialization of users created by createAndManageUser.
+    boolean mAdminBroadcastPending = false;
+    PersistableBundle mInitBundle = null;
+
+    long mPasswordTokenHandle = 0;
+
+    // Whether user's apps are suspended. This flag should only be written AFTER all the needed
+    // apps were suspended or unsuspended.
+    boolean mAppsSuspended = false;
+
+    DevicePolicyData(int userHandle) {
+        mUserHandle = userHandle;
+    }
+
+    /**
+     * Serializes DevicePolicyData object as XML.
+     */
+    static boolean store(DevicePolicyData policyData, JournaledFile file, boolean isFdeDevice) {
+        FileOutputStream stream = null;
+        try {
+            stream = new FileOutputStream(file.chooseForWrite(), false);
+            XmlSerializer out = new FastXmlSerializer();
+            out.setOutput(stream, StandardCharsets.UTF_8.name());
+            out.startDocument(null, true);
+
+            out.startTag(null, "policies");
+            if (policyData.mRestrictionsProvider != null) {
+                out.attribute(null, ATTR_PERMISSION_PROVIDER,
+                        policyData.mRestrictionsProvider.flattenToString());
+            }
+            if (policyData.mUserSetupComplete) {
+                out.attribute(null, ATTR_SETUP_COMPLETE,
+                        Boolean.toString(true));
+            }
+            if (policyData.mPaired) {
+                out.attribute(null, ATTR_DEVICE_PAIRED,
+                        Boolean.toString(true));
+            }
+            if (policyData.mDeviceProvisioningConfigApplied) {
+                out.attribute(null, ATTR_DEVICE_PROVISIONING_CONFIG_APPLIED,
+                        Boolean.toString(true));
+            }
+            if (policyData.mUserProvisioningState != DevicePolicyManager.STATE_USER_UNMANAGED) {
+                out.attribute(null, ATTR_PROVISIONING_STATE,
+                        Integer.toString(policyData.mUserProvisioningState));
+            }
+            if (policyData.mPermissionPolicy != DevicePolicyManager.PERMISSION_POLICY_PROMPT) {
+                out.attribute(null, ATTR_PERMISSION_POLICY,
+                        Integer.toString(policyData.mPermissionPolicy));
+            }
+
+            // Serialize delegations.
+            for (int i = 0; i < policyData.mDelegationMap.size(); ++i) {
+                final String delegatePackage = policyData.mDelegationMap.keyAt(i);
+                final List<String> scopes = policyData.mDelegationMap.valueAt(i);
+
+                // Every "delegation" tag serializes the information of one delegate-scope pair.
+                for (String scope : scopes) {
+                    out.startTag(null, "delegation");
+                    out.attribute(null, "delegatePackage", delegatePackage);
+                    out.attribute(null, "scope", scope);
+                    out.endTag(null, "delegation");
+                }
+            }
+
+            final int n = policyData.mAdminList.size();
+            for (int i = 0; i < n; i++) {
+                ActiveAdmin ap = policyData.mAdminList.get(i);
+                if (ap != null) {
+                    out.startTag(null, "admin");
+                    out.attribute(null, "name", ap.info.getComponent().flattenToString());
+                    ap.writeToXml(out);
+                    out.endTag(null, "admin");
+                }
+            }
+
+            if (policyData.mPasswordOwner >= 0) {
+                out.startTag(null, "password-owner");
+                out.attribute(null, "value", Integer.toString(policyData.mPasswordOwner));
+                out.endTag(null, "password-owner");
+            }
+
+            if (policyData.mFailedPasswordAttempts != 0) {
+                out.startTag(null, "failed-password-attempts");
+                out.attribute(null, "value", Integer.toString(policyData.mFailedPasswordAttempts));
+                out.endTag(null, "failed-password-attempts");
+            }
+
+            // For FDE devices only, we save this flag so we can report on password sufficiency
+            // before the user enters their password for the first time after a reboot.  For
+            // security reasons, we don't want to store the full set of active password metrics.
+            if (isFdeDevice) {
+                out.startTag(null, TAG_PASSWORD_VALIDITY);
+                out.attribute(null, ATTR_VALUE,
+                        Boolean.toString(policyData.mPasswordValidAtLastCheckpoint));
+                out.endTag(null, TAG_PASSWORD_VALIDITY);
+            }
+
+            for (int i = 0; i < policyData.mAcceptedCaCertificates.size(); i++) {
+                out.startTag(null, TAG_ACCEPTED_CA_CERTIFICATES);
+                out.attribute(null, ATTR_NAME, policyData.mAcceptedCaCertificates.valueAt(i));
+                out.endTag(null, TAG_ACCEPTED_CA_CERTIFICATES);
+            }
+
+            for (int i = 0; i < policyData.mLockTaskPackages.size(); i++) {
+                String component = policyData.mLockTaskPackages.get(i);
+                out.startTag(null, TAG_LOCK_TASK_COMPONENTS);
+                out.attribute(null, "name", component);
+                out.endTag(null, TAG_LOCK_TASK_COMPONENTS);
+            }
+
+            if (policyData.mLockTaskFeatures != DevicePolicyManager.LOCK_TASK_FEATURE_NONE) {
+                out.startTag(null, TAG_LOCK_TASK_FEATURES);
+                out.attribute(null, ATTR_VALUE, Integer.toString(policyData.mLockTaskFeatures));
+                out.endTag(null, TAG_LOCK_TASK_FEATURES);
+            }
+
+            if (policyData.mSecondaryLockscreenEnabled) {
+                out.startTag(null, TAG_SECONDARY_LOCK_SCREEN);
+                out.attribute(null, ATTR_VALUE, Boolean.toString(true));
+                out.endTag(null, TAG_SECONDARY_LOCK_SCREEN);
+            }
+
+            if (policyData.mStatusBarDisabled) {
+                out.startTag(null, TAG_STATUS_BAR);
+                out.attribute(null, ATTR_DISABLED, Boolean.toString(policyData.mStatusBarDisabled));
+                out.endTag(null, TAG_STATUS_BAR);
+            }
+
+            if (policyData.mDoNotAskCredentialsOnBoot) {
+                out.startTag(null, TAG_DO_NOT_ASK_CREDENTIALS_ON_BOOT);
+                out.endTag(null, TAG_DO_NOT_ASK_CREDENTIALS_ON_BOOT);
+            }
+
+            for (String id : policyData.mAffiliationIds) {
+                out.startTag(null, TAG_AFFILIATION_ID);
+                out.attribute(null, ATTR_ID, id);
+                out.endTag(null, TAG_AFFILIATION_ID);
+            }
+
+            if (policyData.mLastSecurityLogRetrievalTime >= 0) {
+                out.startTag(null, TAG_LAST_SECURITY_LOG_RETRIEVAL);
+                out.attribute(null, ATTR_VALUE,
+                        Long.toString(policyData.mLastSecurityLogRetrievalTime));
+                out.endTag(null, TAG_LAST_SECURITY_LOG_RETRIEVAL);
+            }
+
+            if (policyData.mLastBugReportRequestTime >= 0) {
+                out.startTag(null, TAG_LAST_BUG_REPORT_REQUEST);
+                out.attribute(null, ATTR_VALUE,
+                        Long.toString(policyData.mLastBugReportRequestTime));
+                out.endTag(null, TAG_LAST_BUG_REPORT_REQUEST);
+            }
+
+            if (policyData.mLastNetworkLogsRetrievalTime >= 0) {
+                out.startTag(null, TAG_LAST_NETWORK_LOG_RETRIEVAL);
+                out.attribute(null, ATTR_VALUE,
+                        Long.toString(policyData.mLastNetworkLogsRetrievalTime));
+                out.endTag(null, TAG_LAST_NETWORK_LOG_RETRIEVAL);
+            }
+
+            if (policyData.mAdminBroadcastPending) {
+                out.startTag(null, TAG_ADMIN_BROADCAST_PENDING);
+                out.attribute(null, ATTR_VALUE,
+                        Boolean.toString(policyData.mAdminBroadcastPending));
+                out.endTag(null, TAG_ADMIN_BROADCAST_PENDING);
+            }
+
+            if (policyData.mInitBundle != null) {
+                out.startTag(null, TAG_INITIALIZATION_BUNDLE);
+                policyData.mInitBundle.saveToXml(out);
+                out.endTag(null, TAG_INITIALIZATION_BUNDLE);
+            }
+
+            if (policyData.mPasswordTokenHandle != 0) {
+                out.startTag(null, TAG_PASSWORD_TOKEN_HANDLE);
+                out.attribute(null, ATTR_VALUE,
+                        Long.toString(policyData.mPasswordTokenHandle));
+                out.endTag(null, TAG_PASSWORD_TOKEN_HANDLE);
+            }
+
+            if (policyData.mCurrentInputMethodSet) {
+                out.startTag(null, TAG_CURRENT_INPUT_METHOD_SET);
+                out.endTag(null, TAG_CURRENT_INPUT_METHOD_SET);
+            }
+
+            for (final String cert : policyData.mOwnerInstalledCaCerts) {
+                out.startTag(null, TAG_OWNER_INSTALLED_CA_CERT);
+                out.attribute(null, ATTR_ALIAS, cert);
+                out.endTag(null, TAG_OWNER_INSTALLED_CA_CERT);
+            }
+
+            for (int i = 0, size = policyData.mUserControlDisabledPackages.size(); i < size; i++) {
+                String packageName = policyData.mUserControlDisabledPackages.get(i);
+                out.startTag(null, TAG_PROTECTED_PACKAGES);
+                out.attribute(null, ATTR_NAME, packageName);
+                out.endTag(null, TAG_PROTECTED_PACKAGES);
+            }
+
+            if (policyData.mAppsSuspended) {
+                out.startTag(null, TAG_APPS_SUSPENDED);
+                out.attribute(null, ATTR_VALUE, Boolean.toString(policyData.mAppsSuspended));
+                out.endTag(null, TAG_APPS_SUSPENDED);
+            }
+
+            out.endTag(null, "policies");
+
+            out.endDocument();
+            stream.flush();
+            FileUtils.sync(stream);
+            stream.close();
+            file.commit();
+            return true;
+        } catch (XmlPullParserException | IOException e) {
+            Slog.w(DevicePolicyManagerService.LOG_TAG, "failed writing file", e);
+            try {
+                if (stream != null) {
+                    stream.close();
+                }
+            } catch (IOException ex) {
+                // Ignore
+            }
+            file.rollback();
+            return false;
+        }
+    }
+
+    /**
+     * @param adminInfoSupplier function that queries DeviceAdminInfo from PackageManager
+     * @param ownerComponent device or profile owner component if any.
+     */
+    static boolean load(DevicePolicyData policy, boolean isFdeDevice, JournaledFile journaledFile,
+            Function<ComponentName, DeviceAdminInfo> adminInfoSupplier,
+            ComponentName ownerComponent) {
+        FileInputStream stream = null;
+        File file = journaledFile.chooseForRead();
+        boolean needsRewrite = false;
+        try {
+            stream = new FileInputStream(file);
+            XmlPullParser parser = Xml.newPullParser();
+            parser.setInput(stream, StandardCharsets.UTF_8.name());
+
+            int type;
+            while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
+                    && type != XmlPullParser.START_TAG) {
+            }
+            String tag = parser.getName();
+            if (!"policies".equals(tag)) {
+                throw new XmlPullParserException(
+                        "Settings do not start with policies tag: found " + tag);
+            }
+
+            // Extract the permission provider component name if available
+            String permissionProvider = parser.getAttributeValue(null, ATTR_PERMISSION_PROVIDER);
+            if (permissionProvider != null) {
+                policy.mRestrictionsProvider =
+                        ComponentName.unflattenFromString(permissionProvider);
+            }
+            String userSetupComplete = parser.getAttributeValue(null, ATTR_SETUP_COMPLETE);
+            if (Boolean.toString(true).equals(userSetupComplete)) {
+                policy.mUserSetupComplete = true;
+            }
+            String paired = parser.getAttributeValue(null, ATTR_DEVICE_PAIRED);
+            if (Boolean.toString(true).equals(paired)) {
+                policy.mPaired = true;
+            }
+            String deviceProvisioningConfigApplied = parser.getAttributeValue(null,
+                    ATTR_DEVICE_PROVISIONING_CONFIG_APPLIED);
+            if (Boolean.toString(true).equals(deviceProvisioningConfigApplied)) {
+                policy.mDeviceProvisioningConfigApplied = true;
+            }
+            String provisioningState = parser.getAttributeValue(null, ATTR_PROVISIONING_STATE);
+            if (!TextUtils.isEmpty(provisioningState)) {
+                policy.mUserProvisioningState = Integer.parseInt(provisioningState);
+            }
+            String permissionPolicy = parser.getAttributeValue(null, ATTR_PERMISSION_POLICY);
+            if (!TextUtils.isEmpty(permissionPolicy)) {
+                policy.mPermissionPolicy = Integer.parseInt(permissionPolicy);
+            }
+
+            parser.next();
+            int outerDepth = parser.getDepth();
+            policy.mLockTaskPackages.clear();
+            policy.mAdminList.clear();
+            policy.mAdminMap.clear();
+            policy.mAffiliationIds.clear();
+            policy.mOwnerInstalledCaCerts.clear();
+            policy.mUserControlDisabledPackages.clear();
+            while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
+                   && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
+                if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
+                    continue;
+                }
+                tag = parser.getName();
+                if ("admin".equals(tag)) {
+                    String name = parser.getAttributeValue(null, "name");
+                    try {
+                        DeviceAdminInfo dai = adminInfoSupplier.apply(
+                                ComponentName.unflattenFromString(name));
+
+                        if (dai != null) {
+                            // b/123415062: If DA, overwrite with the stored policies that were
+                            // agreed by the user to prevent apps from sneaking additional policies
+                            // into updates.
+                            boolean overwritePolicies = !dai.getComponent().equals(ownerComponent);
+                            ActiveAdmin ap = new ActiveAdmin(dai, /* parent */ false);
+                            ap.readFromXml(parser, overwritePolicies);
+                            policy.mAdminMap.put(ap.info.getComponent(), ap);
+                        }
+                    } catch (RuntimeException e) {
+                        Slog.w(DevicePolicyManagerService.LOG_TAG,
+                                "Failed loading admin " + name, e);
+                    }
+                } else if ("delegation".equals(tag)) {
+                    // Parse delegation info.
+                    final String delegatePackage = parser.getAttributeValue(null,
+                            "delegatePackage");
+                    final String scope = parser.getAttributeValue(null, "scope");
+
+                    // Get a reference to the scopes list for the delegatePackage.
+                    List<String> scopes = policy.mDelegationMap.get(delegatePackage);
+                    // Or make a new list if none was found.
+                    if (scopes == null) {
+                        scopes = new ArrayList<>();
+                        policy.mDelegationMap.put(delegatePackage, scopes);
+                    }
+                    // Add the new scope to the list of delegatePackage if it's not already there.
+                    if (!scopes.contains(scope)) {
+                        scopes.add(scope);
+                    }
+                } else if ("failed-password-attempts".equals(tag)) {
+                    policy.mFailedPasswordAttempts = Integer.parseInt(
+                            parser.getAttributeValue(null, "value"));
+                } else if ("password-owner".equals(tag)) {
+                    policy.mPasswordOwner = Integer.parseInt(
+                            parser.getAttributeValue(null, "value"));
+                } else if (TAG_ACCEPTED_CA_CERTIFICATES.equals(tag)) {
+                    policy.mAcceptedCaCertificates.add(parser.getAttributeValue(null, ATTR_NAME));
+                } else if (TAG_LOCK_TASK_COMPONENTS.equals(tag)) {
+                    policy.mLockTaskPackages.add(parser.getAttributeValue(null, "name"));
+                } else if (TAG_LOCK_TASK_FEATURES.equals(tag)) {
+                    policy.mLockTaskFeatures = Integer.parseInt(
+                            parser.getAttributeValue(null, ATTR_VALUE));
+                } else if (TAG_SECONDARY_LOCK_SCREEN.equals(tag)) {
+                    policy.mSecondaryLockscreenEnabled = Boolean.parseBoolean(
+                            parser.getAttributeValue(null, ATTR_VALUE));
+                } else if (TAG_STATUS_BAR.equals(tag)) {
+                    policy.mStatusBarDisabled = Boolean.parseBoolean(
+                            parser.getAttributeValue(null, ATTR_DISABLED));
+                } else if (TAG_DO_NOT_ASK_CREDENTIALS_ON_BOOT.equals(tag)) {
+                    policy.mDoNotAskCredentialsOnBoot = true;
+                } else if (TAG_AFFILIATION_ID.equals(tag)) {
+                    policy.mAffiliationIds.add(parser.getAttributeValue(null, ATTR_ID));
+                } else if (TAG_LAST_SECURITY_LOG_RETRIEVAL.equals(tag)) {
+                    policy.mLastSecurityLogRetrievalTime = Long.parseLong(
+                            parser.getAttributeValue(null, ATTR_VALUE));
+                } else if (TAG_LAST_BUG_REPORT_REQUEST.equals(tag)) {
+                    policy.mLastBugReportRequestTime = Long.parseLong(
+                            parser.getAttributeValue(null, ATTR_VALUE));
+                } else if (TAG_LAST_NETWORK_LOG_RETRIEVAL.equals(tag)) {
+                    policy.mLastNetworkLogsRetrievalTime = Long.parseLong(
+                            parser.getAttributeValue(null, ATTR_VALUE));
+                } else if (TAG_ADMIN_BROADCAST_PENDING.equals(tag)) {
+                    String pending = parser.getAttributeValue(null, ATTR_VALUE);
+                    policy.mAdminBroadcastPending = Boolean.toString(true).equals(pending);
+                } else if (TAG_INITIALIZATION_BUNDLE.equals(tag)) {
+                    policy.mInitBundle = PersistableBundle.restoreFromXml(parser);
+                } else if ("active-password".equals(tag)) {
+                    // Remove password metrics from saved settings, as we no longer wish to store
+                    // these on disk
+                    needsRewrite = true;
+                } else if (TAG_PASSWORD_VALIDITY.equals(tag)) {
+                    if (isFdeDevice) {
+                        // This flag is only used for FDE devices
+                        policy.mPasswordValidAtLastCheckpoint = Boolean.parseBoolean(
+                                parser.getAttributeValue(null, ATTR_VALUE));
+                    }
+                } else if (TAG_PASSWORD_TOKEN_HANDLE.equals(tag)) {
+                    policy.mPasswordTokenHandle = Long.parseLong(
+                            parser.getAttributeValue(null, ATTR_VALUE));
+                } else if (TAG_CURRENT_INPUT_METHOD_SET.equals(tag)) {
+                    policy.mCurrentInputMethodSet = true;
+                } else if (TAG_OWNER_INSTALLED_CA_CERT.equals(tag)) {
+                    policy.mOwnerInstalledCaCerts.add(parser.getAttributeValue(null, ATTR_ALIAS));
+                } else if (TAG_PROTECTED_PACKAGES.equals(tag)) {
+                    policy.mUserControlDisabledPackages.add(
+                            parser.getAttributeValue(null, ATTR_NAME));
+                } else if (TAG_APPS_SUSPENDED.equals(tag)) {
+                    policy.mAppsSuspended =
+                            Boolean.parseBoolean(parser.getAttributeValue(null, ATTR_VALUE));
+                } else {
+                    Slog.w(DevicePolicyManagerService.LOG_TAG, "Unknown tag: " + tag);
+                    XmlUtils.skipCurrentTag(parser);
+                }
+            }
+        } catch (FileNotFoundException e) {
+            // Don't be noisy, this is normal if we haven't defined any policies.
+        } catch (NullPointerException | NumberFormatException | XmlPullParserException | IOException
+                | IndexOutOfBoundsException e) {
+            Slog.w(DevicePolicyManagerService.LOG_TAG, "failed parsing " + file, e);
+        }
+        try {
+            if (stream != null) {
+                stream.close();
+            }
+        } catch (IOException e) {
+            // Ignore
+        }
+
+        // Generate a list of admins from the admin map
+        policy.mAdminList.addAll(policy.mAdminMap.values());
+        return needsRewrite;
+    }
+
+    void validatePasswordOwner() {
+        if (mPasswordOwner >= 0) {
+            boolean haveOwner = false;
+            for (int i = mAdminList.size() - 1; i >= 0; i--) {
+                if (mAdminList.get(i).getUid() == mPasswordOwner) {
+                    haveOwner = true;
+                    break;
+                }
+            }
+            if (!haveOwner) {
+                Slog.w(DevicePolicyManagerService.LOG_TAG, "Previous password owner "
+                        + mPasswordOwner + " no longer active; disabling");
+                mPasswordOwner = -1;
+            }
+        }
+    }
+}
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index 7ec819f..22e309c 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -106,10 +106,6 @@
 import static com.android.server.devicepolicy.TransferOwnershipMetadataManager.ADMIN_TYPE_PROFILE_OWNER;
 import static com.android.server.pm.PackageManagerService.PLATFORM_PACKAGE_NAME;
 
-import static org.xmlpull.v1.XmlPullParser.END_DOCUMENT;
-import static org.xmlpull.v1.XmlPullParser.END_TAG;
-import static org.xmlpull.v1.XmlPullParser.TEXT;
-
 import android.Manifest.permission;
 import android.accessibilityservice.AccessibilityServiceInfo;
 import android.accounts.Account;
@@ -189,7 +185,6 @@
 import android.database.ContentObserver;
 import android.database.Cursor;
 import android.graphics.Bitmap;
-import android.graphics.Color;
 import android.location.LocationManager;
 import android.media.AudioManager;
 import android.media.IAudioService;
@@ -204,7 +199,6 @@
 import android.os.Build;
 import android.os.Bundle;
 import android.os.Environment;
-import android.os.FileUtils;
 import android.os.Handler;
 import android.os.IBinder;
 import android.os.Looper;
@@ -248,7 +242,6 @@
 import android.telephony.data.ApnSetting;
 import android.text.TextUtils;
 import android.text.format.DateUtils;
-import android.util.ArrayMap;
 import android.util.ArraySet;
 import android.util.AtomicFile;
 import android.util.Log;
@@ -280,7 +273,6 @@
 import com.android.internal.util.JournaledFile;
 import com.android.internal.util.Preconditions;
 import com.android.internal.util.StatLogger;
-import com.android.internal.util.XmlUtils;
 import com.android.internal.widget.LockPatternUtils;
 import com.android.internal.widget.LockSettingsInternal;
 import com.android.internal.widget.LockscreenCredential;
@@ -290,7 +282,7 @@
 import com.android.server.PersistentDataBlockManagerInternal;
 import com.android.server.SystemServerInitThreadPool;
 import com.android.server.SystemService;
-import com.android.server.devicepolicy.DevicePolicyManagerService.ActiveAdmin.TrustAgentInfo;
+import com.android.server.devicepolicy.ActiveAdmin.TrustAgentInfo;
 import com.android.server.inputmethod.InputMethodManagerInternal;
 import com.android.server.net.NetworkPolicyManagerInternal;
 import com.android.server.pm.RestrictionsSet;
@@ -310,7 +302,6 @@
 import java.io.File;
 import java.io.FileDescriptor;
 import java.io.FileInputStream;
-import java.io.FileNotFoundException;
 import java.io.FileOutputStream;
 import java.io.IOException;
 import java.io.PrintWriter;
@@ -327,11 +318,9 @@
 import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
-import java.util.Map.Entry;
 import java.util.Objects;
 import java.util.Set;
 import java.util.concurrent.TimeUnit;
-import java.util.concurrent.atomic.AtomicBoolean;
 import java.util.function.Function;
 import java.util.function.Predicate;
 
@@ -349,55 +338,8 @@
     private static final String TRANSFER_OWNERSHIP_PARAMETERS_XML =
             "transfer-ownership-parameters.xml";
 
-    private static final String TAG_ACCEPTED_CA_CERTIFICATES = "accepted-ca-certificate";
-
-    private static final String TAG_LOCK_TASK_COMPONENTS = "lock-task-component";
-
-    private static final String TAG_LOCK_TASK_FEATURES = "lock-task-features";
-
-    private static final String TAG_STATUS_BAR = "statusbar";
-
-    private static final String ATTR_DISABLED = "disabled";
-
-    private static final String ATTR_NAME = "name";
-
-    private static final String DO_NOT_ASK_CREDENTIALS_ON_BOOT_XML =
-            "do-not-ask-credentials-on-boot";
-
-    private static final String TAG_AFFILIATION_ID = "affiliation-id";
-
-    private static final String TAG_LAST_SECURITY_LOG_RETRIEVAL = "last-security-log-retrieval";
-
-    private static final String TAG_LAST_BUG_REPORT_REQUEST = "last-bug-report-request";
-
-    private static final String TAG_LAST_NETWORK_LOG_RETRIEVAL = "last-network-log-retrieval";
-
-    private static final String TAG_ADMIN_BROADCAST_PENDING = "admin-broadcast-pending";
-
-    private static final String TAG_CURRENT_INPUT_METHOD_SET = "current-ime-set";
-
-    private static final String TAG_OWNER_INSTALLED_CA_CERT = "owner-installed-ca-cert";
-
-    private static final String ATTR_ID = "id";
-
-    private static final String ATTR_VALUE = "value";
-
-    private static final String ATTR_ALIAS = "alias";
-
-    private static final String TAG_INITIALIZATION_BUNDLE = "initialization-bundle";
-
-    private static final String TAG_PASSWORD_TOKEN_HANDLE = "password-token";
-
-    private static final String TAG_PASSWORD_VALIDITY = "password-validity";
-
     private static final String TAG_TRANSFER_OWNERSHIP_BUNDLE = "transfer-ownership-bundle";
 
-    private static final String TAG_PROTECTED_PACKAGES = "protected-packages";
-
-    private static final String TAG_SECONDARY_LOCK_SCREEN = "secondary-lock-screen";
-
-    private static final String TAG_APPS_SUSPENDED = "apps-suspended";
-
     private static final int REQUEST_EXPIRE_PASSWORD = 5571;
 
     private static final int REQUEST_PROFILE_OFF_DEADLINE = 5572;
@@ -422,17 +364,6 @@
     static final String ACTION_PROFILE_OFF_DEADLINE =
             "com.android.server.ACTION_PROFILE_OFF_DEADLINE";
 
-    private static final String ATTR_PERMISSION_PROVIDER = "permission-provider";
-    private static final String ATTR_SETUP_COMPLETE = "setup-complete";
-    private static final String ATTR_PROVISIONING_STATE = "provisioning-state";
-    private static final String ATTR_PERMISSION_POLICY = "permission-policy";
-    private static final String ATTR_DEVICE_PROVISIONING_CONFIG_APPLIED =
-            "device-provisioning-config-applied";
-    private static final String ATTR_DEVICE_PAIRED = "device-paired";
-    private static final String ATTR_DELEGATED_CERT_INSTALLER = "delegated-cert-installer";
-    private static final String ATTR_APPLICATION_RESTRICTIONS_MANAGER
-            = "application-restrictions-manager";
-
     private static final String CALLED_FROM_PARENT = "calledFromParent";
     private static final String NOT_CALLED_FROM_PARENT = "notCalledFromParent";
 
@@ -487,7 +418,6 @@
     private static final Set<String> SYSTEM_SETTINGS_WHITELIST;
     private static final Set<Integer> DA_DISALLOWED_POLICIES;
     // A collection of user restrictions that are deprecated and should simply be ignored.
-    private static final Set<String> DEPRECATED_USER_RESTRICTIONS;
     private static final String AB_DEVICE_KEY = "ro.build.ab_update";
 
     static {
@@ -531,10 +461,6 @@
         DA_DISALLOWED_POLICIES.add(DeviceAdminInfo.USES_POLICY_DISABLE_KEYGUARD_FEATURES);
         DA_DISALLOWED_POLICIES.add(DeviceAdminInfo.USES_POLICY_EXPIRE_PASSWORD);
         DA_DISALLOWED_POLICIES.add(DeviceAdminInfo.USES_POLICY_LIMIT_PASSWORD);
-
-        DEPRECATED_USER_RESTRICTIONS = Sets.newHashSet(
-                UserManager.DISALLOW_ADD_MANAGED_PROFILE,
-                UserManager.DISALLOW_REMOVE_MANAGED_PROFILE);
     }
 
     /**
@@ -647,13 +573,11 @@
 
     private final CertificateMonitor mCertificateMonitor;
     private final SecurityLogMonitor mSecurityLogMonitor;
+    private final RemoteBugreportManager mBugreportCollectionManager;
 
     @GuardedBy("getLockObject()")
     private NetworkLogger mNetworkLogger;
 
-    private final AtomicBoolean mRemoteBugreportServiceIsActive = new AtomicBoolean();
-    private final AtomicBoolean mRemoteBugreportSharingAccepted = new AtomicBoolean();
-
     private final SetupContentObserver mSetupContentObserver;
     private final DevicePolicyConstantsObserver mConstantsObserver;
 
@@ -705,44 +629,6 @@
     @VisibleForTesting
     final TransferOwnershipMetadataManager mTransferOwnershipMetadataManager;
 
-    private final Runnable mRemoteBugreportTimeoutRunnable = new Runnable() {
-        @Override
-        public void run() {
-            if(mRemoteBugreportServiceIsActive.get()) {
-                onBugreportFailed();
-            }
-        }
-    };
-
-    /** Listens only if mHasFeature == true. */
-    private final BroadcastReceiver mRemoteBugreportFinishedReceiver = new BroadcastReceiver() {
-
-        @Override
-        public void onReceive(Context context, Intent intent) {
-            if (DevicePolicyManager.ACTION_REMOTE_BUGREPORT_DISPATCH.equals(intent.getAction())
-                    && mRemoteBugreportServiceIsActive.get()) {
-                onBugreportFinished(intent);
-            }
-        }
-    };
-
-    /** Listens only if mHasFeature == true. */
-    private final BroadcastReceiver mRemoteBugreportConsentReceiver = new BroadcastReceiver() {
-
-        @Override
-        public void onReceive(Context context, Intent intent) {
-            String action = intent.getAction();
-            mInjector.getNotificationManager().cancel(LOG_TAG,
-                    RemoteBugreportUtils.NOTIFICATION_ID);
-            if (DevicePolicyManager.ACTION_BUGREPORT_SHARING_ACCEPTED.equals(action)) {
-                onBugreportSharingAccepted();
-            } else if (DevicePolicyManager.ACTION_BUGREPORT_SHARING_DECLINED.equals(action)) {
-                onBugreportSharingDeclined();
-            }
-            mContext.unregisterReceiver(mRemoteBugreportConsentReceiver);
-        }
-    };
-
     public static final class Lifecycle extends SystemService {
         private BaseIDevicePolicyManager mService;
 
@@ -775,88 +661,18 @@
         }
 
         @Override
-        public void onStartUser(int userHandle) {
-            mService.handleStartUser(userHandle);
+        public void onUserStarting(@NonNull TargetUser user) {
+            mService.handleStartUser(user.getUserIdentifier());
         }
 
         @Override
-        public void onUnlockUser(int userHandle) {
-            mService.handleUnlockUser(userHandle);
+        public void onUserUnlocking(@NonNull TargetUser user) {
+            mService.handleUnlockUser(user.getUserIdentifier());
         }
 
         @Override
-        public void onStopUser(int userHandle) {
-            mService.handleStopUser(userHandle);
-        }
-    }
-
-    public static class DevicePolicyData {
-        int mFailedPasswordAttempts = 0;
-        boolean mPasswordValidAtLastCheckpoint = true;
-
-        int mUserHandle;
-        int mPasswordOwner = -1;
-        long mLastMaximumTimeToLock = -1;
-        boolean mUserSetupComplete = false;
-        boolean mPaired = false;
-        int mUserProvisioningState;
-        int mPermissionPolicy;
-
-        boolean mDeviceProvisioningConfigApplied = false;
-
-        final ArrayMap<ComponentName, ActiveAdmin> mAdminMap = new ArrayMap<>();
-        final ArrayList<ActiveAdmin> mAdminList = new ArrayList<>();
-        final ArrayList<ComponentName> mRemovingAdmins = new ArrayList<>();
-
-        // TODO(b/35385311): Keep track of metadata in TrustedCertificateStore instead.
-        final ArraySet<String> mAcceptedCaCertificates = new ArraySet<>();
-
-        // This is the list of component allowed to start lock task mode.
-        List<String> mLockTaskPackages = new ArrayList<>();
-
-        // List of packages protected by device owner
-        List<String> mUserControlDisabledPackages = new ArrayList<>();
-
-        // Bitfield of feature flags to be enabled during LockTask mode.
-        // We default on the power button menu, in order to be consistent with pre-P behaviour.
-        int mLockTaskFeatures = DevicePolicyManager.LOCK_TASK_FEATURE_GLOBAL_ACTIONS;
-
-        boolean mStatusBarDisabled = false;
-
-        ComponentName mRestrictionsProvider;
-
-        // Map of delegate package to delegation scopes
-        final ArrayMap<String, List<String>> mDelegationMap = new ArrayMap<>();
-
-        boolean doNotAskCredentialsOnBoot = false;
-
-        Set<String> mAffiliationIds = new ArraySet<>();
-
-        long mLastSecurityLogRetrievalTime = -1;
-
-        long mLastBugReportRequestTime = -1;
-
-        long mLastNetworkLogsRetrievalTime = -1;
-
-        boolean mCurrentInputMethodSet = false;
-
-        boolean mSecondaryLockscreenEnabled = false;
-
-        // TODO(b/35385311): Keep track of metadata in TrustedCertificateStore instead.
-        Set<String> mOwnerInstalledCaCerts = new ArraySet<>();
-
-        // Used for initialization of users created by createAndManageUser.
-        boolean mAdminBroadcastPending = false;
-        PersistableBundle mInitBundle = null;
-
-        long mPasswordTokenHandle = 0;
-
-        // Whether user's apps are suspended. This flag should only be written AFTER all the needed
-        // apps were suspended or unsuspended.
-        boolean mAppsSuspended = false;
-
-        public DevicePolicyData(int userHandle) {
-            mUserHandle = userHandle;
+        public void onUserStopping(@NonNull TargetUser user) {
+            mService.handleStopUser(user.getUserIdentifier());
         }
     }
 
@@ -890,17 +706,9 @@
                 }
             }
             if (Intent.ACTION_BOOT_COMPLETED.equals(action)
-                    && userHandle == mOwners.getDeviceOwnerUserId()
-                    && getDeviceOwnerRemoteBugreportUri() != null) {
-                IntentFilter filterConsent = new IntentFilter();
-                filterConsent.addAction(DevicePolicyManager.ACTION_BUGREPORT_SHARING_DECLINED);
-                filterConsent.addAction(DevicePolicyManager.ACTION_BUGREPORT_SHARING_ACCEPTED);
-                mContext.registerReceiver(mRemoteBugreportConsentReceiver, filterConsent);
-                mInjector.getNotificationManager().notifyAsUser(LOG_TAG,
-                        RemoteBugreportUtils.NOTIFICATION_ID,
-                        RemoteBugreportUtils.buildNotification(mContext,
-                                DevicePolicyManager.NOTIFICATION_BUGREPORT_FINISHED_NOT_ACCEPTED),
-                        UserHandle.ALL);
+                    && userHandle == mOwners.getDeviceOwnerUserId()) {
+                mBugreportCollectionManager.checkForPendingBugreportAfterBoot();
+
             }
             if (Intent.ACTION_BOOT_COMPLETED.equals(action)
                     || ACTION_EXPIRED_PASSWORD_NOTIFICATION.equals(action)) {
@@ -1044,1002 +852,6 @@
         }
     }
 
-    static class ActiveAdmin {
-        private static final String TAG_DISABLE_KEYGUARD_FEATURES = "disable-keyguard-features";
-        private static final String TAG_TEST_ONLY_ADMIN = "test-only-admin";
-        private static final String TAG_DISABLE_CAMERA = "disable-camera";
-        private static final String TAG_DISABLE_CALLER_ID = "disable-caller-id";
-        private static final String TAG_DISABLE_CONTACTS_SEARCH = "disable-contacts-search";
-        private static final String TAG_DISABLE_BLUETOOTH_CONTACT_SHARING
-                = "disable-bt-contacts-sharing";
-        private static final String TAG_DISABLE_SCREEN_CAPTURE = "disable-screen-capture";
-        private static final String TAG_DISABLE_ACCOUNT_MANAGEMENT = "disable-account-management";
-        private static final String TAG_REQUIRE_AUTO_TIME = "require_auto_time";
-        private static final String TAG_FORCE_EPHEMERAL_USERS = "force_ephemeral_users";
-        private static final String TAG_IS_NETWORK_LOGGING_ENABLED = "is_network_logging_enabled";
-        private static final String TAG_ACCOUNT_TYPE = "account-type";
-        private static final String TAG_PERMITTED_ACCESSIBILITY_SERVICES
-                = "permitted-accessiblity-services";
-        private static final String TAG_ENCRYPTION_REQUESTED = "encryption-requested";
-        private static final String TAG_MANAGE_TRUST_AGENT_FEATURES = "manage-trust-agent-features";
-        private static final String TAG_TRUST_AGENT_COMPONENT_OPTIONS = "trust-agent-component-options";
-        private static final String TAG_TRUST_AGENT_COMPONENT = "component";
-        private static final String TAG_PASSWORD_EXPIRATION_DATE = "password-expiration-date";
-        private static final String TAG_PASSWORD_EXPIRATION_TIMEOUT = "password-expiration-timeout";
-        private static final String TAG_GLOBAL_PROXY_EXCLUSION_LIST = "global-proxy-exclusion-list";
-        private static final String TAG_GLOBAL_PROXY_SPEC = "global-proxy-spec";
-        private static final String TAG_SPECIFIES_GLOBAL_PROXY = "specifies-global-proxy";
-        private static final String TAG_PERMITTED_IMES = "permitted-imes";
-        private static final String TAG_PERMITTED_NOTIFICATION_LISTENERS =
-                "permitted-notification-listeners";
-        private static final String TAG_MAX_FAILED_PASSWORD_WIPE = "max-failed-password-wipe";
-        private static final String TAG_MAX_TIME_TO_UNLOCK = "max-time-to-unlock";
-        private static final String TAG_STRONG_AUTH_UNLOCK_TIMEOUT = "strong-auth-unlock-timeout";
-        private static final String TAG_MIN_PASSWORD_NONLETTER = "min-password-nonletter";
-        private static final String TAG_MIN_PASSWORD_SYMBOLS = "min-password-symbols";
-        private static final String TAG_MIN_PASSWORD_NUMERIC = "min-password-numeric";
-        private static final String TAG_MIN_PASSWORD_LETTERS = "min-password-letters";
-        private static final String TAG_MIN_PASSWORD_LOWERCASE = "min-password-lowercase";
-        private static final String TAG_MIN_PASSWORD_UPPERCASE = "min-password-uppercase";
-        private static final String TAG_PASSWORD_HISTORY_LENGTH = "password-history-length";
-        private static final String TAG_MIN_PASSWORD_LENGTH = "min-password-length";
-        private static final String ATTR_VALUE = "value";
-        private static final String TAG_PASSWORD_QUALITY = "password-quality";
-        private static final String TAG_POLICIES = "policies";
-        private static final String TAG_CROSS_PROFILE_WIDGET_PROVIDERS =
-                "cross-profile-widget-providers";
-        private static final String TAG_PROVIDER = "provider";
-        private static final String TAG_PACKAGE_LIST_ITEM  = "item";
-        private static final String TAG_KEEP_UNINSTALLED_PACKAGES  = "keep-uninstalled-packages";
-        private static final String TAG_USER_RESTRICTIONS = "user-restrictions";
-        private static final String TAG_DEFAULT_ENABLED_USER_RESTRICTIONS =
-                "default-enabled-user-restrictions";
-        private static final String TAG_RESTRICTION = "restriction";
-        private static final String TAG_SHORT_SUPPORT_MESSAGE = "short-support-message";
-        private static final String TAG_LONG_SUPPORT_MESSAGE = "long-support-message";
-        private static final String TAG_PARENT_ADMIN = "parent-admin";
-        private static final String TAG_ORGANIZATION_COLOR = "organization-color";
-        private static final String TAG_ORGANIZATION_NAME = "organization-name";
-        private static final String ATTR_LAST_NETWORK_LOGGING_NOTIFICATION = "last-notification";
-        private static final String ATTR_NUM_NETWORK_LOGGING_NOTIFICATIONS = "num-notifications";
-        private static final String TAG_IS_LOGOUT_ENABLED = "is_logout_enabled";
-        private static final String TAG_START_USER_SESSION_MESSAGE = "start_user_session_message";
-        private static final String TAG_END_USER_SESSION_MESSAGE = "end_user_session_message";
-        private static final String TAG_METERED_DATA_DISABLED_PACKAGES =
-                "metered_data_disabled_packages";
-        private static final String TAG_CROSS_PROFILE_CALENDAR_PACKAGES =
-                "cross-profile-calendar-packages";
-        private static final String TAG_CROSS_PROFILE_CALENDAR_PACKAGES_NULL =
-                "cross-profile-calendar-packages-null";
-        private static final String TAG_CROSS_PROFILE_PACKAGES = "cross-profile-packages";
-        private static final String TAG_FACTORY_RESET_PROTECTION_POLICY =
-                "factory_reset_protection_policy";
-        private static final String TAG_SUSPEND_PERSONAL_APPS = "suspend-personal-apps";
-        private static final String TAG_PROFILE_MAXIMUM_TIME_OFF = "profile-max-time-off";
-        private static final String TAG_PROFILE_OFF_DEADLINE = "profile-off-deadline";
-        private static final String TAG_ALWAYS_ON_VPN_PACKAGE = "vpn-package";
-        private static final String TAG_ALWAYS_ON_VPN_LOCKDOWN = "vpn-lockdown";
-        private static final String TAG_COMMON_CRITERIA_MODE = "common-criteria-mode";
-        DeviceAdminInfo info;
-
-
-        static final int DEF_PASSWORD_HISTORY_LENGTH = 0;
-        int passwordHistoryLength = DEF_PASSWORD_HISTORY_LENGTH;
-
-        @NonNull
-        PasswordPolicy mPasswordPolicy = new PasswordPolicy();
-
-        @Nullable
-        FactoryResetProtectionPolicy mFactoryResetProtectionPolicy = null;
-
-        static final long DEF_MAXIMUM_TIME_TO_UNLOCK = 0;
-        long maximumTimeToUnlock = DEF_MAXIMUM_TIME_TO_UNLOCK;
-
-        long strongAuthUnlockTimeout = 0; // admin doesn't participate by default
-
-        static final int DEF_MAXIMUM_FAILED_PASSWORDS_FOR_WIPE = 0;
-        int maximumFailedPasswordsForWipe = DEF_MAXIMUM_FAILED_PASSWORDS_FOR_WIPE;
-
-        static final long DEF_PASSWORD_EXPIRATION_TIMEOUT = 0;
-        long passwordExpirationTimeout = DEF_PASSWORD_EXPIRATION_TIMEOUT;
-
-        static final long DEF_PASSWORD_EXPIRATION_DATE = 0;
-        long passwordExpirationDate = DEF_PASSWORD_EXPIRATION_DATE;
-
-        static final int DEF_KEYGUARD_FEATURES_DISABLED = 0; // none
-
-        int disabledKeyguardFeatures = DEF_KEYGUARD_FEATURES_DISABLED;
-
-        boolean encryptionRequested = false;
-        boolean testOnlyAdmin = false;
-        boolean disableCamera = false;
-        boolean disableCallerId = false;
-        boolean disableContactsSearch = false;
-        boolean disableBluetoothContactSharing = true;
-        boolean disableScreenCapture = false; // Can only be set by a device/profile owner.
-        boolean requireAutoTime = false; // Can only be set by a device owner.
-        boolean forceEphemeralUsers = false; // Can only be set by a device owner.
-        boolean isNetworkLoggingEnabled = false; // Can only be set by a device owner.
-        boolean isLogoutEnabled = false; // Can only be set by a device owner.
-
-        // one notification after enabling + one more after reboots
-        static final int DEF_MAXIMUM_NETWORK_LOGGING_NOTIFICATIONS_SHOWN = 2;
-        int numNetworkLoggingNotifications = 0;
-        long lastNetworkLoggingNotificationTimeMs = 0; // Time in milliseconds since epoch
-
-        ActiveAdmin parentAdmin;
-        final boolean isParent;
-
-        static class TrustAgentInfo {
-            public PersistableBundle options;
-            TrustAgentInfo(PersistableBundle bundle) {
-                options = bundle;
-            }
-        }
-
-        // The list of packages which are not allowed to use metered data.
-        List<String> meteredDisabledPackages;
-
-        final Set<String> accountTypesWithManagementDisabled = new ArraySet<>();
-
-        // The list of permitted accessibility services package namesas set by a profile
-        // or device owner. Null means all accessibility services are allowed, empty means
-        // none except system services are allowed.
-        List<String> permittedAccessiblityServices;
-
-        // The list of permitted input methods package names as set by a profile or device owner.
-        // Null means all input methods are allowed, empty means none except system imes are
-        // allowed.
-        List<String> permittedInputMethods;
-
-        // The list of packages allowed to use a NotificationListenerService to receive events for
-        // notifications from this user. Null means that all packages are allowed. Empty list means
-        // that only packages from the system are allowed.
-        List<String> permittedNotificationListeners;
-
-        // List of package names to keep cached.
-        List<String> keepUninstalledPackages;
-
-        // TODO: review implementation decisions with frameworks team
-        boolean specifiesGlobalProxy = false;
-        String globalProxySpec = null;
-        String globalProxyExclusionList = null;
-
-        @NonNull ArrayMap<String, TrustAgentInfo> trustAgentInfos = new ArrayMap<>();
-
-        List<String> crossProfileWidgetProviders;
-
-        Bundle userRestrictions;
-
-        // User restrictions that have already been enabled by default for this admin (either when
-        // setting the device or profile owner, or during a system update if one of those "enabled
-        // by default" restrictions is newly added).
-        final Set<String> defaultEnabledRestrictionsAlreadySet = new ArraySet<>();
-
-        // Support text provided by the admin to display to the user.
-        CharSequence shortSupportMessage = null;
-        CharSequence longSupportMessage = null;
-
-        // Background color of confirm credentials screen. Default: teal.
-        static final int DEF_ORGANIZATION_COLOR = Color.parseColor("#00796B");
-        int organizationColor = DEF_ORGANIZATION_COLOR;
-
-        // Default title of confirm credentials screen
-        String organizationName = null;
-
-        // Message for user switcher
-        String startUserSessionMessage = null;
-        String endUserSessionMessage = null;
-
-        // The whitelist of packages that can access cross profile calendar APIs.
-        // This whitelist should be in default an empty list, which indicates that no package
-        // is whitelisted.
-        List<String> mCrossProfileCalendarPackages = Collections.emptyList();
-
-        // The whitelist of packages that the admin has enabled to be able to request consent from
-        // the user to communicate cross-profile. By default, no packages are whitelisted, which is
-        // represented as an empty list.
-        List<String> mCrossProfilePackages = Collections.emptyList();
-
-        // Whether the admin explicitly requires personal apps to be suspended
-        boolean mSuspendPersonalApps = false;
-        // Maximum time the profile owned by this admin can be off.
-        long mProfileMaximumTimeOffMillis = 0;
-        // Time by which the profile should be turned on according to System.currentTimeMillis().
-        long mProfileOffDeadline = 0;
-
-        public String mAlwaysOnVpnPackage;
-        public boolean mAlwaysOnVpnLockdown;
-        boolean mCommonCriteriaMode;
-
-        ActiveAdmin(DeviceAdminInfo _info, boolean parent) {
-            info = _info;
-            isParent = parent;
-        }
-
-        ActiveAdmin getParentActiveAdmin() {
-            Preconditions.checkState(!isParent);
-
-            if (parentAdmin == null) {
-                parentAdmin = new ActiveAdmin(info, /* parent */ true);
-            }
-            return parentAdmin;
-        }
-
-        boolean hasParentActiveAdmin() {
-            return parentAdmin != null;
-        }
-
-        int getUid() { return info.getActivityInfo().applicationInfo.uid; }
-
-        public UserHandle getUserHandle() {
-            return UserHandle.of(UserHandle.getUserId(info.getActivityInfo().applicationInfo.uid));
-        }
-
-        void writeToXml(XmlSerializer out)
-                throws IllegalArgumentException, IllegalStateException, IOException {
-            out.startTag(null, TAG_POLICIES);
-            info.writePoliciesToXml(out);
-            out.endTag(null, TAG_POLICIES);
-            if (mPasswordPolicy.quality != PASSWORD_QUALITY_UNSPECIFIED) {
-                writeAttributeValueToXml(
-                        out, TAG_PASSWORD_QUALITY, mPasswordPolicy.quality);
-                if (mPasswordPolicy.length != PasswordPolicy.DEF_MINIMUM_LENGTH) {
-                    writeAttributeValueToXml(
-                            out, TAG_MIN_PASSWORD_LENGTH, mPasswordPolicy.length);
-                }
-                if (mPasswordPolicy.upperCase != PasswordPolicy.DEF_MINIMUM_UPPER_CASE) {
-                    writeAttributeValueToXml(
-                            out, TAG_MIN_PASSWORD_UPPERCASE, mPasswordPolicy.upperCase);
-                }
-                if (mPasswordPolicy.lowerCase != PasswordPolicy.DEF_MINIMUM_LOWER_CASE) {
-                    writeAttributeValueToXml(
-                            out, TAG_MIN_PASSWORD_LOWERCASE, mPasswordPolicy.lowerCase);
-                }
-                if (mPasswordPolicy.letters != PasswordPolicy.DEF_MINIMUM_LETTERS) {
-                    writeAttributeValueToXml(
-                            out, TAG_MIN_PASSWORD_LETTERS, mPasswordPolicy.letters);
-                }
-                if (mPasswordPolicy.numeric != PasswordPolicy.DEF_MINIMUM_NUMERIC) {
-                    writeAttributeValueToXml(
-                            out, TAG_MIN_PASSWORD_NUMERIC, mPasswordPolicy.numeric);
-                }
-                if (mPasswordPolicy.symbols != PasswordPolicy.DEF_MINIMUM_SYMBOLS) {
-                    writeAttributeValueToXml(
-                            out, TAG_MIN_PASSWORD_SYMBOLS, mPasswordPolicy.symbols);
-                }
-                if (mPasswordPolicy.nonLetter > PasswordPolicy.DEF_MINIMUM_NON_LETTER) {
-                    writeAttributeValueToXml(
-                            out, TAG_MIN_PASSWORD_NONLETTER, mPasswordPolicy.nonLetter);
-                }
-            }
-            if (passwordHistoryLength != DEF_PASSWORD_HISTORY_LENGTH) {
-                writeAttributeValueToXml(
-                        out, TAG_PASSWORD_HISTORY_LENGTH, passwordHistoryLength);
-            }
-            if (maximumTimeToUnlock != DEF_MAXIMUM_TIME_TO_UNLOCK) {
-                writeAttributeValueToXml(
-                        out, TAG_MAX_TIME_TO_UNLOCK, maximumTimeToUnlock);
-            }
-            if (strongAuthUnlockTimeout != DevicePolicyManager.DEFAULT_STRONG_AUTH_TIMEOUT_MS) {
-                writeAttributeValueToXml(
-                        out, TAG_STRONG_AUTH_UNLOCK_TIMEOUT, strongAuthUnlockTimeout);
-            }
-            if (maximumFailedPasswordsForWipe != DEF_MAXIMUM_FAILED_PASSWORDS_FOR_WIPE) {
-                writeAttributeValueToXml(
-                        out, TAG_MAX_FAILED_PASSWORD_WIPE, maximumFailedPasswordsForWipe);
-            }
-            if (specifiesGlobalProxy) {
-                writeAttributeValueToXml(
-                        out, TAG_SPECIFIES_GLOBAL_PROXY, specifiesGlobalProxy);
-                if (globalProxySpec != null) {
-                    writeAttributeValueToXml(out, TAG_GLOBAL_PROXY_SPEC, globalProxySpec);
-                }
-                if (globalProxyExclusionList != null) {
-                    writeAttributeValueToXml(
-                            out, TAG_GLOBAL_PROXY_EXCLUSION_LIST, globalProxyExclusionList);
-                }
-            }
-            if (passwordExpirationTimeout != DEF_PASSWORD_EXPIRATION_TIMEOUT) {
-                writeAttributeValueToXml(
-                        out, TAG_PASSWORD_EXPIRATION_TIMEOUT, passwordExpirationTimeout);
-            }
-            if (passwordExpirationDate != DEF_PASSWORD_EXPIRATION_DATE) {
-                writeAttributeValueToXml(
-                        out, TAG_PASSWORD_EXPIRATION_DATE, passwordExpirationDate);
-            }
-            if (encryptionRequested) {
-                writeAttributeValueToXml(
-                        out, TAG_ENCRYPTION_REQUESTED, encryptionRequested);
-            }
-            if (testOnlyAdmin) {
-                writeAttributeValueToXml(
-                        out, TAG_TEST_ONLY_ADMIN, testOnlyAdmin);
-            }
-            if (disableCamera) {
-                writeAttributeValueToXml(
-                        out, TAG_DISABLE_CAMERA, disableCamera);
-            }
-            if (disableCallerId) {
-                writeAttributeValueToXml(
-                        out, TAG_DISABLE_CALLER_ID, disableCallerId);
-            }
-            if (disableContactsSearch) {
-                writeAttributeValueToXml(
-                        out, TAG_DISABLE_CONTACTS_SEARCH, disableContactsSearch);
-            }
-            if (!disableBluetoothContactSharing) {
-                writeAttributeValueToXml(
-                        out, TAG_DISABLE_BLUETOOTH_CONTACT_SHARING, disableBluetoothContactSharing);
-            }
-            if (disableScreenCapture) {
-                writeAttributeValueToXml(
-                        out, TAG_DISABLE_SCREEN_CAPTURE, disableScreenCapture);
-            }
-            if (requireAutoTime) {
-                writeAttributeValueToXml(
-                        out, TAG_REQUIRE_AUTO_TIME, requireAutoTime);
-            }
-            if (forceEphemeralUsers) {
-                writeAttributeValueToXml(
-                        out, TAG_FORCE_EPHEMERAL_USERS, forceEphemeralUsers);
-            }
-            if (isNetworkLoggingEnabled) {
-                out.startTag(null, TAG_IS_NETWORK_LOGGING_ENABLED);
-                out.attribute(null, ATTR_VALUE, Boolean.toString(isNetworkLoggingEnabled));
-                out.attribute(null, ATTR_NUM_NETWORK_LOGGING_NOTIFICATIONS,
-                        Integer.toString(numNetworkLoggingNotifications));
-                out.attribute(null, ATTR_LAST_NETWORK_LOGGING_NOTIFICATION,
-                        Long.toString(lastNetworkLoggingNotificationTimeMs));
-                out.endTag(null, TAG_IS_NETWORK_LOGGING_ENABLED);
-            }
-            if (disabledKeyguardFeatures != DEF_KEYGUARD_FEATURES_DISABLED) {
-                writeAttributeValueToXml(
-                        out, TAG_DISABLE_KEYGUARD_FEATURES, disabledKeyguardFeatures);
-            }
-            if (!accountTypesWithManagementDisabled.isEmpty()) {
-                writeAttributeValuesToXml(
-                        out, TAG_DISABLE_ACCOUNT_MANAGEMENT, TAG_ACCOUNT_TYPE,
-                        accountTypesWithManagementDisabled);
-            }
-            if (!trustAgentInfos.isEmpty()) {
-                Set<Entry<String, TrustAgentInfo>> set = trustAgentInfos.entrySet();
-                out.startTag(null, TAG_MANAGE_TRUST_AGENT_FEATURES);
-                for (Entry<String, TrustAgentInfo> entry : set) {
-                    TrustAgentInfo trustAgentInfo = entry.getValue();
-                    out.startTag(null, TAG_TRUST_AGENT_COMPONENT);
-                    out.attribute(null, ATTR_VALUE, entry.getKey());
-                    if (trustAgentInfo.options != null) {
-                        out.startTag(null, TAG_TRUST_AGENT_COMPONENT_OPTIONS);
-                        try {
-                            trustAgentInfo.options.saveToXml(out);
-                        } catch (XmlPullParserException e) {
-                            Log.e(LOG_TAG, "Failed to save TrustAgent options", e);
-                        }
-                        out.endTag(null, TAG_TRUST_AGENT_COMPONENT_OPTIONS);
-                    }
-                    out.endTag(null, TAG_TRUST_AGENT_COMPONENT);
-                }
-                out.endTag(null, TAG_MANAGE_TRUST_AGENT_FEATURES);
-            }
-            if (crossProfileWidgetProviders != null && !crossProfileWidgetProviders.isEmpty()) {
-                writeAttributeValuesToXml(
-                        out, TAG_CROSS_PROFILE_WIDGET_PROVIDERS, TAG_PROVIDER,
-                        crossProfileWidgetProviders);
-            }
-            writePackageListToXml(out, TAG_PERMITTED_ACCESSIBILITY_SERVICES,
-                    permittedAccessiblityServices);
-            writePackageListToXml(out, TAG_PERMITTED_IMES, permittedInputMethods);
-            writePackageListToXml(out, TAG_PERMITTED_NOTIFICATION_LISTENERS,
-                    permittedNotificationListeners);
-            writePackageListToXml(out, TAG_KEEP_UNINSTALLED_PACKAGES, keepUninstalledPackages);
-            writePackageListToXml(out, TAG_METERED_DATA_DISABLED_PACKAGES, meteredDisabledPackages);
-            if (hasUserRestrictions()) {
-                UserRestrictionsUtils.writeRestrictions(
-                        out, userRestrictions, TAG_USER_RESTRICTIONS);
-            }
-            if (!defaultEnabledRestrictionsAlreadySet.isEmpty()) {
-                writeAttributeValuesToXml(out, TAG_DEFAULT_ENABLED_USER_RESTRICTIONS,
-                        TAG_RESTRICTION,
-                        defaultEnabledRestrictionsAlreadySet);
-            }
-            if (!TextUtils.isEmpty(shortSupportMessage)) {
-                writeTextToXml(out, TAG_SHORT_SUPPORT_MESSAGE, shortSupportMessage.toString());
-            }
-            if (!TextUtils.isEmpty(longSupportMessage)) {
-                writeTextToXml(out, TAG_LONG_SUPPORT_MESSAGE, longSupportMessage.toString());
-            }
-            if (parentAdmin != null) {
-                out.startTag(null, TAG_PARENT_ADMIN);
-                parentAdmin.writeToXml(out);
-                out.endTag(null, TAG_PARENT_ADMIN);
-            }
-            if (organizationColor != DEF_ORGANIZATION_COLOR) {
-                writeAttributeValueToXml(out, TAG_ORGANIZATION_COLOR, organizationColor);
-            }
-            if (organizationName != null) {
-                writeTextToXml(out, TAG_ORGANIZATION_NAME, organizationName);
-            }
-            if (isLogoutEnabled) {
-                writeAttributeValueToXml(out, TAG_IS_LOGOUT_ENABLED, isLogoutEnabled);
-            }
-            if (startUserSessionMessage != null) {
-                writeTextToXml(out, TAG_START_USER_SESSION_MESSAGE, startUserSessionMessage);
-            }
-            if (endUserSessionMessage != null) {
-                writeTextToXml(out, TAG_END_USER_SESSION_MESSAGE, endUserSessionMessage);
-            }
-            if (mCrossProfileCalendarPackages == null) {
-                out.startTag(null, TAG_CROSS_PROFILE_CALENDAR_PACKAGES_NULL);
-                out.endTag(null, TAG_CROSS_PROFILE_CALENDAR_PACKAGES_NULL);
-            } else {
-                writePackageListToXml(out, TAG_CROSS_PROFILE_CALENDAR_PACKAGES,
-                        mCrossProfileCalendarPackages);
-            }
-            writePackageListToXml(out, TAG_CROSS_PROFILE_PACKAGES, mCrossProfilePackages);
-            if (mFactoryResetProtectionPolicy != null) {
-                out.startTag(null, TAG_FACTORY_RESET_PROTECTION_POLICY);
-                mFactoryResetProtectionPolicy.writeToXml(out);
-                out.endTag(null, TAG_FACTORY_RESET_PROTECTION_POLICY);
-            }
-            if (mSuspendPersonalApps) {
-                writeAttributeValueToXml(out, TAG_SUSPEND_PERSONAL_APPS, mSuspendPersonalApps);
-            }
-            if (mProfileMaximumTimeOffMillis != 0) {
-                writeAttributeValueToXml(out, TAG_PROFILE_MAXIMUM_TIME_OFF,
-                        mProfileMaximumTimeOffMillis);
-            }
-            if (mProfileMaximumTimeOffMillis != 0) {
-                writeAttributeValueToXml(out, TAG_PROFILE_OFF_DEADLINE, mProfileOffDeadline);
-            }
-            if (!TextUtils.isEmpty(mAlwaysOnVpnPackage)) {
-                writeAttributeValueToXml(out, TAG_ALWAYS_ON_VPN_PACKAGE, mAlwaysOnVpnPackage);
-            }
-            if (mAlwaysOnVpnLockdown) {
-                writeAttributeValueToXml(out, TAG_ALWAYS_ON_VPN_LOCKDOWN, mAlwaysOnVpnLockdown);
-            }
-            if (mCommonCriteriaMode) {
-                writeAttributeValueToXml(out, TAG_COMMON_CRITERIA_MODE, mCommonCriteriaMode);
-            }
-        }
-
-        void writeTextToXml(XmlSerializer out, String tag, String text) throws IOException {
-            out.startTag(null, tag);
-            out.text(text);
-            out.endTag(null, tag);
-        }
-
-        void writePackageListToXml(XmlSerializer out, String outerTag,
-                List<String> packageList)
-                throws IllegalArgumentException, IllegalStateException, IOException {
-            if (packageList == null) {
-                return;
-            }
-            writeAttributeValuesToXml(out, outerTag, TAG_PACKAGE_LIST_ITEM, packageList);
-        }
-
-        void writeAttributeValueToXml(XmlSerializer out, String tag, String value)
-                throws IOException {
-            out.startTag(null, tag);
-            out.attribute(null, ATTR_VALUE, value);
-            out.endTag(null, tag);
-        }
-
-        void writeAttributeValueToXml(XmlSerializer out, String tag, int value)
-                throws IOException {
-            out.startTag(null, tag);
-            out.attribute(null, ATTR_VALUE, Integer.toString(value));
-            out.endTag(null, tag);
-        }
-
-        void writeAttributeValueToXml(XmlSerializer out, String tag, long value)
-                throws IOException {
-            out.startTag(null, tag);
-            out.attribute(null, ATTR_VALUE, Long.toString(value));
-            out.endTag(null, tag);
-        }
-
-        void writeAttributeValueToXml(XmlSerializer out, String tag, boolean value)
-                throws IOException {
-            out.startTag(null, tag);
-            out.attribute(null, ATTR_VALUE, Boolean.toString(value));
-            out.endTag(null, tag);
-        }
-
-        void writeAttributeValuesToXml(XmlSerializer out, String outerTag, String innerTag,
-                @NonNull Collection<String> values) throws IOException {
-            out.startTag(null, outerTag);
-            for (String value : values) {
-                out.startTag(null, innerTag);
-                out.attribute(null, ATTR_VALUE, value);
-                out.endTag(null, innerTag);
-            }
-            out.endTag(null, outerTag);
-        }
-
-        void readFromXml(XmlPullParser parser, boolean shouldOverridePolicies)
-                throws XmlPullParserException, IOException {
-            int outerDepth = parser.getDepth();
-            int type;
-            while ((type=parser.next()) != END_DOCUMENT
-                   && (type != END_TAG || parser.getDepth() > outerDepth)) {
-                if (type == END_TAG || type == TEXT) {
-                    continue;
-                }
-                String tag = parser.getName();
-                if (TAG_POLICIES.equals(tag)) {
-                    if (shouldOverridePolicies) {
-                        Log.d(LOG_TAG, "Overriding device admin policies from XML.");
-                        info.readPoliciesFromXml(parser);
-                    }
-                } else if (TAG_PASSWORD_QUALITY.equals(tag)) {
-                    mPasswordPolicy.quality = Integer.parseInt(
-                            parser.getAttributeValue(null, ATTR_VALUE));
-                } else if (TAG_MIN_PASSWORD_LENGTH.equals(tag)) {
-                    mPasswordPolicy.length = Integer.parseInt(
-                            parser.getAttributeValue(null, ATTR_VALUE));
-                } else if (TAG_PASSWORD_HISTORY_LENGTH.equals(tag)) {
-                    passwordHistoryLength = Integer.parseInt(
-                            parser.getAttributeValue(null, ATTR_VALUE));
-                } else if (TAG_MIN_PASSWORD_UPPERCASE.equals(tag)) {
-                    mPasswordPolicy.upperCase = Integer.parseInt(
-                            parser.getAttributeValue(null, ATTR_VALUE));
-                } else if (TAG_MIN_PASSWORD_LOWERCASE.equals(tag)) {
-                    mPasswordPolicy.lowerCase = Integer.parseInt(
-                            parser.getAttributeValue(null, ATTR_VALUE));
-                } else if (TAG_MIN_PASSWORD_LETTERS.equals(tag)) {
-                    mPasswordPolicy.letters = Integer.parseInt(
-                            parser.getAttributeValue(null, ATTR_VALUE));
-                } else if (TAG_MIN_PASSWORD_NUMERIC.equals(tag)) {
-                    mPasswordPolicy.numeric = Integer.parseInt(
-                            parser.getAttributeValue(null, ATTR_VALUE));
-                } else if (TAG_MIN_PASSWORD_SYMBOLS.equals(tag)) {
-                    mPasswordPolicy.symbols = Integer.parseInt(
-                            parser.getAttributeValue(null, ATTR_VALUE));
-                } else if (TAG_MIN_PASSWORD_NONLETTER.equals(tag)) {
-                    mPasswordPolicy.nonLetter = Integer.parseInt(
-                            parser.getAttributeValue(null, ATTR_VALUE));
-                }else if (TAG_MAX_TIME_TO_UNLOCK.equals(tag)) {
-                    maximumTimeToUnlock = Long.parseLong(
-                            parser.getAttributeValue(null, ATTR_VALUE));
-                } else if (TAG_STRONG_AUTH_UNLOCK_TIMEOUT.equals(tag)) {
-                    strongAuthUnlockTimeout = Long.parseLong(
-                            parser.getAttributeValue(null, ATTR_VALUE));
-                } else if (TAG_MAX_FAILED_PASSWORD_WIPE.equals(tag)) {
-                    maximumFailedPasswordsForWipe = Integer.parseInt(
-                            parser.getAttributeValue(null, ATTR_VALUE));
-                } else if (TAG_SPECIFIES_GLOBAL_PROXY.equals(tag)) {
-                    specifiesGlobalProxy = Boolean.parseBoolean(
-                            parser.getAttributeValue(null, ATTR_VALUE));
-                } else if (TAG_GLOBAL_PROXY_SPEC.equals(tag)) {
-                    globalProxySpec =
-                        parser.getAttributeValue(null, ATTR_VALUE);
-                } else if (TAG_GLOBAL_PROXY_EXCLUSION_LIST.equals(tag)) {
-                    globalProxyExclusionList =
-                        parser.getAttributeValue(null, ATTR_VALUE);
-                } else if (TAG_PASSWORD_EXPIRATION_TIMEOUT.equals(tag)) {
-                    passwordExpirationTimeout = Long.parseLong(
-                            parser.getAttributeValue(null, ATTR_VALUE));
-                } else if (TAG_PASSWORD_EXPIRATION_DATE.equals(tag)) {
-                    passwordExpirationDate = Long.parseLong(
-                            parser.getAttributeValue(null, ATTR_VALUE));
-                } else if (TAG_ENCRYPTION_REQUESTED.equals(tag)) {
-                    encryptionRequested = Boolean.parseBoolean(
-                            parser.getAttributeValue(null, ATTR_VALUE));
-                } else if (TAG_TEST_ONLY_ADMIN.equals(tag)) {
-                    testOnlyAdmin = Boolean.parseBoolean(
-                            parser.getAttributeValue(null, ATTR_VALUE));
-                } else if (TAG_DISABLE_CAMERA.equals(tag)) {
-                    disableCamera = Boolean.parseBoolean(
-                            parser.getAttributeValue(null, ATTR_VALUE));
-                } else if (TAG_DISABLE_CALLER_ID.equals(tag)) {
-                    disableCallerId = Boolean.parseBoolean(
-                            parser.getAttributeValue(null, ATTR_VALUE));
-                } else if (TAG_DISABLE_CONTACTS_SEARCH.equals(tag)) {
-                    disableContactsSearch = Boolean.parseBoolean(
-                            parser.getAttributeValue(null, ATTR_VALUE));
-                } else if (TAG_DISABLE_BLUETOOTH_CONTACT_SHARING.equals(tag)) {
-                    disableBluetoothContactSharing = Boolean.parseBoolean(parser
-                            .getAttributeValue(null, ATTR_VALUE));
-                } else if (TAG_DISABLE_SCREEN_CAPTURE.equals(tag)) {
-                    disableScreenCapture = Boolean.parseBoolean(
-                            parser.getAttributeValue(null, ATTR_VALUE));
-                } else if (TAG_REQUIRE_AUTO_TIME.equals(tag)) {
-                    requireAutoTime = Boolean.parseBoolean(
-                            parser.getAttributeValue(null, ATTR_VALUE));
-                } else if (TAG_FORCE_EPHEMERAL_USERS.equals(tag)) {
-                    forceEphemeralUsers = Boolean.parseBoolean(
-                            parser.getAttributeValue(null, ATTR_VALUE));
-                } else if (TAG_IS_NETWORK_LOGGING_ENABLED.equals(tag)) {
-                    isNetworkLoggingEnabled = Boolean.parseBoolean(
-                            parser.getAttributeValue(null, ATTR_VALUE));
-                    lastNetworkLoggingNotificationTimeMs = Long.parseLong(
-                            parser.getAttributeValue(null, ATTR_LAST_NETWORK_LOGGING_NOTIFICATION));
-                    numNetworkLoggingNotifications = Integer.parseInt(
-                            parser.getAttributeValue(null, ATTR_NUM_NETWORK_LOGGING_NOTIFICATIONS));
-                } else if (TAG_DISABLE_KEYGUARD_FEATURES.equals(tag)) {
-                    disabledKeyguardFeatures = Integer.parseInt(
-                            parser.getAttributeValue(null, ATTR_VALUE));
-                } else if (TAG_DISABLE_ACCOUNT_MANAGEMENT.equals(tag)) {
-                    readAttributeValues(
-                            parser, TAG_ACCOUNT_TYPE, accountTypesWithManagementDisabled);
-                } else if (TAG_MANAGE_TRUST_AGENT_FEATURES.equals(tag)) {
-                    trustAgentInfos = getAllTrustAgentInfos(parser, tag);
-                } else if (TAG_CROSS_PROFILE_WIDGET_PROVIDERS.equals(tag)) {
-                    crossProfileWidgetProviders = new ArrayList<>();
-                    readAttributeValues(parser, TAG_PROVIDER, crossProfileWidgetProviders);
-                } else if (TAG_PERMITTED_ACCESSIBILITY_SERVICES.equals(tag)) {
-                    permittedAccessiblityServices = readPackageList(parser, tag);
-                } else if (TAG_PERMITTED_IMES.equals(tag)) {
-                    permittedInputMethods = readPackageList(parser, tag);
-                } else if (TAG_PERMITTED_NOTIFICATION_LISTENERS.equals(tag)) {
-                    permittedNotificationListeners = readPackageList(parser, tag);
-                } else if (TAG_KEEP_UNINSTALLED_PACKAGES.equals(tag)) {
-                    keepUninstalledPackages = readPackageList(parser, tag);
-                } else if (TAG_METERED_DATA_DISABLED_PACKAGES.equals(tag)) {
-                    meteredDisabledPackages = readPackageList(parser, tag);
-                } else if (TAG_USER_RESTRICTIONS.equals(tag)) {
-                    userRestrictions = UserRestrictionsUtils.readRestrictions(parser);
-                } else if (TAG_DEFAULT_ENABLED_USER_RESTRICTIONS.equals(tag)) {
-                    readAttributeValues(
-                            parser, TAG_RESTRICTION, defaultEnabledRestrictionsAlreadySet);
-                } else if (TAG_SHORT_SUPPORT_MESSAGE.equals(tag)) {
-                    type = parser.next();
-                    if (type == XmlPullParser.TEXT) {
-                        shortSupportMessage = parser.getText();
-                    } else {
-                        Log.w(LOG_TAG, "Missing text when loading short support message");
-                    }
-                } else if (TAG_LONG_SUPPORT_MESSAGE.equals(tag)) {
-                    type = parser.next();
-                    if (type == XmlPullParser.TEXT) {
-                        longSupportMessage = parser.getText();
-                    } else {
-                        Log.w(LOG_TAG, "Missing text when loading long support message");
-                    }
-                } else if (TAG_PARENT_ADMIN.equals(tag)) {
-                    Preconditions.checkState(!isParent);
-                    parentAdmin = new ActiveAdmin(info, /* parent */ true);
-                    parentAdmin.readFromXml(parser, shouldOverridePolicies);
-                } else if (TAG_ORGANIZATION_COLOR.equals(tag)) {
-                    organizationColor = Integer.parseInt(
-                            parser.getAttributeValue(null, ATTR_VALUE));
-                } else if (TAG_ORGANIZATION_NAME.equals(tag)) {
-                    type = parser.next();
-                    if (type == XmlPullParser.TEXT) {
-                        organizationName = parser.getText();
-                    } else {
-                        Log.w(LOG_TAG, "Missing text when loading organization name");
-                    }
-                } else if (TAG_IS_LOGOUT_ENABLED.equals(tag)) {
-                    isLogoutEnabled = Boolean.parseBoolean(
-                            parser.getAttributeValue(null, ATTR_VALUE));
-                } else if (TAG_START_USER_SESSION_MESSAGE.equals(tag)) {
-                    type = parser.next();
-                    if (type == XmlPullParser.TEXT) {
-                        startUserSessionMessage = parser.getText();
-                    } else {
-                        Log.w(LOG_TAG, "Missing text when loading start session message");
-                    }
-                } else if (TAG_END_USER_SESSION_MESSAGE.equals(tag)) {
-                    type = parser.next();
-                    if (type == XmlPullParser.TEXT) {
-                        endUserSessionMessage = parser.getText();
-                    } else {
-                        Log.w(LOG_TAG, "Missing text when loading end session message");
-                    }
-                } else if (TAG_CROSS_PROFILE_CALENDAR_PACKAGES.equals(tag)) {
-                    mCrossProfileCalendarPackages = readPackageList(parser, tag);
-                } else if (TAG_CROSS_PROFILE_CALENDAR_PACKAGES_NULL.equals(tag)) {
-                    mCrossProfileCalendarPackages = null;
-                } else if (TAG_CROSS_PROFILE_PACKAGES.equals(tag)) {
-                    mCrossProfilePackages = readPackageList(parser, tag);
-                } else if (TAG_FACTORY_RESET_PROTECTION_POLICY.equals(tag)) {
-                    mFactoryResetProtectionPolicy = FactoryResetProtectionPolicy.readFromXml(
-                                parser);
-                } else if (TAG_SUSPEND_PERSONAL_APPS.equals(tag)) {
-                    mSuspendPersonalApps = Boolean.parseBoolean(
-                            parser.getAttributeValue(null, ATTR_VALUE));
-                } else if (TAG_PROFILE_MAXIMUM_TIME_OFF.equals(tag)) {
-                    mProfileMaximumTimeOffMillis =
-                            Long.parseLong(parser.getAttributeValue(null, ATTR_VALUE));
-                } else if (TAG_PROFILE_OFF_DEADLINE.equals(tag)) {
-                    mProfileOffDeadline =
-                            Long.parseLong(parser.getAttributeValue(null, ATTR_VALUE));
-                } else if (TAG_ALWAYS_ON_VPN_PACKAGE.equals(tag)) {
-                    mAlwaysOnVpnPackage = parser.getAttributeValue(null, ATTR_VALUE);
-                } else if (TAG_ALWAYS_ON_VPN_LOCKDOWN.equals(tag)) {
-                    mAlwaysOnVpnLockdown = Boolean.parseBoolean(
-                            parser.getAttributeValue(null, ATTR_VALUE));
-                } else if (TAG_COMMON_CRITERIA_MODE.equals(tag)) {
-                    mCommonCriteriaMode = Boolean.parseBoolean(
-                            parser.getAttributeValue(null, ATTR_VALUE));
-                } else {
-                    Slog.w(LOG_TAG, "Unknown admin tag: " + tag);
-                    XmlUtils.skipCurrentTag(parser);
-                }
-            }
-        }
-
-        private List<String> readPackageList(XmlPullParser parser,
-                String tag) throws XmlPullParserException, IOException {
-            List<String> result = new ArrayList<String>();
-            int outerDepth = parser.getDepth();
-            int outerType;
-            while ((outerType=parser.next()) != XmlPullParser.END_DOCUMENT
-                    && (outerType != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
-                if (outerType == XmlPullParser.END_TAG || outerType == XmlPullParser.TEXT) {
-                    continue;
-                }
-                String outerTag = parser.getName();
-                if (TAG_PACKAGE_LIST_ITEM.equals(outerTag)) {
-                    String packageName = parser.getAttributeValue(null, ATTR_VALUE);
-                    if (packageName != null) {
-                        result.add(packageName);
-                    } else {
-                        Slog.w(LOG_TAG, "Package name missing under " + outerTag);
-                    }
-                } else {
-                    Slog.w(LOG_TAG, "Unknown tag under " + tag +  ": " + outerTag);
-                }
-            }
-            return result;
-        }
-
-        private void readAttributeValues(
-                XmlPullParser parser, String tag, Collection<String> result)
-                throws XmlPullParserException, IOException {
-            result.clear();
-            int outerDepthDAM = parser.getDepth();
-            int typeDAM;
-            while ((typeDAM=parser.next()) != END_DOCUMENT
-                    && (typeDAM != END_TAG || parser.getDepth() > outerDepthDAM)) {
-                if (typeDAM == END_TAG || typeDAM == TEXT) {
-                    continue;
-                }
-                String tagDAM = parser.getName();
-                if (tag.equals(tagDAM)) {
-                    result.add(parser.getAttributeValue(null, ATTR_VALUE));
-                } else {
-                    Slog.e(LOG_TAG, "Expected tag " + tag +  " but found " + tagDAM);
-                }
-            }
-        }
-
-        @NonNull
-        private ArrayMap<String, TrustAgentInfo> getAllTrustAgentInfos(
-                XmlPullParser parser, String tag) throws XmlPullParserException, IOException {
-            int outerDepthDAM = parser.getDepth();
-            int typeDAM;
-            final ArrayMap<String, TrustAgentInfo> result = new ArrayMap<>();
-            while ((typeDAM=parser.next()) != END_DOCUMENT
-                    && (typeDAM != END_TAG || parser.getDepth() > outerDepthDAM)) {
-                if (typeDAM == END_TAG || typeDAM == TEXT) {
-                    continue;
-                }
-                String tagDAM = parser.getName();
-                if (TAG_TRUST_AGENT_COMPONENT.equals(tagDAM)) {
-                    final String component = parser.getAttributeValue(null, ATTR_VALUE);
-                    final TrustAgentInfo trustAgentInfo = getTrustAgentInfo(parser, tag);
-                    result.put(component, trustAgentInfo);
-                } else {
-                    Slog.w(LOG_TAG, "Unknown tag under " + tag +  ": " + tagDAM);
-                }
-            }
-            return result;
-        }
-
-        private TrustAgentInfo getTrustAgentInfo(XmlPullParser parser, String tag)
-                throws XmlPullParserException, IOException  {
-            int outerDepthDAM = parser.getDepth();
-            int typeDAM;
-            TrustAgentInfo result = new TrustAgentInfo(null);
-            while ((typeDAM=parser.next()) != END_DOCUMENT
-                    && (typeDAM != END_TAG || parser.getDepth() > outerDepthDAM)) {
-                if (typeDAM == END_TAG || typeDAM == TEXT) {
-                    continue;
-                }
-                String tagDAM = parser.getName();
-                if (TAG_TRUST_AGENT_COMPONENT_OPTIONS.equals(tagDAM)) {
-                    result.options = PersistableBundle.restoreFromXml(parser);
-                } else {
-                    Slog.w(LOG_TAG, "Unknown tag under " + tag +  ": " + tagDAM);
-                }
-            }
-            return result;
-        }
-
-        boolean hasUserRestrictions() {
-            return userRestrictions != null && userRestrictions.size() > 0;
-        }
-
-        Bundle ensureUserRestrictions() {
-            if (userRestrictions == null) {
-                userRestrictions = new Bundle();
-            }
-            return userRestrictions;
-        }
-
-        public void transfer(DeviceAdminInfo deviceAdminInfo) {
-            if (hasParentActiveAdmin()) {
-                parentAdmin.info = deviceAdminInfo;
-            }
-            info = deviceAdminInfo;
-        }
-
-        Bundle addSyntheticRestrictions(Bundle restrictions) {
-            if (disableCamera) {
-                restrictions.putBoolean(UserManager.DISALLOW_CAMERA, true);
-            }
-            if (requireAutoTime) {
-                restrictions.putBoolean(UserManager.DISALLOW_CONFIG_DATE_TIME, true);
-            }
-            return restrictions;
-        }
-
-        static Bundle removeDeprecatedRestrictions(Bundle restrictions) {
-            for (String deprecatedRestriction: DEPRECATED_USER_RESTRICTIONS) {
-                restrictions.remove(deprecatedRestriction);
-            }
-            return restrictions;
-        }
-
-        static Bundle filterRestrictions(Bundle restrictions, Predicate<String> filter) {
-            Bundle result = new Bundle();
-            for (String key : restrictions.keySet()) {
-                if (!restrictions.getBoolean(key)) {
-                    continue;
-                }
-                if (filter.test(key)) {
-                    result.putBoolean(key, true);
-                }
-            }
-            return result;
-        }
-
-        Bundle getEffectiveRestrictions() {
-            return addSyntheticRestrictions(
-                    removeDeprecatedRestrictions(new Bundle(ensureUserRestrictions())));
-        }
-
-        Bundle getLocalUserRestrictions(int adminType) {
-            return filterRestrictions(getEffectiveRestrictions(),
-                    key -> UserRestrictionsUtils.isLocal(adminType, key));
-        }
-
-        Bundle getGlobalUserRestrictions(int adminType) {
-            return filterRestrictions(getEffectiveRestrictions(),
-                    key -> UserRestrictionsUtils.isGlobal(adminType, key));
-        }
-
-        void dump(IndentingPrintWriter pw) {
-            pw.print("uid="); pw.println(getUid());
-            pw.print("testOnlyAdmin=");
-            pw.println(testOnlyAdmin);
-            pw.println("policies:");
-            ArrayList<DeviceAdminInfo.PolicyInfo> pols = info.getUsedPolicies();
-            if (pols != null) {
-                pw.increaseIndent();
-                for (int i=0; i<pols.size(); i++) {
-                    pw.println(pols.get(i).tag);
-                }
-                pw.decreaseIndent();
-            }
-            pw.print("passwordQuality=0x");
-                    pw.println(Integer.toHexString(mPasswordPolicy.quality));
-            pw.print("minimumPasswordLength=");
-                    pw.println(mPasswordPolicy.length);
-            pw.print("passwordHistoryLength=");
-                    pw.println(passwordHistoryLength);
-            pw.print("minimumPasswordUpperCase=");
-                    pw.println(mPasswordPolicy.upperCase);
-            pw.print("minimumPasswordLowerCase=");
-                    pw.println(mPasswordPolicy.lowerCase);
-            pw.print("minimumPasswordLetters=");
-                    pw.println(mPasswordPolicy.letters);
-            pw.print("minimumPasswordNumeric=");
-                    pw.println(mPasswordPolicy.numeric);
-            pw.print("minimumPasswordSymbols=");
-                    pw.println(mPasswordPolicy.symbols);
-            pw.print("minimumPasswordNonLetter=");
-                    pw.println(mPasswordPolicy.nonLetter);
-            pw.print("maximumTimeToUnlock=");
-                    pw.println(maximumTimeToUnlock);
-            pw.print("strongAuthUnlockTimeout=");
-                    pw.println(strongAuthUnlockTimeout);
-            pw.print("maximumFailedPasswordsForWipe=");
-                    pw.println(maximumFailedPasswordsForWipe);
-            pw.print("specifiesGlobalProxy=");
-                    pw.println(specifiesGlobalProxy);
-            pw.print("passwordExpirationTimeout=");
-                    pw.println(passwordExpirationTimeout);
-            pw.print("passwordExpirationDate=");
-                    pw.println(passwordExpirationDate);
-            if (globalProxySpec != null) {
-                pw.print("globalProxySpec=");
-                        pw.println(globalProxySpec);
-            }
-            if (globalProxyExclusionList != null) {
-                pw.print("globalProxyEclusionList=");
-                        pw.println(globalProxyExclusionList);
-            }
-            pw.print("encryptionRequested=");
-                    pw.println(encryptionRequested);
-            pw.print("disableCamera=");
-                    pw.println(disableCamera);
-            pw.print("disableCallerId=");
-                    pw.println(disableCallerId);
-            pw.print("disableContactsSearch=");
-                    pw.println(disableContactsSearch);
-            pw.print("disableBluetoothContactSharing=");
-                    pw.println(disableBluetoothContactSharing);
-            pw.print("disableScreenCapture=");
-                    pw.println(disableScreenCapture);
-            pw.print("requireAutoTime=");
-                    pw.println(requireAutoTime);
-            pw.print("forceEphemeralUsers=");
-                    pw.println(forceEphemeralUsers);
-            pw.print("isNetworkLoggingEnabled=");
-                    pw.println(isNetworkLoggingEnabled);
-            pw.print("disabledKeyguardFeatures=");
-                    pw.println(disabledKeyguardFeatures);
-            pw.print("crossProfileWidgetProviders=");
-                    pw.println(crossProfileWidgetProviders);
-            if (permittedAccessiblityServices != null) {
-                pw.print("permittedAccessibilityServices=");
-                    pw.println(permittedAccessiblityServices);
-            }
-            if (permittedInputMethods != null) {
-                pw.print("permittedInputMethods=");
-                    pw.println(permittedInputMethods);
-            }
-            if (permittedNotificationListeners != null) {
-                pw.print("permittedNotificationListeners=");
-                pw.println(permittedNotificationListeners);
-            }
-            if (keepUninstalledPackages != null) {
-                pw.print("keepUninstalledPackages=");
-                    pw.println(keepUninstalledPackages);
-            }
-            pw.print("organizationColor=");
-                    pw.println(organizationColor);
-            if (organizationName != null) {
-                pw.print("organizationName=");
-                    pw.println(organizationName);
-            }
-            pw.println("userRestrictions:");
-            UserRestrictionsUtils.dumpRestrictions(pw, "  ", userRestrictions);
-            pw.print("defaultEnabledRestrictionsAlreadySet=");
-                    pw.println(defaultEnabledRestrictionsAlreadySet);
-            pw.print("isParent=");
-                    pw.println(isParent);
-            if (parentAdmin != null) {
-                pw.println("parentAdmin:");
-                pw.increaseIndent();
-                parentAdmin.dump(pw);
-                pw.decreaseIndent();
-            }
-            if (mCrossProfileCalendarPackages != null) {
-                pw.print("mCrossProfileCalendarPackages=");
-                pw.println(mCrossProfileCalendarPackages);
-            }
-            pw.print("mCrossProfilePackages=");
-                pw.println(mCrossProfilePackages);
-            pw.print("mSuspendPersonalApps=");
-                pw.println(mSuspendPersonalApps);
-            pw.print("mProfileMaximumTimeOffMillis=");
-                pw.println(mProfileMaximumTimeOffMillis);
-            pw.print("mProfileOffDeadline=");
-                pw.println(mProfileOffDeadline);
-            pw.print("mAlwaysOnVpnPackage=");
-            pw.println(mAlwaysOnVpnPackage);
-            pw.print("mAlwaysOnVpnLockdown=");
-            pw.println(mAlwaysOnVpnLockdown);
-            pw.print("mCommonCriteriaMode=");
-            pw.println(mCommonCriteriaMode);
-        }
-    }
-
     private void handlePackagesChanged(@Nullable String packageName, int userHandle) {
         boolean removedAdmin = false;
         if (VERBOSE_LOG) {
@@ -2072,7 +884,7 @@
                 }
             }
             if (removedAdmin) {
-                validatePasswordOwnerLocked(policy);
+                policy.validatePasswordOwner();
             }
 
             boolean removedDelegate = false;
@@ -2573,10 +1385,9 @@
         mCertificateMonitor = new CertificateMonitor(this, mInjector, mBackgroundHandler);
 
         mDeviceAdminServiceController = new DeviceAdminServiceController(this, mConstants);
-
         mOverlayPackagesProvider = new OverlayPackagesProvider(mContext);
-
         mTransferOwnershipMetadataManager = mInjector.newTransferOwnershipMetadataManager();
+        mBugreportCollectionManager = new RemoteBugreportManager(this, mInjector);
 
         if (!mHasFeature) {
             // Skip the rest of the initialization
@@ -2702,6 +1513,48 @@
     }
 
     /**
+     * Creates a new {@link CallerIdentity} object to represent the caller's identity.
+     */
+    private CallerIdentity getCallerIdentity() {
+        final int callerUid = mInjector.binderGetCallingUid();
+        return new CallerIdentity(callerUid, null, null);
+    }
+
+    /**
+     * Creates a new {@link CallerIdentity} object to represent the caller's identity.
+     */
+    private CallerIdentity getCallerIdentity(@NonNull String callerPackage) {
+        final int callerUid = mInjector.binderGetCallingUid();
+
+        if (!isCallingFromPackage(callerPackage, callerUid)) {
+            throw new SecurityException(
+                    String.format("Caller with uid %d is not %s", callerUid, callerPackage));
+        }
+
+        return new CallerIdentity(callerUid, callerPackage, null);
+    }
+
+    /**
+     * Creates a new {@link CallerIdentity} object to represent the caller's identity.
+     * The component name should be an active admin for the calling user.
+     */
+    private CallerIdentity getCallerIdentity(@NonNull ComponentName adminComponent) {
+        final int callerUid = mInjector.binderGetCallingUid();
+        final DevicePolicyData policy = getUserData(UserHandle.getUserId(callerUid));
+        ActiveAdmin admin = policy.mAdminMap.get(adminComponent);
+
+        if (admin == null) {
+            throw new SecurityException(String.format("No active admin for %s", adminComponent));
+        }
+        if (admin.getUid() != callerUid) {
+            throw new SecurityException(
+                    String.format("Admin %s is not owned by uid %d", adminComponent, callerUid));
+        }
+
+        return new CallerIdentity(callerUid, adminComponent.getPackageName(), adminComponent);
+    }
+
+    /**
      * Checks if the device is in COMP mode, and if so migrates it to managed profile on a
      * corporate owned device.
      */
@@ -3231,6 +2084,81 @@
                 reqPolicy, /* permission= */ null);
     }
 
+    @NonNull ActiveAdmin getDeviceOwnerOfCallerLocked(final CallerIdentity caller) {
+        ensureLocked();
+        ComponentName doComponent = mOwners.getDeviceOwnerComponent();
+        Preconditions.checkState(doComponent != null,
+                String.format("No device owner for user %d", caller.getUid()));
+
+        // Use the user ID of the caller instead of mOwners.getDeviceOwnerUserId() because
+        // secondary, affiliated users will have their own admin.
+        ActiveAdmin doAdmin = getUserData(caller.getUserId()).mAdminMap.get(doComponent);
+        Preconditions.checkState(doAdmin != null,
+                String.format("Device owner %s for user %d not found", doComponent,
+                        caller.getUid()));
+
+        Preconditions.checkSecurity(doAdmin.getUid() == caller.getUid(),
+                    String.format("Admin %s is not owned by uid %d, but uid %d", doComponent,
+                            caller.getUid(), doAdmin.getUid()));
+
+        Preconditions.checkSecurity(doAdmin.info.getComponent().equals(caller.getComponentName()),
+                    String.format("Caller component %s is not device owner",
+                        caller.getComponentName()));
+
+        return doAdmin;
+    }
+
+    @NonNull ActiveAdmin getProfileOwnerOfCallerLocked(final CallerIdentity caller) {
+        ensureLocked();
+        final ComponentName poAdminComponent = mOwners.getProfileOwnerComponent(caller.getUserId());
+
+        Preconditions.checkState(poAdminComponent != null,
+                    String.format("No profile owner for user %d", caller.getUid()));
+
+        ActiveAdmin poAdmin = getUserData(caller.getUserId()).mAdminMap.get(poAdminComponent);
+        Preconditions.checkState(poAdmin != null,
+                    String.format("No device profile owner for caller %d", caller.getUid()));
+
+        Preconditions.checkSecurity(poAdmin.getUid() == caller.getUid(),
+                    String.format("Admin %s is not owned by uid %d", poAdminComponent,
+                            caller.getUid()));
+
+        Preconditions.checkSecurity(poAdmin.info.getComponent().equals(caller.getComponentName()),
+                    String.format("Caller component %s is not profile owner",
+                        caller.getComponentName()));
+
+        return poAdmin;
+    }
+
+    @NonNull ActiveAdmin getOrganizationOwnedProfileOwnerLocked(final CallerIdentity caller) {
+        final ActiveAdmin profileOwner = getProfileOwnerOfCallerLocked(caller);
+
+        Preconditions.checkSecurity(
+                mOwners.isProfileOwnerOfOrganizationOwnedDevice(caller.getUserId()),
+                String.format("Admin %s is not of an org-owned device",
+                        profileOwner.info.getComponent()));
+
+        return profileOwner;
+    }
+
+    @NonNull ActiveAdmin getProfileOwnerOrDeviceOwnerLocked(final CallerIdentity caller) {
+        ensureLocked();
+        // Try to find an admin which can use reqPolicy
+        final ComponentName poAdminComponent = mOwners.getProfileOwnerComponent(caller.getUserId());
+        final ComponentName doAdminComponent = mOwners.getDeviceOwnerComponent();
+
+        if (poAdminComponent == null && doAdminComponent == null) {
+            throw new IllegalStateException(
+                    String.format("No profile or device owner for user %d", caller.getUid()));
+        }
+
+        if (poAdminComponent != null) {
+            return getProfileOwnerOfCallerLocked(caller);
+        }
+
+        return getDeviceOwnerOfCallerLocked(caller);
+    }
+
     /**
      * Finds an active admin for the caller then checks {@code permission} if admin check failed.
      *
@@ -3249,9 +2177,7 @@
         ActiveAdmin result = getActiveAdminWithPolicyForUidLocked(who, reqPolicy, callingUid);
         if (result != null) {
             return result;
-        } else if (permission != null
-                && (mContext.checkCallingPermission(permission)
-                        == PackageManager.PERMISSION_GRANTED)) {
+        } else if (permission != null && hasCallingPermission(permission)) {
             return null;
         }
 
@@ -3513,13 +2439,8 @@
         }
     }
 
-
-    public DeviceAdminInfo findAdmin(final ComponentName adminName, final int userHandle,
+    private DeviceAdminInfo findAdmin(final ComponentName adminName, final int userHandle,
             boolean throwForMissingPermission) {
-        if (!mHasFeature) {
-            return null;
-        }
-        enforceFullCrossUsersPermission(userHandle);
         final ActivityInfo ai = mInjector.binderWithCleanCallingIdentity(() -> {
             try {
                 return mIPackageManager.getReceiverInfo(adminName,
@@ -3582,213 +2503,11 @@
     }
 
     private void saveSettingsLocked(int userHandle) {
-        DevicePolicyData policy = getUserData(userHandle);
-        JournaledFile journal = makeJournaledFile(userHandle);
-        FileOutputStream stream = null;
-        try {
-            stream = new FileOutputStream(journal.chooseForWrite(), false);
-            XmlSerializer out = new FastXmlSerializer();
-            out.setOutput(stream, StandardCharsets.UTF_8.name());
-            out.startDocument(null, true);
-
-            out.startTag(null, "policies");
-            if (policy.mRestrictionsProvider != null) {
-                out.attribute(null, ATTR_PERMISSION_PROVIDER,
-                        policy.mRestrictionsProvider.flattenToString());
-            }
-            if (policy.mUserSetupComplete) {
-                out.attribute(null, ATTR_SETUP_COMPLETE,
-                        Boolean.toString(true));
-            }
-            if (policy.mPaired) {
-                out.attribute(null, ATTR_DEVICE_PAIRED,
-                        Boolean.toString(true));
-            }
-            if (policy.mDeviceProvisioningConfigApplied) {
-                out.attribute(null, ATTR_DEVICE_PROVISIONING_CONFIG_APPLIED,
-                        Boolean.toString(true));
-            }
-            if (policy.mUserProvisioningState != DevicePolicyManager.STATE_USER_UNMANAGED) {
-                out.attribute(null, ATTR_PROVISIONING_STATE,
-                        Integer.toString(policy.mUserProvisioningState));
-            }
-            if (policy.mPermissionPolicy != DevicePolicyManager.PERMISSION_POLICY_PROMPT) {
-                out.attribute(null, ATTR_PERMISSION_POLICY,
-                        Integer.toString(policy.mPermissionPolicy));
-            }
-
-            // Serialize delegations.
-            for (int i = 0; i < policy.mDelegationMap.size(); ++i) {
-                final String delegatePackage = policy.mDelegationMap.keyAt(i);
-                final List<String> scopes = policy.mDelegationMap.valueAt(i);
-
-                // Every "delegation" tag serializes the information of one delegate-scope pair.
-                for (String scope : scopes) {
-                    out.startTag(null, "delegation");
-                    out.attribute(null, "delegatePackage", delegatePackage);
-                    out.attribute(null, "scope", scope);
-                    out.endTag(null, "delegation");
-                }
-            }
-
-            final int N = policy.mAdminList.size();
-            for (int i=0; i<N; i++) {
-                ActiveAdmin ap = policy.mAdminList.get(i);
-                if (ap != null) {
-                    out.startTag(null, "admin");
-                    out.attribute(null, "name", ap.info.getComponent().flattenToString());
-                    ap.writeToXml(out);
-                    out.endTag(null, "admin");
-                }
-            }
-
-            if (policy.mPasswordOwner >= 0) {
-                out.startTag(null, "password-owner");
-                out.attribute(null, "value", Integer.toString(policy.mPasswordOwner));
-                out.endTag(null, "password-owner");
-            }
-
-            if (policy.mFailedPasswordAttempts != 0) {
-                out.startTag(null, "failed-password-attempts");
-                out.attribute(null, "value", Integer.toString(policy.mFailedPasswordAttempts));
-                out.endTag(null, "failed-password-attempts");
-            }
-
-            // For FDE devices only, we save this flag so we can report on password sufficiency
-            // before the user enters their password for the first time after a reboot.  For
-            // security reasons, we don't want to store the full set of active password metrics.
-            if (!mInjector.storageManagerIsFileBasedEncryptionEnabled()) {
-                out.startTag(null, TAG_PASSWORD_VALIDITY);
-                out.attribute(null, ATTR_VALUE,
-                        Boolean.toString(policy.mPasswordValidAtLastCheckpoint));
-                out.endTag(null, TAG_PASSWORD_VALIDITY);
-            }
-
-            for (int i = 0; i < policy.mAcceptedCaCertificates.size(); i++) {
-                out.startTag(null, TAG_ACCEPTED_CA_CERTIFICATES);
-                out.attribute(null, ATTR_NAME, policy.mAcceptedCaCertificates.valueAt(i));
-                out.endTag(null, TAG_ACCEPTED_CA_CERTIFICATES);
-            }
-
-            for (int i=0; i<policy.mLockTaskPackages.size(); i++) {
-                String component = policy.mLockTaskPackages.get(i);
-                out.startTag(null, TAG_LOCK_TASK_COMPONENTS);
-                out.attribute(null, "name", component);
-                out.endTag(null, TAG_LOCK_TASK_COMPONENTS);
-            }
-
-            if (policy.mLockTaskFeatures != DevicePolicyManager.LOCK_TASK_FEATURE_NONE) {
-                out.startTag(null, TAG_LOCK_TASK_FEATURES);
-                out.attribute(null, ATTR_VALUE, Integer.toString(policy.mLockTaskFeatures));
-                out.endTag(null, TAG_LOCK_TASK_FEATURES);
-            }
-
-            if (policy.mSecondaryLockscreenEnabled) {
-                out.startTag(null, TAG_SECONDARY_LOCK_SCREEN);
-                out.attribute(null, ATTR_VALUE, Boolean.toString(true));
-                out.endTag(null, TAG_SECONDARY_LOCK_SCREEN);
-            }
-
-            if (policy.mStatusBarDisabled) {
-                out.startTag(null, TAG_STATUS_BAR);
-                out.attribute(null, ATTR_DISABLED, Boolean.toString(policy.mStatusBarDisabled));
-                out.endTag(null, TAG_STATUS_BAR);
-            }
-
-            if (policy.doNotAskCredentialsOnBoot) {
-                out.startTag(null, DO_NOT_ASK_CREDENTIALS_ON_BOOT_XML);
-                out.endTag(null, DO_NOT_ASK_CREDENTIALS_ON_BOOT_XML);
-            }
-
-            for (String id : policy.mAffiliationIds) {
-                out.startTag(null, TAG_AFFILIATION_ID);
-                out.attribute(null, ATTR_ID, id);
-                out.endTag(null, TAG_AFFILIATION_ID);
-            }
-
-            if (policy.mLastSecurityLogRetrievalTime >= 0) {
-                out.startTag(null, TAG_LAST_SECURITY_LOG_RETRIEVAL);
-                out.attribute(null, ATTR_VALUE,
-                        Long.toString(policy.mLastSecurityLogRetrievalTime));
-                out.endTag(null, TAG_LAST_SECURITY_LOG_RETRIEVAL);
-            }
-
-            if (policy.mLastBugReportRequestTime >= 0) {
-                out.startTag(null, TAG_LAST_BUG_REPORT_REQUEST);
-                out.attribute(null, ATTR_VALUE,
-                        Long.toString(policy.mLastBugReportRequestTime));
-                out.endTag(null, TAG_LAST_BUG_REPORT_REQUEST);
-            }
-
-            if (policy.mLastNetworkLogsRetrievalTime >= 0) {
-                out.startTag(null, TAG_LAST_NETWORK_LOG_RETRIEVAL);
-                out.attribute(null, ATTR_VALUE,
-                        Long.toString(policy.mLastNetworkLogsRetrievalTime));
-                out.endTag(null, TAG_LAST_NETWORK_LOG_RETRIEVAL);
-            }
-
-            if (policy.mAdminBroadcastPending) {
-                out.startTag(null, TAG_ADMIN_BROADCAST_PENDING);
-                out.attribute(null, ATTR_VALUE,
-                        Boolean.toString(policy.mAdminBroadcastPending));
-                out.endTag(null, TAG_ADMIN_BROADCAST_PENDING);
-            }
-
-            if (policy.mInitBundle != null) {
-                out.startTag(null, TAG_INITIALIZATION_BUNDLE);
-                policy.mInitBundle.saveToXml(out);
-                out.endTag(null, TAG_INITIALIZATION_BUNDLE);
-            }
-
-            if (policy.mPasswordTokenHandle != 0) {
-                out.startTag(null, TAG_PASSWORD_TOKEN_HANDLE);
-                out.attribute(null, ATTR_VALUE,
-                        Long.toString(policy.mPasswordTokenHandle));
-                out.endTag(null, TAG_PASSWORD_TOKEN_HANDLE);
-            }
-
-            if (policy.mCurrentInputMethodSet) {
-                out.startTag(null, TAG_CURRENT_INPUT_METHOD_SET);
-                out.endTag(null, TAG_CURRENT_INPUT_METHOD_SET);
-            }
-
-            for (final String cert : policy.mOwnerInstalledCaCerts) {
-                out.startTag(null, TAG_OWNER_INSTALLED_CA_CERT);
-                out.attribute(null, ATTR_ALIAS, cert);
-                out.endTag(null, TAG_OWNER_INSTALLED_CA_CERT);
-            }
-
-            for (int i = 0, size = policy.mUserControlDisabledPackages.size(); i < size; i++) {
-                String packageName = policy.mUserControlDisabledPackages.get(i);
-                out.startTag(null, TAG_PROTECTED_PACKAGES);
-                out.attribute(null, ATTR_NAME, packageName);
-                out.endTag(null, TAG_PROTECTED_PACKAGES);
-            }
-
-            if (policy.mAppsSuspended) {
-                out.startTag(null, TAG_APPS_SUSPENDED);
-                out.attribute(null, ATTR_VALUE, Boolean.toString(policy.mAppsSuspended));
-                out.endTag(null, TAG_APPS_SUSPENDED);
-            }
-
-            out.endTag(null, "policies");
-
-            out.endDocument();
-            stream.flush();
-            FileUtils.sync(stream);
-            stream.close();
-            journal.commit();
+        if (DevicePolicyData.store(
+                getUserData(userHandle),
+                makeJournaledFile(userHandle),
+                !mInjector.storageManagerIsFileBasedEncryptionEnabled())) {
             sendChangedNotification(userHandle);
-        } catch (XmlPullParserException | IOException e) {
-            Slog.w(LOG_TAG, "failed writing file", e);
-            try {
-                if (stream != null) {
-                    stream.close();
-                }
-            } catch (IOException ex) {
-                // Ignore
-            }
-            journal.rollback();
         }
     }
 
@@ -3800,225 +2519,19 @@
     }
 
     private void loadSettingsLocked(DevicePolicyData policy, int userHandle) {
-        JournaledFile journal = makeJournaledFile(userHandle);
-        FileInputStream stream = null;
-        File file = journal.chooseForRead();
-        boolean needsRewrite = false;
-        try {
-            stream = new FileInputStream(file);
-            XmlPullParser parser = Xml.newPullParser();
-            parser.setInput(stream, StandardCharsets.UTF_8.name());
-
-            int type;
-            while ((type=parser.next()) != XmlPullParser.END_DOCUMENT
-                    && type != XmlPullParser.START_TAG) {
-            }
-            String tag = parser.getName();
-            if (!"policies".equals(tag)) {
-                throw new XmlPullParserException(
-                        "Settings do not start with policies tag: found " + tag);
-            }
-
-            // Extract the permission provider component name if available
-            String permissionProvider = parser.getAttributeValue(null, ATTR_PERMISSION_PROVIDER);
-            if (permissionProvider != null) {
-                policy.mRestrictionsProvider = ComponentName.unflattenFromString(permissionProvider);
-            }
-            String userSetupComplete = parser.getAttributeValue(null, ATTR_SETUP_COMPLETE);
-            if (userSetupComplete != null && Boolean.toString(true).equals(userSetupComplete)) {
-                policy.mUserSetupComplete = true;
-            }
-            String paired = parser.getAttributeValue(null, ATTR_DEVICE_PAIRED);
-            if (paired != null && Boolean.toString(true).equals(paired)) {
-                policy.mPaired = true;
-            }
-            String deviceProvisioningConfigApplied = parser.getAttributeValue(null,
-                    ATTR_DEVICE_PROVISIONING_CONFIG_APPLIED);
-            if (deviceProvisioningConfigApplied != null
-                    && Boolean.toString(true).equals(deviceProvisioningConfigApplied)) {
-                policy.mDeviceProvisioningConfigApplied = true;
-            }
-            String provisioningState = parser.getAttributeValue(null, ATTR_PROVISIONING_STATE);
-            if (!TextUtils.isEmpty(provisioningState)) {
-                policy.mUserProvisioningState = Integer.parseInt(provisioningState);
-            }
-            String permissionPolicy = parser.getAttributeValue(null, ATTR_PERMISSION_POLICY);
-            if (!TextUtils.isEmpty(permissionPolicy)) {
-                policy.mPermissionPolicy = Integer.parseInt(permissionPolicy);
-            }
-            // Check for delegation compatibility with pre-O.
-            // TODO(edmanp) remove in P.
-            {
-                final String certDelegate = parser.getAttributeValue(null,
-                        ATTR_DELEGATED_CERT_INSTALLER);
-                if (certDelegate != null) {
-                    List<String> scopes = policy.mDelegationMap.get(certDelegate);
-                    if (scopes == null) {
-                        scopes = new ArrayList<>();
-                        policy.mDelegationMap.put(certDelegate, scopes);
-                    }
-                    if (!scopes.contains(DELEGATION_CERT_INSTALL)) {
-                        scopes.add(DELEGATION_CERT_INSTALL);
-                        needsRewrite = true;
-                    }
-                }
-                final String appRestrictionsDelegate = parser.getAttributeValue(null,
-                        ATTR_APPLICATION_RESTRICTIONS_MANAGER);
-                if (appRestrictionsDelegate != null) {
-                    List<String> scopes = policy.mDelegationMap.get(appRestrictionsDelegate);
-                    if (scopes == null) {
-                        scopes = new ArrayList<>();
-                        policy.mDelegationMap.put(appRestrictionsDelegate, scopes);
-                    }
-                    if (!scopes.contains(DELEGATION_APP_RESTRICTIONS)) {
-                        scopes.add(DELEGATION_APP_RESTRICTIONS);
-                        needsRewrite = true;
-                    }
-                }
-            }
-
-            type = parser.next();
-            int outerDepth = parser.getDepth();
-            policy.mLockTaskPackages.clear();
-            policy.mAdminList.clear();
-            policy.mAdminMap.clear();
-            policy.mAffiliationIds.clear();
-            policy.mOwnerInstalledCaCerts.clear();
-            policy.mUserControlDisabledPackages.clear();
-            while ((type=parser.next()) != XmlPullParser.END_DOCUMENT
-                   && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
-                if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
-                    continue;
-                }
-                tag = parser.getName();
-                if ("admin".equals(tag)) {
-                    String name = parser.getAttributeValue(null, "name");
-                    try {
-                        DeviceAdminInfo dai = findAdmin(
-                                ComponentName.unflattenFromString(name), userHandle,
-                                /* throwForMissingPermission= */ false);
-                        if (VERBOSE_LOG
-                                && (UserHandle.getUserId(dai.getActivityInfo().applicationInfo.uid)
-                                != userHandle)) {
-                            Slog.w(LOG_TAG, "findAdmin returned an incorrect uid "
-                                    + dai.getActivityInfo().applicationInfo.uid + " for user "
-                                    + userHandle);
-                        }
-                        if (dai != null) {
-                            boolean shouldOverwritePolicies =
-                                    shouldOverwritePoliciesFromXml(dai.getComponent(), userHandle);
-                            ActiveAdmin ap = new ActiveAdmin(dai, /* parent */ false);
-                            ap.readFromXml(parser, shouldOverwritePolicies);
-                            policy.mAdminMap.put(ap.info.getComponent(), ap);
-                        }
-                    } catch (RuntimeException e) {
-                        Slog.w(LOG_TAG, "Failed loading admin " + name, e);
-                    }
-                } else if ("delegation".equals(tag)) {
-                    // Parse delegation info.
-                    final String delegatePackage = parser.getAttributeValue(null,
-                            "delegatePackage");
-                    final String scope = parser.getAttributeValue(null, "scope");
-
-                    // Get a reference to the scopes list for the delegatePackage.
-                    List<String> scopes = policy.mDelegationMap.get(delegatePackage);
-                    // Or make a new list if none was found.
-                    if (scopes == null) {
-                        scopes = new ArrayList<>();
-                        policy.mDelegationMap.put(delegatePackage, scopes);
-                    }
-                    // Add the new scope to the list of delegatePackage if it's not already there.
-                    if (!scopes.contains(scope)) {
-                        scopes.add(scope);
-                    }
-                } else if ("failed-password-attempts".equals(tag)) {
-                    policy.mFailedPasswordAttempts = Integer.parseInt(
-                            parser.getAttributeValue(null, "value"));
-                } else if ("password-owner".equals(tag)) {
-                    policy.mPasswordOwner = Integer.parseInt(
-                            parser.getAttributeValue(null, "value"));
-                } else if (TAG_ACCEPTED_CA_CERTIFICATES.equals(tag)) {
-                    policy.mAcceptedCaCertificates.add(parser.getAttributeValue(null, ATTR_NAME));
-                } else if (TAG_LOCK_TASK_COMPONENTS.equals(tag)) {
-                    policy.mLockTaskPackages.add(parser.getAttributeValue(null, "name"));
-                } else if (TAG_LOCK_TASK_FEATURES.equals(tag)) {
-                    policy.mLockTaskFeatures = Integer.parseInt(
-                            parser.getAttributeValue(null, ATTR_VALUE));
-                } else if (TAG_SECONDARY_LOCK_SCREEN.equals(tag)) {
-                    policy.mSecondaryLockscreenEnabled = Boolean.parseBoolean(
-                            parser.getAttributeValue(null, ATTR_VALUE));
-                } else if (TAG_STATUS_BAR.equals(tag)) {
-                    policy.mStatusBarDisabled = Boolean.parseBoolean(
-                            parser.getAttributeValue(null, ATTR_DISABLED));
-                } else if (DO_NOT_ASK_CREDENTIALS_ON_BOOT_XML.equals(tag)) {
-                    policy.doNotAskCredentialsOnBoot = true;
-                } else if (TAG_AFFILIATION_ID.equals(tag)) {
-                    policy.mAffiliationIds.add(parser.getAttributeValue(null, ATTR_ID));
-                } else if (TAG_LAST_SECURITY_LOG_RETRIEVAL.equals(tag)) {
-                    policy.mLastSecurityLogRetrievalTime = Long.parseLong(
-                            parser.getAttributeValue(null, ATTR_VALUE));
-                } else if (TAG_LAST_BUG_REPORT_REQUEST.equals(tag)) {
-                    policy.mLastBugReportRequestTime = Long.parseLong(
-                            parser.getAttributeValue(null, ATTR_VALUE));
-                } else if (TAG_LAST_NETWORK_LOG_RETRIEVAL.equals(tag)) {
-                    policy.mLastNetworkLogsRetrievalTime = Long.parseLong(
-                            parser.getAttributeValue(null, ATTR_VALUE));
-                } else if (TAG_ADMIN_BROADCAST_PENDING.equals(tag)) {
-                    String pending = parser.getAttributeValue(null, ATTR_VALUE);
-                    policy.mAdminBroadcastPending = Boolean.toString(true).equals(pending);
-                } else if (TAG_INITIALIZATION_BUNDLE.equals(tag)) {
-                    policy.mInitBundle = PersistableBundle.restoreFromXml(parser);
-                } else if ("active-password".equals(tag)) {
-                    // Remove password metrics from saved settings, as we no longer wish to store
-                    // these on disk
-                    needsRewrite = true;
-                } else if (TAG_PASSWORD_VALIDITY.equals(tag)) {
-                    if (!mInjector.storageManagerIsFileBasedEncryptionEnabled()) {
-                        // This flag is only used for FDE devices
-                        policy.mPasswordValidAtLastCheckpoint = Boolean.parseBoolean(
-                                parser.getAttributeValue(null, ATTR_VALUE));
-                    }
-                } else if (TAG_PASSWORD_TOKEN_HANDLE.equals(tag)) {
-                    policy.mPasswordTokenHandle = Long.parseLong(
-                            parser.getAttributeValue(null, ATTR_VALUE));
-                } else if (TAG_CURRENT_INPUT_METHOD_SET.equals(tag)) {
-                    policy.mCurrentInputMethodSet = true;
-                } else if (TAG_OWNER_INSTALLED_CA_CERT.equals(tag)) {
-                    policy.mOwnerInstalledCaCerts.add(parser.getAttributeValue(null, ATTR_ALIAS));
-                } else if (TAG_PROTECTED_PACKAGES.equals(tag)) {
-                    policy.mUserControlDisabledPackages.add(
-                            parser.getAttributeValue(null, ATTR_NAME));
-                } else if (TAG_APPS_SUSPENDED.equals(tag)) {
-                    policy.mAppsSuspended =
-                            Boolean.parseBoolean(parser.getAttributeValue(null, ATTR_VALUE));
-                } else {
-                    Slog.w(LOG_TAG, "Unknown tag: " + tag);
-                    XmlUtils.skipCurrentTag(parser);
-                }
-            }
-        } catch (FileNotFoundException e) {
-            // Don't be noisy, this is normal if we haven't defined any policies.
-        } catch (NullPointerException | NumberFormatException | XmlPullParserException | IOException
-                | IndexOutOfBoundsException e) {
-            Slog.w(LOG_TAG, "failed parsing " + file, e);
-        }
-        try {
-            if (stream != null) {
-                stream.close();
-            }
-        } catch (IOException e) {
-            // Ignore
-        }
-
-        // Generate a list of admins from the admin map
-        policy.mAdminList.addAll(policy.mAdminMap.values());
+        boolean needsRewrite = DevicePolicyData.load(policy,
+                !mInjector.storageManagerIsFileBasedEncryptionEnabled(),
+                makeJournaledFile(userHandle),
+                component -> findAdmin(
+                        component, userHandle, /* throwForMissingPermission= */ false),
+                getOwnerComponent(userHandle));
 
         // Might need to upgrade the file by rewriting it
         if (needsRewrite) {
             saveSettingsLocked(userHandle);
         }
 
-        validatePasswordOwnerLocked(policy);
+        policy.validatePasswordOwner();
         updateMaximumTimeToLockLocked(userHandle);
         updateLockTaskPackagesLocked(policy.mLockTaskPackages, userHandle);
         updateLockTaskFeaturesLocked(policy.mLockTaskFeatures, userHandle);
@@ -4028,14 +2541,6 @@
         }
     }
 
-    private boolean shouldOverwritePoliciesFromXml(
-            ComponentName deviceAdminComponent, int userHandle) {
-        // http://b/123415062: If DA, overwrite with the stored policies that were agreed by the
-        // user to prevent apps from sneaking additional policies into updates.
-        return !isProfileOwner(deviceAdminComponent, userHandle)
-                && !isDeviceOwner(deviceAdminComponent, userHandle);
-    }
-
     private void updateLockTaskPackagesLocked(List<String> packages, int userId) {
         long ident = mInjector.binderClearCallingIdentity();
         try {
@@ -4097,23 +2602,6 @@
                 + Integer.toHexString(quality));
     }
 
-    void validatePasswordOwnerLocked(DevicePolicyData policy) {
-        if (policy.mPasswordOwner >= 0) {
-            boolean haveOwner = false;
-            for (int i = policy.mAdminList.size() - 1; i >= 0; i--) {
-                if (policy.mAdminList.get(i).getUid() == policy.mPasswordOwner) {
-                    haveOwner = true;
-                    break;
-                }
-            }
-            if (!haveOwner) {
-                Slog.w(LOG_TAG, "Previous password owner " + policy.mPasswordOwner
-                        + " no longer active; disabling");
-                policy.mPasswordOwner = -1;
-            }
-        }
-    }
-
     @VisibleForTesting
     @Override
     void systemReady(int phase) {
@@ -4386,9 +2874,12 @@
 
     private void setActiveAdmin(ComponentName adminReceiver, boolean refreshing, int userHandle,
             Bundle onEnableData) {
-        mContext.enforceCallingOrSelfPermission(
-                android.Manifest.permission.MANAGE_DEVICE_ADMINS, null);
-        enforceFullCrossUsersPermission(userHandle);
+        Preconditions.checkArgumentNonnegative(userHandle, "Invalid userId");
+
+        final CallerIdentity identity = getCallerIdentity();
+        Preconditions.checkCallAuthorization(
+                hasCallingOrSelfPermission(permission.MANAGE_DEVICE_ADMINS));
+        Preconditions.checkCallAuthorization(hasFullCrossUsersPermission(identity, userHandle));
 
         DevicePolicyData policy = getUserData(userHandle);
         DeviceAdminInfo info = findAdmin(adminReceiver, userHandle,
@@ -4528,7 +3019,11 @@
         if (!mHasFeature) {
             return false;
         }
-        enforceFullCrossUsersPermission(userHandle);
+        Preconditions.checkArgumentNonnegative(userHandle, "Invalid userId");
+
+        final CallerIdentity identity = getCallerIdentity();
+        Preconditions.checkCallAuthorization(hasFullCrossUsersPermission(identity, userHandle));
+
         synchronized (getLockObject()) {
             return getActiveAdminUncheckedLocked(adminReceiver, userHandle) != null;
         }
@@ -4539,7 +3034,11 @@
         if (!mHasFeature) {
             return false;
         }
-        enforceFullCrossUsersPermission(userHandle);
+        Preconditions.checkArgumentNonnegative(userHandle, "Invalid userId");
+
+        final CallerIdentity identity = getCallerIdentity();
+        Preconditions.checkCallAuthorization(hasFullCrossUsersPermission(identity, userHandle));
+
         synchronized (getLockObject()) {
             DevicePolicyData policyData = getUserData(userHandle);
             return policyData.mRemovingAdmins.contains(adminReceiver);
@@ -4551,7 +3050,11 @@
         if (!mHasFeature) {
             return false;
         }
-        enforceFullCrossUsersPermission(userHandle);
+        Preconditions.checkArgumentNonnegative(userHandle, "Invalid userId");
+
+        final CallerIdentity identity = getCallerIdentity(adminReceiver);
+        Preconditions.checkCallAuthorization(hasFullCrossUsersPermission(identity, userHandle));
+
         synchronized (getLockObject()) {
             ActiveAdmin administrator = getActiveAdminUncheckedLocked(adminReceiver, userHandle);
             if (administrator == null) {
@@ -4567,8 +3070,11 @@
         if (!mHasFeature) {
             return Collections.EMPTY_LIST;
         }
+        Preconditions.checkArgumentNonnegative(userHandle, "Invalid userId");
 
-        enforceFullCrossUsersPermission(userHandle);
+        final CallerIdentity identity = getCallerIdentity();
+        Preconditions.checkCallAuthorization(hasFullCrossUsersPermission(identity, userHandle));
+
         synchronized (getLockObject()) {
             DevicePolicyData policy = getUserData(userHandle);
             final int N = policy.mAdminList.size();
@@ -4588,7 +3094,11 @@
         if (!mHasFeature) {
             return false;
         }
-        enforceFullCrossUsersPermission(userHandle);
+        Preconditions.checkArgumentNonnegative(userHandle, "Invalid userId");
+
+        final CallerIdentity identity = getCallerIdentity();
+        Preconditions.checkCallAuthorization(hasFullCrossUsersPermission(identity, userHandle));
+
         synchronized (getLockObject()) {
             DevicePolicyData policy = getUserData(userHandle);
             final int N = policy.mAdminList.size();
@@ -4698,8 +3208,12 @@
         if (!mHasFeature) {
             return;
         }
-        enforceFullCrossUsersPermission(userHandle);
+        Preconditions.checkArgumentNonnegative(userHandle, "Invalid userId");
+
+        final CallerIdentity identity = getCallerIdentity();
+        Preconditions.checkCallAuthorization(hasFullCrossUsersPermission(identity, userHandle));
         enforceUserUnlocked(userHandle);
+
         synchronized (getLockObject()) {
             ActiveAdmin admin = getActiveAdminUncheckedLocked(adminReceiver, userHandle);
             if (admin == null) {
@@ -4851,7 +3365,11 @@
         if (!mHasFeature) {
             return PASSWORD_QUALITY_UNSPECIFIED;
         }
-        enforceFullCrossUsersPermission(userHandle);
+        Preconditions.checkArgumentNonnegative(userHandle, "Invalid userId");
+
+        final CallerIdentity identity = getCallerIdentity();
+        Preconditions.checkCallAuthorization(hasFullCrossUsersPermission(identity, userHandle));
+
         synchronized (getLockObject()) {
             int mode = PASSWORD_QUALITY_UNSPECIFIED;
 
@@ -5063,7 +3581,11 @@
         if (!mHasFeature || !mLockPatternUtils.hasSecureLockScreen()) {
             return 0L;
         }
-        enforceFullCrossUsersPermission(userHandle);
+        Preconditions.checkArgumentNonnegative(userHandle, "Invalid userId");
+
+        final CallerIdentity identity = getCallerIdentity();
+        Preconditions.checkCallAuthorization(hasFullCrossUsersPermission(identity, userHandle));
+
         synchronized (getLockObject()) {
             long timeout = 0L;
 
@@ -5089,12 +3611,12 @@
 
     @Override
     public boolean addCrossProfileWidgetProvider(ComponentName admin, String packageName) {
-        final int userId = UserHandle.getCallingUserId();
+        final CallerIdentity identity = getCallerIdentity(admin);
+        Preconditions.checkCallAuthorization(isProfileOwner(identity) || isDeviceOwner(identity));
         List<String> changedProviders = null;
 
         synchronized (getLockObject()) {
-            ActiveAdmin activeAdmin = getActiveAdminForCallerLocked(admin,
-                    DeviceAdminInfo.USES_POLICY_PROFILE_OWNER);
+            ActiveAdmin activeAdmin = getProfileOwnerOrDeviceOwnerLocked(identity);
             if (activeAdmin.crossProfileWidgetProviders == null) {
                 activeAdmin.crossProfileWidgetProviders = new ArrayList<>();
             }
@@ -5102,7 +3624,7 @@
             if (!providers.contains(packageName)) {
                 providers.add(packageName);
                 changedProviders = new ArrayList<>(providers);
-                saveSettingsLocked(userId);
+                saveSettingsLocked(identity.getUserId());
             }
         }
 
@@ -5112,7 +3634,8 @@
                 .write();
 
         if (changedProviders != null) {
-            mLocalService.notifyCrossProfileProvidersChanged(userId, changedProviders);
+            mLocalService.notifyCrossProfileProvidersChanged(identity.getUserId(),
+                    changedProviders);
             return true;
         }
 
@@ -5121,12 +3644,12 @@
 
     @Override
     public boolean removeCrossProfileWidgetProvider(ComponentName admin, String packageName) {
-        final int userId = UserHandle.getCallingUserId();
+        final CallerIdentity identity = getCallerIdentity(admin);
+        Preconditions.checkCallAuthorization(isProfileOwner(identity) || isDeviceOwner(identity));
         List<String> changedProviders = null;
 
         synchronized (getLockObject()) {
-            ActiveAdmin activeAdmin = getActiveAdminForCallerLocked(admin,
-                    DeviceAdminInfo.USES_POLICY_PROFILE_OWNER);
+            ActiveAdmin activeAdmin = getProfileOwnerOrDeviceOwnerLocked(identity);
             if (activeAdmin.crossProfileWidgetProviders == null
                     || activeAdmin.crossProfileWidgetProviders.isEmpty()) {
                 return false;
@@ -5134,7 +3657,7 @@
             List<String> providers = activeAdmin.crossProfileWidgetProviders;
             if (providers.remove(packageName)) {
                 changedProviders = new ArrayList<>(providers);
-                saveSettingsLocked(userId);
+                saveSettingsLocked(identity.getUserId());
             }
         }
 
@@ -5144,7 +3667,8 @@
                 .write();
 
         if (changedProviders != null) {
-            mLocalService.notifyCrossProfileProvidersChanged(userId, changedProviders);
+            mLocalService.notifyCrossProfileProvidersChanged(identity.getUserId(),
+                    changedProviders);
             return true;
         }
 
@@ -5153,9 +3677,11 @@
 
     @Override
     public List<String> getCrossProfileWidgetProviders(ComponentName admin) {
+        final CallerIdentity identity = getCallerIdentity(admin);
+        Preconditions.checkCallAuthorization(isProfileOwner(identity) || isDeviceOwner(identity));
+
         synchronized (getLockObject()) {
-            ActiveAdmin activeAdmin = getActiveAdminForCallerLocked(admin,
-                    DeviceAdminInfo.USES_POLICY_PROFILE_OWNER);
+            ActiveAdmin activeAdmin = getProfileOwnerOrDeviceOwnerLocked(identity);
             if (activeAdmin.crossProfileWidgetProviders == null
                     || activeAdmin.crossProfileWidgetProviders.isEmpty()) {
                 return null;
@@ -5198,7 +3724,11 @@
         if (!mHasFeature || !mLockPatternUtils.hasSecureLockScreen()) {
             return 0L;
         }
-        enforceFullCrossUsersPermission(userHandle);
+        Preconditions.checkArgumentNonnegative(userHandle, "Invalid userId");
+
+        final CallerIdentity identity = getCallerIdentity();
+        Preconditions.checkCallAuthorization(hasFullCrossUsersPermission(identity, userHandle));
+
         synchronized (getLockObject()) {
             return getPasswordExpirationLocked(who, userHandle, parent);
         }
@@ -5404,7 +3934,11 @@
         if (!mHasFeature) {
             return 0;
         }
-        enforceFullCrossUsersPermission(userHandle);
+        Preconditions.checkArgumentNonnegative(userHandle, "Invalid userId");
+
+        final CallerIdentity identity = getCallerIdentity();
+        Preconditions.checkCallAuthorization(hasFullCrossUsersPermission(identity, userHandle));
+
         synchronized (getLockObject()) {
             if (who != null) {
                 final ActiveAdmin admin = getActiveAdminUncheckedLocked(who, userHandle, parent);
@@ -5444,7 +3978,11 @@
         if (!mHasFeature) {
             new PasswordMetrics(CREDENTIAL_TYPE_NONE);
         }
-        enforceFullCrossUsersPermission(userHandle);
+        Preconditions.checkArgumentNonnegative(userHandle, "Invalid userId");
+
+        final CallerIdentity identity = getCallerIdentity();
+        Preconditions.checkCallAuthorization(hasFullCrossUsersPermission(identity, userHandle));
+
         ArrayList<PasswordMetrics> adminMetrics = new ArrayList<>();
         synchronized (getLockObject()) {
             List<ActiveAdmin> admins =
@@ -5461,7 +3999,10 @@
         if (!mHasFeature) {
             return true;
         }
-        enforceFullCrossUsersPermission(userHandle);
+        Preconditions.checkArgumentNonnegative(userHandle, "Invalid userId");
+
+        final CallerIdentity identity = getCallerIdentity();
+        Preconditions.checkCallAuthorization(hasFullCrossUsersPermission(identity, userHandle));
         enforceUserUnlocked(userHandle, parent);
 
         synchronized (getLockObject()) {
@@ -5493,7 +4034,10 @@
         if (!mHasFeature) {
             return true;
         }
-        enforceFullCrossUsersPermission(userHandle);
+        Preconditions.checkArgumentNonnegative(userHandle, "Invalid userId");
+
+        final CallerIdentity identity = getCallerIdentity();
+        Preconditions.checkCallAuthorization(hasFullCrossUsersPermission(identity, userHandle));
         enforceManagedProfile(userHandle, "call APIs refering to the parent profile");
 
         synchronized (getLockObject()) {
@@ -5512,7 +4056,10 @@
         if (!mHasFeature) {
             return true;
         }
-        enforceFullCrossUsersPermission(userHandle);
+        Preconditions.checkArgumentNonnegative(userHandle, "Invalid userId");
+
+        final CallerIdentity identity = getCallerIdentity();
+        Preconditions.checkCallAuthorization(hasFullCrossUsersPermission(identity, userHandle));
         enforceNotManagedProfile(userHandle, "check password sufficiency");
         enforceUserUnlocked(userHandle);
 
@@ -5600,12 +4147,15 @@
         if (!mLockPatternUtils.hasSecureLockScreen()) {
             return 0;
         }
-        enforceFullCrossUsersPermission(userHandle);
+        Preconditions.checkArgumentNonnegative(userHandle, "Invalid userId");
+
+        final CallerIdentity identity = getCallerIdentity();
+        Preconditions.checkCallAuthorization(hasFullCrossUsersPermission(identity, userHandle));
+
         synchronized (getLockObject()) {
-            if (!isCallerWithSystemUid()) {
+            if (!isSystemUid(identity)) {
                 // This API can be called by an active device admin or by keyguard code.
-                if (mContext.checkCallingPermission(permission.ACCESS_KEYGUARD_SECURE_STORAGE)
-                        != PackageManager.PERMISSION_GRANTED) {
+                if (!hasCallingPermission(permission.ACCESS_KEYGUARD_SECURE_STORAGE)) {
                     getActiveAdminForCallerLocked(
                             null, DeviceAdminInfo.USES_POLICY_WATCH_LOGIN, parent);
                 }
@@ -5648,7 +4198,11 @@
         if (!mHasFeature || !mLockPatternUtils.hasSecureLockScreen()) {
             return 0;
         }
-        enforceFullCrossUsersPermission(userHandle);
+        Preconditions.checkArgumentNonnegative(userHandle, "Invalid userId");
+
+        final CallerIdentity identity = getCallerIdentity();
+        Preconditions.checkCallAuthorization(hasFullCrossUsersPermission(identity, userHandle));
+
         synchronized (getLockObject()) {
             ActiveAdmin admin = (who != null)
                     ? getActiveAdminUncheckedLocked(who, userHandle, parent)
@@ -5662,7 +4216,11 @@
         if (!mHasFeature || !mLockPatternUtils.hasSecureLockScreen()) {
             return UserHandle.USER_NULL;
         }
-        enforceFullCrossUsersPermission(userHandle);
+        Preconditions.checkArgumentNonnegative(userHandle, "Invalid userId");
+
+        final CallerIdentity identity = getCallerIdentity();
+        Preconditions.checkCallAuthorization(hasFullCrossUsersPermission(identity, userHandle));
+
         synchronized (getLockObject()) {
             ActiveAdmin admin = getAdminWithMinimumFailedPasswordsForWipeLocked(
                     userHandle, parent);
@@ -5733,8 +4291,7 @@
 
         // As of R, only privlleged caller holding RESET_PASSWORD can call resetPassword() to
         // set password to an unsecured user.
-        if (mContext.checkCallingPermission(permission.RESET_PASSWORD)
-                == PackageManager.PERMISSION_GRANTED) {
+        if (hasCallingPermission(permission.RESET_PASSWORD)) {
             return setPasswordPrivileged(password, flags, callingUid);
         }
 
@@ -5841,8 +4398,8 @@
     private void setDoNotAskCredentialsOnBoot() {
         synchronized (getLockObject()) {
             DevicePolicyData policyData = getUserData(UserHandle.USER_SYSTEM);
-            if (!policyData.doNotAskCredentialsOnBoot) {
-                policyData.doNotAskCredentialsOnBoot = true;
+            if (!policyData.mDoNotAskCredentialsOnBoot) {
+                policyData.mDoNotAskCredentialsOnBoot = true;
                 saveSettingsLocked(UserHandle.USER_SYSTEM);
             }
         }
@@ -5854,7 +4411,7 @@
                 android.Manifest.permission.QUERY_DO_NOT_ASK_CREDENTIALS_ON_BOOT, null);
         synchronized (getLockObject()) {
             DevicePolicyData policyData = getUserData(UserHandle.USER_SYSTEM);
-            return policyData.doNotAskCredentialsOnBoot;
+            return policyData.mDoNotAskCredentialsOnBoot;
         }
     }
 
@@ -5934,7 +4491,11 @@
         if (!mHasFeature) {
             return 0;
         }
-        enforceFullCrossUsersPermission(userHandle);
+        Preconditions.checkArgumentNonnegative(userHandle, "Invalid userId");
+
+        final CallerIdentity identity = getCallerIdentity();
+        Preconditions.checkCallAuthorization(hasFullCrossUsersPermission(identity, userHandle));
+
         synchronized (getLockObject()) {
             if (who != null) {
                 final ActiveAdmin admin = getActiveAdminUncheckedLocked(who, userHandle, parent);
@@ -6007,12 +4568,16 @@
         if (!mHasFeature) {
             return DevicePolicyManager.DEFAULT_STRONG_AUTH_TIMEOUT_MS;
         }
+        Preconditions.checkArgumentNonnegative(userId, "Invalid userId");
+
+        final CallerIdentity identity = getCallerIdentity();
+        Preconditions.checkCallAuthorization(hasFullCrossUsersPermission(identity, userId));
+
         if (!mLockPatternUtils.hasSecureLockScreen()) {
             // No strong auth timeout on devices not supporting the
             // {@link PackageManager#FEATURE_SECURE_LOCK_SCREEN} feature
             return 0;
         }
-        enforceFullCrossUsersPermission(userId);
         synchronized (getLockObject()) {
             if (who != null) {
                 ActiveAdmin admin = getActiveAdminUncheckedLocked(who, userId, parent);
@@ -6046,8 +4611,7 @@
 
     @Override
     public void lockNow(int flags, boolean parent) {
-        if (!mHasFeature && mContext.checkCallingPermission(android.Manifest.permission.LOCK_DEVICE)
-                != PackageManager.PERMISSION_GRANTED) {
+        if (!mHasFeature && !hasCallingPermission(permission.LOCK_DEVICE)) {
             return;
         }
 
@@ -6132,12 +4696,6 @@
         }
     }
 
-    private void enforceDeviceOwner(ComponentName who) {
-        synchronized (getLockObject()) {
-            getActiveAdminForCallerLocked(who, DeviceAdminInfo.USES_POLICY_DEVICE_OWNER);
-        }
-    }
-
     private void enforceProfileOrDeviceOwner(ComponentName who) {
         synchronized (getLockObject()) {
             getActiveAdminForCallerLocked(who, DeviceAdminInfo.USES_POLICY_PROFILE_OWNER);
@@ -6145,8 +4703,7 @@
     }
 
     private void enforceNetworkStackOrProfileOrDeviceOwner(ComponentName who) {
-        if (mContext.checkCallingPermission(PERMISSION_MAINLINE_NETWORK_STACK)
-                == PackageManager.PERMISSION_GRANTED) {
+        if (hasCallingPermission(PERMISSION_MAINLINE_NETWORK_STACK)) {
             return;
         }
         enforceProfileOrDeviceOwner(who);
@@ -6737,20 +5294,23 @@
         Objects.requireNonNull(who, "ComponentName is null");
         Preconditions.checkStringNotEmpty(delegatePackage, "Delegate package is null or empty");
         Preconditions.checkCollectionElementsNotNull(scopeList, "Scopes");
+        final CallerIdentity identity = getCallerIdentity(who);
+
         // Remove possible duplicates.
         final ArrayList<String> scopes = new ArrayList(new ArraySet(scopeList));
         // Ensure given scopes are valid.
         if (scopes.retainAll(Arrays.asList(DELEGATIONS))) {
             throw new IllegalArgumentException("Unexpected delegation scopes");
         }
-        final boolean hasDoDelegation = !Collections.disjoint(scopes, DEVICE_OWNER_DELEGATIONS);
         // Retrieve the user ID of the calling process.
-        final int userId = mInjector.userHandleGetCallingUserId();
+        final int userId = identity.getUserId();
+        final boolean hasDoDelegation = !Collections.disjoint(scopes, DEVICE_OWNER_DELEGATIONS);
         synchronized (getLockObject()) {
             // Ensure calling process is device/profile owner.
             if (hasDoDelegation) {
-                getActiveAdminForCallerLocked(who, DeviceAdminInfo.USES_POLICY_DEVICE_OWNER);
+                Preconditions.checkCallAuthorization(isDeviceOwner(identity));
             } else {
+                // TODO move whole condition out of synchronized block
                 getActiveAdminForCallerLocked(who, DeviceAdminInfo.USES_POLICY_PROFILE_OWNER);
             }
             // Ensure the delegate is installed (skip this for DELEGATION_CERT_INSTALL in pre-N).
@@ -7222,8 +5782,9 @@
         if (!mHasFeature) {
             return;
         }
-
-        enforceFullCrossUsersPermission(mInjector.userHandleGetCallingUserId());
+        final CallerIdentity identity = getCallerIdentity();
+        Preconditions.checkCallAuthorization(isSystemUid(identity) || isRootUid(identity)
+                || hasCallingOrSelfPermission(permission.INTERACT_ACROSS_USERS_FULL));
 
         final ActiveAdmin admin;
         synchronized (getLockObject()) {
@@ -7399,8 +5960,7 @@
         synchronized (getLockObject()) {
             if (who == null) {
                 if ((frpManagementAgentUid != mInjector.binderGetCallingUid())
-                        && (mContext.checkCallingPermission(permission.MASTER_CLEAR)
-                        != PackageManager.PERMISSION_GRANTED)) {
+                        && !hasCallingPermission(permission.MASTER_CLEAR)) {
                     throw new SecurityException(
                             "Must be called by the FRP management agent on device");
                 }
@@ -7438,9 +5998,13 @@
         if (!mHasFeature) {
             return;
         }
-        enforceFullCrossUsersPermission(userHandle);
-        mContext.enforceCallingOrSelfPermission(
-                android.Manifest.permission.BIND_DEVICE_ADMIN, null);
+        Preconditions.checkArgumentNonnegative(userHandle, "Invalid userId");
+
+        final CallerIdentity identity = comp != null
+                ? getCallerIdentity(comp)
+                : getCallerIdentity();
+        Preconditions.checkCallAuthorization(hasFullCrossUsersPermission(identity, userHandle));
+        Preconditions.checkCallAuthorization(hasCallingOrSelfPermission(BIND_DEVICE_ADMIN));
 
         synchronized (getLockObject()) {
             ActiveAdmin admin = getActiveAdminUncheckedLocked(comp, userHandle);
@@ -7517,13 +6081,15 @@
 
     @Override
     public void reportFailedPasswordAttempt(int userHandle) {
-        enforceFullCrossUsersPermission(userHandle);
+        Preconditions.checkArgumentNonnegative(userHandle, "Invalid userId");
+
+        final CallerIdentity identity = getCallerIdentity();
+        Preconditions.checkCallAuthorization(hasFullCrossUsersPermission(identity, userHandle));
+        Preconditions.checkCallAuthorization(hasCallingOrSelfPermission(BIND_DEVICE_ADMIN));
         if (!isSeparateProfileChallengeEnabled(userHandle)) {
             enforceNotManagedProfile(userHandle,
                     "report failed password attempt if separate profile challenge is not in place");
         }
-        mContext.enforceCallingOrSelfPermission(
-                android.Manifest.permission.BIND_DEVICE_ADMIN, null);
 
         boolean wipeData = false;
         ActiveAdmin strictestAdmin = null;
@@ -7596,9 +6162,11 @@
 
     @Override
     public void reportSuccessfulPasswordAttempt(int userHandle) {
-        enforceFullCrossUsersPermission(userHandle);
-        mContext.enforceCallingOrSelfPermission(
-                android.Manifest.permission.BIND_DEVICE_ADMIN, null);
+        Preconditions.checkArgumentNonnegative(userHandle, "Invalid userId");
+
+        final CallerIdentity identity = getCallerIdentity();
+        Preconditions.checkCallAuthorization(hasFullCrossUsersPermission(identity, userHandle));
+        Preconditions.checkCallAuthorization(hasCallingOrSelfPermission(BIND_DEVICE_ADMIN));
 
         synchronized (getLockObject()) {
             DevicePolicyData policy = getUserData(userHandle);
@@ -7624,9 +6192,12 @@
 
     @Override
     public void reportFailedBiometricAttempt(int userHandle) {
-        enforceFullCrossUsersPermission(userHandle);
-        mContext.enforceCallingOrSelfPermission(
-                android.Manifest.permission.BIND_DEVICE_ADMIN, null);
+        Preconditions.checkArgumentNonnegative(userHandle, "Invalid userId");
+
+        final CallerIdentity identity = getCallerIdentity();
+        Preconditions.checkCallAuthorization(hasFullCrossUsersPermission(identity, userHandle));
+        Preconditions.checkCallAuthorization(hasCallingOrSelfPermission(BIND_DEVICE_ADMIN));
+
         if (mInjector.securityLogIsLoggingEnabled()) {
             SecurityLog.writeEvent(SecurityLog.TAG_KEYGUARD_DISMISS_AUTH_ATTEMPT, /*result*/ 0,
                     /*method strength*/ 0);
@@ -7635,9 +6206,12 @@
 
     @Override
     public void reportSuccessfulBiometricAttempt(int userHandle) {
-        enforceFullCrossUsersPermission(userHandle);
-        mContext.enforceCallingOrSelfPermission(
-                android.Manifest.permission.BIND_DEVICE_ADMIN, null);
+        Preconditions.checkArgumentNonnegative(userHandle, "Invalid userId");
+
+        final CallerIdentity identity = getCallerIdentity();
+        Preconditions.checkCallAuthorization(hasFullCrossUsersPermission(identity, userHandle));
+        Preconditions.checkCallAuthorization(hasCallingOrSelfPermission(BIND_DEVICE_ADMIN));
+
         if (mInjector.securityLogIsLoggingEnabled()) {
             SecurityLog.writeEvent(SecurityLog.TAG_KEYGUARD_DISMISS_AUTH_ATTEMPT, /*result*/ 1,
                     /*method strength*/ 0);
@@ -7646,9 +6220,11 @@
 
     @Override
     public void reportKeyguardDismissed(int userHandle) {
-        enforceFullCrossUsersPermission(userHandle);
-        mContext.enforceCallingOrSelfPermission(
-                android.Manifest.permission.BIND_DEVICE_ADMIN, null);
+        Preconditions.checkArgumentNonnegative(userHandle, "Invalid userId");
+
+        final CallerIdentity identity = getCallerIdentity();
+        Preconditions.checkCallAuthorization(hasFullCrossUsersPermission(identity, userHandle));
+        Preconditions.checkCallAuthorization(hasCallingOrSelfPermission(BIND_DEVICE_ADMIN));
 
         if (mInjector.securityLogIsLoggingEnabled()) {
             SecurityLog.writeEvent(SecurityLog.TAG_KEYGUARD_DISMISSED);
@@ -7657,9 +6233,11 @@
 
     @Override
     public void reportKeyguardSecured(int userHandle) {
-        enforceFullCrossUsersPermission(userHandle);
-        mContext.enforceCallingOrSelfPermission(
-                android.Manifest.permission.BIND_DEVICE_ADMIN, null);
+        Preconditions.checkArgumentNonnegative(userHandle, "Invalid userId");
+
+        final CallerIdentity identity = getCallerIdentity();
+        Preconditions.checkCallAuthorization(hasFullCrossUsersPermission(identity, userHandle));
+        Preconditions.checkCallAuthorization(hasCallingOrSelfPermission(BIND_DEVICE_ADMIN));
 
         if (mInjector.securityLogIsLoggingEnabled()) {
             SecurityLog.writeEvent(SecurityLog.TAG_KEYGUARD_SECURED);
@@ -7721,7 +6299,11 @@
         if (!mHasFeature) {
             return null;
         }
-        enforceFullCrossUsersPermission(userHandle);
+        Preconditions.checkArgumentNonnegative(userHandle, "Invalid userId");
+
+        final CallerIdentity identity = getCallerIdentity();
+        Preconditions.checkCallAuthorization(hasFullCrossUsersPermission(identity, userHandle));
+
         synchronized (getLockObject()) {
             DevicePolicyData policy = getUserData(UserHandle.USER_SYSTEM);
             // Scan through active admins and find if anyone has already
@@ -7742,7 +6324,9 @@
 
     @Override
     public void setRecommendedGlobalProxy(ComponentName who, ProxyInfo proxyInfo) {
-        enforceDeviceOwner(who);
+        Objects.requireNonNull(who, "ComponentName is null");
+        final CallerIdentity identity = getCallerIdentity(who);
+        Preconditions.checkCallAuthorization(isDeviceOwner(identity));
         mInjector.binderWithCleanCallingIdentity(
                 () -> mInjector.getConnectivityManager().setGlobalProxy(proxyInfo));
     }
@@ -7853,7 +6437,13 @@
         if (!mHasFeature) {
             return false;
         }
-        enforceFullCrossUsersPermission(userHandle);
+        Preconditions.checkArgumentNonnegative(userHandle, "Invalid userId");
+
+        final CallerIdentity identity = who != null
+                ? getCallerIdentity(who)
+                : getCallerIdentity();
+        Preconditions.checkCallAuthorization(hasFullCrossUsersPermission(identity, userHandle));
+
         synchronized (getLockObject()) {
             // Check for permissions if a particular caller is specified
             if (who != null) {
@@ -7883,7 +6473,12 @@
         if (!mHasFeature) {
             // Ok to return current status.
         }
-        enforceFullCrossUsersPermission(userHandle);
+        Preconditions.checkArgumentNonnegative(userHandle, "Invalid userId");
+
+        final CallerIdentity identity = callerPackage != null
+                ? getCallerIdentity(callerPackage)
+                : getCallerIdentity();
+        Preconditions.checkCallAuthorization(hasFullCrossUsersPermission(identity, userHandle));
 
         // It's not critical here, but let's make sure the package name is correct, in case
         // we start using it for different purposes.
@@ -8028,24 +6623,23 @@
             return;
         }
         Objects.requireNonNull(who, "ComponentName is null");
-        final int userHandle = UserHandle.getCallingUserId();
+        final CallerIdentity identity = getCallerIdentity(who);
+
         boolean requireAutoTimeChanged = false;
         synchronized (getLockObject()) {
-            if (isManagedProfile(userHandle)) {
-                throw new SecurityException("Managed profile cannot set auto time required");
-            }
-            ActiveAdmin admin = getActiveAdminForCallerLocked(who,
-                    DeviceAdminInfo.USES_POLICY_PROFILE_OWNER);
+            Preconditions.checkSecurity(!isManagedProfile(identity.getUserId()),
+                    "Managed profile cannot set auto time required");
+            ActiveAdmin admin = getProfileOwnerOrDeviceOwnerLocked(identity);
             if (admin.requireAutoTime != required) {
                 admin.requireAutoTime = required;
-                saveSettingsLocked(userHandle);
+                saveSettingsLocked(identity.getUserId());
                 requireAutoTimeChanged = true;
             }
         }
         // requireAutoTime is now backed by DISALLOW_CONFIG_DATE_TIME restriction, so propagate
         // updated restrictions to the framework.
         if (requireAutoTimeChanged) {
-            pushUserRestrictions(userHandle);
+            pushUserRestrictions(identity.getUserId());
         }
         // Turn AUTO_TIME on in settings if it is required
         if (required) {
@@ -8163,6 +6757,9 @@
             return;
         }
         Objects.requireNonNull(who, "ComponentName is null");
+        final CallerIdentity identity = getCallerIdentity(who);
+        Preconditions.checkCallAuthorization(isDeviceOwner(identity));
+
         // Allow setting this policy to true only if there is a split system user.
         if (forceEphemeralUsers && !mInjector.userManagerIsSplitSystemUser()) {
             throw new UnsupportedOperationException(
@@ -8170,11 +6767,10 @@
         }
         boolean removeAllUsers = false;
         synchronized (getLockObject()) {
-            final ActiveAdmin deviceOwner =
-                    getActiveAdminForCallerLocked(who, DeviceAdminInfo.USES_POLICY_DEVICE_OWNER);
+            final ActiveAdmin deviceOwner = getDeviceOwnerAdminLocked();
             if (deviceOwner.forceEphemeralUsers != forceEphemeralUsers) {
                 deviceOwner.forceEphemeralUsers = forceEphemeralUsers;
-                saveSettingsLocked(mInjector.userHandleGetCallingUserId());
+                saveSettingsLocked(identity.getUserId());
                 mUserManagerInternal.setForceEphemeralUsers(forceEphemeralUsers);
                 removeAllUsers = forceEphemeralUsers;
             }
@@ -8190,21 +6786,15 @@
             return false;
         }
         Objects.requireNonNull(who, "ComponentName is null");
+        final CallerIdentity identity = getCallerIdentity(who);
+        Preconditions.checkCallAuthorization(isDeviceOwner(identity));
+
         synchronized (getLockObject()) {
-            final ActiveAdmin deviceOwner =
-                    getActiveAdminForCallerLocked(who, DeviceAdminInfo.USES_POLICY_DEVICE_OWNER);
+            final ActiveAdmin deviceOwner = getDeviceOwnerAdminLocked();
             return deviceOwner.forceEphemeralUsers;
         }
     }
 
-    private void ensureDeviceOwnerAndAllUsersAffiliated(ComponentName who)
-            throws SecurityException {
-        synchronized (getLockObject()) {
-            getActiveAdminForCallerLocked(who, DeviceAdminInfo.USES_POLICY_DEVICE_OWNER);
-        }
-        ensureAllUsersAffiliated();
-    }
-
     private void ensureAllUsersAffiliated() throws SecurityException {
         synchronized (getLockObject()) {
             if (!areAllUsersAffiliatedWithDeviceLocked()) {
@@ -8219,51 +6809,31 @@
             return false;
         }
         Objects.requireNonNull(who, "ComponentName is null");
-
         // TODO: If an unaffiliated user is removed, the admin will be able to request a bugreport
         // which could still contain data related to that user. Should we disallow that, e.g. until
         // next boot? Might not be needed given that this still requires user consent.
-        ensureDeviceOwnerAndAllUsersAffiliated(who);
+        final CallerIdentity identity = getCallerIdentity(who);
+        Preconditions.checkCallAuthorization(isDeviceOwner(identity));
+        ensureAllUsersAffiliated();
 
-        if (mRemoteBugreportServiceIsActive.get()
-                || (getDeviceOwnerRemoteBugreportUri() != null)) {
-            Slog.d(LOG_TAG, "Remote bugreport wasn't started because there's already one running.");
-            return false;
-        }
-
-        final long currentTime = System.currentTimeMillis();
-        synchronized (getLockObject()) {
-            DevicePolicyData policyData = getUserData(UserHandle.USER_SYSTEM);
-            if (currentTime > policyData.mLastBugReportRequestTime) {
-                policyData.mLastBugReportRequestTime = currentTime;
-                saveSettingsLocked(UserHandle.USER_SYSTEM);
-            }
-        }
-
-        final long callingIdentity = mInjector.binderClearCallingIdentity();
-        try {
-            mInjector.getIActivityManager().requestRemoteBugReport();
-
-            mRemoteBugreportServiceIsActive.set(true);
-            mRemoteBugreportSharingAccepted.set(false);
-            registerRemoteBugreportReceivers();
-            mInjector.getNotificationManager().notifyAsUser(LOG_TAG,
-                    RemoteBugreportUtils.NOTIFICATION_ID,
-                    RemoteBugreportUtils.buildNotification(mContext,
-                            DevicePolicyManager.NOTIFICATION_BUGREPORT_STARTED), UserHandle.ALL);
-            mHandler.postDelayed(mRemoteBugreportTimeoutRunnable,
-                    RemoteBugreportUtils.REMOTE_BUGREPORT_TIMEOUT_MILLIS);
+        if (mBugreportCollectionManager.requestBugreport()) {
             DevicePolicyEventLogger
                     .createEvent(DevicePolicyEnums.REQUEST_BUGREPORT)
                     .setAdmin(who)
                     .write();
+
+            final long currentTime = System.currentTimeMillis();
+            synchronized (getLockObject()) {
+                DevicePolicyData policyData = getUserData(UserHandle.USER_SYSTEM);
+                if (currentTime > policyData.mLastBugReportRequestTime) {
+                    policyData.mLastBugReportRequestTime = currentTime;
+                    saveSettingsLocked(UserHandle.USER_SYSTEM);
+                }
+            }
+
             return true;
-        } catch (RemoteException re) {
-            // should never happen
-            Slog.e(LOG_TAG, "Failed to make remote calls to start bugreportremote service", re);
+        } else {
             return false;
-        } finally {
-            mInjector.binderRestoreCallingIdentity(callingIdentity);
         }
     }
 
@@ -8307,146 +6877,36 @@
         mContext.sendBroadcastAsUser(intent, UserHandle.of(userId));
     }
 
-    private String getDeviceOwnerRemoteBugreportUri() {
+    void sendBugreportToDeviceOwner(Uri bugreportUri, String bugreportHash) {
         synchronized (getLockObject()) {
-            return mOwners.getDeviceOwnerRemoteBugreportUri();
+            final Intent intent = new Intent(DeviceAdminReceiver.ACTION_BUGREPORT_SHARE);
+            intent.setComponent(mOwners.getDeviceOwnerComponent());
+            intent.setDataAndType(bugreportUri, RemoteBugreportManager.BUGREPORT_MIMETYPE);
+            intent.putExtra(DeviceAdminReceiver.EXTRA_BUGREPORT_HASH, bugreportHash);
+            intent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
+
+            final UriGrantsManagerInternal ugm = LocalServices
+                    .getService(UriGrantsManagerInternal.class);
+            final NeededUriGrants needed = ugm.checkGrantUriPermissionFromIntent(intent,
+                    Process.SHELL_UID, mOwners.getDeviceOwnerComponent().getPackageName(),
+                    mOwners.getDeviceOwnerUserId());
+            ugm.grantUriPermissionUncheckedFromIntent(needed, null);
+
+            mContext.sendBroadcastAsUser(intent, UserHandle.of(mOwners.getDeviceOwnerUserId()));
         }
     }
 
-    private void setDeviceOwnerRemoteBugreportUriAndHash(String bugreportUri,
-            String bugreportHash) {
+    void setDeviceOwnerRemoteBugreportUriAndHash(String bugreportUri, String bugreportHash) {
         synchronized (getLockObject()) {
             mOwners.setDeviceOwnerRemoteBugreportUriAndHash(bugreportUri, bugreportHash);
         }
     }
 
-    private void registerRemoteBugreportReceivers() {
-        try {
-            IntentFilter filterFinished = new IntentFilter(
-                    DevicePolicyManager.ACTION_REMOTE_BUGREPORT_DISPATCH,
-                    RemoteBugreportUtils.BUGREPORT_MIMETYPE);
-            mContext.registerReceiver(mRemoteBugreportFinishedReceiver, filterFinished);
-        } catch (IntentFilter.MalformedMimeTypeException e) {
-            // should never happen, as setting a constant
-            Slog.w(LOG_TAG, "Failed to set type " + RemoteBugreportUtils.BUGREPORT_MIMETYPE, e);
-        }
-        IntentFilter filterConsent = new IntentFilter();
-        filterConsent.addAction(DevicePolicyManager.ACTION_BUGREPORT_SHARING_DECLINED);
-        filterConsent.addAction(DevicePolicyManager.ACTION_BUGREPORT_SHARING_ACCEPTED);
-        mContext.registerReceiver(mRemoteBugreportConsentReceiver, filterConsent);
-    }
-
-    private void onBugreportFinished(Intent intent) {
-        mHandler.removeCallbacks(mRemoteBugreportTimeoutRunnable);
-        mRemoteBugreportServiceIsActive.set(false);
-        Uri bugreportUri = intent.getData();
-        String bugreportUriString = null;
-        if (bugreportUri != null) {
-            bugreportUriString = bugreportUri.toString();
-        }
-        String bugreportHash = intent.getStringExtra(
-                DevicePolicyManager.EXTRA_REMOTE_BUGREPORT_HASH);
-        if (mRemoteBugreportSharingAccepted.get()) {
-            shareBugreportWithDeviceOwnerIfExists(bugreportUriString, bugreportHash);
-            mInjector.getNotificationManager().cancel(LOG_TAG,
-                    RemoteBugreportUtils.NOTIFICATION_ID);
-        } else {
-            setDeviceOwnerRemoteBugreportUriAndHash(bugreportUriString, bugreportHash);
-            mInjector.getNotificationManager().notifyAsUser(LOG_TAG, RemoteBugreportUtils.NOTIFICATION_ID,
-                    RemoteBugreportUtils.buildNotification(mContext,
-                            DevicePolicyManager.NOTIFICATION_BUGREPORT_FINISHED_NOT_ACCEPTED),
-                            UserHandle.ALL);
-        }
-        mContext.unregisterReceiver(mRemoteBugreportFinishedReceiver);
-    }
-
-    private void onBugreportFailed() {
-        mRemoteBugreportServiceIsActive.set(false);
-        mInjector.systemPropertiesSet(RemoteBugreportUtils.CTL_STOP,
-                RemoteBugreportUtils.REMOTE_BUGREPORT_SERVICE);
-        mRemoteBugreportSharingAccepted.set(false);
-        setDeviceOwnerRemoteBugreportUriAndHash(null, null);
-        mInjector.getNotificationManager().cancel(LOG_TAG, RemoteBugreportUtils.NOTIFICATION_ID);
-        Bundle extras = new Bundle();
-        extras.putInt(DeviceAdminReceiver.EXTRA_BUGREPORT_FAILURE_REASON,
-                DeviceAdminReceiver.BUGREPORT_FAILURE_FAILED_COMPLETING);
-        sendDeviceOwnerCommand(DeviceAdminReceiver.ACTION_BUGREPORT_FAILED, extras);
-        mContext.unregisterReceiver(mRemoteBugreportConsentReceiver);
-        mContext.unregisterReceiver(mRemoteBugreportFinishedReceiver);
-    }
-
-    private void onBugreportSharingAccepted() {
-        mRemoteBugreportSharingAccepted.set(true);
-        String bugreportUriString = null;
-        String bugreportHash = null;
+    Pair<String, String> getDeviceOwnerRemoteBugreportUriAndHash() {
         synchronized (getLockObject()) {
-            bugreportUriString = getDeviceOwnerRemoteBugreportUri();
-            bugreportHash = mOwners.getDeviceOwnerRemoteBugreportHash();
-        }
-        if (bugreportUriString != null) {
-            shareBugreportWithDeviceOwnerIfExists(bugreportUriString, bugreportHash);
-        } else if (mRemoteBugreportServiceIsActive.get()) {
-            mInjector.getNotificationManager().notifyAsUser(LOG_TAG, RemoteBugreportUtils.NOTIFICATION_ID,
-                    RemoteBugreportUtils.buildNotification(mContext,
-                            DevicePolicyManager.NOTIFICATION_BUGREPORT_ACCEPTED_NOT_FINISHED),
-                            UserHandle.ALL);
-        }
-    }
-
-    private void onBugreportSharingDeclined() {
-        if (mRemoteBugreportServiceIsActive.get()) {
-            mInjector.systemPropertiesSet(RemoteBugreportUtils.CTL_STOP,
-                    RemoteBugreportUtils.REMOTE_BUGREPORT_SERVICE);
-            mRemoteBugreportServiceIsActive.set(false);
-            mHandler.removeCallbacks(mRemoteBugreportTimeoutRunnable);
-            mContext.unregisterReceiver(mRemoteBugreportFinishedReceiver);
-        }
-        mRemoteBugreportSharingAccepted.set(false);
-        setDeviceOwnerRemoteBugreportUriAndHash(null, null);
-        sendDeviceOwnerCommand(DeviceAdminReceiver.ACTION_BUGREPORT_SHARING_DECLINED, null);
-    }
-
-    private void shareBugreportWithDeviceOwnerIfExists(String bugreportUriString,
-            String bugreportHash) {
-        ParcelFileDescriptor pfd = null;
-        try {
-            if (bugreportUriString == null) {
-                throw new FileNotFoundException();
-            }
-            Uri bugreportUri = Uri.parse(bugreportUriString);
-            pfd = mContext.getContentResolver().openFileDescriptor(bugreportUri, "r");
-
-            synchronized (getLockObject()) {
-                Intent intent = new Intent(DeviceAdminReceiver.ACTION_BUGREPORT_SHARE);
-                intent.setComponent(mOwners.getDeviceOwnerComponent());
-                intent.setDataAndType(bugreportUri, RemoteBugreportUtils.BUGREPORT_MIMETYPE);
-                intent.putExtra(DeviceAdminReceiver.EXTRA_BUGREPORT_HASH, bugreportHash);
-                intent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
-
-                final UriGrantsManagerInternal ugm = LocalServices
-                        .getService(UriGrantsManagerInternal.class);
-                final NeededUriGrants needed = ugm.checkGrantUriPermissionFromIntent(intent,
-                        Process.SHELL_UID, mOwners.getDeviceOwnerComponent().getPackageName(),
-                        mOwners.getDeviceOwnerUserId());
-                ugm.grantUriPermissionUncheckedFromIntent(needed, null);
-
-                mContext.sendBroadcastAsUser(intent, UserHandle.of(mOwners.getDeviceOwnerUserId()));
-            }
-        } catch (FileNotFoundException e) {
-            Bundle extras = new Bundle();
-            extras.putInt(DeviceAdminReceiver.EXTRA_BUGREPORT_FAILURE_REASON,
-                    DeviceAdminReceiver.BUGREPORT_FAILURE_FILE_NO_LONGER_AVAILABLE);
-            sendDeviceOwnerCommand(DeviceAdminReceiver.ACTION_BUGREPORT_FAILED, extras);
-        } finally {
-            try {
-                if (pfd != null) {
-                    pfd.close();
-                }
-            } catch (IOException ex) {
-                // Ignore
-            }
-            mRemoteBugreportSharingAccepted.set(false);
-            setDeviceOwnerRemoteBugreportUriAndHash(null, null);
+            final String uri = mOwners.getDeviceOwnerRemoteBugreportUri();
+            return uri == null ? null
+                    : new Pair<>(uri, mOwners.getDeviceOwnerRemoteBugreportHash());
         }
     }
 
@@ -8579,7 +7039,11 @@
         if (!mHasFeature) {
             return 0;
         }
-        enforceFullCrossUsersPermission(userHandle);
+        Preconditions.checkArgumentNonnegative(userHandle, "Invalid userId");
+
+        final CallerIdentity identity = getCallerIdentity();
+        Preconditions.checkCallAuthorization(hasFullCrossUsersPermission(identity, userHandle));
+
         final long ident = mInjector.binderClearCallingIdentity();
         try {
             synchronized (getLockObject()) {
@@ -8747,6 +7211,14 @@
         }
     }
 
+    private boolean isDeviceOwner(CallerIdentity identity) {
+        synchronized (getLockObject()) {
+            return mOwners.hasDeviceOwner()
+                    && mOwners.getDeviceOwnerUserId() == identity.getUserId()
+                    && mOwners.getDeviceOwnerComponent().equals(identity.getComponentName());
+        }
+    }
+
     private boolean isDeviceOwnerPackage(String packageName, int userId) {
         synchronized (getLockObject()) {
             return mOwners.hasDeviceOwner()
@@ -8767,6 +7239,16 @@
         return who != null && who.equals(profileOwner);
     }
 
+    /**
+     * Returns {@code true} if the provided caller identity is of a profile owner.
+     * @param identity identity of caller.
+     * @return true if {@code identity} is a profile owner, false otherwise.
+     */
+    public boolean isProfileOwner(CallerIdentity identity) {
+        final ComponentName profileOwner = getProfileOwner(identity.getUserId());
+        return profileOwner != null && profileOwner.equals(identity.getComponentName());
+    }
+
     private boolean hasProfileOwner(int userId) {
         synchronized (getLockObject()) {
             return mOwners.hasProfileOwner(userId);
@@ -9321,7 +7803,10 @@
 
     @Override
     public ComponentName getProfileOwnerAsUser(int userHandle) {
-        enforceCrossUsersPermission(userHandle);
+        Preconditions.checkArgumentNonnegative(userHandle, "Invalid userId");
+
+        final CallerIdentity identity = getCallerIdentity();
+        Preconditions.checkCallAuthorization(hasCrossUsersPermission(identity, userHandle));
 
         return getProfileOwner(userHandle);
     }
@@ -9678,56 +8163,31 @@
         }
     }
 
-    private void enforceAcrossUsersPermissions() {
-        final int callingUid = mInjector.binderGetCallingUid();
+    private boolean hasCallingPermission(String permission) {
+        return mContext.checkCallingPermission(permission) == PackageManager.PERMISSION_GRANTED;
+    }
+
+    private boolean hasCallingOrSelfPermission(String permission) {
+        return mContext.checkCallingOrSelfPermission(permission)
+                == PackageManager.PERMISSION_GRANTED;
+    }
+
+    private boolean hasPermissionForPreflight(CallerIdentity identity, String permission) {
         final int callingPid = mInjector.binderGetCallingPid();
         final String packageName = mContext.getPackageName();
 
-        if (isCallerWithSystemUid() || callingUid == Process.ROOT_UID) {
-            return;
-        }
-        if (PermissionChecker.checkPermissionForPreflight(
-                mContext, permission.INTERACT_ACROSS_PROFILES, callingPid, callingUid,
-                packageName) == PermissionChecker.PERMISSION_GRANTED) {
-            return;
-        }
-        if (mContext.checkCallingPermission(permission.INTERACT_ACROSS_USERS)
-                == PackageManager.PERMISSION_GRANTED) {
-            return;
-        }
-        if (mContext.checkCallingPermission(permission.INTERACT_ACROSS_USERS_FULL)
-                == PackageManager.PERMISSION_GRANTED) {
-            return;
-        }
-        throw new SecurityException("Calling user does not have INTERACT_ACROSS_PROFILES or"
-                + "INTERACT_ACROSS_USERS or INTERACT_ACROSS_USERS_FULL permissions");
+        return PermissionChecker.checkPermissionForPreflight(mContext, permission, callingPid,
+                identity.getUid(), packageName) == PermissionChecker.PERMISSION_GRANTED;
     }
 
-    private void enforceFullCrossUsersPermission(int userHandle) {
-        enforceSystemUserOrPermissionIfCrossUser(userHandle,
-                android.Manifest.permission.INTERACT_ACROSS_USERS_FULL);
+    private boolean hasFullCrossUsersPermission(CallerIdentity identity, int userHandle) {
+        return (userHandle == identity.getUserId()) || isSystemUid(identity) || isRootUid(identity)
+                || hasCallingOrSelfPermission(permission.INTERACT_ACROSS_USERS_FULL);
     }
 
-    private void enforceCrossUsersPermission(int userHandle) {
-        enforceSystemUserOrPermissionIfCrossUser(userHandle,
-                android.Manifest.permission.INTERACT_ACROSS_USERS);
-    }
-
-    private void enforceSystemUserOrPermission(String permission) {
-        if (!(isCallerWithSystemUid() || mInjector.binderGetCallingUid() == Process.ROOT_UID)) {
-            mContext.enforceCallingOrSelfPermission(permission,
-                    "Must be system or have " + permission + " permission");
-        }
-    }
-
-    private void enforceSystemUserOrPermissionIfCrossUser(int userHandle, String permission) {
-        if (userHandle < 0) {
-            throw new IllegalArgumentException("Invalid userId " + userHandle);
-        }
-        if (userHandle == mInjector.userHandleGetCallingUserId()) {
-            return;
-        }
-        enforceSystemUserOrPermission(permission);
+    private boolean hasCrossUsersPermission(CallerIdentity identity, int userHandle) {
+        return (userHandle == identity.getUserId()) || isSystemUid(identity) || isRootUid(identity)
+                || hasCallingOrSelfPermission(permission.INTERACT_ACROSS_USERS);
     }
 
     private void enforceManagedProfile(int userId, String message) {
@@ -9787,19 +8247,18 @@
         throw new SecurityException("No active admin found");
     }
 
-    private void enforceProfileOwnerOrFullCrossUsersPermission(int userId) {
-        if (userId == mInjector.userHandleGetCallingUserId()) {
+    private void enforceProfileOwnerOrFullCrossUsersPermission(CallerIdentity identity,
+            int userId) {
+        if (userId == identity.getUserId()) {
             synchronized (getLockObject()) {
                 if (getActiveAdminWithPolicyForUidLocked(null,
-                        DeviceAdminInfo.USES_POLICY_PROFILE_OWNER, mInjector.binderGetCallingUid())
-                                != null) {
+                        DeviceAdminInfo.USES_POLICY_PROFILE_OWNER, identity.getUid()) != null) {
                     // Device Owner/Profile Owner may access the user it runs on.
                     return;
                 }
             }
         }
-        // Otherwise, INTERACT_ACROSS_USERS_FULL permission, system UID or root UID is required.
-        enforceSystemUserOrPermission(android.Manifest.permission.INTERACT_ACROSS_USERS_FULL);
+        Preconditions.checkCallAuthorization(hasFullCrossUsersPermission(identity, userId));
     }
 
     private boolean canUserUseLockTaskLocked(int userId) {
@@ -9853,6 +8312,18 @@
         return UserHandle.isSameApp(mInjector.binderGetCallingUid(), Process.SYSTEM_UID);
     }
 
+    private boolean isSystemUid(CallerIdentity identity) {
+        return UserHandle.isSameApp(identity.getUid(), Process.SYSTEM_UID);
+    }
+
+    private boolean isRootUid(CallerIdentity identity) {
+        return UserHandle.isSameApp(identity.getUid(), Process.ROOT_UID);
+    }
+
+    private boolean isShellUid(CallerIdentity identity) {
+        return UserHandle.isSameApp(identity.getUid(), Process.SHELL_UID);
+    }
+
     protected int getProfileParentId(int userHandle) {
         return mInjector.binderWithCleanCallingIdentity(() -> {
             UserInfo parentUser = mUserManager.getProfileParent(userHandle);
@@ -10024,6 +8495,7 @@
     @Override
     public void setDefaultSmsApplication(ComponentName admin, String packageName, boolean parent) {
         Objects.requireNonNull(admin, "ComponentName is null");
+        final CallerIdentity identity = getCallerIdentity(admin);
 
         if (parent) {
             ActiveAdmin ap = getActiveAdminForCallerLocked(admin,
@@ -10032,7 +8504,7 @@
             mInjector.binderWithCleanCallingIdentity(() -> enforcePackageIsSystemPackage(
                     packageName, getProfileParentId(mInjector.userHandleGetCallingUserId())));
         } else {
-            enforceDeviceOwner(admin);
+            Preconditions.checkCallAuthorization(isDeviceOwner(identity));
         }
 
         mInjector.binderWithCleanCallingIdentity(() ->
@@ -10106,7 +8578,12 @@
             return null;
         }
         Objects.requireNonNull(agent, "agent null");
-        enforceFullCrossUsersPermission(userHandle);
+        Preconditions.checkArgumentNonnegative(userHandle, "Invalid userId");
+
+        final CallerIdentity identity = admin != null
+                ? getCallerIdentity(admin)
+                : getCallerIdentity();
+        Preconditions.checkCallAuthorization(hasFullCrossUsersPermission(identity, userHandle));
 
         synchronized (getLockObject()) {
             final String componentName = agent.flattenToString();
@@ -10306,9 +8783,10 @@
             return false;
         }
         Objects.requireNonNull(who, "ComponentName is null");
+        final CallerIdentity identity = getCallerIdentity(who);
 
         if (packageList != null) {
-            int userId = UserHandle.getCallingUserId();
+            int userId = identity.getUserId();
             List<AccessibilityServiceInfo> enabledServices = null;
             long id = mInjector.binderClearCallingIdentity();
             try {
@@ -10338,8 +8816,7 @@
         }
 
         synchronized (getLockObject()) {
-            ActiveAdmin admin = getActiveAdminForCallerLocked(who,
-                    DeviceAdminInfo.USES_POLICY_PROFILE_OWNER);
+            ActiveAdmin admin = getProfileOwnerOrDeviceOwnerLocked(identity);
             admin.permittedAccessiblityServices = packageList;
             saveSettingsLocked(UserHandle.getCallingUserId());
         }
@@ -10359,10 +8836,11 @@
             return null;
         }
         Objects.requireNonNull(who, "ComponentName is null");
+        final CallerIdentity identity = getCallerIdentity(who);
+        Preconditions.checkCallAuthorization(isDeviceOwner(identity) || isProfileOwner(identity));
 
         synchronized (getLockObject()) {
-            ActiveAdmin admin = getActiveAdminForCallerLocked(who,
-                    DeviceAdminInfo.USES_POLICY_PROFILE_OWNER);
+            ActiveAdmin admin = getProfileOwnerOrDeviceOwnerLocked(identity);
             return admin.permittedAccessiblityServices;
         }
     }
@@ -10457,17 +8935,19 @@
             return false;
         }
         Objects.requireNonNull(who, "ComponentName is null");
-        final int callingUserId = mInjector.userHandleGetCallingUserId();
+        final CallerIdentity identity = getCallerIdentity(who);
+        Preconditions.checkCallAuthorization(isDeviceOwner(identity) || isProfileOwner(identity));
+
         if (packageList != null) {
             List<InputMethodInfo> enabledImes = InputMethodManagerInternal.get()
-                    .getEnabledInputMethodListAsUser(callingUserId);
+                    .getEnabledInputMethodListAsUser(identity.getUserId());
             if (enabledImes != null) {
                 List<String> enabledPackages = new ArrayList<String>();
                 for (InputMethodInfo ime : enabledImes) {
                     enabledPackages.add(ime.getPackageName());
                 }
                 if (!checkPackagesInPermittedListOrSystem(enabledPackages, packageList,
-                        callingUserId)) {
+                        identity.getUserId())) {
                     Slog.e(LOG_TAG, "Cannot set permitted input methods, "
                             + "because it contains already enabled input method.");
                     return false;
@@ -10476,10 +8956,9 @@
         }
 
         synchronized (getLockObject()) {
-            ActiveAdmin admin = getActiveAdminForCallerLocked(who,
-                    DeviceAdminInfo.USES_POLICY_PROFILE_OWNER);
+            ActiveAdmin admin = getProfileOwnerOrDeviceOwnerLocked(identity);
             admin.permittedInputMethods = packageList;
-            saveSettingsLocked(callingUserId);
+            saveSettingsLocked(identity.getUserId());
         }
         final String[] packageArray =
                 packageList != null ? ((List<String>) packageList).toArray(new String[0]) : null;
@@ -10497,10 +8976,11 @@
             return null;
         }
         Objects.requireNonNull(who, "ComponentName is null");
+        final CallerIdentity identity = getCallerIdentity(who);
+        Preconditions.checkCallAuthorization(isDeviceOwner(identity) || isProfileOwner(identity));
 
         synchronized (getLockObject()) {
-            ActiveAdmin admin = getActiveAdminForCallerLocked(who,
-                    DeviceAdminInfo.USES_POLICY_PROFILE_OWNER);
+            ActiveAdmin admin = getProfileOwnerOrDeviceOwnerLocked(identity);
             return admin.permittedInputMethods;
         }
     }
@@ -10574,17 +9054,16 @@
             return false;
         }
         Objects.requireNonNull(who, "ComponentName is null");
+        final CallerIdentity identity = getCallerIdentity(who);
 
-        final int callingUserId = mInjector.userHandleGetCallingUserId();
-        if (!isManagedProfile(callingUserId)) {
+        if (!isManagedProfile(identity.getUserId())) {
             return false;
         }
 
         synchronized (getLockObject()) {
-            ActiveAdmin admin = getActiveAdminForCallerLocked(
-                    who, DeviceAdminInfo.USES_POLICY_PROFILE_OWNER);
+            ActiveAdmin admin = getProfileOwnerOrDeviceOwnerLocked(identity);
             admin.permittedNotificationListeners = packageList;
-            saveSettingsLocked(callingUserId);
+            saveSettingsLocked(identity.getUserId());
         }
         return true;
     }
@@ -10595,10 +9074,12 @@
             return null;
         }
         Objects.requireNonNull(who, "ComponentName is null");
+        final CallerIdentity identity = getCallerIdentity(who);
 
         synchronized (getLockObject()) {
-            ActiveAdmin admin = getActiveAdminForCallerLocked(
-                    who, DeviceAdminInfo.USES_POLICY_PROFILE_OWNER);
+            // API contract is to return null if there are no permitted cross-profile notification
+            // listeners, including in Device Owner mode.
+            ActiveAdmin admin = getProfileOwnerOrDeviceOwnerLocked(identity);
             return admin.permittedNotificationListeners;
         }
     }
@@ -10794,14 +9275,14 @@
     public boolean removeUser(ComponentName who, UserHandle userHandle) {
         Objects.requireNonNull(who, "ComponentName is null");
         Objects.requireNonNull(userHandle, "UserHandle is null");
-        enforceDeviceOwner(who);
+        final CallerIdentity identity = getCallerIdentity(who);
+        Preconditions.checkCallAuthorization(isDeviceOwner(identity));
 
-        final int callingUserId = mInjector.userHandleGetCallingUserId();
         return mInjector.binderWithCleanCallingIdentity(() -> {
             String restriction = isManagedProfile(userHandle.getIdentifier())
                     ? UserManager.DISALLOW_REMOVE_MANAGED_PROFILE
                     : UserManager.DISALLOW_REMOVE_USER;
-            if (isAdminAffectedByRestriction(who, restriction, callingUserId)) {
+            if (isAdminAffectedByRestriction(who, restriction, identity.getUserId())) {
                 Log.w(LOG_TAG, "The device owner cannot remove a user because "
                         + restriction + " is enabled, and was not set by the device owner");
                 return false;
@@ -10827,10 +9308,10 @@
     @Override
     public boolean switchUser(ComponentName who, UserHandle userHandle) {
         Objects.requireNonNull(who, "ComponentName is null");
+        final CallerIdentity identity = getCallerIdentity(who);
+        Preconditions.checkCallAuthorization(isDeviceOwner(identity));
 
         synchronized (getLockObject()) {
-            getActiveAdminForCallerLocked(who, DeviceAdminInfo.USES_POLICY_DEVICE_OWNER);
-
             long id = mInjector.binderClearCallingIdentity();
             try {
                 int userId = UserHandle.USER_SYSTEM;
@@ -10851,7 +9332,8 @@
     public int startUserInBackground(ComponentName who, UserHandle userHandle) {
         Objects.requireNonNull(who, "ComponentName is null");
         Objects.requireNonNull(userHandle, "UserHandle is null");
-        enforceDeviceOwner(who);
+        final CallerIdentity identity = getCallerIdentity(who);
+        Preconditions.checkCallAuthorization(isDeviceOwner(identity));
 
         final int userId = userHandle.getIdentifier();
         if (isManagedProfile(userId)) {
@@ -10883,7 +9365,8 @@
     public int stopUser(ComponentName who, UserHandle userHandle) {
         Objects.requireNonNull(who, "ComponentName is null");
         Objects.requireNonNull(userHandle, "UserHandle is null");
-        enforceDeviceOwner(who);
+        final CallerIdentity identity = getCallerIdentity(who);
+        Preconditions.checkCallAuthorization(isDeviceOwner(identity));
 
         final int userId = userHandle.getIdentifier();
         if (isManagedProfile(userId)) {
@@ -10951,7 +9434,8 @@
     @Override
     public List<UserHandle> getSecondaryUsers(ComponentName who) {
         Objects.requireNonNull(who, "ComponentName is null");
-        enforceDeviceOwner(who);
+        final CallerIdentity identity = getCallerIdentity(who);
+        Preconditions.checkCallAuthorization(isDeviceOwner(identity));
 
         return mInjector.binderWithCleanCallingIdentity(() -> {
             final List<UserInfo> userInfos = mInjector.getUserManager().getUsers(true
@@ -11456,10 +9940,14 @@
 
     @Override
     public String[] getAccountTypesWithManagementDisabledAsUser(int userId, boolean parent) {
-        enforceFullCrossUsersPermission(userId);
         if (!mHasFeature) {
             return null;
         }
+        Preconditions.checkArgumentNonnegative(userId, "Invalid userId");
+
+        final CallerIdentity identity = getCallerIdentity();
+        Preconditions.checkCallAuthorization(hasFullCrossUsersPermission(identity, userId));
+
         synchronized (getLockObject()) {
             final ArraySet<String> resultSet = new ArraySet<>();
 
@@ -11526,10 +10014,12 @@
         final int userId = UserHandle.getCallingUserId();
 
         synchronized (getLockObject()) {
+            //TODO: This is a silly access control check. Remove.
             if (who != null) {
-                getActiveAdminForCallerLocked(who, DeviceAdminInfo.USES_POLICY_PROFILE_OWNER);
+                final CallerIdentity caller = getCallerIdentity(who);
+                Preconditions.checkCallAuthorization(
+                        isProfileOwner(caller) || isDeviceOwner(caller));
             }
-
             long id = mInjector.binderClearCallingIdentity();
             try {
                 return mIPackageManager.getBlockUninstallForUser(packageName, userId);
@@ -11549,12 +10039,14 @@
             return;
         }
         Objects.requireNonNull(who, "ComponentName is null");
+        final CallerIdentity identity = getCallerIdentity(who);
+        Preconditions.checkCallAuthorization(isProfileOwner(identity) || isDeviceOwner(identity));
+
         synchronized (getLockObject()) {
-            ActiveAdmin admin = getActiveAdminForCallerLocked(who,
-                    DeviceAdminInfo.USES_POLICY_PROFILE_OWNER);
+            ActiveAdmin admin = getProfileOwnerOrDeviceOwnerLocked(identity);
             if (admin.disableCallerId != disabled) {
                 admin.disableCallerId = disabled;
-                saveSettingsLocked(mInjector.userHandleGetCallingUserId());
+                saveSettingsLocked(identity.getUserId());
             }
         }
         DevicePolicyEventLogger
@@ -11570,16 +10062,22 @@
             return false;
         }
         Objects.requireNonNull(who, "ComponentName is null");
+        final CallerIdentity identity = getCallerIdentity(who);
+        Preconditions.checkCallAuthorization(isProfileOwner(identity) || isDeviceOwner(identity));
+
         synchronized (getLockObject()) {
-            ActiveAdmin admin = getActiveAdminForCallerLocked(who,
-                    DeviceAdminInfo.USES_POLICY_PROFILE_OWNER);
+            ActiveAdmin admin = getProfileOwnerOrDeviceOwnerLocked(identity);
             return admin.disableCallerId;
         }
     }
 
     @Override
     public boolean getCrossProfileCallerIdDisabledForUser(int userId) {
-        enforceCrossUsersPermission(userId);
+        Preconditions.checkArgumentNonnegative(userId, "Invalid userId");
+
+        final CallerIdentity identity = getCallerIdentity();
+        Preconditions.checkCallAuthorization(hasCrossUsersPermission(identity, userId));
+
         synchronized (getLockObject()) {
             ActiveAdmin admin = getProfileOwnerAdminLocked(userId);
             return (admin != null) ? admin.disableCallerId : false;
@@ -11592,12 +10090,14 @@
             return;
         }
         Objects.requireNonNull(who, "ComponentName is null");
+        final CallerIdentity identity = getCallerIdentity(who);
+        Preconditions.checkCallAuthorization(isProfileOwner(identity) || isDeviceOwner(identity));
+
         synchronized (getLockObject()) {
-            ActiveAdmin admin = getActiveAdminForCallerLocked(who,
-                    DeviceAdminInfo.USES_POLICY_PROFILE_OWNER);
+            ActiveAdmin admin = getProfileOwnerOrDeviceOwnerLocked(identity);
             if (admin.disableContactsSearch != disabled) {
                 admin.disableContactsSearch = disabled;
-                saveSettingsLocked(mInjector.userHandleGetCallingUserId());
+                saveSettingsLocked(identity.getUserId());
             }
         }
         DevicePolicyEventLogger
@@ -11613,16 +10113,22 @@
             return false;
         }
         Objects.requireNonNull(who, "ComponentName is null");
+        final CallerIdentity identity = getCallerIdentity(who);
+        Preconditions.checkCallAuthorization(isProfileOwner(identity) || isDeviceOwner(identity));
+
         synchronized (getLockObject()) {
-            ActiveAdmin admin = getActiveAdminForCallerLocked(who,
-                    DeviceAdminInfo.USES_POLICY_PROFILE_OWNER);
+            ActiveAdmin admin = getProfileOwnerOrDeviceOwnerLocked(identity);
             return admin.disableContactsSearch;
         }
     }
 
     @Override
     public boolean getCrossProfileContactsSearchDisabledForUser(int userId) {
-        enforceCrossUsersPermission(userId);
+        Preconditions.checkArgumentNonnegative(userId, "Invalid userId");
+
+        final CallerIdentity identity = getCallerIdentity();
+        Preconditions.checkCallAuthorization(hasCrossUsersPermission(identity, userId));
+
         synchronized (getLockObject()) {
             ActiveAdmin admin = getProfileOwnerAdminLocked(userId);
             return (admin != null) ? admin.disableContactsSearch : false;
@@ -11693,12 +10199,14 @@
             return;
         }
         Objects.requireNonNull(who, "ComponentName is null");
+        final CallerIdentity identity = getCallerIdentity(who);
+        Preconditions.checkCallAuthorization(isDeviceOwner(identity) || isProfileOwner(identity));
+
         synchronized (getLockObject()) {
-            ActiveAdmin admin = getActiveAdminForCallerLocked(who,
-                    DeviceAdminInfo.USES_POLICY_PROFILE_OWNER);
+            ActiveAdmin admin = getProfileOwnerOrDeviceOwnerLocked(identity);
             if (admin.disableBluetoothContactSharing != disabled) {
                 admin.disableBluetoothContactSharing = disabled;
-                saveSettingsLocked(UserHandle.getCallingUserId());
+                saveSettingsLocked(identity.getUserId());
             }
         }
         DevicePolicyEventLogger
@@ -11714,9 +10222,11 @@
             return false;
         }
         Objects.requireNonNull(who, "ComponentName is null");
+        final CallerIdentity identity = getCallerIdentity(who);
+        Preconditions.checkCallAuthorization(isDeviceOwner(identity) || isProfileOwner(identity));
+
         synchronized (getLockObject()) {
-            ActiveAdmin admin = getActiveAdminForCallerLocked(who,
-                    DeviceAdminInfo.USES_POLICY_PROFILE_OWNER);
+            ActiveAdmin admin = getProfileOwnerOrDeviceOwnerLocked(identity);
             return admin.disableBluetoothContactSharing;
         }
     }
@@ -11913,6 +10423,8 @@
     @Override
     public void setGlobalSetting(ComponentName who, String setting, String value) {
         Objects.requireNonNull(who, "ComponentName is null");
+        final CallerIdentity identity = getCallerIdentity(who);
+        Preconditions.checkCallAuthorization(isDeviceOwner(identity));
 
         DevicePolicyEventLogger
                 .createEvent(DevicePolicyEnums.SET_GLOBAL_SETTING)
@@ -11921,8 +10433,6 @@
                 .write();
 
         synchronized (getLockObject()) {
-            getActiveAdminForCallerLocked(who, DeviceAdminInfo.USES_POLICY_DEVICE_OWNER);
-
             // Some settings are no supported any more. However we do not want to throw a
             // SecurityException to avoid breaking apps.
             if (GLOBAL_SETTINGS_DEPRECATED.contains(setting)) {
@@ -12003,20 +10513,20 @@
 
     @Override
     public void setLocationEnabled(ComponentName who, boolean locationEnabled) {
-        enforceDeviceOwner(Objects.requireNonNull(who));
-
-        UserHandle user = mInjector.binderGetCallingUserHandle();
+        final CallerIdentity identity = getCallerIdentity(who);
+        Preconditions.checkCallAuthorization(isDeviceOwner(identity));
 
         mInjector.binderWithCleanCallingIdentity(() -> {
             boolean wasLocationEnabled = mInjector.getLocationManager().isLocationEnabledForUser(
-                    user);
-            mInjector.getLocationManager().setLocationEnabledForUser(locationEnabled, user);
+                    identity.getUserHandle());
+            mInjector.getLocationManager().setLocationEnabledForUser(locationEnabled,
+                    identity.getUserHandle());
 
             // make a best effort to only show the notification if the admin is actually enabling
             // location. this is subject to race conditions with settings changes, but those are
             // unlikely to realistically interfere
-            if (locationEnabled && (wasLocationEnabled != locationEnabled)) {
-                showLocationSettingsEnabledNotification(user);
+            if (locationEnabled && !wasLocationEnabled) {
+                showLocationSettingsEnabledNotification(identity.getUserHandle());
             }
         });
 
@@ -13543,16 +12053,18 @@
 
     @Override
     public boolean isSystemOnlyUser(ComponentName admin) {
-        enforceDeviceOwner(admin);
-        final int callingUserId = mInjector.userHandleGetCallingUserId();
-        return UserManager.isSplitSystemUser() && callingUserId == UserHandle.USER_SYSTEM;
+        Objects.requireNonNull(admin, "ComponentName is null");
+        final CallerIdentity identity = getCallerIdentity(admin);
+        Preconditions.checkCallAuthorization(isDeviceOwner(identity));
+        return UserManager.isSplitSystemUser() && identity.getUserId() == UserHandle.USER_SYSTEM;
     }
 
     @Override
     public void reboot(ComponentName admin) {
-        Objects.requireNonNull(admin);
-        // Make sure caller has DO.
-        enforceDeviceOwner(admin);
+        Objects.requireNonNull(admin, "ComponentName is null");
+        final CallerIdentity identity = getCallerIdentity(admin);
+        Preconditions.checkCallAuthorization(isDeviceOwner(identity));
+
         mInjector.binderWithCleanCallingIdentity(() -> {
             // Make sure there are no ongoing calls on the device.
             if (mTelephonyManager.getCallState() != TelephonyManager.CALL_STATE_IDLE) {
@@ -13670,13 +12182,12 @@
             return;
         }
         Objects.requireNonNull(who, "ComponentName is null");
-        final int userHandle = mInjector.userHandleGetCallingUserId();
-        enforceManagedProfile(userHandle, "set organization color");
+        final CallerIdentity identity = getCallerIdentity(who);
+        enforceManagedProfile(identity.getUserId(), "set organization color");
         synchronized (getLockObject()) {
-            ActiveAdmin admin = getActiveAdminForCallerLocked(who,
-                    DeviceAdminInfo.USES_POLICY_PROFILE_OWNER);
+            ActiveAdmin admin = getProfileOwnerOrDeviceOwnerLocked(identity);
             admin.organizationColor = color;
-            saveSettingsLocked(userHandle);
+            saveSettingsLocked(identity.getUserId());
         }
         DevicePolicyEventLogger
                 .createEvent(DevicePolicyEnums.SET_ORGANIZATION_COLOR)
@@ -13689,7 +12200,11 @@
         if (!mHasFeature) {
             return;
         }
-        enforceFullCrossUsersPermission(userId);
+        Preconditions.checkArgumentNonnegative(userId, "Invalid userId");
+
+        final CallerIdentity identity = getCallerIdentity();
+        Preconditions.checkCallAuthorization(hasFullCrossUsersPermission(identity, userId));
+
         enforceManageUsers();
         enforceManagedProfile(userId, "set organization color");
         synchronized (getLockObject()) {
@@ -13705,10 +12220,10 @@
             return ActiveAdmin.DEF_ORGANIZATION_COLOR;
         }
         Objects.requireNonNull(who, "ComponentName is null");
-        enforceManagedProfile(mInjector.userHandleGetCallingUserId(), "get organization color");
+        final CallerIdentity identity = getCallerIdentity(who);
+        enforceManagedProfile(identity.getUserId(), "get organization color");
         synchronized (getLockObject()) {
-            ActiveAdmin admin = getActiveAdminForCallerLocked(who,
-                    DeviceAdminInfo.USES_POLICY_PROFILE_OWNER);
+            ActiveAdmin admin = getProfileOwnerOrDeviceOwnerLocked(identity);
             return admin.organizationColor;
         }
     }
@@ -13718,7 +12233,11 @@
         if (!mHasFeature) {
             return ActiveAdmin.DEF_ORGANIZATION_COLOR;
         }
-        enforceFullCrossUsersPermission(userHandle);
+        Preconditions.checkArgumentNonnegative(userHandle, "Invalid userId");
+
+        final CallerIdentity identity = getCallerIdentity();
+        Preconditions.checkCallAuthorization(hasFullCrossUsersPermission(identity, userHandle));
+
         enforceManagedProfile(userHandle, "get organization color");
         synchronized (getLockObject()) {
             ActiveAdmin profileOwner = getProfileOwnerAdminLocked(userHandle);
@@ -13734,15 +12253,14 @@
             return;
         }
         Objects.requireNonNull(who, "ComponentName is null");
-        final int userHandle = mInjector.userHandleGetCallingUserId();
+        final CallerIdentity identity = getCallerIdentity(who);
 
         synchronized (getLockObject()) {
-            ActiveAdmin admin = getActiveAdminForCallerLocked(who,
-                    DeviceAdminInfo.USES_POLICY_PROFILE_OWNER);
+            ActiveAdmin admin = getProfileOwnerOrDeviceOwnerLocked(identity);
             if (!TextUtils.equals(admin.organizationName, text)) {
                 admin.organizationName = (text == null || text.length() == 0)
                         ? null : text.toString();
-                saveSettingsLocked(userHandle);
+                saveSettingsLocked(identity.getUserId());
             }
         }
     }
@@ -13753,10 +12271,10 @@
             return null;
         }
         Objects.requireNonNull(who, "ComponentName is null");
-        enforceManagedProfile(mInjector.userHandleGetCallingUserId(), "get organization name");
+        final CallerIdentity identity = getCallerIdentity(who);
+        enforceManagedProfile(identity.getUserId(), "get organization name");
         synchronized (getLockObject()) {
-            ActiveAdmin admin = getActiveAdminForCallerLocked(who,
-                    DeviceAdminInfo.USES_POLICY_PROFILE_OWNER);
+            ActiveAdmin admin = getProfileOwnerOrDeviceOwnerLocked(identity);
             return admin.organizationName;
         }
     }
@@ -13778,7 +12296,11 @@
         if (!mHasFeature) {
             return null;
         }
-        enforceFullCrossUsersPermission(userHandle);
+        Preconditions.checkArgumentNonnegative(userHandle, "Invalid userId");
+
+        final CallerIdentity identity = getCallerIdentity();
+        Preconditions.checkCallAuthorization(hasFullCrossUsersPermission(identity, userHandle));
+
         enforceManagedProfile(userHandle, "get organization name");
         synchronized (getLockObject()) {
             ActiveAdmin profileOwner = getProfileOwnerAdminLocked(userHandle);
@@ -13792,20 +12314,21 @@
     public List<String> setMeteredDataDisabledPackages(ComponentName who, List<String> packageNames) {
         Objects.requireNonNull(who);
         Objects.requireNonNull(packageNames);
+        final CallerIdentity identity = getCallerIdentity(who);
+        Preconditions.checkSecurity(isDeviceOwner(identity) || isProfileOwner(identity),
+                String.format("Admin %s does not own the profile", identity.getComponentName()));
 
         if (!mHasFeature) {
             return packageNames;
         }
         synchronized (getLockObject()) {
-            final ActiveAdmin admin = getActiveAdminForCallerLocked(who,
-                    DeviceAdminInfo.USES_POLICY_PROFILE_OWNER);
-            final int callingUserId = mInjector.userHandleGetCallingUserId();
+            final ActiveAdmin admin = getProfileOwnerOrDeviceOwnerLocked(identity);
             return mInjector.binderWithCleanCallingIdentity(() -> {
-                final List<String> excludedPkgs
-                        = removeInvalidPkgsForMeteredDataRestriction(callingUserId, packageNames);
+                final List<String> excludedPkgs = removeInvalidPkgsForMeteredDataRestriction(
+                        identity.getUserId(), packageNames);
                 admin.meteredDisabledPackages = packageNames;
-                pushMeteredDisabledPackagesLocked(callingUserId);
-                saveSettingsLocked(callingUserId);
+                pushMeteredDisabledPackagesLocked(identity.getUserId());
+                saveSettingsLocked(identity.getUserId());
                 return excludedPkgs;
             });
         }
@@ -13842,9 +12365,12 @@
         if (!mHasFeature) {
             return new ArrayList<>();
         }
+        final CallerIdentity identity = getCallerIdentity(who);
+        Preconditions.checkSecurity(isDeviceOwner(identity) || isProfileOwner(identity),
+                String.format("Admin %s does not own the profile", identity.getComponentName()));
+
         synchronized (getLockObject()) {
-            final ActiveAdmin admin = getActiveAdminForCallerLocked(who,
-                    DeviceAdminInfo.USES_POLICY_PROFILE_OWNER);
+            final ActiveAdmin admin = getProfileOwnerOrDeviceOwnerLocked(identity);
             return admin.meteredDisabledPackages == null
                     ? new ArrayList<>() : admin.meteredDisabledPackages;
         }
@@ -13869,12 +12395,6 @@
         return false;
     }
 
-    private boolean hasMarkProfileOwnerOnOrganizationOwnedDevicePermission() {
-        return mContext.checkCallingPermission(
-                permission.MARK_DEVICE_ORGANIZATION_OWNED)
-                == PackageManager.PERMISSION_GRANTED;
-    }
-
     @Override
     public void markProfileOwnerOnOrganizationOwnedDevice(ComponentName who, int userId) {
         // As the caller is the system, it must specify the component name of the profile owner
@@ -13887,7 +12407,7 @@
 
         // Only adb or system apps with the right permission can mark a profile owner on
         // organization-owned device.
-        if (!(isAdb() || hasMarkProfileOwnerOnOrganizationOwnedDevicePermission())) {
+        if (!(isAdb() || hasCallingPermission(permission.MARK_DEVICE_ORGANIZATION_OWNED))) {
             throw new SecurityException(
                     "Only the system can mark a profile owner of organization-owned device.");
         }
@@ -14362,7 +12882,7 @@
                     DeviceAdminInfo.USES_POLICY_SETS_GLOBAL_PROXY);
             policy.mAdminList.remove(admin);
             policy.mAdminMap.remove(adminReceiver);
-            validatePasswordOwnerLocked(policy);
+            policy.validatePasswordOwner();
             if (doProxyCleanup) {
                 resetGlobalProxyLocked(policy);
             }
@@ -15015,7 +13535,8 @@
     @Override
     public StringParceledListSlice getOwnerInstalledCaCerts(@NonNull UserHandle user) {
         final int userId = user.getIdentifier();
-        enforceProfileOwnerOrFullCrossUsersPermission(userId);
+        final CallerIdentity identity = getCallerIdentity();
+        enforceProfileOwnerOrFullCrossUsersPermission(identity, userId);
         synchronized (getLockObject()) {
             return new StringParceledListSlice(
                     new ArrayList<>(getUserData(userId).mOwnerInstalledCaCerts));
@@ -15058,18 +13579,18 @@
         if (!mHasFeature) {
             return;
         }
-        Objects.requireNonNull(admin);
+        Objects.requireNonNull(admin, "ComponentName is null");
+        final CallerIdentity identity = getCallerIdentity(admin);
+        Preconditions.checkCallAuthorization(isDeviceOwner(identity));
 
         synchronized (getLockObject()) {
-            ActiveAdmin deviceOwner =
-                    getActiveAdminForCallerLocked(admin, DeviceAdminInfo.USES_POLICY_DEVICE_OWNER);
-
+            ActiveAdmin deviceOwner = getDeviceOwnerAdminLocked();
             if (deviceOwner.isLogoutEnabled == enabled) {
                 // already in the requested state
                 return;
             }
             deviceOwner.isLogoutEnabled = enabled;
-            saveSettingsLocked(mInjector.userHandleGetCallingUserId());
+            saveSettingsLocked(identity.getUserId());
         }
     }
 
@@ -15235,20 +13756,20 @@
         if (!mHasFeature) {
             return;
         }
-        Objects.requireNonNull(admin);
+        Objects.requireNonNull(admin, "ComponentName is null");
+        final CallerIdentity identity = getCallerIdentity(admin);
+        Preconditions.checkCallAuthorization(isDeviceOwner(identity));
 
         final String startUserSessionMessageString =
                 startUserSessionMessage != null ? startUserSessionMessage.toString() : null;
 
         synchronized (getLockObject()) {
-            final ActiveAdmin deviceOwner =
-                    getActiveAdminForCallerLocked(admin, DeviceAdminInfo.USES_POLICY_DEVICE_OWNER);
-
+            final ActiveAdmin deviceOwner = getDeviceOwnerAdminLocked();
             if (TextUtils.equals(deviceOwner.startUserSessionMessage, startUserSessionMessage)) {
                 return;
             }
             deviceOwner.startUserSessionMessage = startUserSessionMessageString;
-            saveSettingsLocked(mInjector.userHandleGetCallingUserId());
+            saveSettingsLocked(identity.getUserId());
         }
 
         mInjector.getActivityManagerInternal()
@@ -15260,20 +13781,20 @@
         if (!mHasFeature) {
             return;
         }
-        Objects.requireNonNull(admin);
+        Objects.requireNonNull(admin, "ComponentName is null");
+        final CallerIdentity identity = getCallerIdentity(admin);
+        Preconditions.checkCallAuthorization(isDeviceOwner(identity));
 
         final String endUserSessionMessageString =
                 endUserSessionMessage != null ? endUserSessionMessage.toString() : null;
 
         synchronized (getLockObject()) {
-            final ActiveAdmin deviceOwner =
-                    getActiveAdminForCallerLocked(admin, DeviceAdminInfo.USES_POLICY_DEVICE_OWNER);
-
+            final ActiveAdmin deviceOwner = getDeviceOwnerAdminLocked();
             if (TextUtils.equals(deviceOwner.endUserSessionMessage, endUserSessionMessage)) {
                 return;
             }
             deviceOwner.endUserSessionMessage = endUserSessionMessageString;
-            saveSettingsLocked(mInjector.userHandleGetCallingUserId());
+            saveSettingsLocked(identity.getUserId());
         }
 
         mInjector.getActivityManagerInternal()
@@ -15285,11 +13806,12 @@
         if (!mHasFeature) {
             return null;
         }
-        Objects.requireNonNull(admin);
+        Objects.requireNonNull(admin, "ComponentName is null");
+        final CallerIdentity identity = getCallerIdentity(admin);
+        Preconditions.checkCallAuthorization(isDeviceOwner(identity));
 
         synchronized (getLockObject()) {
-            final ActiveAdmin deviceOwner =
-                    getActiveAdminForCallerLocked(admin, DeviceAdminInfo.USES_POLICY_DEVICE_OWNER);
+            final ActiveAdmin deviceOwner = getDeviceOwnerAdminLocked();
             return deviceOwner.startUserSessionMessage;
         }
     }
@@ -15299,11 +13821,12 @@
         if (!mHasFeature) {
             return null;
         }
-        Objects.requireNonNull(admin);
+        Objects.requireNonNull(admin, "ComponentName is null");
+        final CallerIdentity identity = getCallerIdentity(admin);
+        Preconditions.checkCallAuthorization(isDeviceOwner(identity));
 
         synchronized (getLockObject()) {
-            final ActiveAdmin deviceOwner =
-                    getActiveAdminForCallerLocked(admin, DeviceAdminInfo.USES_POLICY_DEVICE_OWNER);
+            final ActiveAdmin deviceOwner = getDeviceOwnerAdminLocked();
             return deviceOwner.endUserSessionMessage;
         }
     }
@@ -15342,9 +13865,10 @@
         if (!mHasFeature || !mHasTelephonyFeature) {
             return -1;
         }
-        Objects.requireNonNull(who, "ComponentName is null in addOverrideApn");
+        Objects.requireNonNull(who, "ComponentName is null");
         Objects.requireNonNull(apnSetting, "ApnSetting is null in addOverrideApn");
-        enforceDeviceOwner(who);
+        final CallerIdentity identity = getCallerIdentity(who);
+        Preconditions.checkCallAuthorization(isDeviceOwner(identity));
 
         TelephonyManager tm = mContext.getSystemService(TelephonyManager.class);
         if (tm != null) {
@@ -15362,9 +13886,10 @@
         if (!mHasFeature || !mHasTelephonyFeature) {
             return false;
         }
-        Objects.requireNonNull(who, "ComponentName is null in updateOverrideApn");
+        Objects.requireNonNull(who, "ComponentName is null");
         Objects.requireNonNull(apnSetting, "ApnSetting is null in updateOverrideApn");
-        enforceDeviceOwner(who);
+        final CallerIdentity identity = getCallerIdentity(who);
+        Preconditions.checkCallAuthorization(isDeviceOwner(identity));
 
         if (apnId < 0) {
             return false;
@@ -15384,9 +13909,9 @@
         if (!mHasFeature || !mHasTelephonyFeature) {
             return false;
         }
-        Objects.requireNonNull(who, "ComponentName is null in removeOverrideApn");
-        enforceDeviceOwner(who);
-
+        Objects.requireNonNull(who, "ComponentName is null");
+        final CallerIdentity identity = getCallerIdentity(who);
+        Preconditions.checkCallAuthorization(isDeviceOwner(identity));
         return removeOverrideApnUnchecked(apnId);
     }
 
@@ -15405,9 +13930,9 @@
         if (!mHasFeature || !mHasTelephonyFeature) {
             return Collections.emptyList();
         }
-        Objects.requireNonNull(who, "ComponentName is null in getOverrideApns");
-        enforceDeviceOwner(who);
-
+        Objects.requireNonNull(who, "ComponentName is null");
+        final CallerIdentity identity = getCallerIdentity(who);
+        Preconditions.checkCallAuthorization(isDeviceOwner(identity));
         return getOverrideApnsUnchecked();
     }
 
@@ -15426,9 +13951,9 @@
         if (!mHasFeature || !mHasTelephonyFeature) {
             return;
         }
-        Objects.requireNonNull(who, "ComponentName is null in setOverrideApnEnabled");
-        enforceDeviceOwner(who);
-
+        Objects.requireNonNull(who, "ComponentName is null");
+        final CallerIdentity identity = getCallerIdentity(who);
+        Preconditions.checkCallAuthorization(isDeviceOwner(identity));
         setOverrideApnsEnabledUnchecked(enabled);
     }
 
@@ -15444,8 +13969,9 @@
         if (!mHasFeature || !mHasTelephonyFeature) {
             return false;
         }
-        Objects.requireNonNull(who, "ComponentName is null in isOverrideApnEnabled");
-        enforceDeviceOwner(who);
+        Objects.requireNonNull(who, "ComponentName is null");
+        final CallerIdentity identity = getCallerIdentity(who);
+        Preconditions.checkCallAuthorization(isDeviceOwner(identity));
 
         Cursor enforceCursor = mInjector.binderWithCleanCallingIdentity(
                 () -> mContext.getContentResolver().query(
@@ -15527,11 +14053,9 @@
         if (!mHasFeature) {
             return PRIVATE_DNS_SET_ERROR_FAILURE_SETTING;
         }
-
         Objects.requireNonNull(who, "ComponentName is null");
-        enforceDeviceOwner(who);
-
-        final int returnCode;
+        final CallerIdentity identity = getCallerIdentity(who);
+        Preconditions.checkCallAuthorization(isDeviceOwner(identity));
 
         switch (mode) {
             case PRIVATE_DNS_MODE_OPPORTUNISTIC:
@@ -15565,9 +14089,10 @@
         if (!mHasFeature) {
             return PRIVATE_DNS_MODE_UNKNOWN;
         }
-
         Objects.requireNonNull(who, "ComponentName is null");
-        enforceDeviceOwner(who);
+        final CallerIdentity identity = getCallerIdentity(who);
+        Preconditions.checkCallAuthorization(isDeviceOwner(identity));
+
         String currentMode = mInjector.settingsGlobalGetString(PRIVATE_DNS_MODE);
         if (currentMode == null) {
             currentMode = ConnectivityManager.PRIVATE_DNS_DEFAULT_MODE_FALLBACK;
@@ -15589,10 +14114,9 @@
         if (!mHasFeature) {
             return null;
         }
-
         Objects.requireNonNull(who, "ComponentName is null");
-        enforceDeviceOwner(who);
-
+        final CallerIdentity identity = getCallerIdentity(who);
+        Preconditions.checkCallAuthorization(isDeviceOwner(identity));
         return mInjector.settingsGlobalGetString(PRIVATE_DNS_SPECIFIER);
     }
 
@@ -15629,12 +14153,12 @@
             return;
         }
         Objects.requireNonNull(who, "ComponentName is null");
+        final CallerIdentity identity = getCallerIdentity(who);
 
         synchronized (getLockObject()) {
-            final ActiveAdmin admin = getActiveAdminForCallerLocked(
-                    who, DeviceAdminInfo.USES_POLICY_PROFILE_OWNER);
+            final ActiveAdmin admin = getProfileOwnerOrDeviceOwnerLocked(identity);
             admin.mCrossProfileCalendarPackages = packageNames;
-            saveSettingsLocked(mInjector.userHandleGetCallingUserId());
+            saveSettingsLocked(identity.getUserId());
         }
         DevicePolicyEventLogger
                 .createEvent(DevicePolicyEnums.SET_CROSS_PROFILE_CALENDAR_PACKAGES)
@@ -15650,10 +14174,10 @@
             return Collections.emptyList();
         }
         Objects.requireNonNull(who, "ComponentName is null");
+        final CallerIdentity identity = getCallerIdentity(who);
 
         synchronized (getLockObject()) {
-            final ActiveAdmin admin = getActiveAdminForCallerLocked(
-                    who, DeviceAdminInfo.USES_POLICY_PROFILE_OWNER);
+            final ActiveAdmin admin = getProfileOwnerOrDeviceOwnerLocked(identity);
             return admin.mCrossProfileCalendarPackages;
         }
     }
@@ -15665,8 +14189,11 @@
             return false;
         }
         Preconditions.checkStringNotEmpty(packageName, "Package name is null or empty");
+        Preconditions.checkArgumentNonnegative(userHandle, "Invalid userId");
 
-        enforceCrossUsersPermission(userHandle);
+        final CallerIdentity identity = getCallerIdentity();
+        Preconditions.checkCallAuthorization(hasCrossUsersPermission(identity, userHandle));
+
         synchronized (getLockObject()) {
             if (mInjector.settingsSecureGetIntForUser(
                     Settings.Secure.CROSS_PROFILE_CALENDAR_ENABLED, 0, userHandle) == 0) {
@@ -15688,7 +14215,11 @@
         if (!mHasFeature) {
             return Collections.emptyList();
         }
-        enforceCrossUsersPermission(userHandle);
+        Preconditions.checkArgumentNonnegative(userHandle, "Invalid userId");
+
+        final CallerIdentity identity = getCallerIdentity();
+        Preconditions.checkCallAuthorization(hasCrossUsersPermission(identity, userHandle));
+
         synchronized (getLockObject()) {
             final ActiveAdmin admin = getProfileOwnerAdminLocked(userHandle);
             if (admin != null) {
@@ -15705,16 +14236,17 @@
         }
         Objects.requireNonNull(who, "ComponentName is null");
         Objects.requireNonNull(packageNames, "Package names is null");
+        final CallerIdentity identity = getCallerIdentity(who);
+
         final List<String> previousCrossProfilePackages;
         synchronized (getLockObject()) {
-            final ActiveAdmin admin =
-                    getActiveAdminForCallerLocked(who, DeviceAdminInfo.USES_POLICY_PROFILE_OWNER);
+            final ActiveAdmin admin = getProfileOwnerOrDeviceOwnerLocked(identity);
             previousCrossProfilePackages = admin.mCrossProfilePackages;
             if (packageNames.equals(previousCrossProfilePackages)) {
                 return;
             }
             admin.mCrossProfilePackages = packageNames;
-            saveSettingsLocked(mInjector.userHandleGetCallingUserId());
+            saveSettingsLocked(identity.getUserId());
         }
         logSetCrossProfilePackages(who, packageNames);
         final CrossProfileApps crossProfileApps = mContext.getSystemService(CrossProfileApps.class);
@@ -15737,10 +14269,10 @@
             return Collections.emptyList();
         }
         Objects.requireNonNull(who, "ComponentName is null");
+        final CallerIdentity identity = getCallerIdentity(who);
 
         synchronized (getLockObject()) {
-            final ActiveAdmin admin = getActiveAdminForCallerLocked(
-                    who, DeviceAdminInfo.USES_POLICY_PROFILE_OWNER);
+            final ActiveAdmin admin = getProfileOwnerOrDeviceOwnerLocked(identity);
             return admin.mCrossProfilePackages;
         }
     }
@@ -15750,7 +14282,12 @@
         if (!mHasFeature) {
             return Collections.emptyList();
         }
-        enforceAcrossUsersPermissions();
+        final CallerIdentity identity = getCallerIdentity();
+        Preconditions.checkCallAuthorization(
+                isSystemUid(identity) || isRootUid(identity) || hasCallingPermission(
+                        permission.INTERACT_ACROSS_USERS) || hasCallingPermission(
+                        permission.INTERACT_ACROSS_USERS_FULL) || hasPermissionForPreflight(
+                        identity, permission.INTERACT_ACROSS_PROFILES));
 
         synchronized (getLockObject()) {
             final List<ActiveAdmin> admins = getProfileOwnerAdminsForCurrentProfileGroup();
@@ -15937,13 +14474,13 @@
 
     @Override
     public void setUserControlDisabledPackages(ComponentName who, List<String> packages) {
-        Preconditions.checkNotNull(who, "ComponentName is null");
+        Objects.requireNonNull(who, "ComponentName is null");
         Preconditions.checkNotNull(packages, "packages is null");
+        final CallerIdentity identity = getCallerIdentity(who);
+        Preconditions.checkCallAuthorization(isDeviceOwner(identity));
 
-        enforceDeviceOwner(who);
         synchronized (getLockObject()) {
-            final int userHandle = mInjector.userHandleGetCallingUserId();
-            setUserControlDisabledPackagesLocked(userHandle, packages);
+            setUserControlDisabledPackagesLocked(identity.getUserId(), packages);
             DevicePolicyEventLogger
                     .createEvent(DevicePolicyEnums.SET_USER_CONTROL_DISABLED_PACKAGES)
                     .setAdmin(who)
@@ -15963,12 +14500,12 @@
 
     @Override
     public List<String> getUserControlDisabledPackages(ComponentName who) {
-        Preconditions.checkNotNull(who, "ComponentName is null");
+        final CallerIdentity identity = getCallerIdentity(who);
+        Preconditions.checkCallAuthorization(isDeviceOwner(identity));
 
-        enforceDeviceOwner(who);
-        final int userHandle = mInjector.binderGetCallingUserHandle().getIdentifier();
         synchronized (getLockObject()) {
-            final List<String> packages = getUserData(userHandle).mUserControlDisabledPackages;
+            final List<String> packages =
+                    getUserData(identity.getUserId()).mUserControlDisabledPackages;
             return packages == null ? Collections.EMPTY_LIST : packages;
         }
     }
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/RemoteBugreportManager.java b/services/devicepolicy/java/com/android/server/devicepolicy/RemoteBugreportManager.java
new file mode 100644
index 0000000..46c9aab
--- /dev/null
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/RemoteBugreportManager.java
@@ -0,0 +1,324 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.devicepolicy;
+
+import static android.app.admin.DevicePolicyManager.ACTION_BUGREPORT_SHARING_ACCEPTED;
+import static android.app.admin.DevicePolicyManager.ACTION_BUGREPORT_SHARING_DECLINED;
+import static android.app.admin.DevicePolicyManager.ACTION_REMOTE_BUGREPORT_DISPATCH;
+import static android.app.admin.DevicePolicyManager.EXTRA_BUGREPORT_NOTIFICATION_TYPE;
+import static android.app.admin.DevicePolicyManager.EXTRA_REMOTE_BUGREPORT_HASH;
+import static android.app.admin.DevicePolicyManager.NOTIFICATION_BUGREPORT_ACCEPTED_NOT_FINISHED;
+import static android.app.admin.DevicePolicyManager.NOTIFICATION_BUGREPORT_FINISHED_NOT_ACCEPTED;
+import static android.app.admin.DevicePolicyManager.NOTIFICATION_BUGREPORT_STARTED;
+
+import android.annotation.IntDef;
+import android.app.Notification;
+import android.app.PendingIntent;
+import android.app.admin.DeviceAdminReceiver;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.pm.ActivityInfo;
+import android.content.pm.PackageManager;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.RemoteException;
+import android.os.UserHandle;
+import android.provider.Settings;
+import android.text.format.DateUtils;
+import android.util.Pair;
+import android.util.Slog;
+
+import com.android.internal.R;
+import com.android.internal.messages.nano.SystemMessageProto.SystemMessage;
+import com.android.internal.notification.SystemNotificationChannels;
+
+import java.io.FileNotFoundException;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+/**
+ * Class managing bugreport collection upon device owner's request.
+ */
+public class RemoteBugreportManager {
+    private static final String LOG_TAG = DevicePolicyManagerService.LOG_TAG;
+
+    static final String BUGREPORT_MIMETYPE = "application/vnd.android.bugreport";
+
+    private static final long REMOTE_BUGREPORT_TIMEOUT_MILLIS = 10 * DateUtils.MINUTE_IN_MILLIS;
+    private static final String CTL_STOP = "ctl.stop";
+    private static final String REMOTE_BUGREPORT_SERVICE = "bugreportd";
+    private static final int NOTIFICATION_ID = SystemMessage.NOTE_REMOTE_BUGREPORT;
+
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef({
+            NOTIFICATION_BUGREPORT_STARTED,
+            NOTIFICATION_BUGREPORT_ACCEPTED_NOT_FINISHED,
+            NOTIFICATION_BUGREPORT_FINISHED_NOT_ACCEPTED
+    })
+    @interface RemoteBugreportNotificationType {}
+    private final DevicePolicyManagerService mService;
+    private final DevicePolicyManagerService.Injector mInjector;
+
+    private final AtomicBoolean mRemoteBugreportServiceIsActive = new AtomicBoolean();
+    private final AtomicBoolean mRemoteBugreportSharingAccepted = new AtomicBoolean();
+    private final Context mContext;
+
+    private final Handler mHandler;
+
+    private final Runnable mRemoteBugreportTimeoutRunnable = () -> {
+        if (mRemoteBugreportServiceIsActive.get()) {
+            onBugreportFailed();
+        }
+    };
+
+    private final BroadcastReceiver mRemoteBugreportFinishedReceiver = new BroadcastReceiver() {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            if (ACTION_REMOTE_BUGREPORT_DISPATCH.equals(intent.getAction())
+                    && mRemoteBugreportServiceIsActive.get()) {
+                onBugreportFinished(intent);
+            }
+        }
+    };
+
+    private final BroadcastReceiver mRemoteBugreportConsentReceiver = new BroadcastReceiver() {
+
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            final String action = intent.getAction();
+            mInjector.getNotificationManager().cancel(LOG_TAG, NOTIFICATION_ID);
+            if (ACTION_BUGREPORT_SHARING_ACCEPTED.equals(action)) {
+                onBugreportSharingAccepted();
+            } else if (ACTION_BUGREPORT_SHARING_DECLINED.equals(action)) {
+                onBugreportSharingDeclined();
+            }
+            mContext.unregisterReceiver(mRemoteBugreportConsentReceiver);
+        }
+    };
+
+    public RemoteBugreportManager(
+            DevicePolicyManagerService service, DevicePolicyManagerService.Injector injector) {
+        mService = service;
+        mInjector = injector;
+        mContext = service.mContext;
+        mHandler = service.mHandler;
+    }
+
+    private Notification buildNotification(@RemoteBugreportNotificationType int type) {
+        final Intent dialogIntent = new Intent(Settings.ACTION_SHOW_REMOTE_BUGREPORT_DIALOG);
+        dialogIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
+        dialogIntent.putExtra(EXTRA_BUGREPORT_NOTIFICATION_TYPE, type);
+
+        // Fill the component explicitly to prevent the PendingIntent from being intercepted
+        // and fired with crafted target. b/155183624
+        final ActivityInfo targetInfo = dialogIntent.resolveActivityInfo(
+                mContext.getPackageManager(), PackageManager.MATCH_SYSTEM_ONLY);
+        if (targetInfo != null) {
+            dialogIntent.setComponent(targetInfo.getComponentName());
+        } else {
+            Slog.wtf(LOG_TAG, "Failed to resolve intent for remote bugreport dialog");
+        }
+
+        final PendingIntent pendingDialogIntent = PendingIntent.getActivityAsUser(mContext, type,
+                dialogIntent, 0, null, UserHandle.CURRENT);
+
+        final Notification.Builder builder =
+                new Notification.Builder(mContext, SystemNotificationChannels.DEVICE_ADMIN)
+                        .setSmallIcon(com.android.internal.R.drawable.stat_sys_adb)
+                        .setOngoing(true)
+                        .setLocalOnly(true)
+                        .setContentIntent(pendingDialogIntent)
+                        .setColor(mContext.getColor(
+                                com.android.internal.R.color.system_notification_accent_color));
+
+        if (type == NOTIFICATION_BUGREPORT_ACCEPTED_NOT_FINISHED) {
+            builder.setContentTitle(mContext.getString(
+                        R.string.sharing_remote_bugreport_notification_title))
+                    .setProgress(0, 0, true);
+        } else if (type == NOTIFICATION_BUGREPORT_STARTED) {
+            builder.setContentTitle(mContext.getString(
+                        R.string.taking_remote_bugreport_notification_title))
+                    .setProgress(0, 0, true);
+        } else if (type == NOTIFICATION_BUGREPORT_FINISHED_NOT_ACCEPTED) {
+            final PendingIntent pendingIntentAccept = PendingIntent.getBroadcast(mContext,
+                    NOTIFICATION_ID, new Intent(ACTION_BUGREPORT_SHARING_ACCEPTED),
+                    PendingIntent.FLAG_CANCEL_CURRENT);
+            final PendingIntent pendingIntentDecline = PendingIntent.getBroadcast(mContext,
+                    NOTIFICATION_ID, new Intent(ACTION_BUGREPORT_SHARING_DECLINED),
+                    PendingIntent.FLAG_CANCEL_CURRENT);
+            builder.addAction(new Notification.Action.Builder(null /* icon */, mContext.getString(
+                        R.string.decline_remote_bugreport_action), pendingIntentDecline).build())
+                    .addAction(new Notification.Action.Builder(null /* icon */, mContext.getString(
+                        R.string.share_remote_bugreport_action), pendingIntentAccept).build())
+                    .setContentTitle(mContext.getString(
+                        R.string.share_remote_bugreport_notification_title))
+                    .setContentText(mContext.getString(
+                        R.string.share_remote_bugreport_notification_message_finished))
+                    .setStyle(new Notification.BigTextStyle().bigText(mContext.getString(
+                        R.string.share_remote_bugreport_notification_message_finished)));
+        }
+
+        return builder.build();
+    }
+
+    /**
+     * Initiates bugreport collection.
+     * @return whether collection was initiated successfully.
+     */
+    public boolean requestBugreport() {
+        if (mRemoteBugreportServiceIsActive.get()
+                || (mService.getDeviceOwnerRemoteBugreportUriAndHash() != null)) {
+            Slog.d(LOG_TAG, "Remote bugreport wasn't started because there's already one running.");
+            return false;
+        }
+
+        final long callingIdentity = mInjector.binderClearCallingIdentity();
+        try {
+            mInjector.getIActivityManager().requestRemoteBugReport();
+
+            mRemoteBugreportServiceIsActive.set(true);
+            mRemoteBugreportSharingAccepted.set(false);
+            registerRemoteBugreportReceivers();
+            mInjector.getNotificationManager().notifyAsUser(LOG_TAG, NOTIFICATION_ID,
+                    buildNotification(NOTIFICATION_BUGREPORT_STARTED), UserHandle.ALL);
+            mHandler.postDelayed(mRemoteBugreportTimeoutRunnable, REMOTE_BUGREPORT_TIMEOUT_MILLIS);
+            return true;
+        } catch (RemoteException re) {
+            // should never happen
+            Slog.e(LOG_TAG, "Failed to make remote calls to start bugreportremote service", re);
+            return false;
+        } finally {
+            mInjector.binderRestoreCallingIdentity(callingIdentity);
+        }
+    }
+
+    private void registerRemoteBugreportReceivers() {
+        try {
+            final IntentFilter filterFinished =
+                    new IntentFilter(ACTION_REMOTE_BUGREPORT_DISPATCH, BUGREPORT_MIMETYPE);
+            mContext.registerReceiver(mRemoteBugreportFinishedReceiver, filterFinished);
+        } catch (IntentFilter.MalformedMimeTypeException e) {
+            // should never happen, as setting a constant
+            Slog.w(LOG_TAG, "Failed to set type " + BUGREPORT_MIMETYPE, e);
+        }
+        final IntentFilter filterConsent = new IntentFilter();
+        filterConsent.addAction(ACTION_BUGREPORT_SHARING_DECLINED);
+        filterConsent.addAction(ACTION_BUGREPORT_SHARING_ACCEPTED);
+        mContext.registerReceiver(mRemoteBugreportConsentReceiver, filterConsent);
+    }
+
+    private void onBugreportFinished(Intent intent) {
+        mHandler.removeCallbacks(mRemoteBugreportTimeoutRunnable);
+        mRemoteBugreportServiceIsActive.set(false);
+        final Uri bugreportUri = intent.getData();
+        String bugreportUriString = null;
+        if (bugreportUri != null) {
+            bugreportUriString = bugreportUri.toString();
+        }
+        final String bugreportHash = intent.getStringExtra(EXTRA_REMOTE_BUGREPORT_HASH);
+        if (mRemoteBugreportSharingAccepted.get()) {
+            shareBugreportWithDeviceOwnerIfExists(bugreportUriString, bugreportHash);
+            mInjector.getNotificationManager().cancel(LOG_TAG,
+                    NOTIFICATION_ID);
+        } else {
+            mService.setDeviceOwnerRemoteBugreportUriAndHash(bugreportUriString, bugreportHash);
+            mInjector.getNotificationManager().notifyAsUser(LOG_TAG, NOTIFICATION_ID,
+                    buildNotification(NOTIFICATION_BUGREPORT_FINISHED_NOT_ACCEPTED),
+                    UserHandle.ALL);
+        }
+        mContext.unregisterReceiver(mRemoteBugreportFinishedReceiver);
+    }
+
+    private void onBugreportFailed() {
+        mRemoteBugreportServiceIsActive.set(false);
+        mInjector.systemPropertiesSet(CTL_STOP, REMOTE_BUGREPORT_SERVICE);
+        mRemoteBugreportSharingAccepted.set(false);
+        mService.setDeviceOwnerRemoteBugreportUriAndHash(null, null);
+        mInjector.getNotificationManager().cancel(LOG_TAG, NOTIFICATION_ID);
+        final Bundle extras = new Bundle();
+        extras.putInt(DeviceAdminReceiver.EXTRA_BUGREPORT_FAILURE_REASON,
+                DeviceAdminReceiver.BUGREPORT_FAILURE_FAILED_COMPLETING);
+        mService.sendDeviceOwnerCommand(DeviceAdminReceiver.ACTION_BUGREPORT_FAILED, extras);
+        mContext.unregisterReceiver(mRemoteBugreportConsentReceiver);
+        mContext.unregisterReceiver(mRemoteBugreportFinishedReceiver);
+    }
+
+    private void onBugreportSharingAccepted() {
+        mRemoteBugreportSharingAccepted.set(true);
+        final Pair<String, String> uriAndHash = mService.getDeviceOwnerRemoteBugreportUriAndHash();
+        if (uriAndHash != null) {
+            shareBugreportWithDeviceOwnerIfExists(uriAndHash.first, uriAndHash.second);
+        } else if (mRemoteBugreportServiceIsActive.get()) {
+            mInjector.getNotificationManager().notifyAsUser(LOG_TAG, NOTIFICATION_ID,
+                    buildNotification(NOTIFICATION_BUGREPORT_ACCEPTED_NOT_FINISHED),
+                    UserHandle.ALL);
+        }
+    }
+
+    private void onBugreportSharingDeclined() {
+        if (mRemoteBugreportServiceIsActive.get()) {
+            mInjector.systemPropertiesSet(CTL_STOP,
+                    REMOTE_BUGREPORT_SERVICE);
+            mRemoteBugreportServiceIsActive.set(false);
+            mHandler.removeCallbacks(mRemoteBugreportTimeoutRunnable);
+            mContext.unregisterReceiver(mRemoteBugreportFinishedReceiver);
+        }
+        mRemoteBugreportSharingAccepted.set(false);
+        mService.setDeviceOwnerRemoteBugreportUriAndHash(null, null);
+        mService.sendDeviceOwnerCommand(
+                DeviceAdminReceiver.ACTION_BUGREPORT_SHARING_DECLINED, null);
+    }
+
+    private void shareBugreportWithDeviceOwnerIfExists(
+            String bugreportUriString, String bugreportHash) {
+        try {
+            if (bugreportUriString == null) {
+                throw new FileNotFoundException();
+            }
+            final Uri bugreportUri = Uri.parse(bugreportUriString);
+            mService.sendBugreportToDeviceOwner(bugreportUri, bugreportHash);
+        } catch (FileNotFoundException e) {
+            final Bundle extras = new Bundle();
+            extras.putInt(DeviceAdminReceiver.EXTRA_BUGREPORT_FAILURE_REASON,
+                    DeviceAdminReceiver.BUGREPORT_FAILURE_FILE_NO_LONGER_AVAILABLE);
+            mService.sendDeviceOwnerCommand(DeviceAdminReceiver.ACTION_BUGREPORT_FAILED, extras);
+        } finally {
+            mRemoteBugreportSharingAccepted.set(false);
+            mService.setDeviceOwnerRemoteBugreportUriAndHash(null, null);
+        }
+    }
+
+    /**
+     * Check if a bugreport was collected but not shared before reboot because the user didn't act
+     * upon sharing notification.
+     */
+    public void checkForPendingBugreportAfterBoot() {
+        if (mService.getDeviceOwnerRemoteBugreportUriAndHash() == null) {
+            return;
+        }
+        final IntentFilter filterConsent = new IntentFilter();
+        filterConsent.addAction(ACTION_BUGREPORT_SHARING_DECLINED);
+        filterConsent.addAction(ACTION_BUGREPORT_SHARING_ACCEPTED);
+        mContext.registerReceiver(mRemoteBugreportConsentReceiver, filterConsent);
+        mInjector.getNotificationManager().notifyAsUser(LOG_TAG, NOTIFICATION_ID,
+                buildNotification(NOTIFICATION_BUGREPORT_FINISHED_NOT_ACCEPTED), UserHandle.ALL);
+    }
+}
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/RemoteBugreportUtils.java b/services/devicepolicy/java/com/android/server/devicepolicy/RemoteBugreportUtils.java
deleted file mode 100644
index 1630f27..0000000
--- a/services/devicepolicy/java/com/android/server/devicepolicy/RemoteBugreportUtils.java
+++ /dev/null
@@ -1,121 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.devicepolicy;
-
-import android.annotation.IntDef;
-import android.app.Notification;
-import android.app.PendingIntent;
-import android.app.admin.DevicePolicyManager;
-import android.content.Context;
-import android.content.Intent;
-import android.content.pm.ActivityInfo;
-import android.content.pm.PackageManager;
-import android.os.UserHandle;
-import android.provider.Settings;
-import android.text.format.DateUtils;
-import android.util.Slog;
-
-import com.android.internal.R;
-import com.android.internal.messages.nano.SystemMessageProto.SystemMessage;
-import com.android.internal.notification.SystemNotificationChannels;
-
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-
-/**
- * Utilities class for the remote bugreport operation.
- */
-class RemoteBugreportUtils {
-
-    private static final String TAG = "RemoteBugreportUtils";
-    static final int NOTIFICATION_ID = SystemMessage.NOTE_REMOTE_BUGREPORT;
-
-    @Retention(RetentionPolicy.SOURCE)
-    @IntDef({
-        DevicePolicyManager.NOTIFICATION_BUGREPORT_STARTED,
-        DevicePolicyManager.NOTIFICATION_BUGREPORT_ACCEPTED_NOT_FINISHED,
-        DevicePolicyManager.NOTIFICATION_BUGREPORT_FINISHED_NOT_ACCEPTED
-    })
-    @interface RemoteBugreportNotificationType {}
-
-    static final long REMOTE_BUGREPORT_TIMEOUT_MILLIS = 10 * DateUtils.MINUTE_IN_MILLIS;
-
-    static final String CTL_STOP = "ctl.stop";
-    static final String REMOTE_BUGREPORT_SERVICE = "bugreportd";
-
-    static final String BUGREPORT_MIMETYPE = "application/vnd.android.bugreport";
-
-    static Notification buildNotification(Context context,
-            @RemoteBugreportNotificationType int type) {
-        Intent dialogIntent = new Intent(Settings.ACTION_SHOW_REMOTE_BUGREPORT_DIALOG);
-        dialogIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
-        dialogIntent.putExtra(DevicePolicyManager.EXTRA_BUGREPORT_NOTIFICATION_TYPE, type);
-
-        // Fill the component explicitly to prevent the PendingIntent from being intercepted
-        // and fired with crafted target. b/155183624
-        ActivityInfo targetInfo = dialogIntent.resolveActivityInfo(
-                context.getPackageManager(), PackageManager.MATCH_SYSTEM_ONLY);
-        if (targetInfo != null) {
-            dialogIntent.setComponent(targetInfo.getComponentName());
-        } else {
-            Slog.wtf(TAG, "Failed to resolve intent for remote bugreport dialog");
-        }
-
-        PendingIntent pendingDialogIntent = PendingIntent.getActivityAsUser(context, type,
-                dialogIntent, 0, null, UserHandle.CURRENT);
-
-        Notification.Builder builder =
-                new Notification.Builder(context, SystemNotificationChannels.DEVICE_ADMIN)
-                        .setSmallIcon(com.android.internal.R.drawable.stat_sys_adb)
-                        .setOngoing(true)
-                        .setLocalOnly(true)
-                        .setContentIntent(pendingDialogIntent)
-                        .setColor(context.getColor(
-                                com.android.internal.R.color.system_notification_accent_color));
-
-        if (type == DevicePolicyManager.NOTIFICATION_BUGREPORT_ACCEPTED_NOT_FINISHED) {
-            builder.setContentTitle(context.getString(
-                        R.string.sharing_remote_bugreport_notification_title))
-                    .setProgress(0, 0, true);
-        } else if (type == DevicePolicyManager.NOTIFICATION_BUGREPORT_STARTED) {
-            builder.setContentTitle(context.getString(
-                        R.string.taking_remote_bugreport_notification_title))
-                    .setProgress(0, 0, true);
-        } else if (type == DevicePolicyManager.NOTIFICATION_BUGREPORT_FINISHED_NOT_ACCEPTED) {
-            PendingIntent pendingIntentAccept = PendingIntent.getBroadcast(context, NOTIFICATION_ID,
-                    new Intent(DevicePolicyManager.ACTION_BUGREPORT_SHARING_ACCEPTED),
-                    PendingIntent.FLAG_CANCEL_CURRENT);
-            PendingIntent pendingIntentDecline = PendingIntent.getBroadcast(context,
-                    NOTIFICATION_ID, new Intent(
-                            DevicePolicyManager.ACTION_BUGREPORT_SHARING_DECLINED),
-                    PendingIntent.FLAG_CANCEL_CURRENT);
-            builder.addAction(new Notification.Action.Builder(null /* icon */, context.getString(
-                        R.string.decline_remote_bugreport_action), pendingIntentDecline).build())
-                    .addAction(new Notification.Action.Builder(null /* icon */, context.getString(
-                        R.string.share_remote_bugreport_action), pendingIntentAccept).build())
-                    .setContentTitle(context.getString(
-                        R.string.share_remote_bugreport_notification_title))
-                    .setContentText(context.getString(
-                        R.string.share_remote_bugreport_notification_message_finished))
-                    .setStyle(new Notification.BigTextStyle().bigText(context.getString(
-                        R.string.share_remote_bugreport_notification_message_finished)));
-        }
-
-        return builder.build();
-    }
-}
-
diff --git a/services/incremental/IncrementalService.cpp b/services/incremental/IncrementalService.cpp
index a5f0d04..f7082a9 100644
--- a/services/incremental/IncrementalService.cpp
+++ b/services/incremental/IncrementalService.cpp
@@ -23,7 +23,6 @@
 #include <android-base/properties.h>
 #include <android-base/stringprintf.h>
 #include <binder/AppOpsManager.h>
-#include <binder/Nullable.h>
 #include <binder/Status.h>
 #include <sys/stat.h>
 #include <uuid/uuid.h>
@@ -1404,7 +1403,7 @@
     }
 
     FileSystemControlParcel fsControlParcel;
-    fsControlParcel.incremental = aidl::make_nullable<IncrementalFileSystemControlParcel>();
+    fsControlParcel.incremental = std::make_optional<IncrementalFileSystemControlParcel>();
     fsControlParcel.incremental->cmd.reset(dup(ifs.control.cmd()));
     fsControlParcel.incremental->pendingReads.reset(dup(ifs.control.pendingReads()));
     fsControlParcel.incremental->log.reset(dup(ifs.control.logs()));
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index ae6ccda..97ae505 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -121,9 +121,7 @@
 import com.android.server.integrity.AppIntegrityManagerService;
 import com.android.server.lights.LightsService;
 import com.android.server.location.LocationManagerService;
-import com.android.server.media.MediaResourceMonitorService;
 import com.android.server.media.MediaRouterService;
-import com.android.server.media.MediaSessionService;
 import com.android.server.media.projection.MediaProjectionManagerService;
 import com.android.server.net.NetworkPolicyManagerService;
 import com.android.server.net.NetworkStatsService;
@@ -152,6 +150,7 @@
 import com.android.server.power.PowerManagerService;
 import com.android.server.power.ShutdownThread;
 import com.android.server.power.ThermalManagerService;
+import com.android.server.profcollect.ProfcollectForwardingService;
 import com.android.server.recoverysystem.RecoverySystemService;
 import com.android.server.restrictions.RestrictionsManagerService;
 import com.android.server.role.RoleManagerService;
@@ -311,6 +310,10 @@
             "com.android.server.rollback.RollbackManagerService";
     private static final String ALARM_MANAGER_SERVICE_CLASS =
             "com.android.server.alarm.AlarmManagerService";
+    private static final String MEDIA_SESSION_SERVICE_CLASS =
+            "com.android.server.media.MediaSessionService";
+    private static final String MEDIA_RESOURCE_MONITOR_SERVICE_CLASS =
+            "com.android.server.media.MediaResourceMonitorService";
 
     private static final String TETHERING_CONNECTOR_CLASS = "android.net.ITetheringConnector";
 
@@ -1229,6 +1232,12 @@
             mSystemServiceManager.startService(IorapForwardingService.class);
             t.traceEnd();
 
+            if (Build.IS_DEBUGGABLE) {
+                t.traceBegin("ProfcollectForwardingService");
+                mSystemServiceManager.startService(ProfcollectForwardingService.class);
+                t.traceEnd();
+            }
+
             t.traceBegin("SignedConfigService");
             SignedConfigService.registerUpdateReceiver(mSystemContext);
             t.traceEnd();
@@ -1886,7 +1895,7 @@
             t.traceEnd();
 
             t.traceBegin("StartMediaSessionService");
-            mSystemServiceManager.startService(MediaSessionService.class);
+            mSystemServiceManager.startService(MEDIA_SESSION_SERVICE_CLASS);
             t.traceEnd();
 
             if (mPackageManager.hasSystemFeature(PackageManager.FEATURE_HDMI_CEC)) {
@@ -1910,7 +1919,7 @@
 
             if (mPackageManager.hasSystemFeature(PackageManager.FEATURE_PICTURE_IN_PICTURE)) {
                 t.traceBegin("StartMediaResourceMonitor");
-                mSystemServiceManager.startService(MediaResourceMonitorService.class);
+                mSystemServiceManager.startService(MEDIA_RESOURCE_MONITOR_SERVICE_CLASS);
                 t.traceEnd();
             }
 
diff --git a/services/midi/java/com/android/server/midi/MidiService.java b/services/midi/java/com/android/server/midi/MidiService.java
index 51478b3..2cfdf3f 100644
--- a/services/midi/java/com/android/server/midi/MidiService.java
+++ b/services/midi/java/com/android/server/midi/MidiService.java
@@ -16,6 +16,7 @@
 
 package com.android.server.midi;
 
+import android.annotation.NonNull;
 import android.bluetooth.BluetoothDevice;
 import android.content.ComponentName;
 import android.content.Context;
@@ -47,8 +48,8 @@
 import com.android.internal.content.PackageMonitor;
 import com.android.internal.util.DumpUtils;
 import com.android.internal.util.IndentingPrintWriter;
-import com.android.internal.util.XmlUtils;
 import com.android.server.SystemService;
+import com.android.server.SystemService.TargetUser;
 
 import org.xmlpull.v1.XmlPullParser;
 
@@ -75,8 +76,8 @@
         }
 
         @Override
-        public void onUnlockUser(int userHandle) {
-            if (userHandle == UserHandle.USER_SYSTEM) {
+        public void onUserUnlocking(@NonNull TargetUser user) {
+            if (user.getUserIdentifier()  == UserHandle.USER_SYSTEM) {
                 mMidiService.onUnlockUser();
             }
         }
diff --git a/services/people/java/com/android/server/people/PeopleService.java b/services/people/java/com/android/server/people/PeopleService.java
index 37bf664..33317a3 100644
--- a/services/people/java/com/android/server/people/PeopleService.java
+++ b/services/people/java/com/android/server/people/PeopleService.java
@@ -19,6 +19,8 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.UserIdInt;
+import android.app.people.ConversationChannel;
+import android.app.people.IPeopleManager;
 import android.app.prediction.AppPredictionContext;
 import android.app.prediction.AppPredictionSessionId;
 import android.app.prediction.AppTarget;
@@ -26,8 +28,11 @@
 import android.app.prediction.IPredictionCallback;
 import android.content.Context;
 import android.content.pm.ParceledListSlice;
+import android.os.Binder;
 import android.os.CancellationSignal;
+import android.os.Process;
 import android.os.RemoteException;
+import android.os.UserHandle;
 import android.util.ArrayMap;
 import android.util.Slog;
 
@@ -35,6 +40,7 @@
 import com.android.server.SystemService;
 import com.android.server.people.data.DataManager;
 
+import java.util.ArrayList;
 import java.util.List;
 import java.util.Map;
 import java.util.function.Consumer;
@@ -68,6 +74,7 @@
 
     @Override
     public void onStart() {
+        publishBinderService(Context.PEOPLE_SERVICE, new BinderService());
         publishLocalService(PeopleServiceInternal.class, new LocalService());
     }
 
@@ -81,6 +88,38 @@
         mDataManager.onUserStopping(user.getUserIdentifier());
     }
 
+    /**
+     * Enforces that only the system or root UID can make certain calls.
+     *
+     * @param message used as message if SecurityException is thrown
+     * @throws SecurityException if the caller is not system or root
+     */
+    private static void enforceSystemOrRoot(String message) {
+        int uid = Binder.getCallingUid();
+        if (!UserHandle.isSameApp(uid, Process.SYSTEM_UID) && uid != Process.ROOT_UID) {
+            throw new SecurityException("Only system may " + message);
+        }
+    }
+
+    private final class BinderService extends IPeopleManager.Stub {
+
+        @Override
+        public ParceledListSlice<ConversationChannel> getRecentConversations() {
+            enforceSystemOrRoot("get recent conversations");
+            return new ParceledListSlice<>(new ArrayList<>());
+        }
+
+        @Override
+        public void removeRecentConversation(String packageName, int userId, String shortcutId) {
+            enforceSystemOrRoot("remove a recent conversation");
+        }
+
+        @Override
+        public void removeAllRecentConversations() {
+            enforceSystemOrRoot("remove all recent conversations");
+        }
+    }
+
     @VisibleForTesting
     final class LocalService extends PeopleServiceInternal {
 
diff --git a/services/print/java/com/android/server/print/PrintManagerService.java b/services/print/java/com/android/server/print/PrintManagerService.java
index d064f7e..1cdcbd8 100644
--- a/services/print/java/com/android/server/print/PrintManagerService.java
+++ b/services/print/java/com/android/server/print/PrintManagerService.java
@@ -72,6 +72,7 @@
 import com.android.internal.util.dump.DualDumpOutputStream;
 import com.android.server.LocalServices;
 import com.android.server.SystemService;
+import com.android.server.SystemService.TargetUser;
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
@@ -101,13 +102,13 @@
     }
 
     @Override
-    public void onUnlockUser(int userHandle) {
-        mPrintManagerImpl.handleUserUnlocked(userHandle);
+    public void onUserUnlocking(@NonNull TargetUser user) {
+        mPrintManagerImpl.handleUserUnlocked(user.getUserIdentifier());
     }
 
     @Override
-    public void onStopUser(int userHandle) {
-        mPrintManagerImpl.handleUserStopped(userHandle);
+    public void onUserStopping(@NonNull TargetUser user) {
+        mPrintManagerImpl.handleUserStopped(user.getUserIdentifier());
     }
 
     class PrintManagerImpl extends IPrintManager.Stub {
diff --git a/services/print/java/com/android/server/print/UserState.java b/services/print/java/com/android/server/print/UserState.java
index e8266a5..b93c519 100644
--- a/services/print/java/com/android/server/print/UserState.java
+++ b/services/print/java/com/android/server/print/UserState.java
@@ -30,6 +30,7 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.annotation.UserIdInt;
 import android.app.PendingIntent;
 import android.content.ComponentName;
 import android.content.Context;
@@ -132,7 +133,7 @@
 
     private final Context mContext;
 
-    private final int mUserId;
+    private final @UserIdInt int mUserId;
 
     private final RemotePrintSpooler mSpooler;
 
@@ -650,7 +651,7 @@
 
                 mPrintServiceRecommendationsService =
                         new RemotePrintServiceRecommendationService(mContext,
-                                UserHandle.getUserHandleForUid(mUserId), this);
+                                UserHandle.of(mUserId), this);
             }
             mPrintServiceRecommendationsChangeListenerRecords.add(
                     new ListenerRecord<IRecommendationsChangeListener>(listener) {
diff --git a/services/profcollect/Android.bp b/services/profcollect/Android.bp
new file mode 100644
index 0000000..68fba55
--- /dev/null
+++ b/services/profcollect/Android.bp
@@ -0,0 +1,35 @@
+// 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.
+
+filegroup {
+  name: "services.profcollect-javasources",
+  srcs: ["src/**/*.java"],
+  path: "src",
+  visibility: ["//frameworks/base/services"],
+}
+
+filegroup {
+  name: "services.profcollect-sources",
+  srcs: [
+    ":services.profcollect-javasources",
+    ":profcollectd_aidl",
+  ],
+  visibility: ["//frameworks/base/services:__subpackages__"],
+}
+
+java_library_static {
+  name: "services.profcollect",
+  srcs: [":services.profcollect-sources"],
+  libs: ["services.core"],
+}
diff --git a/services/profcollect/OWNERS b/services/profcollect/OWNERS
new file mode 100644
index 0000000..b380e39
--- /dev/null
+++ b/services/profcollect/OWNERS
@@ -0,0 +1,3 @@
+srhines@google.com
+yabinc@google.com
+yikong@google.com
diff --git a/services/profcollect/src/com/android/server/profcollect/ProfcollectForwardingService.java b/services/profcollect/src/com/android/server/profcollect/ProfcollectForwardingService.java
new file mode 100644
index 0000000..12c69ea
--- /dev/null
+++ b/services/profcollect/src/com/android/server/profcollect/ProfcollectForwardingService.java
@@ -0,0 +1,197 @@
+/**
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.profcollect;
+
+import android.content.Context;
+import android.content.Intent;
+import android.os.Handler;
+import android.os.IBinder.DeathRecipient;
+import android.os.Looper;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.os.SystemProperties;
+import android.util.Log;
+
+import com.android.server.IoThread;
+import com.android.server.LocalServices;
+import com.android.server.SystemService;
+import com.android.server.wm.ActivityMetricsLaunchObserver;
+import com.android.server.wm.ActivityMetricsLaunchObserverRegistry;
+import com.android.server.wm.ActivityTaskManagerInternal;
+
+import java.util.concurrent.ThreadLocalRandom;
+
+/**
+ * System-server-local proxy into the {@code IProfcollectd} native service.
+ */
+public final class ProfcollectForwardingService extends SystemService {
+    public static final String LOG_TAG = "ProfcollectForwardingService";
+
+    private IProfCollectd mIProfcollect;
+    private ProfcollectForwardingService mSelfService;
+    private final Handler mHandler = new ProfcollectdHandler(IoThread.getHandler().getLooper());
+
+    public ProfcollectForwardingService(Context context) {
+        super(context);
+
+        if (mSelfService != null) {
+            throw new AssertionError("only one service instance allowed");
+        }
+        mSelfService = this;
+    }
+
+    @Override
+    public void onStart() {
+        Log.i(LOG_TAG, "Profcollect forwarding service start");
+        connectNativeService();
+        if (mIProfcollect == null) {
+            return;
+        }
+        if (serviceHasSupportedTraceProvider()) {
+            registerObservers();
+        }
+    }
+
+    private boolean serviceHasSupportedTraceProvider() {
+        if (mIProfcollect == null) {
+            return false;
+        }
+        try {
+            return !mIProfcollect.GetSupportedProvider().isEmpty();
+        } catch (RemoteException e) {
+            Log.e(LOG_TAG, e.getMessage());
+            return false;
+        }
+    }
+
+    private boolean tryConnectNativeService() {
+        if (connectNativeService()) {
+            return true;
+        }
+        // Cannot connect to the native service at this time, retry after a short delay.
+        mHandler.sendEmptyMessageDelayed(ProfcollectdHandler.MESSAGE_BINDER_CONNECT, 5000);
+        return false;
+    }
+
+    private boolean connectNativeService() {
+        try {
+            IProfCollectd profcollectd =
+                    IProfCollectd.Stub.asInterface(
+                            ServiceManager.getServiceOrThrow("profcollectd"));
+            profcollectd.asBinder().linkToDeath(new ProfcollectdDeathRecipient(), /*flags*/0);
+            mIProfcollect = profcollectd;
+            return true;
+        } catch (ServiceManager.ServiceNotFoundException | RemoteException e) {
+            Log.w(LOG_TAG, "Failed to connect profcollectd binder service.");
+            return false;
+        }
+    }
+
+    private class ProfcollectdHandler extends Handler {
+        public ProfcollectdHandler(Looper looper) {
+            super(looper);
+        }
+
+        public static final int MESSAGE_BINDER_CONNECT = 0;
+
+        @Override
+        public void handleMessage(android.os.Message message) {
+            switch (message.what) {
+                case MESSAGE_BINDER_CONNECT:
+                    connectNativeService();
+                    break;
+                default:
+                    throw new AssertionError("Unknown message: " + message.toString());
+            }
+        }
+    }
+
+    private class ProfcollectdDeathRecipient implements DeathRecipient {
+        @Override
+        public void binderDied() {
+            Log.w(LOG_TAG, "profcollectd has died");
+
+            mIProfcollect = null;
+            tryConnectNativeService();
+        }
+    }
+
+    // Event observers
+    private void registerObservers() {
+        registerAppLaunchObserver();
+    }
+
+    private final AppLaunchObserver mAppLaunchObserver = new AppLaunchObserver();
+    private void registerAppLaunchObserver() {
+        ActivityTaskManagerInternal atmInternal =
+                LocalServices.getService(ActivityTaskManagerInternal.class);
+        ActivityMetricsLaunchObserverRegistry launchObserverRegistry =
+                atmInternal.getLaunchObserverRegistry();
+        launchObserverRegistry.registerLaunchObserver(mAppLaunchObserver);
+    }
+
+    private void traceOnAppStart(String packageName) {
+        if (mIProfcollect == null) {
+            return;
+        }
+
+        // Sample for a fraction of app launches.
+        int traceFrequency =
+                SystemProperties.getInt("persist.profcollectd.applaunch_trace_freq", 2);
+        int randomNum = ThreadLocalRandom.current().nextInt(100);
+        if (randomNum < traceFrequency) {
+            try {
+                Log.i(LOG_TAG, "Tracing on app launch event: " + packageName);
+                mIProfcollect.TraceOnce("applaunch");
+            } catch (RemoteException e) {
+                Log.e(LOG_TAG, e.getMessage());
+            }
+        }
+    }
+
+    private class AppLaunchObserver implements ActivityMetricsLaunchObserver {
+        @Override
+        public void onIntentStarted(Intent intent, long timestampNanos) {
+            traceOnAppStart(intent.getPackage());
+        }
+
+        @Override
+        public void onIntentFailed() {
+            // Ignored
+        }
+
+        @Override
+        public void onActivityLaunched(byte[] activity, int temperature) {
+            // Ignored
+        }
+
+        @Override
+        public void onActivityLaunchCancelled(byte[] abortingActivity) {
+            // Ignored
+        }
+
+        @Override
+        public void onActivityLaunchFinished(byte[] finalActivity, long timestampNanos) {
+            // Ignored
+        }
+
+        @Override
+        public void onReportFullyDrawn(byte[] activity, long timestampNanos) {
+            // Ignored
+        }
+    }
+}
diff --git a/services/robotests/backup/src/com/android/server/backup/BackupManagerServiceRoboTest.java b/services/robotests/backup/src/com/android/server/backup/BackupManagerServiceRoboTest.java
index 4a73efe..cbebe69 100644
--- a/services/robotests/backup/src/com/android/server/backup/BackupManagerServiceRoboTest.java
+++ b/services/robotests/backup/src/com/android/server/backup/BackupManagerServiceRoboTest.java
@@ -38,7 +38,6 @@
 
 import android.annotation.UserIdInt;
 import android.app.Application;
-import android.app.backup.BackupManager;
 import android.app.backup.BackupManager.OperationType;
 import android.app.backup.IBackupManagerMonitor;
 import android.app.backup.IBackupObserver;
@@ -46,6 +45,7 @@
 import android.app.backup.ISelectBackupTransportCallback;
 import android.content.Context;
 import android.content.Intent;
+import android.content.pm.UserInfo;
 import android.os.IBinder;
 import android.os.ParcelFileDescriptor;
 import android.os.Process;
@@ -54,6 +54,7 @@
 import android.platform.test.annotations.Presubmit;
 import android.util.SparseArray;
 
+import com.android.server.SystemService.TargetUser;
 import com.android.server.backup.testing.TransportData;
 import com.android.server.testing.shadows.ShadowApplicationPackageManager;
 import com.android.server.testing.shadows.ShadowBinder;
@@ -1091,9 +1092,11 @@
         registerUser(backupManagerService, mUserOneId, mUserOneService);
         setCallerAndGrantInteractUserPermission(mUserOneId, /* shouldGrantPermission */ false);
 
-        backupManagerService.beginRestoreSession(mUserOneId, TEST_PACKAGE, TEST_TRANSPORT);
+        backupManagerService.beginRestoreSession(mUserOneId, TEST_PACKAGE, TEST_TRANSPORT,
+                OperationType.BACKUP);
 
-        verify(mUserOneService).beginRestoreSession(TEST_PACKAGE, TEST_TRANSPORT);
+        verify(mUserOneService).beginRestoreSession(TEST_PACKAGE, TEST_TRANSPORT,
+                OperationType.BACKUP);
     }
 
     /** Test that the backup service does not route methods for non-registered users. */
@@ -1103,9 +1106,11 @@
         registerUser(backupManagerService, mUserOneId, mUserOneService);
         setCallerAndGrantInteractUserPermission(mUserTwoId, /* shouldGrantPermission */ false);
 
-        backupManagerService.beginRestoreSession(mUserTwoId, TEST_PACKAGE, TEST_TRANSPORT);
+        backupManagerService.beginRestoreSession(mUserTwoId, TEST_PACKAGE, TEST_TRANSPORT,
+                OperationType.BACKUP);
 
-        verify(mUserOneService, never()).beginRestoreSession(TEST_PACKAGE, TEST_TRANSPORT);
+        verify(mUserOneService, never()).beginRestoreSession(TEST_PACKAGE, TEST_TRANSPORT,
+                OperationType.BACKUP);
     }
 
     /** Test that the backup service routes methods correctly to the user that requests it. */
@@ -1601,7 +1606,7 @@
         BackupManagerService.Lifecycle lifecycle =
                 new BackupManagerService.Lifecycle(mContext, backupManagerService);
 
-        lifecycle.onUnlockUser(UserHandle.USER_SYSTEM);
+        lifecycle.onUserUnlocking(new TargetUser(new UserInfo(UserHandle.USER_SYSTEM, null, 0)));
 
         verify(backupManagerService).onUnlockUser(UserHandle.USER_SYSTEM);
     }
@@ -1613,7 +1618,7 @@
         BackupManagerService.Lifecycle lifecycle =
                 new BackupManagerService.Lifecycle(mContext, backupManagerService);
 
-        lifecycle.onStopUser(UserHandle.USER_SYSTEM);
+        lifecycle.onUserStopping(new TargetUser(new UserInfo(UserHandle.USER_SYSTEM, null, 0)));
 
         verify(backupManagerService).onStopUser(UserHandle.USER_SYSTEM);
     }
diff --git a/services/robotests/backup/src/com/android/server/backup/keyvalue/KeyValueBackupTaskTest.java b/services/robotests/backup/src/com/android/server/backup/keyvalue/KeyValueBackupTaskTest.java
index 6184c4e..09e3bfe 100644
--- a/services/robotests/backup/src/com/android/server/backup/keyvalue/KeyValueBackupTaskTest.java
+++ b/services/robotests/backup/src/com/android/server/backup/keyvalue/KeyValueBackupTaskTest.java
@@ -794,7 +794,7 @@
         setUpAgent(PACKAGE_1);
         doThrow(SecurityException.class)
                 .when(mBackupManagerService)
-                .bindToAgentSynchronous(argThat(applicationInfo(PACKAGE_1)), anyInt());
+                .bindToAgentSynchronous(argThat(applicationInfo(PACKAGE_1)), anyInt(), anyInt());
         KeyValueBackupTask task = createKeyValueBackupTask(transportMock, PACKAGE_1);
 
         runTask(task);
@@ -812,7 +812,7 @@
         setUpAgent(PACKAGE_1);
         doThrow(SecurityException.class)
                 .when(mBackupManagerService)
-                .bindToAgentSynchronous(argThat(applicationInfo(PACKAGE_1)), anyInt());
+                .bindToAgentSynchronous(argThat(applicationInfo(PACKAGE_1)), anyInt(), anyInt());
         KeyValueBackupTask task = createKeyValueBackupTask(transportMock, true, PACKAGE_1);
 
         runTask(task);
@@ -2593,11 +2593,13 @@
             if (packageData.available) {
                 doReturn(backupAgentBinder)
                         .when(mBackupManagerService)
-                        .bindToAgentSynchronous(argThat(applicationInfo(packageData)), anyInt());
+                        .bindToAgentSynchronous(argThat(applicationInfo(packageData)), anyInt(),
+                                anyInt());
             } else {
                 doReturn(null)
                         .when(mBackupManagerService)
-                        .bindToAgentSynchronous(argThat(applicationInfo(packageData)), anyInt());
+                        .bindToAgentSynchronous(argThat(applicationInfo(packageData)), anyInt(),
+                                anyInt());
             }
             return new AgentMock(backupAgentBinder, backupAgent);
         } catch (RemoteException e) {
diff --git a/services/robotests/backup/src/com/android/server/backup/restore/ActiveRestoreSessionTest.java b/services/robotests/backup/src/com/android/server/backup/restore/ActiveRestoreSessionTest.java
index 3fc421d..5883c1c 100644
--- a/services/robotests/backup/src/com/android/server/backup/restore/ActiveRestoreSessionTest.java
+++ b/services/robotests/backup/src/com/android/server/backup/restore/ActiveRestoreSessionTest.java
@@ -57,6 +57,7 @@
 import com.android.server.backup.testing.TransportData;
 import com.android.server.backup.testing.TransportTestUtils;
 import com.android.server.backup.testing.TransportTestUtils.TransportMock;
+import com.android.server.backup.utils.BackupEligibilityRules;
 import com.android.server.testing.shadows.ShadowApplicationPackageManager;
 import com.android.server.testing.shadows.ShadowEventLog;
 import com.android.server.testing.shadows.ShadowPerformUnifiedRestoreTask;
@@ -96,6 +97,7 @@
     @Mock private TransportManager mTransportManager;
     @Mock private IRestoreObserver mObserver;
     @Mock private IBackupManagerMonitor mMonitor;
+    @Mock private BackupEligibilityRules mBackupEligibilityRules;
     private ShadowLooper mShadowBackupLooper;
     private ShadowApplication mShadowApplication;
     private UserBackupManagerService.BackupWakeLock mWakeLock;
@@ -576,7 +578,8 @@
     private IRestoreSession createActiveRestoreSession(
             String packageName, TransportData transport) {
         return new ActiveRestoreSession(
-                mBackupManagerService, packageName, transport.transportName);
+                mBackupManagerService, packageName, transport.transportName,
+                mBackupEligibilityRules);
     }
 
     private IRestoreSession createActiveRestoreSessionWithRestoreSets(
@@ -584,7 +587,8 @@
             throws RemoteException {
         ActiveRestoreSession restoreSession =
                 new ActiveRestoreSession(
-                        mBackupManagerService, packageName, transport.transportName);
+                        mBackupManagerService, packageName, transport.transportName,
+                        mBackupEligibilityRules);
         restoreSession.setRestoreSets(restoreSets);
         return restoreSession;
     }
diff --git a/services/systemcaptions/java/com/android/server/systemcaptions/SystemCaptionsManagerService.java b/services/systemcaptions/java/com/android/server/systemcaptions/SystemCaptionsManagerService.java
index 27a116c..1586a33 100644
--- a/services/systemcaptions/java/com/android/server/systemcaptions/SystemCaptionsManagerService.java
+++ b/services/systemcaptions/java/com/android/server/systemcaptions/SystemCaptionsManagerService.java
@@ -34,7 +34,8 @@
                         context,
                         com.android.internal.R.string.config_defaultSystemCaptionsManagerService),
                 /*disallowProperty=*/ null,
-                /*packageUpdatePolicy=*/ PACKAGE_UPDATE_POLICY_REFRESH_EAGER);
+                /*packageUpdatePolicy=*/ PACKAGE_UPDATE_POLICY_REFRESH_EAGER
+                    | PACKAGE_RESTART_POLICY_REFRESH_EAGER);
     }
 
     @Override
diff --git a/services/tests/PackageManagerComponentOverrideTests/src/com/android/server/pm/test/override/PackageManagerComponentLabelIconOverrideTest.kt b/services/tests/PackageManagerComponentOverrideTests/src/com/android/server/pm/test/override/PackageManagerComponentLabelIconOverrideTest.kt
index ecdb30f..09552082 100644
--- a/services/tests/PackageManagerComponentOverrideTests/src/com/android/server/pm/test/override/PackageManagerComponentLabelIconOverrideTest.kt
+++ b/services/tests/PackageManagerComponentOverrideTests/src/com/android/server/pm/test/override/PackageManagerComponentLabelIconOverrideTest.kt
@@ -33,7 +33,6 @@
 import com.android.server.pm.parsing.pkg.ParsedPackage
 import com.android.server.pm.permission.PermissionManagerServiceInternal
 import com.android.server.pm.test.override.PackageManagerComponentLabelIconOverrideTest.Companion.Params.AppType
-import com.android.server.pm.test.override.R
 import com.android.server.testutils.TestHandler
 import com.android.server.testutils.mock
 import com.android.server.testutils.mockThrowOnUnmocked
@@ -266,7 +265,7 @@
                     .hideAsFinal()
 
     private fun makePkgSetting(pkgName: String) = spy(PackageSetting(pkgName, null, File("/test"),
-            File("/test"), null, null, null, null, 0, 0, 0, 0, null, null, null)) {
+            null, null, null, null, 0, 0, 0, 0, null, null, null)) {
         this.pkgState.isUpdatedSystemApp = params.isUpdatedSystemApp
     }
 
diff --git a/services/tests/PackageManagerServiceTests/host/Android.bp b/services/tests/PackageManagerServiceTests/host/Android.bp
index 41dfade..4f636ef 100644
--- a/services/tests/PackageManagerServiceTests/host/Android.bp
+++ b/services/tests/PackageManagerServiceTests/host/Android.bp
@@ -22,20 +22,41 @@
     ],
     static_libs: [
         "frameworks-base-hostutils",
+        "PackageManagerServiceHostTestsIntentVerifyUtils",
     ],
     test_suites: ["general-tests"],
     java_resources: [
-        ":PackageManagerDummyAppVersion1",
-        ":PackageManagerDummyAppVersion2",
-        ":PackageManagerDummyAppVersion3",
-        ":PackageManagerDummyAppVersion4",
-        ":PackageManagerDummyAppOriginalOverride",
-        ":PackageManagerServiceHostTestsResources",
+        ":PackageManagerTestAppStub",
+        ":PackageManagerTestAppVersion1",
+        ":PackageManagerTestAppVersion2",
+        ":PackageManagerTestAppVersion3",
+        ":PackageManagerTestAppVersion3Invalid",
+        ":PackageManagerTestAppVersion4",
+        ":PackageManagerTestAppOriginalOverride",
+        ":PackageManagerServiceDeviceSideTests",
+        ":PackageManagerTestIntentVerifier",
+        ":PackageManagerTestIntentVerifierTarget1",
+        ":PackageManagerTestIntentVerifierTarget2",
+        ":PackageManagerTestIntentVerifierTarget3",
+        ":PackageManagerTestIntentVerifierTarget4Base",
+        ":PackageManagerTestIntentVerifierTarget4NoAutoVerify",
+        ":PackageManagerTestIntentVerifierTarget4Wildcard",
+        ":PackageManagerTestIntentVerifierTarget4WildcardNoAutoVerify",
     ]
 }
 
-filegroup {
-    name: "PackageManagerServiceHostTestsResources",
-    srcs: [ "resources/*" ],
-    path: "resources/"
+genrule {
+    name: "PackageManagerTestAppVersion3Invalid",
+    tools: [
+        "soong_zip",
+        "zipalign",
+    ],
+    srcs: [
+        ":PackageManagerTestAppVersion3",
+    ],
+    out: ["PackageManagerTestAppVersion3Invalid.apk"],
+    cmd: "mkdir -p $(genDir)/apk && unzip $(in) -d $(genDir)/apk" +
+        " && truncate -s 800 $(genDir)/apk/META-INF/CERT.RSA" +
+        " && $(location soong_zip) -o $(genDir)/temp.apk -L 0 -C $(genDir)/apk -D $(genDir)/apk" +
+        " && $(location zipalign) -f 4 $(genDir)/temp.apk $(out)",
 }
diff --git a/services/tests/PackageManagerServiceTests/host/libs/IntentVerifyUtils/Android.bp b/services/tests/PackageManagerServiceTests/host/libs/IntentVerifyUtils/Android.bp
new file mode 100644
index 0000000..b7a0624
--- /dev/null
+++ b/services/tests/PackageManagerServiceTests/host/libs/IntentVerifyUtils/Android.bp
@@ -0,0 +1,19 @@
+// 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.
+
+java_library {
+    name: "PackageManagerServiceHostTestsIntentVerifyUtils",
+    srcs: ["src/**/*.kt"],
+    host_supported: true,
+}
diff --git a/tests/AutoVerify/app1/src/com/android/test/autoverify/MainActivity.java b/services/tests/PackageManagerServiceTests/host/libs/IntentVerifyUtils/src/com/android/server/pm/test/intent/verify/IntentVerifyTestParams.kt
similarity index 78%
copy from tests/AutoVerify/app1/src/com/android/test/autoverify/MainActivity.java
copy to services/tests/PackageManagerServiceTests/host/libs/IntentVerifyUtils/src/com/android/server/pm/test/intent/verify/IntentVerifyTestParams.kt
index 09ef472..48119e0 100644
--- a/tests/AutoVerify/app1/src/com/android/test/autoverify/MainActivity.java
+++ b/services/tests/PackageManagerServiceTests/host/libs/IntentVerifyUtils/src/com/android/server/pm/test/intent/verify/IntentVerifyTestParams.kt
@@ -13,3 +13,12 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
+
+package com.android.server.pm.test.intent.verify
+
+interface IntentVerifyTestParams {
+
+    val methodName: String
+
+    fun toArgsMap(): Map<String, String>
+}
diff --git a/services/tests/PackageManagerServiceTests/host/libs/IntentVerifyUtils/src/com/android/server/pm/test/intent/verify/SetActivityAsAlwaysParams.kt b/services/tests/PackageManagerServiceTests/host/libs/IntentVerifyUtils/src/com/android/server/pm/test/intent/verify/SetActivityAsAlwaysParams.kt
new file mode 100644
index 0000000..26c3903
--- /dev/null
+++ b/services/tests/PackageManagerServiceTests/host/libs/IntentVerifyUtils/src/com/android/server/pm/test/intent/verify/SetActivityAsAlwaysParams.kt
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.pm.test.intent.verify
+
+data class SetActivityAsAlwaysParams(
+    val uri: String,
+    val packageName: String,
+    val activityName: String,
+    override val methodName: String = "setActivityAsAlways"
+) : IntentVerifyTestParams {
+
+    companion object {
+        private const val KEY_URI = "uri"
+        private const val KEY_PACKAGE_NAME = "packageName"
+        private const val KEY_ACTIVITY_NAME = "activityName"
+
+        fun fromArgs(args: Map<String, String>) = SetActivityAsAlwaysParams(
+                args.getValue(KEY_URI),
+                args.getValue(KEY_PACKAGE_NAME),
+                args.getValue(KEY_ACTIVITY_NAME)
+        )
+    }
+
+    override fun toArgsMap() = mapOf(
+            KEY_URI to uri,
+            KEY_PACKAGE_NAME to packageName,
+            KEY_ACTIVITY_NAME to activityName
+    )
+}
diff --git a/services/tests/PackageManagerServiceTests/host/libs/IntentVerifyUtils/src/com/android/server/pm/test/intent/verify/StartActivityParams.kt b/services/tests/PackageManagerServiceTests/host/libs/IntentVerifyUtils/src/com/android/server/pm/test/intent/verify/StartActivityParams.kt
new file mode 100644
index 0000000..7eddcfb
--- /dev/null
+++ b/services/tests/PackageManagerServiceTests/host/libs/IntentVerifyUtils/src/com/android/server/pm/test/intent/verify/StartActivityParams.kt
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.pm.test.intent.verify
+
+data class StartActivityParams(
+    val uri: String,
+    val expected: List<String>,
+    val withBrowsers: Boolean = false,
+    override val methodName: String = "verifyActivityStart"
+) : IntentVerifyTestParams {
+    companion object {
+        private const val KEY_URI = "uri"
+        private const val KEY_EXPECTED = "expected"
+        private const val KEY_BROWSER = "browser"
+
+        fun fromArgs(args: Map<String, String>) = StartActivityParams(
+                args.getValue(KEY_URI),
+                args.getValue(KEY_EXPECTED).split(","),
+                args.getValue(KEY_BROWSER).toBoolean()
+        )
+    }
+
+    constructor(
+        uri: String,
+        expected: String,
+        withBrowsers: Boolean = false
+    ) : this(uri, listOf(expected), withBrowsers)
+
+    override fun toArgsMap() = mapOf(
+            KEY_URI to uri,
+            KEY_EXPECTED to expected.joinToString(separator = ","),
+            KEY_BROWSER to withBrowsers.toString()
+    )
+}
diff --git a/services/tests/PackageManagerServiceTests/host/libs/IntentVerifyUtils/src/com/android/server/pm/test/intent/verify/VerifyRequest.kt b/services/tests/PackageManagerServiceTests/host/libs/IntentVerifyUtils/src/com/android/server/pm/test/intent/verify/VerifyRequest.kt
new file mode 100644
index 0000000..f93b1e0
--- /dev/null
+++ b/services/tests/PackageManagerServiceTests/host/libs/IntentVerifyUtils/src/com/android/server/pm/test/intent/verify/VerifyRequest.kt
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.pm.test.intent.verify
+
+data class VerifyRequest(
+    val id: Int = -1,
+    val scheme: String,
+    val hosts: List<String>,
+    val packageName: String
+) {
+
+    companion object {
+        fun deserialize(value: String?): VerifyRequest {
+            val lines = value?.trim()?.lines()
+                    ?: return VerifyRequest(scheme = "", hosts = emptyList(), packageName = "")
+            return VerifyRequest(
+                    lines[0].removePrefix("id=").toInt(),
+                    lines[1].removePrefix("scheme="),
+                    lines[2].removePrefix("hosts=").split(","),
+                    lines[3].removePrefix("packageName=")
+            )
+        }
+    }
+
+    constructor(id: Int = -1, scheme: String, host: String, packageName: String) :
+            this(id, scheme, listOf(host), packageName)
+
+    fun serializeToString() = """
+        id=$id
+        scheme=$scheme
+        hosts=${hosts.joinToString(separator = ",")}
+        packageName=$packageName
+    """.trimIndent() + "\n"
+}
diff --git a/services/tests/PackageManagerServiceTests/host/resources/PackageManagerDummyAppVersion3Invalid.apk b/services/tests/PackageManagerServiceTests/host/resources/PackageManagerDummyAppVersion3Invalid.apk
deleted file mode 100644
index 127886c..0000000
--- a/services/tests/PackageManagerServiceTests/host/resources/PackageManagerDummyAppVersion3Invalid.apk
+++ /dev/null
Binary files differ
diff --git a/services/tests/PackageManagerServiceTests/host/src/com/android/server/pm/test/FactoryPackageTest.kt b/services/tests/PackageManagerServiceTests/host/src/com/android/server/pm/test/FactoryPackageTest.kt
new file mode 100644
index 0000000..e17358d
--- /dev/null
+++ b/services/tests/PackageManagerServiceTests/host/src/com/android/server/pm/test/FactoryPackageTest.kt
@@ -0,0 +1,72 @@
+package com.android.server.pm.test
+
+import com.android.internal.util.test.SystemPreparer
+import com.android.tradefed.testtype.DeviceJUnit4ClassRunner
+import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test
+import com.google.common.truth.Truth.assertThat
+import org.junit.After
+import org.junit.Before
+import org.junit.ClassRule
+import org.junit.Rule
+import org.junit.Test
+import org.junit.rules.RuleChain
+import org.junit.rules.TemporaryFolder
+import org.junit.runner.RunWith
+
+@RunWith(DeviceJUnit4ClassRunner::class)
+class FactoryPackageTest : BaseHostJUnit4Test() {
+
+    companion object {
+        private const val TEST_PKG_NAME = "com.android.server.pm.test.test_app"
+
+        private const val VERSION_ONE = "PackageManagerTestAppVersion1.apk"
+        private const val VERSION_TWO = "PackageManagerTestAppVersion2.apk"
+        private const val DEVICE_SIDE = "PackageManagerServiceDeviceSideTests.apk"
+
+        @get:ClassRule
+        val deviceRebootRule = SystemPreparer.TestRuleDelegate(true)
+    }
+
+    private val tempFolder = TemporaryFolder()
+    private val preparer: SystemPreparer = SystemPreparer(tempFolder,
+            SystemPreparer.RebootStrategy.FULL, deviceRebootRule) { this.device }
+
+    @Rule
+    @JvmField
+    val rules = RuleChain.outerRule(tempFolder).around(preparer)!!
+    private val filePath =
+            HostUtils.makePathForApk("PackageManagerTestApp.apk", Partition.SYSTEM)
+
+    @Before
+    @After
+    fun removeApk() {
+        device.uninstallPackage(TEST_PKG_NAME)
+        device.deleteFile(filePath.parent.toString())
+        device.reboot()
+    }
+
+    @Test
+    fun testGetInstalledPackagesFactoryOnlyFlag() {
+        // First, push a system app to the device and then update it so there's a data variant
+        preparer.pushResourceFile(VERSION_ONE, filePath.toString())
+                .reboot()
+
+        val versionTwoFile = HostUtils.copyResourceToHostFile(VERSION_TWO, tempFolder.newFile())
+
+        assertThat(device.installPackage(versionTwoFile, true)).isNull()
+
+        runDeviceTest("testGetInstalledPackagesWithFactoryOnly")
+    }
+
+    /**
+     * Run a device side test from com.android.server.pm.test.deviceside.DeviceSide
+     *
+     * @param method the method to run
+     */
+    fun runDeviceTest(method: String) {
+        val deviceSideFile = HostUtils.copyResourceToHostFile(DEVICE_SIDE, tempFolder.newFile())
+        assertThat(device.installPackage(deviceSideFile, true)).isNull()
+        runDeviceTests(device, "com.android.server.pm.test.deviceside",
+                "com.android.server.pm.test.deviceside.DeviceSide", method)
+    }
+}
diff --git a/services/tests/PackageManagerServiceTests/host/src/com/android/server/pm/test/HostUtils.kt b/services/tests/PackageManagerServiceTests/host/src/com/android/server/pm/test/HostUtils.kt
index 234fcf1..24c714c 100644
--- a/services/tests/PackageManagerServiceTests/host/src/com/android/server/pm/test/HostUtils.kt
+++ b/services/tests/PackageManagerServiceTests/host/src/com/android/server/pm/test/HostUtils.kt
@@ -18,11 +18,13 @@
 
 import com.android.internal.util.test.SystemPreparer
 import com.android.tradefed.device.ITestDevice
+import org.junit.rules.TemporaryFolder
 import java.io.File
 import java.io.FileOutputStream
 
-internal fun SystemPreparer.pushApk(file: String, partition: Partition) =
-        pushResourceFile(file, HostUtils.makePathForApk(file, partition).toString())
+internal fun SystemPreparer.pushApk(javaResourceName: String, partition: Partition) =
+        pushResourceFile(javaResourceName, HostUtils.makePathForApk(javaResourceName, partition)
+                .toString())
 
 internal fun SystemPreparer.deleteApkFolders(
     partition: Partition,
@@ -33,6 +35,19 @@
     }
 }
 
+internal fun ITestDevice.installJavaResourceApk(
+    tempFolder: TemporaryFolder,
+    javaResource: String,
+    reinstall: Boolean = true,
+    extraArgs: Array<String> = emptyArray()
+): String? {
+    val file = HostUtils.copyResourceToHostFile(javaResource, tempFolder.newFile())
+    return installPackage(file, reinstall, *extraArgs)
+}
+
+internal fun ITestDevice.uninstallPackages(vararg pkgNames: String) =
+        pkgNames.forEach { uninstallPackage(it) }
+
 internal object HostUtils {
 
     fun getDataDir(device: ITestDevice, pkgName: String) =
@@ -58,4 +73,55 @@
         }
         return file
     }
+
+    /**
+     * dumpsys package and therefore device.getAppPackageInfo doesn't work immediately after reboot,
+     * so the following methods parse the package dump directly to see if the path matches.
+     */
+    fun getCodePaths(device: ITestDevice, pkgName: String) =
+            device.executeShellCommand("pm dump $pkgName")
+                    .lineSequence()
+                    .map(String::trim)
+                    .filter { it.startsWith("codePath=") }
+                    .map { it.removePrefix("codePath=") }
+                    .toList()
+
+    private fun userIdLineSequence(device: ITestDevice, pkgName: String) =
+            device.executeShellCommand("pm dump $pkgName")
+                    .lineSequence()
+                    .dropWhile { !it.startsWith("Packages:") }
+                    .takeWhile {
+                        !it.startsWith("Hidden system packages:") &&
+                                !it.startsWith("Queries:")
+                    }
+                    .map(String::trim)
+                    .filter { it.startsWith("User ") }
+
+    fun getUserIdToPkgEnabledState(device: ITestDevice, pkgName: String) =
+            userIdLineSequence(device, pkgName).associate {
+                val userId = it.removePrefix("User ")
+                        .takeWhile(Char::isDigit)
+                        .toInt()
+                val enabled = it.substringAfter("enabled=")
+                        .takeWhile(Char::isDigit)
+                        .toInt()
+                        .let {
+                            when (it) {
+                                0, 1 -> true
+                                else -> false
+                            }
+                        }
+                userId to enabled
+            }
+
+    fun getUserIdToPkgInstalledState(device: ITestDevice, pkgName: String) =
+            userIdLineSequence(device, pkgName).associate {
+                val userId = it.removePrefix("User ")
+                        .takeWhile(Char::isDigit)
+                        .toInt()
+                val installed = it.substringAfter("installed=")
+                        .takeWhile { !it.isWhitespace() }
+                        .toBoolean()
+                userId to installed
+            }
 }
diff --git a/services/tests/PackageManagerServiceTests/host/src/com/android/server/pm/test/InvalidNewSystemAppTest.kt b/services/tests/PackageManagerServiceTests/host/src/com/android/server/pm/test/InvalidNewSystemAppTest.kt
index 39b40d8..37c999c 100644
--- a/services/tests/PackageManagerServiceTests/host/src/com/android/server/pm/test/InvalidNewSystemAppTest.kt
+++ b/services/tests/PackageManagerServiceTests/host/src/com/android/server/pm/test/InvalidNewSystemAppTest.kt
@@ -33,11 +33,11 @@
 class InvalidNewSystemAppTest : BaseHostJUnit4Test() {
 
     companion object {
-        private const val TEST_PKG_NAME = "com.android.server.pm.test.dummy_app"
-        private const val VERSION_ONE = "PackageManagerDummyAppVersion1.apk"
-        private const val VERSION_TWO = "PackageManagerDummyAppVersion2.apk"
-        private const val VERSION_THREE_INVALID = "PackageManagerDummyAppVersion3Invalid.apk"
-        private const val VERSION_FOUR = "PackageManagerDummyAppVersion4.apk"
+        private const val TEST_PKG_NAME = "com.android.server.pm.test.test_app"
+        private const val VERSION_ONE = "PackageManagerTestAppVersion1.apk"
+        private const val VERSION_TWO = "PackageManagerTestAppVersion2.apk"
+        private const val VERSION_THREE_INVALID = "PackageManagerTestAppVersion3Invalid.apk"
+        private const val VERSION_FOUR = "PackageManagerTestAppVersion4.apk"
 
         @get:ClassRule
         val deviceRebootRule = SystemPreparer.TestRuleDelegate(true)
@@ -47,16 +47,17 @@
     private val preparer: SystemPreparer = SystemPreparer(tempFolder,
             SystemPreparer.RebootStrategy.FULL, deviceRebootRule) { this.device }
 
-    @get:Rule
+    @Rule
+    @JvmField
     val rules = RuleChain.outerRule(tempFolder).around(preparer)!!
-    private val filePath = HostUtils.makePathForApk("PackageManagerDummyApp.apk", Partition.PRODUCT)
+    private val filePath = HostUtils.makePathForApk("PackageManagerTestApp.apk", Partition.PRODUCT)
 
     @Before
     @After
     fun removeApk() {
         device.uninstallPackage(TEST_PKG_NAME)
-        device.deleteFile(filePath.parent.toString())
-        device.reboot()
+        preparer.deleteFile(filePath.parent.toString())
+                .reboot()
     }
 
     @Test
diff --git a/services/tests/PackageManagerServiceTests/host/src/com/android/server/pm/test/OriginalPackageMigrationTest.kt b/services/tests/PackageManagerServiceTests/host/src/com/android/server/pm/test/OriginalPackageMigrationTest.kt
index fb0348c..4becae6 100644
--- a/services/tests/PackageManagerServiceTests/host/src/com/android/server/pm/test/OriginalPackageMigrationTest.kt
+++ b/services/tests/PackageManagerServiceTests/host/src/com/android/server/pm/test/OriginalPackageMigrationTest.kt
@@ -33,11 +33,11 @@
 class OriginalPackageMigrationTest : BaseHostJUnit4Test() {
 
     companion object {
-        private const val TEST_PKG_NAME = "com.android.server.pm.test.dummy_app"
-        private const val VERSION_ONE = "PackageManagerDummyAppVersion1.apk"
-        private const val VERSION_TWO = "PackageManagerDummyAppVersion2.apk"
-        private const val VERSION_THREE = "PackageManagerDummyAppVersion3.apk"
-        private const val NEW_PKG = "PackageManagerDummyAppOriginalOverride.apk"
+        private const val TEST_PKG_NAME = "com.android.server.pm.test.test_app"
+        private const val VERSION_ONE = "PackageManagerTestAppVersion1.apk"
+        private const val VERSION_TWO = "PackageManagerTestAppVersion2.apk"
+        private const val VERSION_THREE = "PackageManagerTestAppVersion3.apk"
+        private const val NEW_PKG = "PackageManagerTestAppOriginalOverride.apk"
 
         @get:ClassRule
         val deviceRebootRule = SystemPreparer.TestRuleDelegate(true)
@@ -47,7 +47,8 @@
     private val preparer: SystemPreparer = SystemPreparer(tempFolder,
             SystemPreparer.RebootStrategy.FULL, deviceRebootRule) { this.device }
 
-    @get:Rule
+    @Rule
+    @JvmField
     val rules = RuleChain.outerRule(tempFolder).around(preparer)!!
 
     @Before
@@ -55,6 +56,7 @@
     fun deleteApkFolders() {
         preparer.deleteApkFolders(Partition.SYSTEM, VERSION_ONE, VERSION_TWO, VERSION_THREE,
                 NEW_PKG)
+                .reboot()
     }
 
     @Test
@@ -99,9 +101,7 @@
     }
 
     private fun assertCodePath(apk: String) {
-        // dumpsys package and therefore device.getAppPackageInfo doesn't work here for some reason,
-        // so parse the package dump directly to see if the path matches.
-        assertThat(device.executeShellCommand("pm dump $TEST_PKG_NAME"))
-                .contains(HostUtils.makePathForApk(apk, Partition.SYSTEM).parent.toString())
+        assertThat(HostUtils.getCodePaths(device, TEST_PKG_NAME))
+                .containsExactly(HostUtils.makePathForApk(apk, Partition.SYSTEM).parent.toString())
     }
 }
diff --git a/services/tests/PackageManagerServiceTests/host/src/com/android/server/pm/test/Partition.kt b/services/tests/PackageManagerServiceTests/host/src/com/android/server/pm/test/Partition.kt
index 654c11c..6479f58 100644
--- a/services/tests/PackageManagerServiceTests/host/src/com/android/server/pm/test/Partition.kt
+++ b/services/tests/PackageManagerServiceTests/host/src/com/android/server/pm/test/Partition.kt
@@ -22,6 +22,7 @@
 // Unfortunately no easy way to access PMS SystemPartitions, so mock them here
 internal enum class Partition(val baseAppFolder: Path) {
     SYSTEM("/system/app"),
+    SYSTEM_PRIVILEGED("/system/priv-app"),
     VENDOR("/vendor/app"),
     PRODUCT("/product/app"),
     SYSTEM_EXT("/system_ext/app")
diff --git a/services/tests/PackageManagerServiceTests/host/src/com/android/server/pm/test/SystemStubMultiUserDisableUninstallTest.kt b/services/tests/PackageManagerServiceTests/host/src/com/android/server/pm/test/SystemStubMultiUserDisableUninstallTest.kt
new file mode 100644
index 0000000..46120af
--- /dev/null
+++ b/services/tests/PackageManagerServiceTests/host/src/com/android/server/pm/test/SystemStubMultiUserDisableUninstallTest.kt
@@ -0,0 +1,654 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.pm.test
+
+import com.android.internal.util.test.SystemPreparer
+import com.android.tradefed.device.ITestDevice
+import com.android.tradefed.device.UserInfo
+import com.android.tradefed.testtype.DeviceJUnit4ClassRunner
+import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test
+import com.google.common.truth.Truth.assertThat
+import org.junit.AfterClass
+import org.junit.Before
+import org.junit.ClassRule
+import org.junit.Rule
+import org.junit.Test
+import org.junit.rules.RuleChain
+import org.junit.rules.TemporaryFolder
+import org.junit.runner.RunWith
+import java.io.File
+import java.util.zip.GZIPOutputStream
+
+@RunWith(DeviceJUnit4ClassRunner::class)
+class SystemStubMultiUserDisableUninstallTest : BaseHostJUnit4Test() {
+
+    companion object {
+        private const val TEST_PKG_NAME = "com.android.server.pm.test.test_app"
+        private const val VERSION_STUB = "PackageManagerTestAppStub.apk"
+        private const val VERSION_ONE = "PackageManagerTestAppVersion1.apk"
+
+        /**
+         * How many total users on device to test, including primary. This will clean up any
+         * users created specifically for this test.
+         */
+        private const val USER_COUNT = 3
+
+        /**
+         * Whether to manually reset state at each test method without rebooting
+         * for faster iterative development.
+         */
+        private const val DEBUG_NO_REBOOT = false
+
+        @get:ClassRule
+        val deviceRebootRule = SystemPreparer.TestRuleDelegate(true)
+
+        private val parentClassName = SystemStubMultiUserDisableUninstallTest::class.java.simpleName
+
+        private val deviceCompressedFile =
+                HostUtils.makePathForApk("$parentClassName.apk", Partition.PRODUCT).parent
+                        .resolve("$parentClassName.apk.gz")
+
+        private val stubFile =
+                HostUtils.makePathForApk("$parentClassName-Stub.apk", Partition.PRODUCT)
+
+        private val secondaryUsers = mutableListOf<Int>()
+        private val usersToRemove = mutableListOf<Int>()
+        private var savedDevice: ITestDevice? = null
+        private var savedPreparer: SystemPreparer? = null
+
+        private fun setUpUsers(device: ITestDevice) {
+            if (this.savedDevice != null) return
+            this.savedDevice = device
+            secondaryUsers.clear()
+            secondaryUsers += device.userInfos.values.map(UserInfo::userId).filterNot { it == 0 }
+            while (secondaryUsers.size < USER_COUNT) {
+                secondaryUsers += device.createUser(parentClassName + secondaryUsers.size)
+                        .also { usersToRemove += it }
+            }
+        }
+
+        @JvmStatic
+        @AfterClass
+        fun cleanUp() {
+            savedDevice ?: return
+
+            usersToRemove.forEach {
+                savedDevice?.removeUser(it)
+            }
+
+            savedDevice?.uninstallPackage(TEST_PKG_NAME)
+            savedDevice?.deleteFile(stubFile.parent.toString())
+            savedDevice?.deleteFile(deviceCompressedFile.parent.toString())
+            savedDevice?.reboot()
+            savedDevice = null
+
+            if (DEBUG_NO_REBOOT) {
+                savedPreparer?.after()
+                savedPreparer = null
+            }
+        }
+    }
+
+    private val tempFolder = TemporaryFolder()
+
+    // TODO(b/160159215): Use START_STOP rather than FULL once it's fixed. This will drastically
+    //  improve pre/post-submit times.
+    private val preparer: SystemPreparer = SystemPreparer(tempFolder,
+            SystemPreparer.RebootStrategy.FULL, deviceRebootRule) { this.device }
+
+    @Rule
+    @JvmField
+    val rules = RuleChain.outerRule(tempFolder).let {
+        if (DEBUG_NO_REBOOT) {
+            it!!
+        } else {
+            it.around(preparer)!!
+        }
+    }
+
+    private var hostCompressedFile: File? = null
+
+    private val previousCodePaths = mutableListOf<String>()
+
+    @Before
+    fun ensureUserAndCompressStubAndInstall() {
+        setUpUsers(device)
+
+        val initialized = hostCompressedFile != null
+        if (!initialized) {
+            hostCompressedFile = tempFolder.newFile()
+            hostCompressedFile!!.outputStream().use {
+                javaClass.classLoader
+                        .getResource(VERSION_ONE)!!
+                        .openStream()
+                        .use { input ->
+                            GZIPOutputStream(it).use { output ->
+                                input.copyTo(output)
+                            }
+                        }
+            }
+        }
+
+        device.uninstallPackage(TEST_PKG_NAME)
+
+        if (!initialized || !DEBUG_NO_REBOOT) {
+            savedPreparer = preparer
+            preparer.pushResourceFile(VERSION_STUB, stubFile.toString())
+                    .pushFile(hostCompressedFile, deviceCompressedFile.toString())
+                    .reboot()
+        }
+
+        // This test forces the state to installed/enabled for all users,
+        // since it only tests the uninstall/disable side.
+        installExisting(User.PRIMARY)
+        installExisting(User.SECONDARY)
+
+        ensureEnabled()
+
+        // Ensure data app isn't re-installed multiple times by comparing against the original path
+        val codePath = HostUtils.getCodePaths(device, TEST_PKG_NAME).first()
+        assertThat(codePath).contains("/data/app")
+        assertThat(codePath).contains(TEST_PKG_NAME)
+
+        previousCodePaths.clear()
+        previousCodePaths += codePath
+
+        assertState(
+                primaryInstalled = true, primaryEnabled = true,
+                secondaryInstalled = true, secondaryEnabled = true,
+                codePaths = listOf(CodePath.SAME, CodePath.SYSTEM)
+        )
+    }
+
+    @Test
+    fun disablePrimaryFirstAndUninstall() {
+        toggleEnabled(false, User.PRIMARY)
+
+        assertState(
+                primaryInstalled = true, primaryEnabled = false,
+                secondaryInstalled = true, secondaryEnabled = true,
+                codePaths = listOf(CodePath.SAME, CodePath.SYSTEM)
+        )
+
+        toggleEnabled(false, User.SECONDARY)
+
+        assertState(
+                primaryInstalled = true, primaryEnabled = false,
+                secondaryInstalled = true, secondaryEnabled = false,
+                codePaths = listOf(CodePath.SAME, CodePath.SYSTEM)
+        )
+
+        device.uninstallPackage(TEST_PKG_NAME)
+
+        assertState(
+                primaryInstalled = true, primaryEnabled = false,
+                secondaryInstalled = true, secondaryEnabled = false,
+                codePaths = listOf(CodePath.SYSTEM)
+        )
+    }
+
+    @Test
+    fun disableSecondaryFirstAndUninstall() {
+        toggleEnabled(false, User.SECONDARY)
+
+        assertState(
+                primaryInstalled = true, primaryEnabled = true,
+                secondaryInstalled = true, secondaryEnabled = false,
+                codePaths = listOf(CodePath.SAME, CodePath.SYSTEM)
+        )
+
+        toggleEnabled(false, User.PRIMARY)
+
+        assertState(
+                primaryInstalled = true, primaryEnabled = false,
+                secondaryInstalled = true, secondaryEnabled = false,
+                codePaths = listOf(CodePath.SAME, CodePath.SYSTEM)
+        )
+
+        device.uninstallPackage(TEST_PKG_NAME)
+
+        assertState(
+                primaryInstalled = true, primaryEnabled = false,
+                secondaryInstalled = true, secondaryEnabled = false,
+                codePaths = listOf(CodePath.SYSTEM)
+        )
+    }
+
+    @Test
+    fun disabledUninstalledEnablePrimaryFirst() {
+        toggleEnabled(false, User.PRIMARY)
+        toggleEnabled(false, User.SECONDARY)
+        device.uninstallPackage(TEST_PKG_NAME)
+
+        toggleEnabled(true, User.PRIMARY)
+
+        assertState(
+                primaryInstalled = true, primaryEnabled = true,
+                secondaryInstalled = true, secondaryEnabled = false,
+                codePaths = listOf(CodePath.DIFFERENT, CodePath.SYSTEM)
+        )
+
+        toggleEnabled(true, User.SECONDARY)
+
+        assertState(
+                primaryInstalled = true, primaryEnabled = true,
+                secondaryInstalled = true, secondaryEnabled = true,
+                codePaths = listOf(CodePath.SAME, CodePath.SYSTEM)
+        )
+    }
+
+    @Test
+    fun disabledUninstalledEnableSecondaryFirst() {
+        toggleEnabled(false, User.PRIMARY)
+        toggleEnabled(false, User.SECONDARY)
+        device.uninstallPackage(TEST_PKG_NAME)
+
+        toggleEnabled(true, User.SECONDARY)
+
+        assertState(
+                primaryInstalled = true, primaryEnabled = false,
+                secondaryInstalled = true, secondaryEnabled = true,
+                codePaths = listOf(CodePath.DIFFERENT, CodePath.SYSTEM)
+        )
+
+        toggleEnabled(true, User.PRIMARY)
+
+        assertState(
+                primaryInstalled = true, primaryEnabled = true,
+                secondaryInstalled = true, secondaryEnabled = true,
+                codePaths = listOf(CodePath.SAME, CodePath.SYSTEM)
+        )
+    }
+
+    @Test
+    fun uninstallPrimaryFirstByUserAndInstallExistingPrimaryFirst() {
+        uninstall(User.PRIMARY)
+
+        assertState(
+                primaryInstalled = false, primaryEnabled = true,
+                secondaryInstalled = true, secondaryEnabled = true,
+                codePaths = listOf(CodePath.DIFFERENT, CodePath.SYSTEM)
+        )
+
+        uninstall(User.SECONDARY)
+
+        assertState(
+                primaryInstalled = false, primaryEnabled = true,
+                secondaryInstalled = false, secondaryEnabled = true,
+                codePaths = listOf(CodePath.DIFFERENT, CodePath.SYSTEM)
+        )
+
+        installExisting(User.PRIMARY)
+
+        assertState(
+                primaryInstalled = true, primaryEnabled = true,
+                secondaryInstalled = false, secondaryEnabled = true,
+                codePaths = listOf(CodePath.SAME, CodePath.SYSTEM)
+        )
+
+        installExisting(User.SECONDARY)
+
+        assertState(
+                primaryInstalled = true, primaryEnabled = true,
+                secondaryInstalled = true, secondaryEnabled = true,
+                codePaths = listOf(CodePath.SAME, CodePath.SYSTEM)
+        )
+    }
+
+    @Test
+    fun uninstallSecondaryFirstByUserAndInstallExistingSecondaryFirst() {
+        uninstall(User.PRIMARY)
+
+        assertState(
+                primaryInstalled = false, primaryEnabled = true,
+                secondaryInstalled = true, secondaryEnabled = true,
+                codePaths = listOf(CodePath.DIFFERENT, CodePath.SYSTEM)
+        )
+
+        uninstall(User.SECONDARY)
+
+        assertState(
+                primaryInstalled = false, primaryEnabled = true,
+                secondaryInstalled = false, secondaryEnabled = true,
+                codePaths = listOf(CodePath.DIFFERENT, CodePath.SYSTEM)
+        )
+
+        installExisting(User.SECONDARY)
+
+        assertState(
+                primaryInstalled = false, primaryEnabled = true,
+                secondaryInstalled = true, secondaryEnabled = true,
+                codePaths = listOf(CodePath.SAME, CodePath.SYSTEM)
+        )
+
+        installExisting(User.PRIMARY)
+
+        assertState(
+                primaryInstalled = true, primaryEnabled = true,
+                secondaryInstalled = true, secondaryEnabled = true,
+                codePaths = listOf(CodePath.SAME, CodePath.SYSTEM)
+        )
+    }
+
+    @Test
+    fun uninstallUpdatesAndEnablePrimaryFirst() {
+        device.executeShellCommand("pm uninstall-system-updates $TEST_PKG_NAME")
+
+        // Uninstall-system-updates always disables system user 0
+        // TODO: Is this intentional? There is no user argument for this command.
+        assertState(
+                primaryInstalled = true, primaryEnabled = false,
+                secondaryInstalled = true, secondaryEnabled = true,
+                // If any user is enabled when uninstalling updates, /data is re-uncompressed
+                codePaths = listOf(CodePath.DIFFERENT, CodePath.SYSTEM)
+        )
+
+        toggleEnabled(true, User.PRIMARY)
+
+        assertState(
+                primaryInstalled = true, primaryEnabled = true,
+                secondaryInstalled = true, secondaryEnabled = true,
+                codePaths = listOf(CodePath.SAME, CodePath.SYSTEM)
+        )
+
+        // Test enabling secondary to ensure path does not change, even though it's already enabled
+        toggleEnabled(true, User.SECONDARY)
+
+        assertState(
+                primaryInstalled = true, primaryEnabled = true,
+                secondaryInstalled = true, secondaryEnabled = true,
+                codePaths = listOf(CodePath.SAME, CodePath.SYSTEM)
+        )
+    }
+
+    @Test
+    fun uninstallUpdatesAndEnableSecondaryFirst() {
+        device.executeShellCommand("pm uninstall-system-updates $TEST_PKG_NAME")
+
+        // Uninstall-system-updates always disables system user 0
+        assertState(
+                primaryInstalled = true, primaryEnabled = false,
+                secondaryInstalled = true, secondaryEnabled = true,
+                // If any user is enabled when uninstalling updates, /data is re-uncompressed
+                codePaths = listOf(CodePath.DIFFERENT, CodePath.SYSTEM)
+        )
+
+        toggleEnabled(true, User.SECONDARY)
+
+        assertState(
+                primaryInstalled = true, primaryEnabled = false,
+                secondaryInstalled = true, secondaryEnabled = true,
+                codePaths = listOf(CodePath.SAME, CodePath.SYSTEM)
+        )
+
+        toggleEnabled(true, User.PRIMARY)
+
+        assertState(
+                primaryInstalled = true, primaryEnabled = true,
+                secondaryInstalled = true, secondaryEnabled = true,
+                codePaths = listOf(CodePath.SAME, CodePath.SYSTEM)
+        )
+    }
+
+    @Test
+    fun disabledUninstallUpdatesAndEnablePrimaryFirst() {
+        toggleEnabled(false, User.PRIMARY)
+        toggleEnabled(false, User.SECONDARY)
+
+        device.executeShellCommand("pm uninstall-system-updates $TEST_PKG_NAME")
+
+        assertState(
+                primaryInstalled = true, primaryEnabled = false,
+                secondaryInstalled = true, secondaryEnabled = false,
+                codePaths = listOf(CodePath.SYSTEM)
+        )
+
+        toggleEnabled(true, User.PRIMARY)
+
+        assertState(
+                primaryInstalled = true, primaryEnabled = true,
+                secondaryInstalled = true, secondaryEnabled = false,
+                codePaths = listOf(CodePath.DIFFERENT, CodePath.SYSTEM)
+        )
+
+        toggleEnabled(true, User.SECONDARY)
+
+        assertState(
+                primaryInstalled = true, primaryEnabled = true,
+                secondaryInstalled = true, secondaryEnabled = true,
+                codePaths = listOf(CodePath.SAME, CodePath.SYSTEM)
+        )
+    }
+
+    @Test
+    fun disabledUninstallUpdatesAndEnableSecondaryFirst() {
+        toggleEnabled(false, User.PRIMARY)
+        toggleEnabled(false, User.SECONDARY)
+
+        device.executeShellCommand("pm uninstall-system-updates $TEST_PKG_NAME")
+
+        assertState(
+                primaryInstalled = true, primaryEnabled = false,
+                secondaryInstalled = true, secondaryEnabled = false,
+                codePaths = listOf(CodePath.SYSTEM)
+        )
+
+        toggleEnabled(true, User.SECONDARY)
+
+        assertState(
+                primaryInstalled = true, primaryEnabled = false,
+                secondaryInstalled = true, secondaryEnabled = true,
+                codePaths = listOf(CodePath.DIFFERENT, CodePath.SYSTEM)
+        )
+
+        toggleEnabled(true, User.PRIMARY)
+
+        assertState(
+                primaryInstalled = true, primaryEnabled = true,
+                secondaryInstalled = true, secondaryEnabled = true,
+                codePaths = listOf(CodePath.SAME, CodePath.SYSTEM)
+        )
+    }
+
+    @Test
+    fun uninstalledUninstallUpdatesAndEnablePrimaryFirst() {
+        uninstall(User.PRIMARY)
+        uninstall(User.SECONDARY)
+
+        device.executeShellCommand("pm uninstall-system-updates $TEST_PKG_NAME")
+
+        // Uninstall-system-updates always disables system user 0
+        assertState(
+                primaryInstalled = false, primaryEnabled = false,
+                secondaryInstalled = false, secondaryEnabled = true,
+                codePaths = listOf(CodePath.SYSTEM)
+        )
+
+        toggleEnabled(true, User.PRIMARY)
+
+        assertState(
+                primaryInstalled = false, primaryEnabled = true,
+                secondaryInstalled = false, secondaryEnabled = true,
+                codePaths = listOf(CodePath.DIFFERENT, CodePath.SYSTEM)
+        )
+
+        toggleEnabled(true, User.SECONDARY)
+
+        assertState(
+                primaryInstalled = false, primaryEnabled = true,
+                secondaryInstalled = false, secondaryEnabled = true,
+                codePaths = listOf(CodePath.SAME, CodePath.SYSTEM)
+        )
+    }
+
+    @Test
+    fun uninstalledUninstallUpdatesAndEnableSecondaryFirst() {
+        uninstall(User.PRIMARY)
+        uninstall(User.SECONDARY)
+
+        device.executeShellCommand("pm uninstall-system-updates $TEST_PKG_NAME")
+
+        // Uninstall-system-updates always disables system user 0
+        assertState(
+                primaryInstalled = false, primaryEnabled = false,
+                secondaryInstalled = false, secondaryEnabled = true,
+                codePaths = listOf(CodePath.SYSTEM)
+        )
+
+        toggleEnabled(true, User.SECONDARY)
+
+        assertState(
+                primaryInstalled = false, primaryEnabled = false,
+                secondaryInstalled = false, secondaryEnabled = true,
+                codePaths = listOf(CodePath.DIFFERENT, CodePath.SYSTEM)
+        )
+
+        toggleEnabled(true, User.PRIMARY)
+
+        assertState(
+                primaryInstalled = false, primaryEnabled = true,
+                secondaryInstalled = false, secondaryEnabled = true,
+                codePaths = listOf(CodePath.SAME, CodePath.SYSTEM)
+        )
+    }
+
+    private fun ensureEnabled() {
+        toggleEnabled(true, User.PRIMARY)
+        toggleEnabled(true, User.SECONDARY)
+
+        assertThat(HostUtils.getUserIdToPkgEnabledState(device, TEST_PKG_NAME).all { it.value })
+                .isTrue()
+    }
+
+    private fun toggleEnabled(enabled: Boolean, user: User, pkgName: String = TEST_PKG_NAME) {
+        val command = if (enabled) "enable" else "disable"
+        @Suppress("UNUSED_VARIABLE") val exhaust: Any = when (user) {
+            User.PRIMARY -> {
+                device.executeShellCommand("pm $command --user 0 $pkgName")
+            }
+            User.SECONDARY -> {
+                secondaryUsers.forEach {
+                    device.executeShellCommand("pm $command --user $it $pkgName")
+                }
+            }
+        }
+    }
+
+    private fun uninstall(user: User, pkgName: String = TEST_PKG_NAME) {
+        @Suppress("UNUSED_VARIABLE") val exhaust: Any = when (user) {
+            User.PRIMARY -> {
+                device.executeShellCommand("pm uninstall --user 0 $pkgName")
+            }
+            User.SECONDARY -> {
+                secondaryUsers.forEach {
+                    device.executeShellCommand("pm uninstall --user $it $pkgName")
+                }
+            }
+        }
+    }
+
+    private fun installExisting(user: User, pkgName: String = TEST_PKG_NAME) {
+        @Suppress("UNUSED_VARIABLE") val exhaust: Any = when (user) {
+            User.PRIMARY -> {
+                device.executeShellCommand("pm install-existing --user 0 $pkgName")
+            }
+            User.SECONDARY -> {
+                secondaryUsers.forEach {
+                    device.executeShellCommand("pm install-existing --user $it $pkgName")
+                }
+            }
+        }
+    }
+
+    private fun assertState(
+        primaryInstalled: Boolean,
+        primaryEnabled: Boolean,
+        secondaryInstalled: Boolean,
+        secondaryEnabled: Boolean,
+        codePaths: List<CodePath>
+    ) {
+        HostUtils.getUserIdToPkgInstalledState(device, TEST_PKG_NAME)
+                .forEach { (userId, installed) ->
+                    if (userId == 0) {
+                        assertThat(installed).isEqualTo(primaryInstalled)
+                    } else {
+                        assertThat(installed).isEqualTo(secondaryInstalled)
+                    }
+                }
+
+        HostUtils.getUserIdToPkgEnabledState(device, TEST_PKG_NAME)
+                .forEach { (userId, enabled) ->
+                    if (userId == 0) {
+                        assertThat(enabled).isEqualTo(primaryEnabled)
+                    } else {
+                        assertThat(enabled).isEqualTo(secondaryEnabled)
+                    }
+                }
+
+        assertCodePaths(codePaths.first(), codePaths.getOrNull(1))
+    }
+
+    private fun assertCodePaths(firstCodePath: CodePath, secondCodePath: CodePath? = null) {
+        val codePaths = HostUtils.getCodePaths(device, TEST_PKG_NAME)
+        assertThat(codePaths).hasSize(listOfNotNull(firstCodePath, secondCodePath).size)
+
+        when (firstCodePath) {
+            CodePath.SAME -> {
+                assertThat(codePaths[0]).contains("/data/app")
+                assertThat(codePaths[0]).contains(TEST_PKG_NAME)
+                assertThat(codePaths[0]).isEqualTo(previousCodePaths.last())
+            }
+            CodePath.DIFFERENT -> {
+                assertThat(codePaths[0]).contains("/data/app")
+                assertThat(codePaths[0]).contains(TEST_PKG_NAME)
+                assertThat(previousCodePaths).doesNotContain(codePaths[0])
+                previousCodePaths.add(codePaths[0])
+            }
+            CodePath.SYSTEM -> assertThat(codePaths[0]).isEqualTo(stubFile.parent.toString())
+        }
+
+        when (secondCodePath) {
+            CodePath.SAME, CodePath.DIFFERENT ->
+                throw AssertionError("secondDataPath cannot be a data path")
+            CodePath.SYSTEM -> assertThat(codePaths[1]).isEqualTo(stubFile.parent.toString())
+        }
+    }
+
+    enum class User {
+        /** The primary system user 0 */
+        PRIMARY,
+
+        /**
+         * All other users on the device that are not 0. This is split into an enum so that all
+         * methods that handle secondary act on all non-system users. Some behaviors only occur
+         * if a package state is marked for all non-primary users on the device, which can be
+         * more than just 1.
+         */
+        SECONDARY
+    }
+
+    enum class CodePath {
+        /** The data code path hasn't changed */
+        SAME,
+
+        /** New data code path */
+        DIFFERENT,
+
+        /** The static system code path */
+        SYSTEM
+    }
+}
diff --git a/services/tests/PackageManagerServiceTests/host/src/com/android/server/pm/test/intent/verify/IntentFilterVerificationTest.kt b/services/tests/PackageManagerServiceTests/host/src/com/android/server/pm/test/intent/verify/IntentFilterVerificationTest.kt
new file mode 100644
index 0000000..fffda8e
--- /dev/null
+++ b/services/tests/PackageManagerServiceTests/host/src/com/android/server/pm/test/intent/verify/IntentFilterVerificationTest.kt
@@ -0,0 +1,413 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.pm.test.intent.verify
+
+import com.android.internal.util.test.SystemPreparer
+import com.android.server.pm.test.Partition
+import com.android.server.pm.test.deleteApkFolders
+import com.android.server.pm.test.installJavaResourceApk
+import com.android.server.pm.test.pushApk
+import com.android.server.pm.test.uninstallPackages
+import com.android.tradefed.testtype.DeviceJUnit4ClassRunner
+import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test
+import com.google.common.truth.Truth.assertThat
+import org.junit.After
+import org.junit.Before
+import org.junit.ClassRule
+import org.junit.Rule
+import org.junit.Test
+import org.junit.rules.RuleChain
+import org.junit.rules.TemporaryFolder
+import org.junit.runner.RunWith
+import java.io.File
+import java.util.concurrent.TimeUnit
+
+@RunWith(DeviceJUnit4ClassRunner::class)
+class IntentFilterVerificationTest : BaseHostJUnit4Test() {
+
+    companion object {
+        private const val VERIFIER = "PackageManagerTestIntentVerifier.apk"
+        private const val VERIFIER_PKG_NAME = "com.android.server.pm.test.intent.verifier"
+        private const val TARGET_PKG_PREFIX = "$VERIFIER_PKG_NAME.target"
+        private const val TARGET_APK_PREFIX = "PackageManagerTestIntentVerifierTarget"
+        private const val TARGET_ONE = "${TARGET_APK_PREFIX}1.apk"
+        private const val TARGET_ONE_PKG_NAME = "$TARGET_PKG_PREFIX.one"
+        private const val TARGET_TWO = "${TARGET_APK_PREFIX}2.apk"
+        private const val TARGET_TWO_PKG_NAME = "$TARGET_PKG_PREFIX.two"
+        private const val TARGET_THREE = "${TARGET_APK_PREFIX}3.apk"
+        private const val TARGET_THREE_PKG_NAME = "$TARGET_PKG_PREFIX.three"
+        private const val TARGET_FOUR_BASE = "${TARGET_APK_PREFIX}4Base.apk"
+        private const val TARGET_FOUR_PKG_NAME = "$TARGET_PKG_PREFIX.four"
+        private const val TARGET_FOUR_NO_AUTO_VERIFY = "${TARGET_APK_PREFIX}4NoAutoVerify.apk"
+        private const val TARGET_FOUR_WILDCARD = "${TARGET_APK_PREFIX}4Wildcard.apk"
+        private const val TARGET_FOUR_WILDCARD_NO_AUTO_VERIFY =
+                "${TARGET_APK_PREFIX}4WildcardNoAutoVerify.apk"
+
+        @get:ClassRule
+        val deviceRebootRule = SystemPreparer.TestRuleDelegate(true)
+    }
+
+    private val tempFolder = TemporaryFolder()
+    private val preparer: SystemPreparer = SystemPreparer(tempFolder,
+            SystemPreparer.RebootStrategy.FULL, deviceRebootRule) { this.device }
+
+    @Rule
+    @JvmField
+    val rules = RuleChain.outerRule(tempFolder).around(preparer)!!
+
+    private val permissionsFile = File("/system/etc/permissions" +
+            "/privapp-PackageManagerIntentFilterVerificationTest-permissions.xml")
+
+    @Before
+    fun cleanupAndPushPermissionsFile() {
+        // In order for the test app to be the verification agent, it needs a permission file
+        // which can be pushed onto the system and removed afterwards.
+        val file = tempFolder.newFile().apply {
+            """
+                <permissions>
+                    <privapp-permissions package="$VERIFIER_PKG_NAME">
+                        <permission name="android.permission.INTENT_FILTER_VERIFICATION_AGENT"/>
+                    </privapp-permissions>
+                </permissions>
+            """
+                    .trimIndent()
+                    .let { writeText(it) }
+        }
+        device.uninstallPackages(TARGET_ONE_PKG_NAME, TARGET_TWO_PKG_NAME, TARGET_THREE_PKG_NAME,
+                TARGET_FOUR_PKG_NAME)
+        preparer.pushApk(VERIFIER, Partition.SYSTEM_PRIVILEGED)
+                .pushFile(file, permissionsFile.toString())
+                .reboot()
+        runTest("clearResponse")
+    }
+
+    @After
+    fun cleanupAndDeletePermissionsFile() {
+        device.uninstallPackages(TARGET_ONE_PKG_NAME, TARGET_TWO_PKG_NAME, TARGET_THREE_PKG_NAME,
+                TARGET_FOUR_PKG_NAME)
+        preparer.deleteApkFolders(Partition.SYSTEM_PRIVILEGED, VERIFIER)
+                .deleteFile(permissionsFile.toString())
+        device.reboot()
+    }
+
+    @Test
+    fun verifyOne() {
+        installPackage(TARGET_ONE)
+
+        assertReceivedRequests(true, VerifyRequest(
+                scheme = "https",
+                hosts = listOf(
+                        "https_only.pm.server.android.com",
+                        "other_activity.pm.server.android.com",
+                        "http_only.pm.server.android.com",
+                        "verify.pm.server.android.com",
+                        "https_plus_non_web_scheme.pm.server.android.com",
+                        "multiple.pm.server.android.com",
+                        // TODO(b/159952358): the following domain should not be
+                        //  verified, this is because the verifier tries to verify all web domains,
+                        //  even in intent filters not marked for auto verify
+                        "no_verify.pm.server.android.com"
+                ),
+                packageName = TARGET_ONE_PKG_NAME
+        ))
+
+        runTest(StartActivityParams(
+                uri = "https://https_only.pm.server.android.com",
+                expected = "$TARGET_ONE_PKG_NAME.TargetActivity"
+        ))
+    }
+
+    @Test
+    fun nonWebScheme() {
+        installPackage(TARGET_TWO)
+        assertReceivedRequests(null)
+    }
+
+    @Test
+    fun verifyHttpNonSecureOnly() {
+        installPackage(TARGET_THREE)
+        assertReceivedRequests(true, VerifyRequest(
+                scheme = "https",
+                hosts = listOf(
+                        "multiple.pm.server.android.com"
+                ),
+                packageName = TARGET_THREE_PKG_NAME
+        ))
+
+        runTest(StartActivityParams(
+                uri = "http://multiple.pm.server.android.com",
+                expected = "$TARGET_THREE_PKG_NAME.TargetActivity"
+        ))
+    }
+
+    @Test
+    fun multipleResults() {
+        installPackage(TARGET_ONE)
+        installPackage(TARGET_THREE)
+        assertReceivedRequests(true, VerifyRequest(
+                scheme = "https",
+                hosts = listOf(
+                        "https_only.pm.server.android.com",
+                        "other_activity.pm.server.android.com",
+                        "http_only.pm.server.android.com",
+                        "verify.pm.server.android.com",
+                        "https_plus_non_web_scheme.pm.server.android.com",
+                        "multiple.pm.server.android.com",
+                        // TODO(b/159952358): the following domain should not be
+                        //  verified, this is because the verifier tries to verify all web domains,
+                        //  even in intent filters not marked for auto verify
+                        "no_verify.pm.server.android.com"
+                ),
+                packageName = TARGET_ONE_PKG_NAME
+        ), VerifyRequest(
+                scheme = "https",
+                hosts = listOf(
+                        "multiple.pm.server.android.com"
+                ),
+                packageName = TARGET_THREE_PKG_NAME
+        ))
+
+        // Target3 declares http non-s, so it should be included in the set here
+        runTest(StartActivityParams(
+                uri = "http://multiple.pm.server.android.com",
+                expected = listOf(
+                        "$TARGET_ONE_PKG_NAME.TargetActivity2",
+                        "$TARGET_THREE_PKG_NAME.TargetActivity"
+                )
+        ))
+
+        // But it excludes https, so it shouldn't resolve here
+        runTest(StartActivityParams(
+                uri = "https://multiple.pm.server.android.com",
+                expected = "$TARGET_ONE_PKG_NAME.TargetActivity2"
+        ))
+
+        // Remove Target3 and return to single verified Target1 app for http non-s
+        device.uninstallPackage(TARGET_THREE_PKG_NAME)
+        runTest(StartActivityParams(
+                uri = "http://multiple.pm.server.android.com",
+                expected = "$TARGET_ONE_PKG_NAME.TargetActivity2"
+        ))
+    }
+
+    @Test
+    fun demoteAlways() {
+        installPackage(TARGET_FOUR_BASE)
+        assertReceivedRequests(false, VerifyRequest(
+                scheme = "https",
+                host = "failing.pm.server.android.com",
+                packageName = TARGET_FOUR_PKG_NAME
+        ))
+
+        runTest(StartActivityParams(
+                uri = "https://failing.pm.server.android.com",
+                expected = "$TARGET_FOUR_PKG_NAME.TargetActivity",
+                withBrowsers = true
+        ))
+        runTest(SetActivityAsAlwaysParams(
+                uri = "https://failing.pm.server.android.com",
+                packageName = TARGET_FOUR_PKG_NAME,
+                activityName = "$TARGET_FOUR_PKG_NAME.TargetActivity"
+        ))
+        runTest(StartActivityParams(
+                uri = "https://failing.pm.server.android.com",
+                expected = "$TARGET_FOUR_PKG_NAME.TargetActivity"
+        ))
+
+        // Re-installing with same host/verify set will maintain always setting
+        installPackage(TARGET_FOUR_BASE)
+        assertReceivedRequests(null)
+        runTest(StartActivityParams(
+                uri = "https://failing.pm.server.android.com",
+                expected = "$TARGET_FOUR_PKG_NAME.TargetActivity"
+        ))
+
+        // Installing with new wildcard host will downgrade out of always, re-including browsers
+        installPackage(TARGET_FOUR_WILDCARD)
+
+        // TODO(b/159952358): The first request without the wildcard should not be sent. This is
+        //  caused by the request being queued even if it should be dropped from the previous
+        //  install case since the host set didn't change.
+        assertReceivedRequests(false, VerifyRequest(
+                scheme = "https",
+                hosts = listOf("failing.pm.server.android.com"),
+                packageName = TARGET_FOUR_PKG_NAME
+        ), VerifyRequest(
+                scheme = "https",
+                hosts = listOf("failing.pm.server.android.com", "wildcard.tld"),
+                packageName = TARGET_FOUR_PKG_NAME
+        ))
+        runTest(StartActivityParams(
+                uri = "https://failing.pm.server.android.com",
+                expected = "$TARGET_FOUR_PKG_NAME.TargetActivity",
+                withBrowsers = true
+        ))
+    }
+
+    @Test
+    fun unverifiedReinstallResendRequest() {
+        installPackage(TARGET_FOUR_BASE)
+        assertReceivedRequests(false, VerifyRequest(
+                scheme = "https",
+                host = "failing.pm.server.android.com",
+                packageName = TARGET_FOUR_PKG_NAME
+        ))
+
+        installPackage(TARGET_FOUR_BASE)
+
+        assertReceivedRequests(false, VerifyRequest(
+                scheme = "https",
+                host = "failing.pm.server.android.com",
+                packageName = TARGET_FOUR_PKG_NAME
+        ))
+    }
+
+    @Test
+    fun unverifiedUpdateRemovingDomainNoRequestDemoteAlways() {
+        installPackage(TARGET_FOUR_WILDCARD)
+        assertReceivedRequests(false, VerifyRequest(
+                scheme = "https",
+                hosts = listOf("failing.pm.server.android.com", "wildcard.tld"),
+                packageName = TARGET_FOUR_PKG_NAME
+        ))
+
+        runTest(SetActivityAsAlwaysParams(
+                uri = "https://failing.pm.server.android.com",
+                packageName = TARGET_FOUR_PKG_NAME,
+                activityName = "$TARGET_FOUR_PKG_NAME.TargetActivity"
+        ))
+
+        // Re-installing with a smaller host/verify set will not request re-verification
+        installPackage(TARGET_FOUR_BASE)
+        assertReceivedRequests(null)
+        runTest(StartActivityParams(
+                uri = "https://failing.pm.server.android.com",
+                expected = "$TARGET_FOUR_PKG_NAME.TargetActivity"
+        ))
+
+        // Re-installing with a (now) larger host/verify set will re-request and demote
+        installPackage(TARGET_FOUR_WILDCARD)
+        // TODO(b/159952358): The first request should not be sent. This is caused by the request
+        //  being queued even if it should be dropped from the previous install case.
+        assertReceivedRequests(false, VerifyRequest(
+                scheme = "https",
+                host = "failing.pm.server.android.com",
+                packageName = TARGET_FOUR_PKG_NAME
+        ), VerifyRequest(
+                scheme = "https",
+                hosts = listOf("failing.pm.server.android.com", "wildcard.tld"),
+                packageName = TARGET_FOUR_PKG_NAME
+        ))
+
+        runTest(StartActivityParams(
+                uri = "https://failing.pm.server.android.com",
+                expected = "$TARGET_FOUR_PKG_NAME.TargetActivity",
+                withBrowsers = true
+        ))
+    }
+
+    // TODO(b/159952358): I would expect this to demote
+    // TODO(b/32810168)
+    @Test
+    fun verifiedUpdateRemovingAutoVerifyMaintainsAlways() {
+        installPackage(TARGET_FOUR_BASE)
+        assertReceivedRequests(true, VerifyRequest(
+                scheme = "https",
+                host = "failing.pm.server.android.com",
+                packageName = TARGET_FOUR_PKG_NAME
+        ))
+
+        runTest(StartActivityParams(
+                uri = "https://failing.pm.server.android.com",
+                expected = "$TARGET_FOUR_PKG_NAME.TargetActivity"
+        ))
+
+        installPackage(TARGET_FOUR_NO_AUTO_VERIFY)
+        assertReceivedRequests(null)
+
+        runTest(StartActivityParams(
+                uri = "https://failing.pm.server.android.com",
+                expected = "$TARGET_FOUR_PKG_NAME.TargetActivity"
+        ))
+    }
+
+    @Test
+    fun verifiedUpdateRemovingAutoVerifyAddingDomainDemotesAlways() {
+        installPackage(TARGET_FOUR_BASE)
+
+        assertReceivedRequests(true, VerifyRequest(
+                scheme = "https",
+                host = "failing.pm.server.android.com",
+                packageName = TARGET_FOUR_PKG_NAME
+        ))
+
+        runTest(StartActivityParams(
+                uri = "https://failing.pm.server.android.com",
+                expected = "$TARGET_FOUR_PKG_NAME.TargetActivity"
+        ))
+
+        installPackage(TARGET_FOUR_WILDCARD_NO_AUTO_VERIFY)
+        assertReceivedRequests(null)
+
+        runTest(StartActivityParams(
+                uri = "https://failing.pm.server.android.com",
+                expected = "$TARGET_FOUR_PKG_NAME.TargetActivity",
+                withBrowsers = true
+        ))
+    }
+
+    private fun installPackage(javaResourceName: String) {
+        // Need to pass --user as verification is not currently run for all user installs
+        assertThat(device.installJavaResourceApk(tempFolder, javaResourceName,
+                extraArgs = arrayOf("--user", device.currentUser.toString()))).isNull()
+    }
+
+    private fun assertReceivedRequests(success: Boolean?, vararg expected: VerifyRequest?) {
+        // TODO(b/159952358): This can probably be less than 10
+        // Because tests have to assert that multiple broadcasts aren't received, there's no real
+        // better way to await for a value than sleeping for a long enough time.
+        TimeUnit.SECONDS.sleep(10)
+
+        val params = mutableMapOf<String, String>()
+        if (expected.any { it != null }) {
+            params["expected"] = expected.filterNotNull()
+                    .joinToString(separator = "") { it.serializeToString() }
+        }
+        runTest("compareLastReceived", params)
+
+        if (success != null) {
+            if (success) {
+                runTest("verifyPreviousReceivedSuccess")
+            } else {
+                runTest("verifyPreviousReceivedFailure")
+            }
+            runTest("clearResponse")
+        }
+    }
+
+    private fun runTest(params: IntentVerifyTestParams) =
+            runTest(params.methodName, params.toArgsMap())
+
+    private fun runTest(testName: String, args: Map<String, String> = emptyMap()) {
+        val escapedArgs = args.mapValues {
+            // Need to escape strings so that args are passed properly through the shell command
+            "\"${it.value.trim('"')}\""
+        }
+        runDeviceTests(device, null, VERIFIER_PKG_NAME, "$VERIFIER_PKG_NAME.VerifyReceiverTest",
+                testName, null, 10 * 60 * 1000L, 10 * 60 * 1000L, 0L, true, false, escapedArgs)
+    }
+}
diff --git a/services/tests/PackageManagerServiceTests/host/test-apps/Android.bp b/services/tests/PackageManagerServiceTests/host/test-apps/Android.bp
index c9b2927..4a3076e 100644
--- a/services/tests/PackageManagerServiceTests/host/test-apps/Android.bp
+++ b/services/tests/PackageManagerServiceTests/host/test-apps/Android.bp
@@ -13,26 +13,32 @@
 // limitations under the License.
 
 android_test_helper_app {
-    name: "PackageManagerDummyAppVersion1",
+    name: "PackageManagerTestAppStub",
+    manifest: "AndroidManifestVersion1.xml",
+    srcs: []
+}
+
+android_test_helper_app {
+    name: "PackageManagerTestAppVersion1",
     manifest: "AndroidManifestVersion1.xml"
 }
 
 android_test_helper_app {
-    name: "PackageManagerDummyAppVersion2",
+    name: "PackageManagerTestAppVersion2",
     manifest: "AndroidManifestVersion2.xml"
 }
 
 android_test_helper_app {
-    name: "PackageManagerDummyAppVersion3",
+    name: "PackageManagerTestAppVersion3",
     manifest: "AndroidManifestVersion3.xml"
 }
 
 android_test_helper_app {
-    name: "PackageManagerDummyAppVersion4",
+    name: "PackageManagerTestAppVersion4",
     manifest: "AndroidManifestVersion4.xml"
 }
 
 android_test_helper_app {
-    name: "PackageManagerDummyAppOriginalOverride",
+    name: "PackageManagerTestAppOriginalOverride",
     manifest: "AndroidManifestOriginalOverride.xml"
 }
diff --git a/services/tests/PackageManagerServiceTests/host/test-apps/AndroidManifestOriginalOverride.xml b/services/tests/PackageManagerServiceTests/host/test-apps/AndroidManifestOriginalOverride.xml
index f16e1bc..cba580e 100644
--- a/services/tests/PackageManagerServiceTests/host/test-apps/AndroidManifestOriginalOverride.xml
+++ b/services/tests/PackageManagerServiceTests/host/test-apps/AndroidManifestOriginalOverride.xml
@@ -16,10 +16,10 @@
   -->
 <manifest
     xmlns:android="http://schemas.android.com/apk/res/android"
-    package="com.android.server.pm.test.dummy_app.override"
+    package="com.android.server.pm.test.test_app.override"
     android:versionCode="2"
     >
 
-    <original-package android:name="com.android.server.pm.test.dummy_app"/>
+    <original-package android:name="com.android.server.pm.test.test_app"/>
 
 </manifest>
diff --git a/services/tests/PackageManagerServiceTests/host/test-apps/AndroidManifestVersion1.xml b/services/tests/PackageManagerServiceTests/host/test-apps/AndroidManifestVersion1.xml
index b492a31..efc7372 100644
--- a/services/tests/PackageManagerServiceTests/host/test-apps/AndroidManifestVersion1.xml
+++ b/services/tests/PackageManagerServiceTests/host/test-apps/AndroidManifestVersion1.xml
@@ -16,12 +16,12 @@
   -->
 <manifest
     xmlns:android="http://schemas.android.com/apk/res/android"
-    package="com.android.server.pm.test.dummy_app"
+    package="com.android.server.pm.test.test_app"
     android:versionCode="1"
     >
 
     <permission
-        android:name="com.android.server.pm.test.dummy_app.TEST_PERMISSION"
+        android:name="com.android.server.pm.test.test_app.TEST_PERMISSION"
         android:protectionLevel="normal"
         />
 
diff --git a/services/tests/PackageManagerServiceTests/host/test-apps/AndroidManifestVersion2.xml b/services/tests/PackageManagerServiceTests/host/test-apps/AndroidManifestVersion2.xml
index 25e9f8e..620054c 100644
--- a/services/tests/PackageManagerServiceTests/host/test-apps/AndroidManifestVersion2.xml
+++ b/services/tests/PackageManagerServiceTests/host/test-apps/AndroidManifestVersion2.xml
@@ -16,12 +16,12 @@
   -->
 <manifest
     xmlns:android="http://schemas.android.com/apk/res/android"
-    package="com.android.server.pm.test.dummy_app"
+    package="com.android.server.pm.test.test_app"
     android:versionCode="2"
     >
 
     <permission
-        android:name="com.android.server.pm.test.dummy_app.TEST_PERMISSION"
+        android:name="com.android.server.pm.test.test_app.TEST_PERMISSION"
         android:protectionLevel="normal"
         />
 
diff --git a/services/tests/PackageManagerServiceTests/host/test-apps/AndroidManifestVersion3.xml b/services/tests/PackageManagerServiceTests/host/test-apps/AndroidManifestVersion3.xml
index 935f5e6..1997771 100644
--- a/services/tests/PackageManagerServiceTests/host/test-apps/AndroidManifestVersion3.xml
+++ b/services/tests/PackageManagerServiceTests/host/test-apps/AndroidManifestVersion3.xml
@@ -16,12 +16,12 @@
   -->
 <manifest
     xmlns:android="http://schemas.android.com/apk/res/android"
-    package="com.android.server.pm.test.dummy_app"
+    package="com.android.server.pm.test.test_app"
     android:versionCode="3"
     >
 
     <permission
-        android:name="com.android.server.pm.test.dummy_app.TEST_PERMISSION"
+        android:name="com.android.server.pm.test.test_app.TEST_PERMISSION"
         android:protectionLevel="normal"
         />
 
diff --git a/services/tests/PackageManagerServiceTests/host/test-apps/AndroidManifestVersion4.xml b/services/tests/PackageManagerServiceTests/host/test-apps/AndroidManifestVersion4.xml
index d0643cb..d6ade03 100644
--- a/services/tests/PackageManagerServiceTests/host/test-apps/AndroidManifestVersion4.xml
+++ b/services/tests/PackageManagerServiceTests/host/test-apps/AndroidManifestVersion4.xml
@@ -16,12 +16,12 @@
   -->
 <manifest
     xmlns:android="http://schemas.android.com/apk/res/android"
-    package="com.android.server.pm.test.dummy_app"
+    package="com.android.server.pm.test.test_app"
     android:versionCode="4"
     >
 
     <permission
-        android:name="com.android.server.pm.test.dummy_app.TEST_PERMISSION"
+        android:name="com.android.server.pm.test.test_app.TEST_PERMISSION"
         android:protectionLevel="normal"
         />
 
diff --git a/libs/WindowManager/Shell/tests/Android.bp b/services/tests/PackageManagerServiceTests/host/test-apps/DeviceSide/Android.bp
similarity index 67%
copy from libs/WindowManager/Shell/tests/Android.bp
copy to services/tests/PackageManagerServiceTests/host/test-apps/DeviceSide/Android.bp
index 9868879..af0ac77 100644
--- a/libs/WindowManager/Shell/tests/Android.bp
+++ b/services/tests/PackageManagerServiceTests/host/test-apps/DeviceSide/Android.bp
@@ -1,3 +1,4 @@
+//
 // Copyright (C) 2019 The Android Open Source Project
 //
 // Licensed under the Apache License, Version 2.0 (the "License");
@@ -11,32 +12,22 @@
 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 // See the License for the specific language governing permissions and
 // limitations under the License.
+//
 
-android_test {
-    name: "WindowManagerShellTests",
-
-    srcs: ["**/*.java"],
-
+android_test_helper_app {
+    name: "PackageManagerServiceDeviceSideTests",
+    sdk_version: "test_current",
+    srcs: ["src/**/*.kt"],
+    libs: [
+        "android.test.base",
+    ],
     static_libs: [
-        "WindowManager-Shell",
+        "androidx.annotation_annotation",
         "junit",
-        "androidx.test.runner",
-        "androidx.test.rules",
+        "junit-params",
         "androidx.test.ext.junit",
-        "mockito-target-extended-minus-junit4",
+        "androidx.test.rules",
         "truth-prebuilt",
     ],
-    libs: [
-        "android.test.mock",
-        "android.test.base",
-        "android.test.runner",
-    ],
-    jni_libs: [
-        "libdexmakerjvmtiagent",
-        "libstaticjvmtiagent",
-    ],
-
-    optimize: {
-        enabled: false,
-    },
+    platform_apis: true,
 }
diff --git a/services/tests/PackageManagerServiceTests/host/test-apps/DeviceSide/AndroidManifest.xml b/services/tests/PackageManagerServiceTests/host/test-apps/DeviceSide/AndroidManifest.xml
new file mode 100644
index 0000000..286ad56
--- /dev/null
+++ b/services/tests/PackageManagerServiceTests/host/test-apps/DeviceSide/AndroidManifest.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="com.android.server.pm.test.deviceside">
+
+    <uses-permission android:name="android.permission.QUERY_ALL_PACKAGES" />
+
+    <instrumentation
+        android:name="androidx.test.runner.AndroidJUnitRunner"
+        android:targetPackage="com.android.server.pm.test.deviceside" />
+</manifest>
+
diff --git a/services/tests/PackageManagerServiceTests/host/test-apps/DeviceSide/src/com/android/server/pm/cts/test/deviceside/DeviceSide.kt b/services/tests/PackageManagerServiceTests/host/test-apps/DeviceSide/src/com/android/server/pm/cts/test/deviceside/DeviceSide.kt
new file mode 100644
index 0000000..d140662
--- /dev/null
+++ b/services/tests/PackageManagerServiceTests/host/test-apps/DeviceSide/src/com/android/server/pm/cts/test/deviceside/DeviceSide.kt
@@ -0,0 +1,42 @@
+package com.android.server.pm.test.deviceside
+
+import android.content.pm.PackageManager.MATCH_FACTORY_ONLY
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.platform.app.InstrumentationRegistry
+import com.google.common.truth.Truth
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+class DeviceSide {
+    companion object {
+        private const val TEST_PKG_NAME = "com.android.server.pm.test.test_app"
+    }
+
+    @Test
+    fun testGetInstalledPackagesWithFactoryOnly() {
+        val instrumentation = InstrumentationRegistry.getInstrumentation()
+        val uiAutomation = instrumentation.uiAutomation
+        val ctx = instrumentation.context
+
+        uiAutomation.adoptShellPermissionIdentity()
+        try {
+            val packages1 = ctx.packageManager.getInstalledPackages(0)
+                    .filter { it.packageName == TEST_PKG_NAME }
+            val packages2 = ctx.packageManager.getInstalledPackages(MATCH_FACTORY_ONLY)
+                    .filter { it.packageName == TEST_PKG_NAME }
+
+            Truth.assertWithMessage("Incorrect number of packages found")
+                    .that(packages1.size).isEqualTo(1)
+            Truth.assertWithMessage("Incorrect number of packages found")
+                    .that(packages2.size).isEqualTo(1)
+
+            Truth.assertWithMessage("Incorrect version code for updated package")
+                    .that(packages1[0].longVersionCode).isEqualTo(2)
+            Truth.assertWithMessage("Incorrect version code for factory package")
+                    .that(packages2[0].longVersionCode).isEqualTo(1)
+        } finally {
+            uiAutomation.dropShellPermissionIdentity()
+        }
+    }
+}
diff --git a/services/tests/PackageManagerServiceTests/host/test-apps/IntentVerifier/Android.bp b/services/tests/PackageManagerServiceTests/host/test-apps/IntentVerifier/Android.bp
new file mode 100644
index 0000000..e82f57d
--- /dev/null
+++ b/services/tests/PackageManagerServiceTests/host/test-apps/IntentVerifier/Android.bp
@@ -0,0 +1,28 @@
+// 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.
+
+android_test_helper_app {
+    name: "PackageManagerTestIntentVerifier",
+    srcs: [ "src/**/*.kt" ],
+    static_libs: [
+        "androidx.test.core",
+        "androidx.test.espresso.core",
+        "androidx.test.runner",
+        "compatibility-device-util-axt",
+        "junit",
+        "truth-prebuilt",
+        "PackageManagerServiceHostTestsIntentVerifyUtils",
+    ],
+    platform_apis: true,
+}
diff --git a/services/tests/PackageManagerServiceTests/host/test-apps/IntentVerifier/AndroidManifest.xml b/services/tests/PackageManagerServiceTests/host/test-apps/IntentVerifier/AndroidManifest.xml
new file mode 100644
index 0000000..17b50b0
--- /dev/null
+++ b/services/tests/PackageManagerServiceTests/host/test-apps/IntentVerifier/AndroidManifest.xml
@@ -0,0 +1,38 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+  ~ Copyright (C) 2020 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="com.android.server.pm.test.intent.verifier"
+    >
+
+    <uses-permission android:name="android.permission.INTENT_FILTER_VERIFICATION_AGENT" />
+    <uses-permission android:name="android.permission.QUERY_ALL_PACKAGES" />
+    <uses-permission android:name="android.permission.SET_PREFERRED_APPLICATIONS" />
+
+    <instrumentation
+        android:name="androidx.test.runner.AndroidJUnitRunner"
+        android:targetPackage="com.android.server.pm.test.intent.verifier"
+        />
+
+    <application>
+        <receiver android:name=".VerifyReceiver" android:exported="true">
+            <intent-filter android:priority="999">
+                <action android:name="android.intent.action.INTENT_FILTER_NEEDS_VERIFICATION"/>
+                <data android:mimeType="application/vnd.android.package-archive"/>
+            </intent-filter>
+        </receiver>
+    </application>
+
+</manifest>
diff --git a/services/tests/PackageManagerServiceTests/host/test-apps/IntentVerifier/src/com/android/server/pm/test/intent/verifier/VerifyReceiver.kt b/services/tests/PackageManagerServiceTests/host/test-apps/IntentVerifier/src/com/android/server/pm/test/intent/verifier/VerifyReceiver.kt
new file mode 100644
index 0000000..073c2be
--- /dev/null
+++ b/services/tests/PackageManagerServiceTests/host/test-apps/IntentVerifier/src/com/android/server/pm/test/intent/verifier/VerifyReceiver.kt
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.pm.test.intent.verifier
+
+import android.content.BroadcastReceiver
+import android.content.ComponentName
+import android.content.Context
+import android.content.Intent
+import android.content.pm.PackageManager
+import com.android.server.pm.test.intent.verify.VerifyRequest
+
+class VerifyReceiver : BroadcastReceiver() {
+
+    override fun onReceive(context: Context, intent: Intent) {
+        if (intent.action != Intent.ACTION_INTENT_FILTER_NEEDS_VERIFICATION) return
+        val params = intent.toVerifyParams()
+
+        // If the receiver is called for a normal request, proxy it to the real verifier on device
+        if (params.hosts.none { it.contains("pm.server.android.com") }) {
+            sendToRealVerifier(context, Intent(intent))
+            return
+        }
+
+        // When the receiver is invoked for a test install, there is no direct connection to host,
+        // so store the result in a file to read and assert on later. Append is intentional so that
+        // amount of invocations and clean up can be verified.
+        context.filesDir.resolve("test.txt")
+                .appendText(params.serializeToString())
+    }
+
+    private fun sendToRealVerifier(context: Context, intent: Intent) {
+        context.packageManager.queryBroadcastReceivers(intent, 0)
+                .first { it.activityInfo?.packageName != context.packageName }
+                .let { it.activityInfo!! }
+                .let { intent.setComponent(ComponentName(it.packageName, it.name)) }
+                .run { context.sendBroadcast(intent) }
+    }
+
+    private fun Intent.toVerifyParams() = VerifyRequest(
+            id = getIntExtra(PackageManager.EXTRA_INTENT_FILTER_VERIFICATION_ID, -1),
+            scheme = getStringExtra(PackageManager.EXTRA_INTENT_FILTER_VERIFICATION_URI_SCHEME)!!,
+            hosts = getStringExtra(PackageManager.EXTRA_INTENT_FILTER_VERIFICATION_HOSTS)!!
+                    .split(' '),
+            packageName = getStringExtra(
+                    PackageManager.EXTRA_INTENT_FILTER_VERIFICATION_PACKAGE_NAME)!!
+
+    )
+}
diff --git a/services/tests/PackageManagerServiceTests/host/test-apps/IntentVerifier/src/com/android/server/pm/test/intent/verifier/VerifyReceiverTest.kt b/services/tests/PackageManagerServiceTests/host/test-apps/IntentVerifier/src/com/android/server/pm/test/intent/verifier/VerifyReceiverTest.kt
new file mode 100644
index 0000000..6de3d4e
--- /dev/null
+++ b/services/tests/PackageManagerServiceTests/host/test-apps/IntentVerifier/src/com/android/server/pm/test/intent/verifier/VerifyReceiverTest.kt
@@ -0,0 +1,160 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.pm.test.intent.verifier
+
+import android.content.ComponentName
+import android.content.Context
+import android.content.Intent
+import android.content.IntentFilter
+import android.content.pm.PackageManager
+import android.net.Uri
+import android.os.Bundle
+import android.os.UserHandle
+import androidx.test.InstrumentationRegistry
+import androidx.test.runner.AndroidJUnit4
+import com.android.compatibility.common.util.ShellIdentityUtils
+import com.android.server.pm.test.intent.verify.SetActivityAsAlwaysParams
+import com.android.server.pm.test.intent.verify.StartActivityParams
+import com.android.server.pm.test.intent.verify.VerifyRequest
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+import java.io.File
+
+@RunWith(AndroidJUnit4::class)
+class VerifyReceiverTest {
+
+    val args: Bundle = InstrumentationRegistry.getArguments()
+    val context: Context = InstrumentationRegistry.getContext()
+
+    private val file = context.filesDir.resolve("test.txt")
+
+    @Test
+    fun clearResponse() {
+        file.delete()
+    }
+
+    @Test
+    fun compareLastReceived() {
+        val lastReceivedText = file.readTextIfExists()
+        val expectedText = args.getString("expected")
+        if (expectedText.isNullOrEmpty()) {
+            assertThat(lastReceivedText).isEmpty()
+            return
+        }
+
+        val expectedParams = expectedText.parseParams()
+        val lastReceivedParams = lastReceivedText.parseParams()
+
+        assertThat(lastReceivedParams).hasSize(expectedParams.size)
+
+        lastReceivedParams.zip(expectedParams).forEach { (actual, expected) ->
+            assertThat(actual.hosts).containsExactlyElementsIn(expected.hosts)
+            assertThat(actual.packageName).isEqualTo(expected.packageName)
+            assertThat(actual.scheme).isEqualTo(expected.scheme)
+        }
+    }
+
+    @Test
+    fun setActivityAsAlways() {
+        val params = SetActivityAsAlwaysParams.fromArgs(
+                args.keySet().associateWith { args.getString(it)!! })
+        val uri = Uri.parse(params.uri)
+        val filter = IntentFilter().apply {
+            addAction(Intent.ACTION_VIEW)
+            addCategory(Intent.CATEGORY_DEFAULT)
+            addDataScheme(uri.scheme)
+            addDataAuthority(uri.authority, null)
+        }
+
+        val intent = Intent(Intent.ACTION_VIEW, uri).apply {
+            addCategory(Intent.CATEGORY_DEFAULT)
+        }
+        val allResults = context.packageManager.queryIntentActivities(intent, 0)
+        val allComponents = allResults
+                .map { ComponentName(it.activityInfo.packageName, it.activityInfo.name) }
+                .toTypedArray()
+        val matchingInfo = allResults.first {
+            it.activityInfo.packageName == params.packageName &&
+                    it.activityInfo.name == params.activityName
+        }
+
+        ShellIdentityUtils.invokeMethodWithShellPermissions(context.packageManager,
+                ShellIdentityUtils.ShellPermissionMethodHelper<Unit, PackageManager> {
+                    it.addUniquePreferredActivity(filter, matchingInfo.match, allComponents,
+                            ComponentName(matchingInfo.activityInfo.packageName,
+                                    matchingInfo.activityInfo.name))
+                    it.updateIntentVerificationStatusAsUser(params.packageName,
+                            PackageManager.INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ALWAYS,
+                            UserHandle.myUserId())
+                }, "android.permission.SET_PREFERRED_APPLICATIONS")
+    }
+
+    @Test
+    fun verifyPreviousReceivedSuccess() {
+        file.readTextIfExists()
+                .parseParams()
+                .forEach {
+                    context.packageManager.verifyIntentFilter(it.id,
+                            PackageManager.INTENT_FILTER_VERIFICATION_SUCCESS, emptyList())
+                }
+    }
+
+    @Test
+    fun verifyPreviousReceivedFailure() {
+        file.readTextIfExists()
+                .parseParams()
+                .forEach {
+                    context.packageManager.verifyIntentFilter(it.id,
+                            PackageManager.INTENT_FILTER_VERIFICATION_FAILURE, it.hosts)
+                }
+    }
+
+    @Test
+    fun verifyActivityStart() {
+        val params = StartActivityParams
+                .fromArgs(args.keySet().associateWith { args.getString(it)!! })
+        val uri = Uri.parse(params.uri)
+        val intent = Intent(Intent.ACTION_VIEW).apply {
+            data = uri
+            addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
+        }
+
+        val expectedActivities = params.expected.toMutableList()
+
+        if (params.withBrowsers) {
+            // Since the host doesn't know what browsers the device has, query here and add it to
+            // set if it's expected that browser are returned
+            val browserIntent = Intent(Intent.ACTION_VIEW, Uri.parse("https://example.com"))
+            expectedActivities += context.packageManager.queryIntentActivities(browserIntent, 0)
+                    .map { it.activityInfo.name }
+        }
+
+        val infos = context.packageManager.queryIntentActivities(intent, 0)
+                .map { it.activityInfo.name }
+        assertThat(infos).containsExactlyElementsIn(expectedActivities)
+    }
+
+    private fun File.readTextIfExists() = if (exists()) readText() else ""
+
+    // Rudimentary list deserialization by splitting text block into 4 line sections
+    private fun String.parseParams() = trim()
+            .lines()
+            .windowed(4, 4)
+            .map { it.joinToString(separator = "\n") }
+            .map { VerifyRequest.deserialize(it) }
+}
diff --git a/services/tests/PackageManagerServiceTests/host/test-apps/IntentVerifierTarget/Android.bp b/services/tests/PackageManagerServiceTests/host/test-apps/IntentVerifierTarget/Android.bp
new file mode 100644
index 0000000..7161fdd
--- /dev/null
+++ b/services/tests/PackageManagerServiceTests/host/test-apps/IntentVerifierTarget/Android.bp
@@ -0,0 +1,48 @@
+// 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.
+
+android_test_helper_app {
+    name: "PackageManagerTestIntentVerifierTarget1",
+    manifest: "AndroidManifest1.xml",
+}
+
+android_test_helper_app {
+    name: "PackageManagerTestIntentVerifierTarget2",
+    manifest: "AndroidManifest2.xml",
+}
+
+android_test_helper_app {
+    name: "PackageManagerTestIntentVerifierTarget3",
+    manifest: "AndroidManifest3.xml",
+}
+
+android_test_helper_app {
+    name: "PackageManagerTestIntentVerifierTarget4Base",
+    manifest: "AndroidManifest4Base.xml",
+}
+
+android_test_helper_app {
+    name: "PackageManagerTestIntentVerifierTarget4NoAutoVerify",
+    manifest: "AndroidManifest4NoAutoVerify.xml",
+}
+
+android_test_helper_app {
+    name: "PackageManagerTestIntentVerifierTarget4Wildcard",
+    manifest: "AndroidManifest4Wildcard.xml",
+}
+
+android_test_helper_app {
+    name: "PackageManagerTestIntentVerifierTarget4WildcardNoAutoVerify",
+    manifest: "AndroidManifest4WildcardNoAutoVerify.xml",
+}
diff --git a/services/tests/PackageManagerServiceTests/host/test-apps/IntentVerifierTarget/AndroidManifest1.xml b/services/tests/PackageManagerServiceTests/host/test-apps/IntentVerifierTarget/AndroidManifest1.xml
new file mode 100644
index 0000000..6cf5c76
--- /dev/null
+++ b/services/tests/PackageManagerServiceTests/host/test-apps/IntentVerifierTarget/AndroidManifest1.xml
@@ -0,0 +1,94 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+  ~ Copyright (C) 2020 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="com.android.server.pm.test.intent.verifier.target.one" android:versionCode="1">
+
+    <application>
+        <activity android:name=".TargetActivity" android:exported="true">
+            <intent-filter android:autoVerify="true">
+                <action android:name="android.intent.action.VIEW" />
+                <category android:name="android.intent.category.BROWSABLE" />
+                <category android:name="android.intent.category.DEFAULT" />
+                <data android:scheme="http" />
+                <data android:scheme="https" />
+                <data android:host="verify.pm.server.android.com" />
+            </intent-filter>
+
+            <intent-filter android:autoVerify="false">
+                <action android:name="android.intent.action.VIEW" />
+                <category android:name="android.intent.category.BROWSABLE" />
+                <category android:name="android.intent.category.DEFAULT" />
+                <data android:scheme="http" />
+                <data android:scheme="https" />
+                <data android:host="no_verify.pm.server.android.com" />
+            </intent-filter>
+
+            <intent-filter android:autoVerify="true">
+                <action android:name="android.intent.action.VIEW" />
+                <category android:name="android.intent.category.BROWSABLE" />
+                <category android:name="android.intent.category.DEFAULT" />
+                <data android:scheme="http" />
+                <data android:host="http_only.pm.server.android.com" />
+            </intent-filter>
+
+            <intent-filter android:autoVerify="true">
+                <action android:name="android.intent.action.VIEW" />
+                <category android:name="android.intent.category.BROWSABLE" />
+                <category android:name="android.intent.category.DEFAULT" />
+                <data android:scheme="https" />
+                <data android:host="https_only.pm.server.android.com" />
+            </intent-filter>
+
+            <intent-filter android:autoVerify="true">
+                <action android:name="android.intent.action.VIEW" />
+                <category android:name="android.intent.category.BROWSABLE" />
+                <category android:name="android.intent.category.DEFAULT" />
+                <data android:scheme="htttps" />
+                <data android:host="non_http.pm.server.android.com" />
+            </intent-filter>
+
+            <intent-filter android:autoVerify="true">
+                <action android:name="android.intent.action.VIEW" />
+                <category android:name="android.intent.category.BROWSABLE" />
+                <category android:name="android.intent.category.DEFAULT" />
+                <data android:scheme="https" />
+                <data android:scheme="non_web_scheme" />
+                <data android:host="https_plus_non_web_scheme.pm.server.android.com" />
+            </intent-filter>
+        </activity>
+
+        <activity android:name=".TargetActivity2" android:exported="true">
+            <intent-filter android:autoVerify="true">
+                <action android:name="android.intent.action.VIEW" />
+                <category android:name="android.intent.category.BROWSABLE" />
+                <category android:name="android.intent.category.DEFAULT" />
+                <data android:scheme="http" />
+                <data android:scheme="https" />
+                <data android:host="other_activity.pm.server.android.com" />
+            </intent-filter>
+
+            <intent-filter android:autoVerify="true">
+                <action android:name="android.intent.action.VIEW" />
+                <category android:name="android.intent.category.BROWSABLE" />
+                <category android:name="android.intent.category.DEFAULT" />
+                <data android:scheme="http" />
+                <data android:scheme="https" />
+                <data android:host="multiple.pm.server.android.com" />
+            </intent-filter>
+        </activity>
+    </application>
+
+</manifest>
diff --git a/services/tests/PackageManagerServiceTests/host/test-apps/IntentVerifierTarget/AndroidManifest2.xml b/services/tests/PackageManagerServiceTests/host/test-apps/IntentVerifierTarget/AndroidManifest2.xml
new file mode 100644
index 0000000..087ef705
--- /dev/null
+++ b/services/tests/PackageManagerServiceTests/host/test-apps/IntentVerifierTarget/AndroidManifest2.xml
@@ -0,0 +1,34 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+  ~ Copyright (C) 2020 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="com.android.server.pm.test.intent.verifier.target.two"
+    android:versionCode="1">
+
+    <application>
+        <activity android:name=".TargetActivity" android:exported="true">
+            <intent-filter android:autoVerify="true">
+                <action android:name="android.intent.action.VIEW" />
+                <category android:name="android.intent.category.BROWSABLE" />
+                <category android:name="android.intent.category.DEFAULT" />
+                <data android:scheme="http" />
+                <data android:scheme="https" />
+                <data android:scheme="non_web_scheme" />
+                <data android:host="only_https_plus_non_web_scheme.pm.server.android.com" />
+            </intent-filter>
+        </activity>
+    </application>
+
+</manifest>
diff --git a/services/tests/PackageManagerServiceTests/host/test-apps/IntentVerifierTarget/AndroidManifest3.xml b/services/tests/PackageManagerServiceTests/host/test-apps/IntentVerifierTarget/AndroidManifest3.xml
new file mode 100644
index 0000000..eb75b5e
--- /dev/null
+++ b/services/tests/PackageManagerServiceTests/host/test-apps/IntentVerifierTarget/AndroidManifest3.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+  ~ Copyright (C) 2020 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="com.android.server.pm.test.intent.verifier.target.three"
+    android:versionCode="1">
+
+    <application>
+        <activity android:name=".TargetActivity" android:exported="true">
+            <intent-filter android:autoVerify="true">
+                <action android:name="android.intent.action.VIEW" />
+                <category android:name="android.intent.category.BROWSABLE" />
+                <category android:name="android.intent.category.DEFAULT" />
+                <data android:scheme="http" />
+                <data android:host="multiple.pm.server.android.com" />
+            </intent-filter>
+        </activity>
+    </application>
+
+</manifest>
diff --git a/services/tests/PackageManagerServiceTests/host/test-apps/IntentVerifierTarget/AndroidManifest4Base.xml b/services/tests/PackageManagerServiceTests/host/test-apps/IntentVerifierTarget/AndroidManifest4Base.xml
new file mode 100644
index 0000000..7eacb8b
--- /dev/null
+++ b/services/tests/PackageManagerServiceTests/host/test-apps/IntentVerifierTarget/AndroidManifest4Base.xml
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+  ~ Copyright (C) 2020 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="com.android.server.pm.test.intent.verifier.target.four"
+    android:versionCode="1">
+
+    <application>
+        <activity android:name=".TargetActivity" android:exported="true">
+            <intent-filter android:autoVerify="true">
+                <action android:name="android.intent.action.VIEW" />
+                <category android:name="android.intent.category.BROWSABLE" />
+                <category android:name="android.intent.category.DEFAULT" />
+                <data android:scheme="http" />
+                <data android:scheme="https" />
+                <data android:host="failing.pm.server.android.com" />
+            </intent-filter>
+        </activity>
+    </application>
+
+</manifest>
diff --git a/services/tests/PackageManagerServiceTests/host/test-apps/IntentVerifierTarget/AndroidManifest4NoAutoVerify.xml b/services/tests/PackageManagerServiceTests/host/test-apps/IntentVerifierTarget/AndroidManifest4NoAutoVerify.xml
new file mode 100644
index 0000000..ecfee55
--- /dev/null
+++ b/services/tests/PackageManagerServiceTests/host/test-apps/IntentVerifierTarget/AndroidManifest4NoAutoVerify.xml
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+  ~ Copyright (C) 2020 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="com.android.server.pm.test.intent.verifier.target.four"
+    android:versionCode="1">
+
+    <application>
+        <activity android:name=".TargetActivity" android:exported="true">
+            <intent-filter android:autoVerify="false">
+                <action android:name="android.intent.action.VIEW" />
+                <category android:name="android.intent.category.BROWSABLE" />
+                <category android:name="android.intent.category.DEFAULT" />
+                <data android:scheme="http" />
+                <data android:scheme="https" />
+                <data android:host="failing.pm.server.android.com" />
+            </intent-filter>
+        </activity>
+    </application>
+
+</manifest>
diff --git a/services/tests/PackageManagerServiceTests/host/test-apps/IntentVerifierTarget/AndroidManifest4Wildcard.xml b/services/tests/PackageManagerServiceTests/host/test-apps/IntentVerifierTarget/AndroidManifest4Wildcard.xml
new file mode 100644
index 0000000..0f0f53b
--- /dev/null
+++ b/services/tests/PackageManagerServiceTests/host/test-apps/IntentVerifierTarget/AndroidManifest4Wildcard.xml
@@ -0,0 +1,34 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+  ~ Copyright (C) 2020 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="com.android.server.pm.test.intent.verifier.target.four"
+    android:versionCode="1">
+
+    <application>
+        <activity android:name=".TargetActivity" android:exported="true">
+            <intent-filter android:autoVerify="true">
+                <action android:name="android.intent.action.VIEW" />
+                <category android:name="android.intent.category.BROWSABLE" />
+                <category android:name="android.intent.category.DEFAULT" />
+                <data android:scheme="http" />
+                <data android:scheme="https" />
+                <data android:host="failing.pm.server.android.com" />
+                <data android:host="*.wildcard.tld" />
+            </intent-filter>
+        </activity>
+    </application>
+
+</manifest>
diff --git a/services/tests/PackageManagerServiceTests/host/test-apps/IntentVerifierTarget/AndroidManifest4WildcardNoAutoVerify.xml b/services/tests/PackageManagerServiceTests/host/test-apps/IntentVerifierTarget/AndroidManifest4WildcardNoAutoVerify.xml
new file mode 100644
index 0000000..d5652e1
--- /dev/null
+++ b/services/tests/PackageManagerServiceTests/host/test-apps/IntentVerifierTarget/AndroidManifest4WildcardNoAutoVerify.xml
@@ -0,0 +1,34 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+  ~ Copyright (C) 2020 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="com.android.server.pm.test.intent.verifier.target.four"
+    android:versionCode="1">
+
+    <application>
+        <activity android:name=".TargetActivity" android:exported="true">
+            <intent-filter android:autoVerify="false">
+                <action android:name="android.intent.action.VIEW" />
+                <category android:name="android.intent.category.BROWSABLE" />
+                <category android:name="android.intent.category.DEFAULT" />
+                <data android:scheme="http" />
+                <data android:scheme="https" />
+                <data android:host="failing.pm.server.android.com" />
+                <data android:host="*.wildcard.tld" />
+            </intent-filter>
+        </activity>
+    </application>
+
+</manifest>
diff --git a/services/tests/mockingservicestests/AndroidManifest.xml b/services/tests/mockingservicestests/AndroidManifest.xml
index 44eb828..a398961 100644
--- a/services/tests/mockingservicestests/AndroidManifest.xml
+++ b/services/tests/mockingservicestests/AndroidManifest.xml
@@ -17,6 +17,8 @@
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
         package="com.android.frameworks.mockingservicestests">
 
+    <uses-sdk android:targetSdkVersion="30" />
+
     <uses-permission android:name="android.permission.LOG_COMPAT_CHANGE"/>
     <uses-permission android:name="android.permission.READ_COMPAT_CHANGE_CONFIG"/>
     <uses-permission android:name="android.permission.CHANGE_CONFIGURATION" />
diff --git a/services/tests/mockingservicestests/src/com/android/server/AppStateTrackerTest.java b/services/tests/mockingservicestests/src/com/android/server/AppStateTrackerTest.java
index 2f5e883..3220dff 100644
--- a/services/tests/mockingservicestests/src/com/android/server/AppStateTrackerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/AppStateTrackerTest.java
@@ -48,6 +48,7 @@
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
+import android.net.Uri;
 import android.os.BatteryManager;
 import android.os.Handler;
 import android.os.Looper;
@@ -282,7 +283,7 @@
                 eq(ServiceType.FORCE_ALL_APPS_STANDBY),
                 powerSaveObserverCaptor.capture());
 
-        verify(mMockContext).registerReceiver(
+        verify(mMockContext, times(2)).registerReceiver(
                 receiverCaptor.capture(), any(IntentFilter.class));
         verify(mMockAppStandbyInternal).addListener(
                 appIdleStateChangeListenerCaptor.capture());
@@ -1242,6 +1243,67 @@
         assertTrue(instance.isForceAllAppsStandbyEnabled());
     }
 
+    @Test
+    public void testStateClearedOnPackageRemoved() throws Exception {
+        final AppStateTrackerTestable instance = newInstance();
+        callStart(instance);
+
+        instance.mActiveUids.put(UID_1, true);
+        instance.mForegroundUids.put(UID_2, true);
+        instance.mRunAnyRestrictedPackages.add(Pair.create(UID_1, PACKAGE_1));
+        instance.mExemptedBucketPackages.add(UserHandle.getUserId(UID_2), PACKAGE_2);
+
+        // Replace PACKAGE_1, nothing should change
+        Intent packageRemoved = new Intent(Intent.ACTION_PACKAGE_REMOVED)
+                .putExtra(Intent.EXTRA_USER_HANDLE, UserHandle.getUserId(UID_1))
+                .putExtra(Intent.EXTRA_UID, UID_1)
+                .putExtra(Intent.EXTRA_REPLACING, true)
+                .setData(Uri.fromParts(IntentFilter.SCHEME_PACKAGE, PACKAGE_1, null));
+        mReceiver.onReceive(mMockContext, packageRemoved);
+
+        assertEquals(1, instance.mActiveUids.size());
+        assertEquals(1, instance.mForegroundUids.size());
+        assertEquals(1, instance.mRunAnyRestrictedPackages.size());
+        assertEquals(1, instance.mExemptedBucketPackages.size());
+
+        // Replace PACKAGE_2, nothing should change
+        packageRemoved = new Intent(Intent.ACTION_PACKAGE_REMOVED)
+                .putExtra(Intent.EXTRA_USER_HANDLE, UserHandle.getUserId(UID_2))
+                .putExtra(Intent.EXTRA_UID, UID_2)
+                .putExtra(Intent.EXTRA_REPLACING, true)
+                .setData(Uri.fromParts(IntentFilter.SCHEME_PACKAGE, PACKAGE_2, null));
+        mReceiver.onReceive(mMockContext, packageRemoved);
+
+        assertEquals(1, instance.mActiveUids.size());
+        assertEquals(1, instance.mForegroundUids.size());
+        assertEquals(1, instance.mRunAnyRestrictedPackages.size());
+        assertEquals(1, instance.mExemptedBucketPackages.size());
+
+        // Remove PACKAGE_1
+        packageRemoved = new Intent(Intent.ACTION_PACKAGE_REMOVED)
+                .putExtra(Intent.EXTRA_USER_HANDLE, UserHandle.getUserId(UID_1))
+                .putExtra(Intent.EXTRA_UID, UID_1)
+                .setData(Uri.fromParts(IntentFilter.SCHEME_PACKAGE, PACKAGE_1, null));
+        mReceiver.onReceive(mMockContext, packageRemoved);
+
+        assertEquals(0, instance.mActiveUids.size());
+        assertEquals(1, instance.mForegroundUids.size());
+        assertEquals(0, instance.mRunAnyRestrictedPackages.size());
+        assertEquals(1, instance.mExemptedBucketPackages.size());
+
+        // Remove PACKAGE_2
+        packageRemoved = new Intent(Intent.ACTION_PACKAGE_REMOVED)
+                .putExtra(Intent.EXTRA_USER_HANDLE, UserHandle.getUserId(UID_2))
+                .putExtra(Intent.EXTRA_UID, UID_2)
+                .setData(Uri.fromParts(IntentFilter.SCHEME_PACKAGE, PACKAGE_2, null));
+        mReceiver.onReceive(mMockContext, packageRemoved);
+
+        assertEquals(0, instance.mActiveUids.size());
+        assertEquals(0, instance.mForegroundUids.size());
+        assertEquals(0, instance.mRunAnyRestrictedPackages.size());
+        assertEquals(0, instance.mExemptedBucketPackages.size());
+    }
+
     static int[] array(int... appIds) {
         Arrays.sort(appIds);
         return appIds;
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java b/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java
index 2a267c4..82726c7 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java
@@ -99,6 +99,7 @@
 import org.junit.BeforeClass;
 import org.junit.Test;
 
+import java.io.File;
 import java.lang.reflect.Field;
 import java.lang.reflect.Modifier;
 import java.util.ArrayList;
@@ -165,7 +166,7 @@
         setFieldValue(ActivityManagerService.class, sService, "mHandler",
                 mock(ActivityManagerService.MainHandler.class));
         setFieldValue(ActivityManagerService.class, sService, "mProcessStats",
-                mock(ProcessStatsService.class));
+                new ProcessStatsService(sService, new File(sContext.getFilesDir(), "procstats")));
         setFieldValue(ActivityManagerService.class, sService, "mBackupTargets",
                 mock(SparseArray.class));
         setFieldValue(ActivityManagerService.class, sService, "mOomAdjProfiler",
@@ -500,7 +501,7 @@
     public void testUpdateOomAdj_DoOne_Backup() {
         ProcessRecord app = spy(makeDefaultProcessRecord(MOCKAPP_PID, MOCKAPP_UID,
                 MOCKAPP_PROCESSNAME, MOCKAPP_PACKAGENAME, true));
-        BackupRecord backupTarget = new BackupRecord(null, 0, 0);
+        BackupRecord backupTarget = new BackupRecord(null, 0, 0, 0);
         backupTarget.app = app;
         doReturn(backupTarget).when(sService.mBackupTargets).get(anyInt());
         sService.mWakefulness = PowerManagerInternal.WAKEFULNESS_AWAKE;
@@ -802,7 +803,7 @@
         ProcessRecord client = spy(makeDefaultProcessRecord(MOCKAPP2_PID, MOCKAPP2_UID,
                 MOCKAPP2_PROCESSNAME, MOCKAPP2_PACKAGENAME, false));
         bindService(app, client, null, Context.BIND_ABOVE_CLIENT, mock(IBinder.class));
-        BackupRecord backupTarget = new BackupRecord(null, 0, 0);
+        BackupRecord backupTarget = new BackupRecord(null, 0, 0, 0);
         backupTarget.app = client;
         doReturn(backupTarget).when(sService.mBackupTargets).get(anyInt());
         sService.mWakefulness = PowerManagerInternal.WAKEFULNESS_AWAKE;
diff --git a/services/tests/mockingservicestests/src/com/android/server/appop/AppOpsServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/appop/AppOpsServiceTest.java
index 0a61c44..7a3a950 100644
--- a/services/tests/mockingservicestests/src/com/android/server/appop/AppOpsServiceTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/appop/AppOpsServiceTest.java
@@ -130,7 +130,9 @@
     }
 
     @After
-    public void resetStaticMocks() {
+    public void tearDown() {
+        mAppOpsService.shutdown();
+
         mMockingSession.finishMocking();
     }
 
@@ -216,9 +218,8 @@
                 false);
         mAppOpsService.writeState();
 
-        // Create a new app ops service, and initialize its state from XML.
+        // Create a new app ops service which will initialize its state from XML.
         setupAppOpsService();
-        mAppOpsService.readState();
 
         // Query the state of the 2nd service.
         List<PackageOps> loggedOps = getLoggedOps();
@@ -233,9 +234,8 @@
         mAppOpsService.noteOperation(OP_READ_SMS, mMyUid, sMyPackageName, null, false, null, false);
         mAppOpsService.shutdown();
 
-        // Create a new app ops service, and initialize its state from XML.
+        // Create a new app ops service which will initialize its state from XML.
         setupAppOpsService();
-        mAppOpsService.readState();
 
         // Query the state of the 2nd service.
         List<PackageOps> loggedOps = getLoggedOps();
diff --git a/services/tests/mockingservicestests/src/com/android/server/location/LocationProviderManagerTest.java b/services/tests/mockingservicestests/src/com/android/server/location/LocationProviderManagerTest.java
new file mode 100644
index 0000000..fdcadf3
--- /dev/null
+++ b/services/tests/mockingservicestests/src/com/android/server/location/LocationProviderManagerTest.java
@@ -0,0 +1,987 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.location;
+
+import static android.app.AlarmManager.ELAPSED_REALTIME_WAKEUP;
+import static android.app.AlarmManager.WINDOW_EXACT;
+import static android.app.AppOpsManager.OP_FINE_LOCATION;
+import static android.app.AppOpsManager.OP_MONITOR_HIGH_POWER_LOCATION;
+import static android.app.AppOpsManager.OP_MONITOR_LOCATION;
+import static android.location.Criteria.ACCURACY_COARSE;
+import static android.location.Criteria.ACCURACY_FINE;
+import static android.location.Criteria.POWER_HIGH;
+import static android.location.LocationManager.PASSIVE_PROVIDER;
+import static android.os.PowerManager.LOCATION_MODE_THROTTLE_REQUESTS_WHEN_SCREEN_OFF;
+
+import static androidx.test.ext.truth.location.LocationSubject.assertThat;
+
+import static com.android.internal.util.ConcurrentUtils.DIRECT_EXECUTOR;
+import static com.android.server.location.LocationPermissions.PERMISSION_COARSE;
+import static com.android.server.location.LocationPermissions.PERMISSION_FINE;
+import static com.android.server.location.LocationUtils.createLocation;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyLong;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.ArgumentMatchers.isNull;
+import static org.mockito.ArgumentMatchers.nullable;
+import static org.mockito.Mockito.after;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.timeout;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
+import static org.mockito.MockitoAnnotations.initMocks;
+
+import android.app.AlarmManager;
+import android.app.AlarmManager.OnAlarmListener;
+import android.content.Context;
+import android.location.ILocationCallback;
+import android.location.ILocationListener;
+import android.location.Location;
+import android.location.LocationManagerInternal;
+import android.location.LocationManagerInternal.ProviderEnabledListener;
+import android.location.LocationRequest;
+import android.location.util.identity.CallerIdentity;
+import android.os.Bundle;
+import android.os.CancellationSignal;
+import android.os.Handler;
+import android.os.ICancellationSignal;
+import android.os.IRemoteCallback;
+import android.os.PowerManager;
+import android.os.Process;
+import android.os.RemoteException;
+import android.os.SystemClock;
+import android.os.WorkSource;
+import android.platform.test.annotations.Presubmit;
+import android.util.Log;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.internal.location.ProviderProperties;
+import com.android.internal.location.ProviderRequest;
+import com.android.server.FgThread;
+import com.android.server.LocalServices;
+import com.android.server.location.listeners.ListenerRegistration;
+import com.android.server.location.util.FakeUserInfoHelper;
+import com.android.server.location.util.TestInjector;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.util.Collections;
+import java.util.Random;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+@Presubmit
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class LocationProviderManagerTest {
+
+    private static final String TAG = "LocationProviderManagerTest";
+
+    private static final long TIMEOUT_MS = 1000;
+
+    private static final int CURRENT_USER = FakeUserInfoHelper.DEFAULT_USERID;
+    private static final int OTHER_USER = CURRENT_USER + 10;
+
+    private static final String NAME = "test";
+    private static final ProviderProperties PROPERTIES = new ProviderProperties(false, false, false,
+            false, true, true, true, POWER_HIGH, ACCURACY_FINE);
+    private static final CallerIdentity IDENTITY = CallerIdentity.forTest(CURRENT_USER, 1,
+            "mypackage",
+            "attribution");
+
+    private Random mRandom;
+
+    @Mock
+    private LocationManagerInternal mInternal;
+    @Mock
+    private Context mContext;
+    @Mock
+    private AlarmManager mAlarmManager;
+    @Mock
+    private PowerManager mPowerManager;
+    @Mock
+    private PowerManager.WakeLock mWakeLock;
+
+    private TestInjector mInjector;
+    private PassiveLocationProviderManager mPassive;
+    private TestProvider mProvider;
+
+    private LocationProviderManager mManager;
+
+    @Before
+    public void setUp() {
+        initMocks(this);
+
+        long seed = System.currentTimeMillis();
+        Log.i(TAG, "location random seed: " + seed);
+
+        mRandom = new Random(seed);
+
+        LocalServices.addService(LocationManagerInternal.class, mInternal);
+
+        doReturn("android").when(mContext).getPackageName();
+        doReturn(mAlarmManager).when(mContext).getSystemService(AlarmManager.class);
+        doReturn(mPowerManager).when(mContext).getSystemService(PowerManager.class);
+        doReturn(mWakeLock).when(mPowerManager).newWakeLock(anyInt(), anyString());
+
+        mInjector = new TestInjector();
+        mInjector.getUserInfoHelper().startUser(OTHER_USER);
+
+        mPassive = new PassiveLocationProviderManager(mContext, mInjector);
+        mPassive.startManager();
+        mPassive.setRealProvider(new PassiveProvider(mContext));
+
+        mProvider = new TestProvider(PROPERTIES, IDENTITY);
+        mProvider.setProviderAllowed(true);
+
+        mManager = new LocationProviderManager(mContext, mInjector, NAME, mPassive);
+        mManager.startManager();
+        mManager.setRealProvider(mProvider);
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        LocalServices.removeServiceForTest(LocationManagerInternal.class);
+
+        // some test failures may leave the fg thread stuck, interrupt until we get out of it
+        CountDownLatch latch = new CountDownLatch(1);
+        FgThread.getExecutor().execute(latch::countDown);
+        int count = 0;
+        while (++count < 10 && !latch.await(10, TimeUnit.MILLISECONDS)) {
+            FgThread.get().getLooper().getThread().interrupt();
+        }
+    }
+
+    @Test
+    public void testProperties() {
+        assertThat(mManager.getName()).isEqualTo(NAME);
+        assertThat(mManager.getProperties()).isEqualTo(PROPERTIES);
+        assertThat(mManager.getIdentity()).isEqualTo(IDENTITY);
+        assertThat(mManager.hasProvider()).isTrue();
+
+        ProviderProperties newProperties = new ProviderProperties(true, true, true,
+                true, false, false, false, POWER_HIGH, ACCURACY_COARSE);
+        mProvider.setProperties(newProperties);
+        assertThat(mManager.getProperties()).isEqualTo(newProperties);
+
+        CallerIdentity newIdentity = CallerIdentity.forTest(OTHER_USER, 1, "otherpackage",
+                "otherattribution");
+        mProvider.setIdentity(newIdentity);
+        assertThat(mManager.getIdentity()).isEqualTo(newIdentity);
+
+        mManager.setRealProvider(null);
+        assertThat(mManager.hasProvider()).isFalse();
+    }
+
+    @Test
+    public void testRemoveProvider() {
+        mManager.setRealProvider(null);
+        assertThat(mManager.hasProvider()).isFalse();
+    }
+
+    @Test
+    public void testIsEnabled() {
+        assertThat(mManager.isEnabled(CURRENT_USER)).isTrue();
+
+        mInjector.getSettingsHelper().setLocationEnabled(false, CURRENT_USER);
+        assertThat(mManager.isEnabled(CURRENT_USER)).isFalse();
+
+        mInjector.getSettingsHelper().setLocationEnabled(true, CURRENT_USER);
+        mProvider.setAllowed(false);
+        assertThat(mManager.isEnabled(CURRENT_USER)).isFalse();
+
+        mProvider.setAllowed(true);
+        mInjector.getUserInfoHelper().setCurrentUserId(OTHER_USER);
+        assertThat(mManager.isEnabled(CURRENT_USER)).isFalse();
+        assertThat(mManager.isEnabled(OTHER_USER)).isTrue();
+
+        mInjector.getUserInfoHelper().setCurrentUserId(CURRENT_USER);
+        assertThat(mManager.isEnabled(CURRENT_USER)).isTrue();
+        assertThat(mManager.isEnabled(OTHER_USER)).isFalse();
+    }
+
+    @Test
+    public void testIsEnabledListener() {
+        ProviderEnabledListener listener = mock(ProviderEnabledListener.class);
+        mManager.addEnabledListener(listener);
+        verify(listener, never()).onProviderEnabledChanged(anyString(), anyInt(), anyBoolean());
+
+        mInjector.getSettingsHelper().setLocationEnabled(false, CURRENT_USER);
+        verify(listener, timeout(TIMEOUT_MS).times(1)).onProviderEnabledChanged(NAME, CURRENT_USER,
+                false);
+
+        mInjector.getSettingsHelper().setLocationEnabled(true, CURRENT_USER);
+        verify(listener, timeout(TIMEOUT_MS).times(1)).onProviderEnabledChanged(NAME, CURRENT_USER,
+                true);
+
+        mProvider.setAllowed(false);
+        verify(listener, timeout(TIMEOUT_MS).times(2)).onProviderEnabledChanged(NAME, CURRENT_USER,
+                false);
+
+        mProvider.setAllowed(true);
+        verify(listener, timeout(TIMEOUT_MS).times(2)).onProviderEnabledChanged(NAME, CURRENT_USER,
+                true);
+
+        mInjector.getUserInfoHelper().setCurrentUserId(OTHER_USER);
+        verify(listener, timeout(TIMEOUT_MS).times(3)).onProviderEnabledChanged(NAME, CURRENT_USER,
+                false);
+        verify(listener, timeout(TIMEOUT_MS).times(1)).onProviderEnabledChanged(NAME, OTHER_USER,
+                true);
+
+        mInjector.getUserInfoHelper().setCurrentUserId(CURRENT_USER);
+        verify(listener, timeout(TIMEOUT_MS).times(3)).onProviderEnabledChanged(NAME, CURRENT_USER,
+                true);
+        verify(listener, timeout(TIMEOUT_MS).times(1)).onProviderEnabledChanged(NAME, OTHER_USER,
+                false);
+
+        mManager.removeEnabledListener(listener);
+        mInjector.getSettingsHelper().setLocationEnabled(false, CURRENT_USER);
+        verifyNoMoreInteractions(listener);
+    }
+
+    @Test
+    public void testGetLastLocation_Fine() {
+        LocationRequest request = LocationRequest.createFromDeprecatedProvider(NAME, 0, 0, false);
+        assertThat(mManager.getLastLocation(request, IDENTITY, PERMISSION_FINE)).isNull();
+
+        Location loc = createLocation(NAME, mRandom);
+        mProvider.setProviderLocation(loc);
+        assertThat(mManager.getLastLocation(request, IDENTITY, PERMISSION_FINE)).isEqualTo(loc);
+    }
+
+    @Test
+    public void testGetLastLocation_Coarse() {
+        LocationRequest request = LocationRequest.createFromDeprecatedProvider(NAME, 0, 0, false);
+        assertThat(mManager.getLastLocation(request, IDENTITY, PERMISSION_FINE)).isNull();
+
+        Location loc = createLocation(NAME, mRandom);
+        mProvider.setProviderLocation(loc);
+        Location coarse = mManager.getLastLocation(request, IDENTITY, PERMISSION_COARSE);
+        assertThat(coarse).isNotEqualTo(loc);
+        assertThat(coarse).isNearby(loc, 5000);
+    }
+
+    @Test
+    public void testGetLastLocation_Bypass() {
+        LocationRequest request = LocationRequest.createFromDeprecatedProvider(NAME, 0, 0, false);
+        LocationRequest bypassRequest = LocationRequest.createFromDeprecatedProvider(NAME, 0, 0,
+                false).setLocationSettingsIgnored(true);
+        assertThat(mManager.getLastLocation(request, IDENTITY, PERMISSION_FINE)).isNull();
+        assertThat(mManager.getLastLocation(bypassRequest, IDENTITY, PERMISSION_FINE)).isNull();
+
+        Location loc = createLocation(NAME, mRandom);
+        mProvider.setProviderLocation(loc);
+        assertThat(mManager.getLastLocation(request, IDENTITY, PERMISSION_FINE)).isEqualTo(loc);
+        assertThat(mManager.getLastLocation(bypassRequest, IDENTITY, PERMISSION_FINE)).isEqualTo(
+                loc);
+
+        mProvider.setProviderAllowed(false);
+        assertThat(mManager.getLastLocation(request, IDENTITY, PERMISSION_FINE)).isNull();
+        assertThat(mManager.getLastLocation(bypassRequest, IDENTITY, PERMISSION_FINE)).isEqualTo(
+                loc);
+
+        loc = createLocation(NAME, mRandom);
+        mProvider.setProviderLocation(loc);
+        assertThat(mManager.getLastLocation(request, IDENTITY, PERMISSION_FINE)).isNull();
+        assertThat(mManager.getLastLocation(bypassRequest, IDENTITY, PERMISSION_FINE)).isEqualTo(
+                loc);
+
+        mProvider.setProviderAllowed(true);
+        assertThat(mManager.getLastLocation(request, IDENTITY, PERMISSION_FINE)).isNull();
+        assertThat(mManager.getLastLocation(bypassRequest, IDENTITY, PERMISSION_FINE)).isEqualTo(
+                loc);
+
+        loc = createLocation(NAME, mRandom);
+        mProvider.setProviderLocation(loc);
+        assertThat(mManager.getLastLocation(request, IDENTITY, PERMISSION_FINE)).isEqualTo(loc);
+        assertThat(mManager.getLastLocation(bypassRequest, IDENTITY, PERMISSION_FINE)).isEqualTo(
+                loc);
+    }
+
+    @Test
+    public void testGetLastLocation_ClearOnMockRemoval() {
+        MockProvider mockProvider = new MockProvider(PROPERTIES, IDENTITY);
+        mockProvider.setAllowed(true);
+        mManager.setMockProvider(mockProvider);
+
+        LocationRequest request = LocationRequest.createFromDeprecatedProvider(NAME, 0, 0, false);
+        Location loc = createLocation(NAME, mRandom);
+        mockProvider.setProviderLocation(loc);
+        assertThat(mManager.getLastLocation(request, IDENTITY, PERMISSION_FINE)).isEqualTo(loc);
+
+        mManager.setMockProvider(null);
+        assertThat(mManager.getLastLocation(request, IDENTITY, PERMISSION_FINE)).isNull();
+    }
+
+    @Test
+    public void testInjectLastLocation() {
+        Location loc1 = createLocation(NAME, mRandom);
+        mManager.injectLastLocation(loc1, CURRENT_USER);
+
+        LocationRequest request = LocationRequest.createFromDeprecatedProvider(NAME, 0, 0, false);
+        assertThat(mManager.getLastLocation(request, IDENTITY, PERMISSION_FINE)).isEqualTo(loc1);
+
+        Location loc2 = createLocation(NAME, mRandom);
+        mManager.injectLastLocation(loc2, CURRENT_USER);
+
+        assertThat(mManager.getLastLocation(request, IDENTITY, PERMISSION_FINE)).isEqualTo(loc1);
+    }
+
+    @Test
+    public void testPassive_Listener() throws Exception {
+        ILocationListener listener = createMockLocationListener();
+        LocationRequest request = LocationRequest.createFromDeprecatedProvider(PASSIVE_PROVIDER, 0,
+                0, false);
+        mPassive.registerLocationRequest(request, IDENTITY, PERMISSION_FINE, listener);
+
+        Location loc = createLocation(NAME, mRandom);
+        mProvider.setProviderLocation(loc);
+
+        ArgumentCaptor<Location> locationCaptor = ArgumentCaptor.forClass(Location.class);
+        verify(listener).onLocationChanged(locationCaptor.capture(),
+                nullable(IRemoteCallback.class));
+        assertThat(locationCaptor.getValue()).isEqualTo(loc);
+    }
+
+    @Test
+    public void testPassive_LastLocation() {
+        Location loc = createLocation(NAME, mRandom);
+        mProvider.setProviderLocation(loc);
+
+        LocationRequest request = LocationRequest.createFromDeprecatedProvider(PASSIVE_PROVIDER, 0,
+                0, false);
+        assertThat(mPassive.getLastLocation(request, IDENTITY, PERMISSION_FINE)).isEqualTo(loc);
+    }
+
+    @Test
+    public void testRegisterListener() throws Exception {
+        ArgumentCaptor<Location> locationCaptor = ArgumentCaptor.forClass(Location.class);
+
+        ILocationListener listener = createMockLocationListener();
+        mManager.registerLocationRequest(
+                LocationRequest.createFromDeprecatedProvider(NAME, 0, 0, false), IDENTITY,
+                PERMISSION_FINE, listener);
+
+        Location loc = createLocation(NAME, mRandom);
+        mProvider.setProviderLocation(loc);
+        verify(listener, times(1)).onLocationChanged(locationCaptor.capture(),
+                nullable(IRemoteCallback.class));
+        assertThat(locationCaptor.getValue()).isEqualTo(loc);
+
+        mInjector.getSettingsHelper().setLocationEnabled(false, CURRENT_USER);
+        verify(listener, timeout(TIMEOUT_MS).times(1)).onProviderEnabledChanged(NAME, false);
+        loc = createLocation(NAME, mRandom);
+        mProvider.setProviderLocation(loc);
+        verify(listener, times(1)).onLocationChanged(any(Location.class),
+                nullable(IRemoteCallback.class));
+
+        mInjector.getSettingsHelper().setLocationEnabled(true, CURRENT_USER);
+        verify(listener, timeout(TIMEOUT_MS).times(1)).onProviderEnabledChanged(NAME, true);
+
+        mProvider.setAllowed(false);
+        verify(listener, timeout(TIMEOUT_MS).times(2)).onProviderEnabledChanged(NAME, false);
+        loc = createLocation(NAME, mRandom);
+        mProvider.setProviderLocation(loc);
+        verify(listener, times(1)).onLocationChanged(any(Location.class),
+                nullable(IRemoteCallback.class));
+
+        mProvider.setAllowed(true);
+        verify(listener, timeout(TIMEOUT_MS).times(2)).onProviderEnabledChanged(NAME, true);
+
+        mInjector.getUserInfoHelper().setCurrentUserId(OTHER_USER);
+        verify(listener, timeout(TIMEOUT_MS).times(3)).onProviderEnabledChanged(NAME, false);
+        loc = createLocation(NAME, mRandom);
+        mProvider.setProviderLocation(loc);
+        verify(listener, times(1)).onLocationChanged(any(Location.class),
+                nullable(IRemoteCallback.class));
+
+        mInjector.getUserInfoHelper().setCurrentUserId(CURRENT_USER);
+        verify(listener, timeout(TIMEOUT_MS).times(3)).onProviderEnabledChanged(NAME, true);
+
+        loc = createLocation(NAME, mRandom);
+        mProvider.setProviderLocation(loc);
+        verify(listener, times(2)).onLocationChanged(locationCaptor.capture(),
+                nullable(IRemoteCallback.class));
+        assertThat(locationCaptor.getValue()).isEqualTo(loc);
+    }
+
+    @Test
+    public void testRegisterListener_SameProcess() throws Exception {
+        ArgumentCaptor<Location> locationCaptor = ArgumentCaptor.forClass(Location.class);
+
+        CallerIdentity identity = CallerIdentity.forTest(CURRENT_USER, Process.myPid(), "mypackage",
+                "attribution");
+
+        ILocationListener listener = createMockLocationListener();
+        mManager.registerLocationRequest(
+                LocationRequest.createFromDeprecatedProvider(NAME, 0, 0, false), identity,
+                PERMISSION_FINE, listener);
+
+        Location loc = createLocation(NAME, mRandom);
+        mProvider.setProviderLocation(loc);
+        verify(listener, timeout(TIMEOUT_MS).times(1)).onLocationChanged(locationCaptor.capture(),
+                nullable(IRemoteCallback.class));
+        assertThat(locationCaptor.getValue()).isEqualTo(loc);
+    }
+
+    @Test
+    public void testRegisterListener_Unregister() throws Exception {
+        ILocationListener listener = createMockLocationListener();
+        mManager.registerLocationRequest(
+                LocationRequest.createFromDeprecatedProvider(NAME, 0, 0, false), IDENTITY,
+                PERMISSION_FINE, listener);
+        mManager.unregisterLocationRequest(listener);
+
+        mProvider.setProviderLocation(createLocation(NAME, mRandom));
+        verify(listener, never()).onLocationChanged(any(Location.class),
+                nullable(IRemoteCallback.class));
+
+        mInjector.getSettingsHelper().setLocationEnabled(false, CURRENT_USER);
+        verify(listener, after(TIMEOUT_MS).never()).onProviderEnabledChanged(NAME, false);
+    }
+
+    @Test
+    public void testRegisterListener_Unregister_SameProcess() throws Exception {
+        CallerIdentity identity = CallerIdentity.forTest(CURRENT_USER, Process.myPid(), "mypackage",
+                "attribution");
+
+        ILocationListener listener = createMockLocationListener();
+        mManager.registerLocationRequest(
+                LocationRequest.createFromDeprecatedProvider(NAME, 0, 0, false), identity,
+                PERMISSION_FINE, listener);
+
+        CountDownLatch blocker = new CountDownLatch(1);
+        ListenerRegistration.IN_PROCESS_EXECUTOR.execute(() -> {
+            try {
+                blocker.await();
+            } catch (InterruptedException e) {
+                // do nothing
+            }
+        });
+
+        mProvider.setProviderLocation(createLocation(NAME, mRandom));
+        mManager.unregisterLocationRequest(listener);
+        blocker.countDown();
+        verify(listener, after(TIMEOUT_MS).never()).onLocationChanged(any(Location.class),
+                nullable(IRemoteCallback.class));
+    }
+
+    @Test
+    public void testRegisterListener_NumUpdates() throws Exception {
+        ILocationListener listener = createMockLocationListener();
+        LocationRequest request = LocationRequest.createFromDeprecatedProvider(NAME, 0, 0,
+                false).setNumUpdates(5);
+        mManager.registerLocationRequest(request, IDENTITY, PERMISSION_FINE, listener);
+
+        mProvider.setProviderLocation(createLocation(NAME, mRandom));
+        mProvider.setProviderLocation(createLocation(NAME, mRandom));
+        mProvider.setProviderLocation(createLocation(NAME, mRandom));
+        mProvider.setProviderLocation(createLocation(NAME, mRandom));
+        mProvider.setProviderLocation(createLocation(NAME, mRandom));
+        mProvider.setProviderLocation(createLocation(NAME, mRandom));
+
+        verify(listener, times(5)).onLocationChanged(any(Location.class),
+                nullable(IRemoteCallback.class));
+    }
+
+    @Test
+    public void testRegisterListener_ExpiringAlarm() throws Exception {
+        ILocationListener listener = createMockLocationListener();
+        LocationRequest request = LocationRequest.createFromDeprecatedProvider(NAME, 0, 0,
+                false).setExpireIn(5000);
+        mManager.registerLocationRequest(request, IDENTITY, PERMISSION_FINE, listener);
+        long baseTimeMs = SystemClock.elapsedRealtime();
+
+        ArgumentCaptor<Long> timeoutCapture = ArgumentCaptor.forClass(Long.class);
+        ArgumentCaptor<OnAlarmListener> listenerCapture = ArgumentCaptor.forClass(
+                OnAlarmListener.class);
+        verify(mAlarmManager).set(eq(ELAPSED_REALTIME_WAKEUP), timeoutCapture.capture(),
+                eq(WINDOW_EXACT), eq(0L), listenerCapture.capture(), any(Handler.class),
+                any(WorkSource.class));
+
+        assertThat(timeoutCapture.getValue()).isAtLeast(baseTimeMs + 4000);
+        assertThat(timeoutCapture.getValue()).isAtMost(baseTimeMs + 5000);
+        listenerCapture.getValue().onAlarm();
+
+        mProvider.setProviderLocation(createLocation(NAME, mRandom));
+        verify(listener, never()).onLocationChanged(any(Location.class),
+                nullable(IRemoteCallback.class));
+    }
+
+    @Test
+    public void testRegisterListener_ExpiringNoAlarm() throws Exception {
+        ILocationListener listener = createMockLocationListener();
+        LocationRequest request = LocationRequest.createFromDeprecatedProvider(NAME, 0, 0,
+                false).setExpireIn(25);
+        mManager.registerLocationRequest(request, IDENTITY, PERMISSION_FINE, listener);
+
+        Thread.sleep(25);
+
+        mProvider.setProviderLocation(createLocation(NAME, mRandom));
+        verify(listener, never()).onLocationChanged(any(Location.class),
+                nullable(IRemoteCallback.class));
+    }
+
+    @Test
+    public void testRegisterListener_AlreadyExpired() throws Exception {
+        ILocationListener listener = createMockLocationListener();
+        LocationRequest request = LocationRequest.createFromDeprecatedProvider(NAME, 0, 0,
+                false).setExpireIn(-1);
+        mManager.registerLocationRequest(request, IDENTITY, PERMISSION_FINE, listener);
+
+        mProvider.setProviderLocation(createLocation(NAME, mRandom));
+        verify(listener, never()).onLocationChanged(any(Location.class),
+                nullable(IRemoteCallback.class));
+    }
+
+    @Test
+    public void testRegisterListener_FastestInterval() throws Exception {
+        ILocationListener listener = createMockLocationListener();
+        LocationRequest request = LocationRequest.createFromDeprecatedProvider(NAME, 5000, 0,
+                false).setFastestInterval(5000);
+        mManager.registerLocationRequest(request, IDENTITY, PERMISSION_FINE, listener);
+
+        mProvider.setProviderLocation(createLocation(NAME, mRandom));
+        mProvider.setProviderLocation(createLocation(NAME, mRandom));
+
+        verify(listener, times(1)).onLocationChanged(any(Location.class),
+                nullable(IRemoteCallback.class));
+    }
+
+    @Test
+    public void testRegisterListener_SmallestDisplacement() throws Exception {
+        ILocationListener listener = createMockLocationListener();
+        LocationRequest request = LocationRequest.createFromDeprecatedProvider(NAME, 5000, 0,
+                false).setSmallestDisplacement(1f);
+        mManager.registerLocationRequest(request, IDENTITY, PERMISSION_FINE, listener);
+
+        Location loc = createLocation(NAME, mRandom);
+        mProvider.setProviderLocation(loc);
+        mProvider.setProviderLocation(loc);
+
+        verify(listener, times(1)).onLocationChanged(any(Location.class),
+                nullable(IRemoteCallback.class));
+    }
+
+    @Test
+    public void testRegisterListener_NoteOpFailure() throws Exception {
+        ILocationListener listener = createMockLocationListener();
+        LocationRequest request = LocationRequest.createFromDeprecatedProvider(NAME, 0, 0, false);
+        mManager.registerLocationRequest(request, IDENTITY, PERMISSION_FINE, listener);
+
+        mInjector.getAppOpsHelper().setAppOpAllowed(OP_FINE_LOCATION, IDENTITY.getPackageName(),
+                false);
+
+        mProvider.setProviderLocation(createLocation(NAME, mRandom));
+
+        verify(listener, never()).onLocationChanged(any(Location.class),
+                nullable(IRemoteCallback.class));
+    }
+
+    @Test
+    public void testRegisterListener_Wakelock() throws Exception {
+        CallerIdentity identity = CallerIdentity.forTest(CURRENT_USER, Process.myPid(), "mypackage",
+                "attribution");
+
+        ILocationListener listener = createMockLocationListener();
+        mManager.registerLocationRequest(
+                LocationRequest.createFromDeprecatedProvider(NAME, 0, 0, false), identity,
+                PERMISSION_FINE, listener);
+
+        CountDownLatch blocker = new CountDownLatch(1);
+        ListenerRegistration.IN_PROCESS_EXECUTOR.execute(() -> {
+            try {
+                blocker.await();
+            } catch (InterruptedException e) {
+                // do nothing
+            }
+        });
+
+        mProvider.setProviderLocation(createLocation(NAME, mRandom));
+        verify(mWakeLock).acquire(anyLong());
+        verify(mWakeLock, never()).release();
+
+        blocker.countDown();
+        verify(listener, timeout(TIMEOUT_MS)).onLocationChanged(any(Location.class),
+                nullable(IRemoteCallback.class));
+        verify(mWakeLock).acquire(anyLong());
+        verify(mWakeLock, timeout(TIMEOUT_MS)).release();
+    }
+
+    @Test
+    public void testGetCurrentLocation() throws Exception {
+        ArgumentCaptor<Location> locationCaptor = ArgumentCaptor.forClass(Location.class);
+
+        ILocationCallback listener = createMockGetCurrentLocationListener();
+        LocationRequest locationRequest = LocationRequest.createFromDeprecatedProvider(NAME, 0, 0,
+                false);
+        ICancellationSignal cancellationSignal = CancellationSignal.createTransport();
+        mManager.getCurrentLocation(locationRequest, IDENTITY,
+                PERMISSION_FINE, cancellationSignal, listener);
+
+        Location loc = createLocation(NAME, mRandom);
+        mProvider.setProviderLocation(loc);
+        mProvider.setProviderLocation(createLocation(NAME, mRandom));
+
+        verify(listener, times(1)).onLocation(locationCaptor.capture());
+        assertThat(locationCaptor.getValue()).isEqualTo(loc);
+    }
+
+    @Test
+    public void testGetCurrentLocation_Cancel() throws Exception {
+        ILocationCallback listener = createMockGetCurrentLocationListener();
+        LocationRequest locationRequest = LocationRequest.createFromDeprecatedProvider(NAME, 0, 0,
+                false);
+        ICancellationSignal cancellationSignal = CancellationSignal.createTransport();
+        mManager.getCurrentLocation(locationRequest, IDENTITY,
+                PERMISSION_FINE, cancellationSignal, listener);
+
+        cancellationSignal.cancel();
+        mProvider.setProviderLocation(createLocation(NAME, mRandom));
+
+        verify(listener, never()).onLocation(nullable(Location.class));
+    }
+
+    @Test
+    public void testGetCurrentLocation_ProviderDisabled() throws Exception {
+        ILocationCallback listener = createMockGetCurrentLocationListener();
+        LocationRequest locationRequest = LocationRequest.createFromDeprecatedProvider(NAME, 0, 0,
+                false);
+        ICancellationSignal cancellationSignal = CancellationSignal.createTransport();
+        mManager.getCurrentLocation(locationRequest, IDENTITY,
+                PERMISSION_FINE, cancellationSignal, listener);
+
+        mProvider.setProviderAllowed(false);
+        mProvider.setProviderAllowed(true);
+        mProvider.setProviderLocation(createLocation(NAME, mRandom));
+        verify(listener, times(1)).onLocation(isNull());
+    }
+
+    @Test
+    public void testGetCurrentLocation_ProviderAlreadyDisabled() throws Exception {
+        mProvider.setProviderAllowed(false);
+
+        ILocationCallback listener = createMockGetCurrentLocationListener();
+        LocationRequest locationRequest = LocationRequest.createFromDeprecatedProvider(NAME, 0, 0,
+                false);
+        ICancellationSignal cancellationSignal = CancellationSignal.createTransport();
+        mManager.getCurrentLocation(locationRequest, IDENTITY,
+                PERMISSION_FINE, cancellationSignal, listener);
+
+        mProvider.setProviderAllowed(true);
+        mProvider.setProviderLocation(createLocation(NAME, mRandom));
+        verify(listener, times(1)).onLocation(isNull());
+    }
+
+    @Test
+    public void testGetCurrentLocation_LastLocation() throws Exception {
+        ArgumentCaptor<Location> locationCaptor = ArgumentCaptor.forClass(Location.class);
+
+        Location loc = createLocation(NAME, mRandom);
+        mProvider.setProviderLocation(loc);
+
+        ILocationCallback listener = createMockGetCurrentLocationListener();
+        LocationRequest locationRequest = LocationRequest.createFromDeprecatedProvider(NAME, 0, 0,
+                false);
+        ICancellationSignal cancellationSignal = CancellationSignal.createTransport();
+        mManager.getCurrentLocation(locationRequest, IDENTITY,
+                PERMISSION_FINE, cancellationSignal, listener);
+
+        verify(listener, times(1)).onLocation(locationCaptor.capture());
+        assertThat(locationCaptor.getValue()).isEqualTo(loc);
+    }
+
+    @Test
+    public void testGetCurrentLocation_Timeout() throws Exception {
+        ILocationCallback listener = createMockGetCurrentLocationListener();
+        LocationRequest locationRequest = LocationRequest.createFromDeprecatedProvider(NAME, 0, 0,
+                false);
+        ICancellationSignal cancellationSignal = CancellationSignal.createTransport();
+        mManager.getCurrentLocation(locationRequest, IDENTITY,
+                PERMISSION_FINE, cancellationSignal, listener);
+
+        ArgumentCaptor<OnAlarmListener> listenerCapture = ArgumentCaptor.forClass(
+                OnAlarmListener.class);
+        verify(mAlarmManager).set(eq(ELAPSED_REALTIME_WAKEUP), anyLong(),
+                eq(WINDOW_EXACT), eq(0L), listenerCapture.capture(), any(Handler.class),
+                any(WorkSource.class));
+        listenerCapture.getValue().onAlarm();
+
+        verify(listener, times(1)).onLocation(isNull());
+    }
+
+    @Test
+    public void testLocationMonitoring() {
+        assertThat(mInjector.getAppOpsHelper().isAppOpStarted(OP_MONITOR_LOCATION,
+                IDENTITY.getPackageName())).isFalse();
+        assertThat(mInjector.getAppOpsHelper().isAppOpStarted(OP_MONITOR_HIGH_POWER_LOCATION,
+                IDENTITY.getPackageName())).isFalse();
+
+        ILocationListener listener = createMockLocationListener();
+        LocationRequest request = LocationRequest.createFromDeprecatedProvider(NAME, 0, 0, false);
+        mManager.registerLocationRequest(request, IDENTITY, PERMISSION_FINE, listener);
+
+        assertThat(mInjector.getAppOpsHelper().isAppOpStarted(OP_MONITOR_LOCATION,
+                IDENTITY.getPackageName())).isTrue();
+        assertThat(mInjector.getAppOpsHelper().isAppOpStarted(OP_MONITOR_HIGH_POWER_LOCATION,
+                IDENTITY.getPackageName())).isTrue();
+
+        mInjector.getAppForegroundHelper().setAppForeground(IDENTITY.getUid(), false);
+
+        assertThat(mInjector.getAppOpsHelper().isAppOpStarted(OP_MONITOR_LOCATION,
+                IDENTITY.getPackageName())).isTrue();
+        assertThat(mInjector.getAppOpsHelper().isAppOpStarted(OP_MONITOR_HIGH_POWER_LOCATION,
+                IDENTITY.getPackageName())).isFalse();
+
+        mManager.unregisterLocationRequest(listener);
+
+        assertThat(mInjector.getAppOpsHelper().isAppOpStarted(OP_MONITOR_LOCATION,
+                IDENTITY.getPackageName())).isFalse();
+        assertThat(mInjector.getAppOpsHelper().isAppOpStarted(OP_MONITOR_HIGH_POWER_LOCATION,
+                IDENTITY.getPackageName())).isFalse();
+    }
+
+    @Test
+    public void testProviderRequest() {
+        assertThat(mProvider.getRequest().reportLocation).isFalse();
+        assertThat(mProvider.getRequest().locationRequests).isEmpty();
+
+        ILocationListener listener1 = createMockLocationListener();
+        LocationRequest request1 = LocationRequest.createFromDeprecatedProvider(NAME, 5, 0, false);
+        mManager.registerLocationRequest(request1, IDENTITY, PERMISSION_FINE, listener1);
+
+        assertThat(mProvider.getRequest().reportLocation).isTrue();
+        assertThat(mProvider.getRequest().locationRequests).containsExactly(request1);
+        assertThat(mProvider.getRequest().locationSettingsIgnored).isFalse();
+        assertThat(mProvider.getRequest().interval).isEqualTo(5);
+        assertThat(mProvider.getRequest().lowPowerMode).isFalse();
+        assertThat(mProvider.getRequest().workSource).isNotNull();
+
+        ILocationListener listener2 = createMockLocationListener();
+        LocationRequest request2 = LocationRequest.createFromDeprecatedProvider(NAME, 1, 0,
+                false).setLowPowerMode(true);
+        mManager.registerLocationRequest(request2, IDENTITY, PERMISSION_FINE, listener2);
+
+        assertThat(mProvider.getRequest().reportLocation).isTrue();
+        assertThat(mProvider.getRequest().locationRequests).containsExactly(request1, request2);
+        assertThat(mProvider.getRequest().locationSettingsIgnored).isFalse();
+        assertThat(mProvider.getRequest().interval).isEqualTo(1);
+        assertThat(mProvider.getRequest().lowPowerMode).isFalse();
+        assertThat(mProvider.getRequest().workSource).isNotNull();
+
+        mManager.unregisterLocationRequest(listener1);
+
+        assertThat(mProvider.getRequest().reportLocation).isTrue();
+        assertThat(mProvider.getRequest().locationRequests).containsExactly(request2);
+        assertThat(mProvider.getRequest().locationSettingsIgnored).isFalse();
+        assertThat(mProvider.getRequest().interval).isEqualTo(1);
+        assertThat(mProvider.getRequest().lowPowerMode).isTrue();
+        assertThat(mProvider.getRequest().workSource).isNotNull();
+
+        mManager.unregisterLocationRequest(listener2);
+
+        assertThat(mProvider.getRequest().reportLocation).isFalse();
+        assertThat(mProvider.getRequest().locationRequests).isEmpty();
+    }
+
+    @Test
+    public void testProviderRequest_BackgroundThrottle() {
+        ILocationListener listener1 = createMockLocationListener();
+        LocationRequest request1 = LocationRequest.createFromDeprecatedProvider(NAME, 5, 0, false);
+        mManager.registerLocationRequest(request1, IDENTITY, PERMISSION_FINE, listener1);
+
+        assertThat(mProvider.getRequest().interval).isEqualTo(5);
+
+        mInjector.getAppForegroundHelper().setAppForeground(IDENTITY.getUid(), false);
+        assertThat(mProvider.getRequest().interval).isEqualTo(
+                mInjector.getSettingsHelper().getBackgroundThrottleIntervalMs());
+    }
+
+    @Test
+    public void testProviderRequest_IgnoreLocationSettings() {
+        mInjector.getSettingsHelper().setIgnoreSettingsPackageWhitelist(
+                Collections.singleton(IDENTITY.getPackageName()));
+
+        ILocationListener listener1 = createMockLocationListener();
+        LocationRequest request1 = LocationRequest.createFromDeprecatedProvider(NAME, 5, 0, false);
+        mManager.registerLocationRequest(request1, IDENTITY, PERMISSION_FINE, listener1);
+
+        assertThat(mProvider.getRequest().reportLocation).isTrue();
+        assertThat(mProvider.getRequest().interval).isEqualTo(5);
+        assertThat(mProvider.getRequest().locationSettingsIgnored).isFalse();
+
+        ILocationListener listener2 = createMockLocationListener();
+        LocationRequest request2 = LocationRequest.createFromDeprecatedProvider(NAME, 1, 0,
+                false).setLocationSettingsIgnored(true);
+        mManager.registerLocationRequest(request2, IDENTITY, PERMISSION_FINE, listener2);
+
+        assertThat(mProvider.getRequest().reportLocation).isTrue();
+        assertThat(mProvider.getRequest().interval).isEqualTo(1);
+        assertThat(mProvider.getRequest().locationSettingsIgnored).isTrue();
+    }
+
+    @Test
+    public void testProviderRequest_IgnoreLocationSettings_ProviderDisabled() {
+        mInjector.getSettingsHelper().setIgnoreSettingsPackageWhitelist(
+                Collections.singleton(IDENTITY.getPackageName()));
+
+        ILocationListener listener1 = createMockLocationListener();
+        LocationRequest request1 = LocationRequest.createFromDeprecatedProvider(NAME, 1, 0, false);
+        mManager.registerLocationRequest(request1, IDENTITY, PERMISSION_FINE, listener1);
+
+        ILocationListener listener2 = createMockLocationListener();
+        LocationRequest request2 = LocationRequest.createFromDeprecatedProvider(NAME, 5, 0,
+                false).setLocationSettingsIgnored(true);
+        mManager.registerLocationRequest(request2, IDENTITY, PERMISSION_FINE, listener2);
+
+        mInjector.getSettingsHelper().setLocationEnabled(false, IDENTITY.getUserId());
+
+        assertThat(mProvider.getRequest().reportLocation).isTrue();
+        assertThat(mProvider.getRequest().locationRequests).containsExactly(request2);
+        assertThat(mProvider.getRequest().interval).isEqualTo(5);
+        assertThat(mProvider.getRequest().locationSettingsIgnored).isTrue();
+    }
+
+    @Test
+    public void testProviderRequest_IgnoreLocationSettings_NoAllowlist() {
+        mInjector.getSettingsHelper().setIgnoreSettingsPackageWhitelist(
+                Collections.singleton(IDENTITY.getPackageName()));
+
+        ILocationListener listener = createMockLocationListener();
+        LocationRequest request = LocationRequest.createFromDeprecatedProvider(NAME, 1, 0,
+                false).setLocationSettingsIgnored(true);
+        mManager.registerLocationRequest(request, IDENTITY, PERMISSION_FINE, listener);
+
+        mInjector.getSettingsHelper().setIgnoreSettingsPackageWhitelist(Collections.emptySet());
+
+        assertThat(mProvider.getRequest().reportLocation).isTrue();
+        assertThat(mProvider.getRequest().interval).isEqualTo(1);
+        assertThat(mProvider.getRequest().locationSettingsIgnored).isFalse();
+    }
+
+    @Test
+    public void testProviderRequest_BackgroundThrottle_IgnoreLocationSettings() {
+        mInjector.getSettingsHelper().setIgnoreSettingsPackageWhitelist(
+                Collections.singleton(IDENTITY.getPackageName()));
+
+        ILocationListener listener1 = createMockLocationListener();
+        LocationRequest request1 = LocationRequest.createFromDeprecatedProvider(NAME, 5, 0,
+                false).setLocationSettingsIgnored(true);
+        mManager.registerLocationRequest(request1, IDENTITY, PERMISSION_FINE, listener1);
+
+        assertThat(mProvider.getRequest().interval).isEqualTo(5);
+
+        mInjector.getAppForegroundHelper().setAppForeground(IDENTITY.getUid(), false);
+        assertThat(mProvider.getRequest().interval).isEqualTo(5);
+    }
+
+    @Test
+    public void testProviderRequest_BatterySaver_ScreenOnOff() {
+        mInjector.getLocationPowerSaveModeHelper().setLocationPowerSaveMode(
+                LOCATION_MODE_THROTTLE_REQUESTS_WHEN_SCREEN_OFF);
+
+        ILocationListener listener = createMockLocationListener();
+        LocationRequest request = LocationRequest.createFromDeprecatedProvider(NAME, 5, 0, false);
+        mManager.registerLocationRequest(request, IDENTITY, PERMISSION_FINE, listener);
+
+        assertThat(mProvider.getRequest().reportLocation).isTrue();
+
+        mInjector.getScreenInteractiveHelper().setScreenInteractive(false);
+        assertThat(mProvider.getRequest().reportLocation).isFalse();
+    }
+
+    private ILocationListener createMockLocationListener() {
+        return spy(new ILocationListener.Stub() {
+            @Override
+            public void onLocationChanged(Location location, IRemoteCallback onCompleteCallback) {
+                if (onCompleteCallback != null) {
+                    try {
+                        onCompleteCallback.sendResult(null);
+                    } catch (RemoteException e) {
+                        e.rethrowFromSystemServer();
+                    }
+                }
+            }
+
+            @Override
+            public void onProviderEnabledChanged(String provider, boolean enabled) {
+            }
+        });
+    }
+
+    private ILocationCallback createMockGetCurrentLocationListener() {
+        return spy(new ILocationCallback.Stub() {
+            @Override
+            public void onLocation(Location location) {
+            }
+        });
+    }
+
+    private static class TestProvider extends AbstractLocationProvider {
+
+        private ProviderRequest mProviderRequest = ProviderRequest.EMPTY_REQUEST;
+
+        TestProvider(ProviderProperties properties, CallerIdentity identity) {
+            super(DIRECT_EXECUTOR, identity);
+            setProperties(properties);
+        }
+
+        public void setProviderAllowed(boolean allowed) {
+            setAllowed(allowed);
+        }
+
+        public void setProviderLocation(Location l) {
+            reportLocation(new Location(l));
+        }
+
+        public ProviderRequest getRequest() {
+            return mProviderRequest;
+        }
+
+        @Override
+        public void onSetRequest(ProviderRequest request) {
+            mProviderRequest = request;
+        }
+
+        @Override
+        protected void onExtraCommand(int uid, int pid, String command, Bundle extras) {
+        }
+
+        @Override
+        public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+        }
+    }
+}
diff --git a/services/tests/servicestests/assets/NetworkPolicyManagerServiceTest/netpolicy/restrict-background-lists-whitelist-format.xml b/services/tests/servicestests/assets/NetworkPolicyManagerServiceTest/netpolicy/restrict-background-lists-allowlist-format.xml
similarity index 100%
rename from services/tests/servicestests/assets/NetworkPolicyManagerServiceTest/netpolicy/restrict-background-lists-whitelist-format.xml
rename to services/tests/servicestests/assets/NetworkPolicyManagerServiceTest/netpolicy/restrict-background-lists-allowlist-format.xml
diff --git a/services/tests/servicestests/assets/NetworkPolicyManagerServiceTest/netpolicy/uidA-whitelisted-restrict-background-off.xml b/services/tests/servicestests/assets/NetworkPolicyManagerServiceTest/netpolicy/uidA-allowlisted-restrict-background-off.xml
similarity index 100%
rename from services/tests/servicestests/assets/NetworkPolicyManagerServiceTest/netpolicy/uidA-whitelisted-restrict-background-off.xml
rename to services/tests/servicestests/assets/NetworkPolicyManagerServiceTest/netpolicy/uidA-allowlisted-restrict-background-off.xml
diff --git a/services/tests/servicestests/assets/NetworkPolicyManagerServiceTest/netpolicy/uidA-whitelisted-restrict-background-on.xml b/services/tests/servicestests/assets/NetworkPolicyManagerServiceTest/netpolicy/uidA-allowlisted-restrict-background-on.xml
similarity index 100%
rename from services/tests/servicestests/assets/NetworkPolicyManagerServiceTest/netpolicy/uidA-whitelisted-restrict-background-on.xml
rename to services/tests/servicestests/assets/NetworkPolicyManagerServiceTest/netpolicy/uidA-allowlisted-restrict-background-on.xml
diff --git a/services/tests/servicestests/assets/NetworkPolicyManagerServiceTest/netpolicy/uidA-blacklisted-restrict-background-off.xml b/services/tests/servicestests/assets/NetworkPolicyManagerServiceTest/netpolicy/uidA-denylisted-restrict-background-off.xml
similarity index 100%
rename from services/tests/servicestests/assets/NetworkPolicyManagerServiceTest/netpolicy/uidA-blacklisted-restrict-background-off.xml
rename to services/tests/servicestests/assets/NetworkPolicyManagerServiceTest/netpolicy/uidA-denylisted-restrict-background-off.xml
diff --git a/services/tests/servicestests/assets/NetworkPolicyManagerServiceTest/netpolicy/uidA-blacklisted-restrict-background-on.xml b/services/tests/servicestests/assets/NetworkPolicyManagerServiceTest/netpolicy/uidA-denylisted-restrict-background-on.xml
similarity index 100%
rename from services/tests/servicestests/assets/NetworkPolicyManagerServiceTest/netpolicy/uidA-blacklisted-restrict-background-on.xml
rename to services/tests/servicestests/assets/NetworkPolicyManagerServiceTest/netpolicy/uidA-denylisted-restrict-background-on.xml
diff --git a/services/tests/servicestests/src/android/location/timezone/LocationTimeZoneEventTest.java b/services/tests/servicestests/src/android/location/timezone/LocationTimeZoneEventTest.java
index 80373ac..f9dd7dc 100644
--- a/services/tests/servicestests/src/android/location/timezone/LocationTimeZoneEventTest.java
+++ b/services/tests/servicestests/src/android/location/timezone/LocationTimeZoneEventTest.java
@@ -23,6 +23,8 @@
 
 import static java.util.Collections.singletonList;
 
+import android.os.UserHandle;
+
 import org.junit.Test;
 
 import java.util.List;
@@ -33,6 +35,10 @@
 
     private static final List<String> ARBITRARY_TIME_ZONE_IDS = singletonList("Europe/London");
 
+    private static final UserHandle ARBITRARY_USER_HANDLE = UserHandle.SYSTEM;
+    private static final UserHandle ARBITRARY_USER_HANDLE2 =
+            UserHandle.of(ARBITRARY_USER_HANDLE.getIdentifier() + 1);
+
     @Test(expected = RuntimeException.class)
     public void testSetInvalidEventType() {
         new LocationTimeZoneEvent.Builder().setEventType(Integer.MAX_VALUE);
@@ -41,6 +47,7 @@
     @Test(expected = RuntimeException.class)
     public void testBuildUnsetEventType() {
         new LocationTimeZoneEvent.Builder()
+                .setUserHandle(ARBITRARY_USER_HANDLE)
                 .setTimeZoneIds(ARBITRARY_TIME_ZONE_IDS)
                 .setElapsedRealtimeNanos(ARBITRARY_ELAPSED_REALTIME_NANOS)
                 .build();
@@ -49,6 +56,7 @@
     @Test(expected = RuntimeException.class)
     public void testInvalidTimeZoneIds() {
         new LocationTimeZoneEvent.Builder()
+                .setUserHandle(ARBITRARY_USER_HANDLE)
                 .setEventType(LocationTimeZoneEvent.EVENT_TYPE_UNCERTAIN)
                 .setTimeZoneIds(ARBITRARY_TIME_ZONE_IDS)
                 .setElapsedRealtimeNanos(ARBITRARY_ELAPSED_REALTIME_NANOS)
@@ -58,6 +66,7 @@
     @Test
     public void testEquals() {
         LocationTimeZoneEvent.Builder builder1 = new LocationTimeZoneEvent.Builder()
+                .setUserHandle(ARBITRARY_USER_HANDLE)
                 .setEventType(LocationTimeZoneEvent.EVENT_TYPE_UNCERTAIN)
                 .setElapsedRealtimeNanos(ARBITRARY_ELAPSED_REALTIME_NANOS);
         {
@@ -66,6 +75,7 @@
         }
 
         LocationTimeZoneEvent.Builder builder2 = new LocationTimeZoneEvent.Builder()
+                .setUserHandle(ARBITRARY_USER_HANDLE)
                 .setEventType(LocationTimeZoneEvent.EVENT_TYPE_UNCERTAIN)
                 .setElapsedRealtimeNanos(ARBITRARY_ELAPSED_REALTIME_NANOS);
         {
@@ -75,6 +85,22 @@
             assertEquals(two, one);
         }
 
+        builder1.setUserHandle(ARBITRARY_USER_HANDLE2);
+        {
+            LocationTimeZoneEvent one = builder1.build();
+            LocationTimeZoneEvent two = builder2.build();
+            assertNotEquals(one, two);
+            assertNotEquals(two, one);
+        }
+
+        builder2.setUserHandle(ARBITRARY_USER_HANDLE2);
+        {
+            LocationTimeZoneEvent one = builder1.build();
+            LocationTimeZoneEvent two = builder2.build();
+            assertEquals(one, two);
+            assertEquals(two, one);
+        }
+
         builder1.setElapsedRealtimeNanos(ARBITRARY_ELAPSED_REALTIME_NANOS + 1);
         {
             LocationTimeZoneEvent one = builder1.build();
@@ -127,6 +153,7 @@
     @Test
     public void testParcelable() {
         LocationTimeZoneEvent.Builder builder = new LocationTimeZoneEvent.Builder()
+                .setUserHandle(ARBITRARY_USER_HANDLE)
                 .setEventType(LocationTimeZoneEvent.EVENT_TYPE_PERMANENT_FAILURE)
                 .setElapsedRealtimeNanos(ARBITRARY_ELAPSED_REALTIME_NANOS);
         assertRoundTripParcelable(builder.build());
diff --git a/services/tests/servicestests/src/com/android/server/VibratorServiceTest.java b/services/tests/servicestests/src/com/android/server/VibratorServiceTest.java
index c692253..b7a36f2 100644
--- a/services/tests/servicestests/src/com/android/server/VibratorServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/VibratorServiceTest.java
@@ -21,12 +21,13 @@
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
 import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.anyBoolean;
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.anyLong;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.ArgumentMatchers.intThat;
+import static org.mockito.ArgumentMatchers.isNull;
 import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.inOrder;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.times;
@@ -65,6 +66,7 @@
 import org.junit.Rule;
 import org.junit.Test;
 import org.mockito.ArgumentCaptor;
+import org.mockito.InOrder;
 import org.mockito.Mock;
 import org.mockito.Mockito;
 import org.mockito.junit.MockitoJUnit;
@@ -183,13 +185,12 @@
 
     @Test
     public void hasAmplitudeControl_withAmplitudeControlSupport_returnsTrue() {
-        when(mNativeWrapperMock.vibratorSupportsAmplitudeControl()).thenReturn(true);
+        mockVibratorCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL);
         assertTrue(createService().hasAmplitudeControl());
     }
 
     @Test
     public void hasAmplitudeControl_withNoAmplitudeControlSupport_returnsFalse() {
-        when(mNativeWrapperMock.vibratorSupportsAmplitudeControl()).thenReturn(false);
         assertFalse(createService().hasAmplitudeControl());
     }
 
@@ -222,9 +223,24 @@
     }
 
     @Test
-    public void arePrimitivesSupported_withComposeCapability_returnsAlwaysTrue() {
+    public void arePrimitivesSupported_withNullResultFromNative_returnsAlwaysFalse() {
         mockVibratorCapabilities(IVibrator.CAP_COMPOSE_EFFECTS);
-        assertArrayEquals(new boolean[]{true, true},
+        when(mNativeWrapperMock.vibratorGetSupportedPrimitives()).thenReturn(null);
+
+        assertArrayEquals(new boolean[]{false, false},
+                createService().arePrimitivesSupported(new int[]{
+                        VibrationEffect.Composition.PRIMITIVE_CLICK,
+                        VibrationEffect.Composition.PRIMITIVE_QUICK_RISE
+                }));
+    }
+
+    @Test
+    public void arePrimitivesSupported_withSomeSupportedPrimitives_returnsBasedOnNativeResult() {
+        mockVibratorCapabilities(IVibrator.CAP_COMPOSE_EFFECTS);
+        when(mNativeWrapperMock.vibratorGetSupportedPrimitives())
+                .thenReturn(new int[]{VibrationEffect.Composition.PRIMITIVE_CLICK});
+
+        assertArrayEquals(new boolean[]{true, false},
                 createService().arePrimitivesSupported(new int[]{
                         VibrationEffect.Composition.PRIMITIVE_CLICK,
                         VibrationEffect.Composition.PRIMITIVE_QUICK_RISE
@@ -270,7 +286,7 @@
 
     @Test
     public void vibrate_withOneShotAndAmplitudeControl_turnsVibratorOnAndSetsAmplitude() {
-        when(mNativeWrapperMock.vibratorSupportsAmplitudeControl()).thenReturn(true);
+        mockVibratorCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL);
         VibratorService service = createService();
         Mockito.clearInvocations(mNativeWrapperMock);
 
@@ -278,7 +294,7 @@
         assertTrue(service.isVibrating());
 
         verify(mNativeWrapperMock).vibratorOff();
-        verify(mNativeWrapperMock).vibratorOn(eq(100L));
+        verify(mNativeWrapperMock).vibratorOn(eq(100L), any(VibratorService.Vibration.class));
         verify(mNativeWrapperMock).vibratorSetAmplitude(eq(128));
     }
 
@@ -291,7 +307,7 @@
         assertTrue(service.isVibrating());
 
         verify(mNativeWrapperMock).vibratorOff();
-        verify(mNativeWrapperMock).vibratorOn(eq(100L));
+        verify(mNativeWrapperMock).vibratorOn(eq(100L), any(VibratorService.Vibration.class));
         verify(mNativeWrapperMock, never()).vibratorSetAmplitude(anyInt());
     }
 
@@ -308,7 +324,7 @@
         verify(mNativeWrapperMock).vibratorPerformEffect(
                 eq((long) VibrationEffect.EFFECT_CLICK),
                 eq((long) VibrationEffect.EFFECT_STRENGTH_STRONG),
-                any(VibratorService.Vibration.class), eq(false));
+                any(VibratorService.Vibration.class));
     }
 
     @Test
@@ -340,81 +356,143 @@
     @Test
     public void vibrate_withWaveform_controlsVibratorAmplitudeDuringTotalVibrationTime()
             throws Exception {
-        when(mNativeWrapperMock.vibratorSupportsAmplitudeControl()).thenReturn(true);
+        mockVibratorCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL);
         VibratorService service = createService();
         Mockito.clearInvocations(mNativeWrapperMock);
 
         VibrationEffect effect = VibrationEffect.createWaveform(
-                new long[] { 10, 10, 10 }, new int[] { 100, 200, 50 }, -1);
+                new long[]{10, 10, 10}, new int[]{100, 200, 50}, -1);
         vibrate(service, effect);
 
         verify(mNativeWrapperMock).vibratorOff();
 
+        // Wait for VibrateThread to turn vibrator ON with total timing and no callback.
         Thread.sleep(5);
-        verify(mNativeWrapperMock).vibratorOn(eq(30L));
+        verify(mNativeWrapperMock).vibratorOn(eq(30L), isNull());
+
+        // First amplitude set right away.
         verify(mNativeWrapperMock).vibratorSetAmplitude(eq(100));
 
+        // Second amplitude set after first timing is finished.
         Thread.sleep(10);
         verify(mNativeWrapperMock).vibratorSetAmplitude(eq(200));
 
+        // Third amplitude set after second timing is finished.
         Thread.sleep(10);
         verify(mNativeWrapperMock).vibratorSetAmplitude(eq(50));
     }
 
     @Test
-    public void vibrate_withCallback_finishesVibrationWhenCallbackTriggered() {
-        mockVibratorCapabilities(IVibrator.CAP_COMPOSE_EFFECTS);
+    public void vibrate_withOneShotAndNativeCallbackTriggered_finishesVibration() {
+        doAnswer(invocation -> {
+            ((VibratorService.Vibration) invocation.getArgument(1)).onComplete();
+            return null;
+        }).when(mNativeWrapperMock).vibratorOn(anyLong(), any(VibratorService.Vibration.class));
         VibratorService service = createService();
         Mockito.clearInvocations(mNativeWrapperMock);
 
+        vibrate(service, VibrationEffect.createOneShot(100, VibrationEffect.DEFAULT_AMPLITUDE));
+
+        InOrder inOrderVerifier = inOrder(mNativeWrapperMock);
+        inOrderVerifier.verify(mNativeWrapperMock).vibratorOff();
+        inOrderVerifier.verify(mNativeWrapperMock).vibratorOn(eq(100L),
+                any(VibratorService.Vibration.class));
+        inOrderVerifier.verify(mNativeWrapperMock).vibratorOff();
+    }
+
+    @Test
+    public void vibrate_withPrebakedAndNativeCallbackTriggered_finishesVibration() {
+        when(mNativeWrapperMock.vibratorGetSupportedEffects())
+                .thenReturn(new int[]{VibrationEffect.EFFECT_CLICK});
+        doAnswer(invocation -> {
+            ((VibratorService.Vibration) invocation.getArgument(2)).onComplete();
+            return 10_000L; // 10s
+        }).when(mNativeWrapperMock).vibratorPerformEffect(
+                anyLong(), anyLong(), any(VibratorService.Vibration.class));
+        VibratorService service = createService();
+        Mockito.clearInvocations(mNativeWrapperMock);
+
+        vibrate(service, VibrationEffect.createPredefined(VibrationEffect.EFFECT_CLICK));
+
+        InOrder inOrderVerifier = inOrder(mNativeWrapperMock);
+        inOrderVerifier.verify(mNativeWrapperMock).vibratorOff();
+        inOrderVerifier.verify(mNativeWrapperMock).vibratorPerformEffect(
+                eq((long) VibrationEffect.EFFECT_CLICK),
+                eq((long) VibrationEffect.EFFECT_STRENGTH_STRONG),
+                any(VibratorService.Vibration.class));
+        inOrderVerifier.verify(mNativeWrapperMock).vibratorOff();
+    }
+
+    @Test
+    public void vibrate_withWaveformAndNativeCallback_callbackCannotBeTriggeredByNative()
+            throws Exception {
+        VibratorService service = createService();
+        Mockito.clearInvocations(mNativeWrapperMock);
+
+        VibrationEffect effect = VibrationEffect.createWaveform(new long[]{1, 3, 1, 2}, -1);
+        vibrate(service, effect);
+
+        // Wait for VibrateThread to finish: 1ms OFF, 3ms ON, 1ms OFF, 2ms ON.
+        Thread.sleep(15);
+        InOrder inOrderVerifier = inOrder(mNativeWrapperMock);
+        inOrderVerifier.verify(mNativeWrapperMock).vibratorOff();
+        inOrderVerifier.verify(mNativeWrapperMock).vibratorOn(eq(3L), isNull());
+        inOrderVerifier.verify(mNativeWrapperMock).vibratorOn(eq(2L), isNull());
+        inOrderVerifier.verify(mNativeWrapperMock).vibratorOff();
+    }
+
+    @Test
+    public void vibrate_withComposedAndNativeCallbackTriggered_finishesVibration() {
+        mockVibratorCapabilities(IVibrator.CAP_COMPOSE_EFFECTS);
         doAnswer(invocation -> {
             ((VibratorService.Vibration) invocation.getArgument(1)).onComplete();
             return null;
         }).when(mNativeWrapperMock).vibratorPerformComposedEffect(
                 any(), any(VibratorService.Vibration.class));
+        VibratorService service = createService();
+        Mockito.clearInvocations(mNativeWrapperMock);
 
-        // Use vibration with delay so there is time for the callback to be triggered.
         VibrationEffect effect = VibrationEffect.startComposition()
                 .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 1f, 10)
                 .compose();
         vibrate(service, effect);
 
-        // Vibration canceled once before perform and once by native callback.
-        verify(mNativeWrapperMock, times(2)).vibratorOff();
-        verify(mNativeWrapperMock).vibratorPerformComposedEffect(
+        InOrder inOrderVerifier = inOrder(mNativeWrapperMock);
+        inOrderVerifier.verify(mNativeWrapperMock).vibratorOff();
+        inOrderVerifier.verify(mNativeWrapperMock).vibratorPerformComposedEffect(
                 any(VibrationEffect.Composition.PrimitiveEffect[].class),
                 any(VibratorService.Vibration.class));
+        inOrderVerifier.verify(mNativeWrapperMock).vibratorOff();
     }
 
     @Test
     public void vibrate_whenBinderDies_cancelsVibration() {
         mockVibratorCapabilities(IVibrator.CAP_COMPOSE_EFFECTS);
-        VibratorService service = createService();
-        Mockito.clearInvocations(mNativeWrapperMock);
-
         doAnswer(invocation -> {
             ((VibratorService.Vibration) invocation.getArgument(1)).binderDied();
             return null;
         }).when(mNativeWrapperMock).vibratorPerformComposedEffect(
                 any(), any(VibratorService.Vibration.class));
+        VibratorService service = createService();
+        Mockito.clearInvocations(mNativeWrapperMock);
 
-        // Use vibration with delay so there is time for the callback to be triggered.
         VibrationEffect effect = VibrationEffect.startComposition()
                 .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 1f, 10)
                 .compose();
         vibrate(service, effect);
 
-        // Vibration canceled once before perform and once by native binder death.
-        verify(mNativeWrapperMock, times(2)).vibratorOff();
-        verify(mNativeWrapperMock).vibratorPerformComposedEffect(
+        InOrder inOrderVerifier = inOrder(mNativeWrapperMock);
+        inOrderVerifier.verify(mNativeWrapperMock).vibratorOff();
+        inOrderVerifier.verify(mNativeWrapperMock).vibratorPerformComposedEffect(
                 any(VibrationEffect.Composition.PrimitiveEffect[].class),
                 any(VibratorService.Vibration.class));
+        inOrderVerifier.verify(mNativeWrapperMock).vibratorOff();
     }
 
     @Test
     public void cancelVibrate_withDeviceVibrating_callsVibratorOff() {
         VibratorService service = createService();
-        vibrate(service, VibrationEffect.createOneShot(100, 128));
+        vibrate(service, VibrationEffect.createOneShot(100, VibrationEffect.DEFAULT_AMPLITUDE));
         assertTrue(service.isVibrating());
         Mockito.clearInvocations(mNativeWrapperMock);
 
@@ -435,18 +513,20 @@
 
     @Test
     public void registerVibratorStateListener_callbacksAreTriggered() throws Exception {
+        doAnswer(invocation -> {
+            ((VibratorService.Vibration) invocation.getArgument(1)).onComplete();
+            return null;
+        }).when(mNativeWrapperMock).vibratorOn(anyLong(), any(VibratorService.Vibration.class));
         VibratorService service = createService();
 
         service.registerVibratorStateListener(mVibratorStateListenerMock);
         verify(mVibratorStateListenerMock).onVibrating(false);
+        Mockito.clearInvocations(mVibratorStateListenerMock);
 
         vibrate(service, VibrationEffect.createOneShot(10, VibrationEffect.DEFAULT_AMPLITUDE));
-        verify(mVibratorStateListenerMock).onVibrating(true);
-
-        // Run the scheduled callback to finish one-shot vibration.
-        mTestLooper.moveTimeForward(10);
-        mTestLooper.dispatchAll();
-        verify(mVibratorStateListenerMock, times(2)).onVibrating(false);
+        InOrder inOrderVerifier = inOrder(mVibratorStateListenerMock);
+        inOrderVerifier.verify(mVibratorStateListenerMock).onVibrating(eq(true));
+        inOrderVerifier.verify(mVibratorStateListenerMock).onVibrating(eq(false));
     }
 
     @Test
@@ -489,15 +569,15 @@
 
         verify(mNativeWrapperMock).vibratorPerformEffect(
                 eq((long) VibrationEffect.EFFECT_CLICK),
-                eq((long) VibrationEffect.EFFECT_STRENGTH_STRONG), any(), anyBoolean());
+                eq((long) VibrationEffect.EFFECT_STRENGTH_STRONG), any());
         verify(mNativeWrapperMock).vibratorPerformEffect(
                 eq((long) VibrationEffect.EFFECT_TICK),
-                eq((long) VibrationEffect.EFFECT_STRENGTH_MEDIUM), any(), anyBoolean());
+                eq((long) VibrationEffect.EFFECT_STRENGTH_MEDIUM), any());
         verify(mNativeWrapperMock).vibratorPerformEffect(
                 eq((long) VibrationEffect.EFFECT_DOUBLE_CLICK),
-                eq((long) VibrationEffect.EFFECT_STRENGTH_LIGHT), any(), anyBoolean());
+                eq((long) VibrationEffect.EFFECT_STRENGTH_LIGHT), any());
         verify(mNativeWrapperMock, never()).vibratorPerformEffect(
-                eq((long) VibrationEffect.EFFECT_HEAVY_CLICK), anyLong(), any(), anyBoolean());
+                eq((long) VibrationEffect.EFFECT_HEAVY_CLICK), anyLong(), any());
     }
 
     @Test
@@ -511,7 +591,7 @@
         setVibrationIntensityUserSetting(Settings.System.RING_VIBRATION_INTENSITY,
                 Vibrator.VIBRATION_INTENSITY_OFF);
 
-        when(mNativeWrapperMock.vibratorSupportsAmplitudeControl()).thenReturn(true);
+        mockVibratorCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL);
         VibratorService service = createService();
         service.systemReady();
 
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/gestures/TouchExplorerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/gestures/TouchExplorerTest.java
index 6450a0f..763654d 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/gestures/TouchExplorerTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/gestures/TouchExplorerTest.java
@@ -42,6 +42,7 @@
 import android.testing.DexmakerShareClassLoaderRule;
 import android.view.InputDevice;
 import android.view.MotionEvent;
+import android.view.ViewConfiguration;
 import android.view.accessibility.AccessibilityEvent;
 
 import androidx.test.InstrumentationRegistry;
@@ -87,6 +88,8 @@
     private MotionEvent mLastEvent;
     private TestHandler mHandler;
     private TouchExplorer mTouchExplorer;
+    private Context mContext;
+    private int mTouchSlop;
     private long mLastDownTime = Integer.MIN_VALUE;
 
     // mock package-private GestureManifold class
@@ -121,12 +124,13 @@
         if (Looper.myLooper() == null) {
             Looper.prepare();
         }
-        Context context = InstrumentationRegistry.getContext();
-        AccessibilityManagerService ams = new AccessibilityManagerService(context);
+        mContext = InstrumentationRegistry.getContext();
+        mTouchSlop = ViewConfiguration.get(mContext).getScaledTouchSlop();
+        AccessibilityManagerService ams = new AccessibilityManagerService(mContext);
         GestureManifold detector = mock(GestureManifold.class);
         mCaptor = new EventCaptor();
         mHandler = new TestHandler();
-        mTouchExplorer = new TouchExplorer(context, ams, detector, mHandler);
+        mTouchExplorer = new TouchExplorer(mContext, ams, detector, mHandler);
         mTouchExplorer.setNext(mCaptor);
     }
 
@@ -354,12 +358,12 @@
                     break;
                 case STATE_DRAGGING_2FINGERS:
                     goFromStateClearTo(STATE_TOUCH_EXPLORING_2FINGER);
-                    moveEachPointers(mLastEvent, p(10, 0), p(10, 0));
+                    moveEachPointers(mLastEvent, p(mTouchSlop, 0), p(mTouchSlop, 0));
                     send(mLastEvent);
                     break;
                 case STATE_PINCH_2FINGERS:
                     goFromStateClearTo(STATE_DRAGGING_2FINGERS);
-                    moveEachPointers(mLastEvent, p(10, 0), p(-10, 1));
+                    moveEachPointers(mLastEvent, p(mTouchSlop, 0), p(-mTouchSlop, 1));
                     send(mLastEvent);
                     break;
                 case STATE_MOVING_3FINGERS:
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationControllerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationControllerTest.java
index a9f2e4a..57bfbf3 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationControllerTest.java
@@ -27,6 +27,7 @@
 import static org.mockito.Matchers.eq;
 import static org.mockito.Mockito.doAnswer;
 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;
@@ -95,12 +96,15 @@
 
     ValueAnimator mMockValueAnimator;
     ValueAnimator.AnimatorUpdateListener mTargetAnimationListener;
+    ValueAnimator.AnimatorListener mStateListener;
 
     FullScreenMagnificationController mFullScreenMagnificationController;
+    Runnable mEndCallback;
 
     @Before
     public void setUp() {
         Looper looper = InstrumentationRegistry.getContext().getMainLooper();
+        mEndCallback = Mockito.mock(Runnable.class);
         // Pretending ID of the Thread associated with looper as main thread ID in controller
         when(mMockContext.getMainLooper()).thenReturn(looper);
         when(mMockControllerCtx.getContext()).thenReturn(mMockContext);
@@ -319,6 +323,7 @@
         for (int i = 0; i < DISPLAY_COUNT; i++) {
             setScaleAndCenter_animated_stateChangesAndAnimationHappens(i);
             resetMockWindowManager();
+            Mockito.reset(mEndCallback);
         }
     }
 
@@ -331,7 +336,7 @@
         MagnificationSpec endSpec = getMagnificationSpec(scale, offsets);
 
         assertTrue(mFullScreenMagnificationController.setScaleAndCenter(displayId, scale,
-                newCenter.x, newCenter.y, true, SERVICE_ID_1));
+                newCenter.x, newCenter.y, mEndCallback, SERVICE_ID_1));
         mMessageCapturingHandler.sendAllMessages();
 
         assertEquals(newCenter.x, mFullScreenMagnificationController.getCenterX(displayId), 0.5);
@@ -358,7 +363,33 @@
         Mockito.reset(mMockWindowManager);
         when(mMockValueAnimator.getAnimatedFraction()).thenReturn(1.0f);
         mTargetAnimationListener.onAnimationUpdate(mMockValueAnimator);
+        mStateListener.onAnimationEnd(mMockValueAnimator);
         verify(mMockWindowManager).setMagnificationSpec(eq(displayId), argThat(closeTo(endSpec)));
+        verify(mEndCallback).run();
+    }
+
+    @Test
+    public void testSetScaleAndCenterWithAnimation_sameSpec_noAnimationButInvokeEndCallback() {
+        for (int i = 0; i < DISPLAY_COUNT; i++) {
+            setScaleAndCenter_sameSpec_noAnimationButInvokeEndCallback(i);
+            Mockito.reset(mEndCallback);
+        }
+    }
+
+    private void setScaleAndCenter_sameSpec_noAnimationButInvokeEndCallback(int displayId) {
+        register(displayId);
+        final PointF center = INITIAL_BOUNDS_LOWER_RIGHT_2X_CENTER;
+        final float targetScale = 2.0f;
+        assertTrue(mFullScreenMagnificationController.setScaleAndCenter(displayId,
+                targetScale, center.x, center.y, false, SERVICE_ID_1));
+        mMessageCapturingHandler.sendAllMessages();
+
+        assertFalse(mFullScreenMagnificationController.setScaleAndCenter(displayId,
+                targetScale, center.x, center.y, mEndCallback, SERVICE_ID_1));
+        mMessageCapturingHandler.sendAllMessages();
+
+        verify(mMockValueAnimator, never()).start();
+        verify(mEndCallback).run();
     }
 
     @Test
@@ -639,6 +670,69 @@
     }
 
     @Test
+    public void testReset_notMagnifying_noStateChangeButInvokeCallback() {
+        for (int i = 0; i < DISPLAY_COUNT; i++) {
+            reset_notMagnifying_noStateChangeButInvokeCallback(i);
+            Mockito.reset(mEndCallback);
+        }
+    }
+
+    private void reset_notMagnifying_noStateChangeButInvokeCallback(int displayId) {
+        register(displayId);
+
+        assertFalse(mFullScreenMagnificationController.reset(displayId, mEndCallback));
+        mMessageCapturingHandler.sendAllMessages();
+
+        verify(mMockAms, never()).notifyMagnificationChanged(eq(displayId),
+                any(Region.class), anyFloat(), anyFloat(), anyFloat());
+        verify(mEndCallback).run();
+    }
+
+    @Test
+    public void testReset_Magnifying_resetsMagnificationAndInvokeLastEndCallback() {
+        for (int i = 0; i < DISPLAY_COUNT; i++) {
+            reset_Magnifying_resetsMagnificationAndInvokeLastEndCallback(i);
+        }
+    }
+
+    private void reset_Magnifying_resetsMagnificationAndInvokeLastEndCallback(int displayId) {
+        register(displayId);
+        float scale = 2.5f;
+        PointF firstCenter = INITIAL_BOUNDS_LOWER_RIGHT_2X_CENTER;
+        assertTrue(mFullScreenMagnificationController.setScaleAndCenter(displayId,
+                scale, firstCenter.x, firstCenter.y, mEndCallback, SERVICE_ID_1));
+        mMessageCapturingHandler.sendAllMessages();
+        Mockito.reset(mMockValueAnimator);
+        // Stubs the logic after the animation is started.
+        doAnswer(invocation -> {
+            mStateListener.onAnimationEnd(mMockValueAnimator);
+            return null;
+        }).when(mMockValueAnimator).cancel();
+        when(mMockValueAnimator.isRunning()).thenReturn(true);
+        // Intermediate point
+        float fraction = 0.33f;
+        when(mMockValueAnimator.getAnimatedFraction()).thenReturn(fraction);
+        mTargetAnimationListener.onAnimationUpdate(mMockValueAnimator);
+        Runnable lastEndCallback = Mockito.mock(Runnable.class);
+
+        assertTrue(mFullScreenMagnificationController.reset(displayId, lastEndCallback));
+        mMessageCapturingHandler.sendAllMessages();
+
+        // Verify expected actions.
+        verify(mEndCallback, never()).run();
+        verify(mMockValueAnimator).start();
+        verify(mMockValueAnimator).cancel();
+
+        // Fast-forward the animation to the end.
+        when(mMockValueAnimator.getAnimatedFraction()).thenReturn(1.0f);
+        mTargetAnimationListener.onAnimationUpdate(mMockValueAnimator);
+        mStateListener.onAnimationEnd(mMockValueAnimator);
+
+        assertFalse(mFullScreenMagnificationController.isMagnifying(DISPLAY_0));
+        verify(lastEndCallback).run();
+    }
+
+    @Test
     public void testTurnScreenOff_resetsMagnification() {
         register(DISPLAY_0);
         register(DISPLAY_1);
@@ -1043,6 +1137,10 @@
                 ArgumentCaptor.forClass(ValueAnimator.AnimatorUpdateListener.class);
         verify(mMockValueAnimator).addUpdateListener(listenerArgumentCaptor.capture());
         mTargetAnimationListener = listenerArgumentCaptor.getValue();
+        ArgumentCaptor<ValueAnimator.AnimatorListener> animatorListenerArgumentCaptor =
+                ArgumentCaptor.forClass(ValueAnimator.AnimatorListener.class);
+        verify(mMockValueAnimator).addListener(animatorListenerArgumentCaptor.capture());
+        mStateListener = animatorListenerArgumentCaptor.getValue();
         Mockito.reset(mMockValueAnimator); // Ignore other initialization
     }
 
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/magnification/MockWindowMagnificationConnection.java b/services/tests/servicestests/src/com/android/server/accessibility/magnification/MockWindowMagnificationConnection.java
index c29c510..42ba842f 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/magnification/MockWindowMagnificationConnection.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/magnification/MockWindowMagnificationConnection.java
@@ -17,23 +17,33 @@
 package com.android.server.accessibility.magnification;
 
 import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyFloat;
+import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.doAnswer;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.when;
 
+import android.graphics.Rect;
 import android.os.Binder;
 import android.os.IBinder;
 import android.os.RemoteException;
+import android.view.Display;
 import android.view.accessibility.IWindowMagnificationConnection;
 import android.view.accessibility.IWindowMagnificationConnectionCallback;
 
+/**
+ * Mocks the basic logic of window magnification in System UI. We assume the screen size is
+ * unlimited, so source bounds is always on the center of the mirror window bounds.
+ */
 class MockWindowMagnificationConnection  {
 
+    public static final int TEST_DISPLAY = Display.DEFAULT_DISPLAY;
     private final IWindowMagnificationConnection mConnection;
     private final Binder mBinder;
     private IBinder.DeathRecipient mDeathRecipient;
     private IWindowMagnificationConnectionCallback mIMirrorWindowCallback;
+    private Rect mMirrorWindowFrame = new Rect(0, 0, 500, 500);
 
     MockWindowMagnificationConnection() throws RemoteException {
         mConnection = mock(IWindowMagnificationConnection.class);
@@ -50,6 +60,30 @@
             return null;
         }).when(mBinder).linkToDeath(
                 any(IBinder.DeathRecipient.class), eq(0));
+        stubConnection();
+    }
+
+    private void stubConnection() throws RemoteException {
+        doAnswer((invocation) -> {
+            final int displayId = invocation.getArgument(0);
+            if (displayId != TEST_DISPLAY) {
+                throw new IllegalArgumentException("only support default display :" + displayId);
+            }
+            computeMirrorWindowFrame(invocation.getArgument(1), invocation.getArgument(2));
+
+            mIMirrorWindowCallback.onWindowMagnifierBoundsChanged(TEST_DISPLAY,
+                    mMirrorWindowFrame);
+            return null;
+        }).when(mConnection).enableWindowMagnification(anyInt(),
+                anyFloat(), anyFloat(), anyFloat());
+    }
+
+    private void computeMirrorWindowFrame(float centerX, float centerY) {
+        final float offsetX = Float.isNaN(centerX) ? 0
+                : centerX - mMirrorWindowFrame.exactCenterX();
+        final float offsetY = Float.isNaN(centerY) ? 0
+                : centerY - mMirrorWindowFrame.exactCenterY();
+        mMirrorWindowFrame.offset((int) offsetX, (int) offsetY);
     }
 
     IWindowMagnificationConnection getConnection() {
@@ -60,12 +94,16 @@
         return mBinder;
     }
 
-    public IBinder.DeathRecipient getDeathRecipient() {
+    IBinder.DeathRecipient getDeathRecipient() {
         return mDeathRecipient;
     }
 
-    public IWindowMagnificationConnectionCallback getConnectionCallback() {
+    IWindowMagnificationConnectionCallback getConnectionCallback() {
         return mIMirrorWindowCallback;
     }
+
+    public Rect getMirrorWindowFrame() {
+        return new Rect(mMirrorWindowFrame);
+    }
 }
 
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/magnification/TwoFingersDownTest.java b/services/tests/servicestests/src/com/android/server/accessibility/magnification/TwoFingersDownTest.java
index 2b1bdc5..ed8dc4e 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/magnification/TwoFingersDownTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/magnification/TwoFingersDownTest.java
@@ -87,15 +87,15 @@
         secondPointerCoords.x = DEFAULT_X + 10;
         secondPointerCoords.y = DEFAULT_Y + 10;
 
-        final MotionEvent pointerDownEvent = TouchEventGenerator.pointerDownEvent(
+        final MotionEvent twoPointersDownEvent = TouchEventGenerator.twoPointersDownEvent(
                 Display.DEFAULT_DISPLAY, defPointerCoords, secondPointerCoords);
 
         mGesturesObserver.onMotionEvent(downEvent, downEvent, 0);
-        mGesturesObserver.onMotionEvent(pointerDownEvent, pointerDownEvent, 0);
+        mGesturesObserver.onMotionEvent(twoPointersDownEvent, twoPointersDownEvent, 0);
 
         verify(mListener, timeout(sTimeoutMillis)).onGestureCompleted(
-                MagnificationGestureMatcher.GESTURE_TWO_FINGER_DOWN, pointerDownEvent,
-                pointerDownEvent, 0);
+                MagnificationGestureMatcher.GESTURE_TWO_FINGER_DOWN, twoPointersDownEvent,
+                twoPointersDownEvent, 0);
     }
 
     @Test
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/magnification/WindowMagnificationGestureHandlerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/magnification/WindowMagnificationGestureHandlerTest.java
index e580340..bec9f26 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/magnification/WindowMagnificationGestureHandlerTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/magnification/WindowMagnificationGestureHandlerTest.java
@@ -16,14 +16,9 @@
 
 package com.android.server.accessibility.magnification;
 
-import static android.view.MotionEvent.ACTION_POINTER_DOWN;
-
 import static com.android.server.testutils.TestUtils.strictMock;
 
 import static org.junit.Assert.fail;
-import static org.mockito.ArgumentMatchers.anyFloat;
-import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Mockito.doAnswer;
 import static org.mockito.Mockito.mock;
 
 import android.content.Context;
@@ -63,11 +58,9 @@
     public static final int LAST_STATE = STATE_SHOW_MAGNIFIER_TRIPLE_TAP;
 
     // Co-prime x and y, to potentially catch x-y-swapped errors
-    public static final float DEFAULT_X = 301;
-    public static final float DEFAULT_Y = 299;
-    //Assume first pointer position (DEFAULT_X,DEFAULT_Y) is in the window.
-    public static Rect DEFAULT_WINDOW_FRAME = new Rect(0, 0, 500, 500);
-    private static final int DISPLAY_0 = 0;
+    public static final float DEFAULT_TAP_X = 301;
+    public static final float DEFAULT_TAP_Y = 299;
+    private static final int DISPLAY_0 = MockWindowMagnificationConnection.TEST_DISPLAY;
 
     private Context mContext;
     private WindowMagnificationManager mWindowMagnificationManager;
@@ -83,14 +76,6 @@
                 mContext, mWindowMagnificationManager, mock(ScaleChangedListener.class),
                 /** detectTripleTap= */true,   /** detectShortcutTrigger= */true, DISPLAY_0);
         mWindowMagnificationManager.setConnection(mMockConnection.getConnection());
-        mMockConnection.getConnectionCallback().onWindowMagnifierBoundsChanged(DISPLAY_0,
-                DEFAULT_WINDOW_FRAME);
-        doAnswer((invocation) -> {
-            mMockConnection.getConnectionCallback().onWindowMagnifierBoundsChanged(DISPLAY_0,
-                    DEFAULT_WINDOW_FRAME);
-            return null;
-        }).when(mMockConnection.getConnection()).enableWindowMagnification(eq(DISPLAY_0),
-                anyFloat(), anyFloat(), anyFloat());
         mWindowMagnificationGestureHandler.setNext(strictMock(EventStreamTransformation.class));
     }
 
@@ -208,10 +193,11 @@
                 break;
                 case STATE_TWO_FINGERS_DOWN: {
                     goFromStateIdleTo(STATE_SHOW_MAGNIFIER);
-                    send(downEvent());
+                    final Rect frame = mMockConnection.getMirrorWindowFrame();
+                    send(downEvent(frame.centerX(), frame.centerY()));
                     //Second finger is outside the window.
-                    send(pointerEvent(ACTION_POINTER_DOWN, DEFAULT_WINDOW_FRAME.right + 10,
-                            DEFAULT_WINDOW_FRAME.bottom + 10));
+                    send(twoPointerDownEvent(new float[]{frame.centerX(), frame.centerX() + 10},
+                            new float[]{frame.centerY(), frame.centerY() + 10}));
                 }
                 break;
                 case STATE_SHOW_MAGNIFIER_TRIPLE_TAP: {
@@ -243,7 +229,8 @@
             }
             break;
             case STATE_TWO_FINGERS_DOWN: {
-                send(upEvent());
+                final Rect frame = mMockConnection.getMirrorWindowFrame();
+                send(upEvent(frame.centerX(), frame.centerY()));
                 returnToNormalFrom(STATE_SHOW_MAGNIFIER);
             }
             break;
@@ -286,12 +273,8 @@
         }
     }
 
-    private MotionEvent downEvent() {
-        return TouchEventGenerator.downEvent(DISPLAY_0, DEFAULT_X, DEFAULT_Y);
-    }
-
-    private MotionEvent upEvent() {
-        return upEvent(DEFAULT_X, DEFAULT_Y);
+    private MotionEvent downEvent(float x, float y) {
+        return TouchEventGenerator.downEvent(DISPLAY_0, x, y);
     }
 
     private MotionEvent upEvent(float x, float y) {
@@ -299,18 +282,18 @@
     }
 
     private void tap() {
-        send(downEvent());
-        send(upEvent());
+        send(downEvent(DEFAULT_TAP_X, DEFAULT_TAP_Y));
+        send(upEvent(DEFAULT_TAP_X, DEFAULT_TAP_Y));
     }
 
-    private MotionEvent pointerEvent(int action, float x, float y) {
+    private MotionEvent twoPointerDownEvent(float[] x, float[] y) {
         final MotionEvent.PointerCoords defPointerCoords = new MotionEvent.PointerCoords();
-        defPointerCoords.x = DEFAULT_X;
-        defPointerCoords.y = DEFAULT_Y;
+        defPointerCoords.x = x[0];
+        defPointerCoords.y = y[0];
         final MotionEvent.PointerCoords pointerCoords = new MotionEvent.PointerCoords();
-        pointerCoords.x = x;
-        pointerCoords.y = y;
-        return TouchEventGenerator.pointerDownEvent(DISPLAY_0, defPointerCoords, pointerCoords);
+        pointerCoords.x = x[1];
+        pointerCoords.y = y[1];
+        return TouchEventGenerator.twoPointersDownEvent(DISPLAY_0, defPointerCoords, pointerCoords);
     }
 
     private String stateDump() {
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/utils/TouchEventGenerator.java b/services/tests/servicestests/src/com/android/server/accessibility/utils/TouchEventGenerator.java
index 7cbf3ee..a05881f 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/utils/TouchEventGenerator.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/utils/TouchEventGenerator.java
@@ -43,9 +43,9 @@
         return generateSingleTouchEvent(displayId, ACTION_UP, x, y);
     }
 
-    public static MotionEvent pointerDownEvent(int displayId, PointerCoords defPointerCoords,
+    public static MotionEvent twoPointersDownEvent(int displayId, PointerCoords defPointerCoords,
             PointerCoords pointerCoords) {
-        return generatePointerEvent(displayId, ACTION_POINTER_DOWN, defPointerCoords,
+        return generateTwoPointersEvent(displayId, ACTION_POINTER_DOWN, defPointerCoords,
                 pointerCoords);
     }
 
@@ -59,7 +59,7 @@
         return ev;
     }
 
-    private static MotionEvent generatePointerEvent(int displayId, int action,
+    private static MotionEvent generateTwoPointersEvent(int displayId, int action,
             PointerCoords defPointerCoords, PointerCoords pointerCoords) {
         final long  downTime = SystemClock.uptimeMillis();
         MotionEvent.PointerProperties defPointerProperties = new MotionEvent.PointerProperties();
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 6a797f3..03dce4c 100644
--- a/services/tests/servicestests/src/com/android/server/am/ActivityManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/am/ActivityManagerServiceTest.java
@@ -32,7 +32,6 @@
 import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
 
 import static com.android.server.am.ActivityManagerInternalTest.CustomThread;
-import static com.android.server.am.ActivityManagerService.DISPATCH_UIDS_CHANGED_UI_MSG;
 import static com.android.server.am.ActivityManagerService.Injector;
 import static com.android.server.am.ProcessList.NETWORK_STATE_BLOCK;
 import static com.android.server.am.ProcessList.NETWORK_STATE_NO_CHANGE;
@@ -548,10 +547,10 @@
             pendingChange.processState = procStatesForPendingUidRecords[i];
             pendingChange.procStateSeq = i;
             changeItems.put(changesForPendingUidRecords[i], pendingChange);
-            mAms.mPendingUidChanges.add(pendingChange);
+            mAms.mUidObserverController.mPendingUidChanges.add(pendingChange);
         }
 
-        mAms.dispatchUidsChanged();
+        mAms.mUidObserverController.dispatchUidsChanged();
         // Verify the required changes have been dispatched to observers.
         for (int i = 0; i < observers.length; ++i) {
             final int changeToObserve = changesToObserve[i];
@@ -647,8 +646,8 @@
         changeItem.change = UidRecord.CHANGE_PROCSTATE;
         changeItem.processState = ActivityManager.PROCESS_STATE_LAST_ACTIVITY;
         changeItem.procStateSeq = 111;
-        mAms.mPendingUidChanges.add(changeItem);
-        mAms.dispatchUidsChanged();
+        mAms.mUidObserverController.mPendingUidChanges.add(changeItem);
+        mAms.mUidObserverController.dispatchUidsChanged();
         // First process state message is always delivered regardless of whether the process state
         // change is above or below the cutpoint (PROCESS_STATE_SERVICE).
         verify(observer).onUidStateChanged(TEST_UID,
@@ -657,15 +656,15 @@
         verifyNoMoreInteractions(observer);
 
         changeItem.processState = ActivityManager.PROCESS_STATE_RECEIVER;
-        mAms.mPendingUidChanges.add(changeItem);
-        mAms.dispatchUidsChanged();
+        mAms.mUidObserverController.mPendingUidChanges.add(changeItem);
+        mAms.mUidObserverController.dispatchUidsChanged();
         // Previous process state change is below cutpoint (PROCESS_STATE_SERVICE) and
         // the current process state change is also below cutpoint, so no callback will be invoked.
         verifyNoMoreInteractions(observer);
 
         changeItem.processState = ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE;
-        mAms.mPendingUidChanges.add(changeItem);
-        mAms.dispatchUidsChanged();
+        mAms.mUidObserverController.mPendingUidChanges.add(changeItem);
+        mAms.mUidObserverController.dispatchUidsChanged();
         // Previous process state change is below cutpoint (PROCESS_STATE_SERVICE) and
         // the current process state change is above cutpoint, so callback will be invoked with the
         // current process state change.
@@ -675,15 +674,15 @@
         verifyNoMoreInteractions(observer);
 
         changeItem.processState = ActivityManager.PROCESS_STATE_TOP;
-        mAms.mPendingUidChanges.add(changeItem);
-        mAms.dispatchUidsChanged();
+        mAms.mUidObserverController.mPendingUidChanges.add(changeItem);
+        mAms.mUidObserverController.dispatchUidsChanged();
         // Previous process state change is above cutpoint (PROCESS_STATE_SERVICE) and
         // the current process state change is also above cutpoint, so no callback will be invoked.
         verifyNoMoreInteractions(observer);
 
         changeItem.processState = ActivityManager.PROCESS_STATE_CACHED_EMPTY;
-        mAms.mPendingUidChanges.add(changeItem);
-        mAms.dispatchUidsChanged();
+        mAms.mUidObserverController.mPendingUidChanges.add(changeItem);
+        mAms.mUidObserverController.dispatchUidsChanged();
         // Previous process state change is above cutpoint (PROCESS_STATE_SERVICE) and
         // the current process state change is below cutpoint, so callback will be invoked with the
         // current process state change.
@@ -720,20 +719,21 @@
 
         // Verify that when there no observers listening to uid state changes, then there will
         // be no changes to validateUids.
-        mAms.mPendingUidChanges.addAll(pendingItemsForUids);
-        mAms.dispatchUidsChanged();
+        mAms.mUidObserverController.mPendingUidChanges.addAll(pendingItemsForUids);
+        mAms.mUidObserverController.dispatchUidsChanged();
         assertEquals("No observers registered, so validateUids should be empty",
-                0, mAms.mValidateUids.size());
+                0, mAms.mUidObserverController.mValidateUids.size());
 
         final IUidObserver observer = mock(IUidObserver.Stub.class);
         when(observer.asBinder()).thenReturn((IBinder) observer);
         mAms.registerUidObserver(observer, 0, 0, null);
         // Verify that when observers are registered, then validateUids is correctly updated.
-        mAms.mPendingUidChanges.addAll(pendingItemsForUids);
-        mAms.dispatchUidsChanged();
+        mAms.mUidObserverController.mPendingUidChanges.addAll(pendingItemsForUids);
+        mAms.mUidObserverController.dispatchUidsChanged();
         for (int i = 0; i < pendingItemsForUids.size(); ++i) {
             final UidRecord.ChangeItem item = pendingItemsForUids.get(i);
-            final UidRecord validateUidRecord = mAms.mValidateUids.get(item.uid);
+            final UidRecord validateUidRecord =
+                    mAms.mUidObserverController.mValidateUids.get(item.uid);
             if ((item.change & UidRecord.CHANGE_GONE) != 0) {
                 assertNull("validateUidRecord should be null since the change is either "
                         + "CHANGE_GONE or CHANGE_GONE_IDLE", validateUidRecord);
@@ -759,7 +759,7 @@
         // Verify that when uid state changes to CHANGE_GONE or CHANGE_GONE_IDLE, then it
         // will be removed from validateUids.
         assertNotEquals("validateUids should not be empty", 0,
-                mAms.mValidateUids.size());
+                mAms.mUidObserverController.mValidateUids.size());
         for (int i = 0; i < pendingItemsForUids.size(); ++i) {
             final UidRecord.ChangeItem item = pendingItemsForUids.get(i);
             // Assign CHANGE_GONE_IDLE to some items and CHANGE_GONE to the others, using even/odd
@@ -767,10 +767,11 @@
             item.change = (i % 2) == 0 ? (UidRecord.CHANGE_GONE | UidRecord.CHANGE_IDLE)
                     : UidRecord.CHANGE_GONE;
         }
-        mAms.mPendingUidChanges.addAll(pendingItemsForUids);
-        mAms.dispatchUidsChanged();
-        assertEquals("validateUids should be empty, size=" + mAms.mValidateUids.size(),
-                0, mAms.mValidateUids.size());
+        mAms.mUidObserverController.mPendingUidChanges.addAll(pendingItemsForUids);
+        mAms.mUidObserverController.dispatchUidsChanged();
+        assertEquals("validateUids should be empty, size="
+                + mAms.mUidObserverController.mValidateUids.size(),
+                        0, mAms.mUidObserverController.mValidateUids.size());
     }
 
     @Test
@@ -792,7 +793,7 @@
     @Test
     public void testEnqueueUidChangeLocked_nullUidRecord() {
         // Use "null" uidRecord to make sure there is no crash.
-        mAms.enqueueUidChangeLocked(null, TEST_UID, UidRecord.CHANGE_ACTIVE);
+        mAms.mUidObserverController.enqueueUidChangeLocked(null, TEST_UID, UidRecord.CHANGE_ACTIVE);
     }
 
     private void verifyLastProcStateSeqUpdated(UidRecord uidRecord, int uid, long curProcstateSeq) {
@@ -801,7 +802,7 @@
             final int changeToDispatch = UID_RECORD_CHANGES[i];
             // Reset lastProcStateSeqDispatchToObservers after every test.
             uidRecord.lastDispatchedProcStateSeq = 0;
-            mAms.enqueueUidChangeLocked(uidRecord, uid, changeToDispatch);
+            mAms.mUidObserverController.enqueueUidChangeLocked(uidRecord, uid, changeToDispatch);
             // Verify there is no effect on curProcStateSeq.
             assertEquals(curProcstateSeq, uidRecord.curProcStateSeq);
             if ((changeToDispatch & UidRecord.CHANGE_GONE) != 0) {
@@ -833,9 +834,9 @@
             // Reset the current state
             mHandler.reset();
             uidRecord.pendingChange = null;
-            mAms.mPendingUidChanges.clear();
+            mAms.mUidObserverController.mPendingUidChanges.clear();
 
-            mAms.enqueueUidChangeLocked(uidRecord, -1, changeToDispatch);
+            mAms.mUidObserverController.enqueueUidChangeLocked(uidRecord, -1, changeToDispatch);
 
             // Verify that UidRecord.pendingChange is updated correctly.
             assertNotNull(uidRecord.pendingChange);
@@ -843,8 +844,7 @@
             assertEquals(expectedProcState, uidRecord.pendingChange.processState);
             assertEquals(TEST_PROC_STATE_SEQ1, uidRecord.pendingChange.procStateSeq);
 
-            // Verify that DISPATCH_UIDS_CHANGED_UI_MSG is posted to handler.
-            mHandler.waitForMessage(DISPATCH_UIDS_CHANGED_UI_MSG);
+            // TODO: Verify that DISPATCH_UIDS_CHANGED_UI_MSG is posted to handler.
         }
     }
 
diff --git a/services/tests/servicestests/src/com/android/server/audio/NoOpAudioSystemAdapter.java b/services/tests/servicestests/src/com/android/server/audio/NoOpAudioSystemAdapter.java
index a9cef20..609af8d 100644
--- a/services/tests/servicestests/src/com/android/server/audio/NoOpAudioSystemAdapter.java
+++ b/services/tests/servicestests/src/com/android/server/audio/NoOpAudioSystemAdapter.java
@@ -21,6 +21,8 @@
 import android.media.AudioSystem;
 import android.util.Log;
 
+import java.util.List;
+
 /**
  * Provides an adapter for AudioSystem that does nothing.
  * Overridden methods can be configured.
@@ -66,13 +68,13 @@
     }
 
     @Override
-    public int setPreferredDeviceForStrategy(int strategy,
-            @NonNull AudioDeviceAttributes device) {
+    public int setDevicesRoleForStrategy(int strategy, int role,
+            @NonNull List<AudioDeviceAttributes> devices) {
         return AudioSystem.AUDIO_STATUS_OK;
     }
 
     @Override
-    public int removePreferredDeviceForStrategy(int strategy) {
+    public int removeDevicesRoleForStrategy(int strategy, int role) {
         return AudioSystem.AUDIO_STATUS_OK;
     }
 
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/AuthServiceTest.java b/services/tests/servicestests/src/com/android/server/biometrics/AuthServiceTest.java
index a5df532..94cad1e 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/AuthServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/AuthServiceTest.java
@@ -269,21 +269,6 @@
                 eq(callback), eq(UserHandle.getCallingUserId()));
     }
 
-    @Test
-    public void testResetLockout_callsBiometricServiceResetLockout() throws
-            Exception {
-        mAuthService = new AuthService(mContext, mInjector);
-        mAuthService.onStart();
-
-        final int userId = 100;
-        final byte[] token = new byte[0];
-
-        mAuthService.mImpl.resetLockout(userId, token);
-
-        waitForIdle();
-        verify(mBiometricService).resetLockout(eq(userId), AdditionalMatchers.aryEq(token));
-    }
-
     private static void waitForIdle() {
         InstrumentationRegistry.getInstrumentation().waitForIdleSync();
     }
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/BiometricSchedulerTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/BiometricSchedulerTest.java
index dad360d4..36c55cc 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/BiometricSchedulerTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/BiometricSchedulerTest.java
@@ -17,7 +17,6 @@
 package com.android.server.biometrics.sensors;
 
 import android.content.Context;
-import android.os.IBinder;
 import android.platform.test.annotations.Presubmit;
 
 import androidx.annotation.NonNull;
@@ -56,8 +55,8 @@
         mScheduler.scheduleClientMonitor(client1);
         mScheduler.scheduleClientMonitor(client2);
 
-        client1.mFinishCallback.onClientFinished(client1, true /* success */);
-        client1.mFinishCallback.onClientFinished(client1, true /* success */);
+        client1.mCallback.onClientFinished(client1, true /* success */);
+        client1.mCallback.onClientFinished(client1, true /* success */);
     }
 
     private static class TestClientMonitor extends ClientMonitor<Object> {
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 9a465a91..e6fc792 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
@@ -149,6 +149,7 @@
     public static final String NOT_PROFILE_OWNER_MSG = "does not own the profile";
     public static final String NOT_ORG_OWNED_PROFILE_OWNER_MSG =
             "not the profile owner on organization-owned device";
+    public static final String INVALID_CALLING_IDENTITY_MSG = "Calling identity is not authorized";
     public static final String ONGOING_CALL_MSG = "ongoing call on the device";
 
     // TODO replace all instances of this with explicit {@link #mServiceContext}.
@@ -1604,7 +1605,7 @@
         dpm.setApplicationRestrictionsManagingPackage(admin1, RESTRICTIONS_DELEGATE);
 
         // DPMS correctly stores and retrieves the delegates
-        DevicePolicyManagerService.DevicePolicyData policy = dpms.mUserData.get(userHandle);
+        DevicePolicyData policy = dpms.mUserData.get(userHandle);
         assertEquals(2, policy.mDelegationMap.size());
         MoreAsserts.assertContentsInAnyOrder(policy.mDelegationMap.get(CERT_DELEGATE),
             DELEGATION_CERT_INSTALL);
@@ -1846,11 +1847,11 @@
         reset(getServices().userManagerInternal);
     }
 
-    private DevicePolicyManagerService.ActiveAdmin getDeviceOwner() {
+    private ActiveAdmin getDeviceOwner() {
         ComponentName component = dpms.mOwners.getDeviceOwnerComponent();
-        DevicePolicyManagerService.DevicePolicyData policy =
+        DevicePolicyData policy =
                 dpms.getUserData(dpms.mOwners.getDeviceOwnerUserId());
-        for (DevicePolicyManagerService.ActiveAdmin admin : policy.mAdminList) {
+        for (ActiveAdmin admin : policy.mAdminList) {
             if (component.equals(admin.info.getComponent())) {
                 return admin;
             }
@@ -2404,13 +2405,13 @@
         // Set admin1 as DA.
         dpm.setActiveAdmin(admin1, false);
         assertTrue(dpm.isAdminActive(admin1));
-        assertExpectException(SecurityException.class, /* messageRegex= */ NOT_DEVICE_OWNER_MSG,
-                () -> dpm.reboot(admin1));
+        assertExpectException(SecurityException.class, /* messageRegex= */
+                INVALID_CALLING_IDENTITY_MSG, () -> dpm.reboot(admin1));
 
         // Set admin1 as PO.
         assertTrue(dpm.setProfileOwner(admin1, null, UserHandle.USER_SYSTEM));
-        assertExpectException(SecurityException.class, /* messageRegex= */ NOT_DEVICE_OWNER_MSG,
-                () -> dpm.reboot(admin1));
+        assertExpectException(SecurityException.class, /* messageRegex= */
+                INVALID_CALLING_IDENTITY_MSG, () -> dpm.reboot(admin1));
 
         // Remove PO and add DO.
         dpm.clearProfileOwner(admin1);
@@ -2623,7 +2624,7 @@
                         UserHandle.myUserId(), UserManager.RESTRICTION_SOURCE_DEVICE_OWNER))
         ).when(getServices().userManager).getUserRestrictionSources(
                 eq(UserManager.DISALLOW_ADJUST_VOLUME),
-                eq(UserHandle.getUserHandleForUid(UserHandle.myUserId())));
+                eq(UserHandle.of(UserHandle.myUserId())));
         intent = dpm.createAdminSupportIntent(UserManager.DISALLOW_ADJUST_VOLUME);
         assertNotNull(intent);
         assertEquals(Settings.ACTION_SHOW_ADMIN_SUPPORT_DETAILS, intent.getAction());
@@ -3745,8 +3746,7 @@
         setUserSetupCompleteForUser(false, userId);
 
         // GIVEN userComplete is true in DPM
-        DevicePolicyManagerService.DevicePolicyData userData =
-                new DevicePolicyManagerService.DevicePolicyData(userId);
+        DevicePolicyData userData = new DevicePolicyData(userId);
         userData.mUserSetupComplete = true;
         dpms.mUserData.put(UserHandle.USER_SYSTEM, userData);
 
@@ -3770,8 +3770,7 @@
         setUserSetupCompleteForUser(false, userId);
 
         // GIVEN userComplete is true in DPM
-        DevicePolicyManagerService.DevicePolicyData userData =
-                new DevicePolicyManagerService.DevicePolicyData(userId);
+        DevicePolicyData userData = new DevicePolicyData(userId);
         userData.mUserSetupComplete = true;
         dpms.mUserData.put(UserHandle.USER_SYSTEM, userData);
 
@@ -4466,6 +4465,7 @@
         final int MANAGED_PROFILE_ADMIN_UID = UserHandle.getUid(MANAGED_PROFILE_USER_ID, 19436);
         addManagedProfile(admin1, MANAGED_PROFILE_ADMIN_UID, admin1);
         mContext.binder.callingUid = MANAGED_PROFILE_ADMIN_UID;
+        mServiceContext.permissions.add(permission.INTERACT_ACROSS_USERS_FULL);
 
         // Even if the caller is the managed profile, the current user is the user 0
         when(getServices().iactivityManager.getCurrentUser())
@@ -5695,6 +5695,7 @@
 
         final long ident = mServiceContext.binder.clearCallingIdentity();
         configureContextForAccess(mServiceContext, true);
+        mServiceContext.permissions.add(permission.MARK_DEVICE_ORGANIZATION_OWNED);
 
         mServiceContext.binder.callingUid =
                 UserHandle.getUid(CALLER_USER_HANDLE,
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DpmMockContext.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DpmMockContext.java
index ce7ac9e..09b6d7b 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DpmMockContext.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DpmMockContext.java
@@ -259,18 +259,7 @@
 
     @Override
     public int checkPermission(String permission, int pid, int uid) {
-        if (UserHandle.isSameApp(binder.getCallingUid(), SYSTEM_UID)) {
-            return PackageManager.PERMISSION_GRANTED; // Assume system has all permissions.
-        }
-        List<String> permissions = binder.callingPermissions.get(binder.getCallingUid());
-        if (permissions == null) {
-            permissions = callerPermissions;
-        }
-        if (permissions.contains(permission)) {
-            return PackageManager.PERMISSION_GRANTED;
-        } else {
-            return PackageManager.PERMISSION_DENIED;
-        }
+        return checkPermission(permission);
     }
 
     @Override
@@ -480,11 +469,32 @@
 
     @Override
     public int checkCallingPermission(String permission) {
-        return spiedContext.checkCallingPermission(permission);
+        return checkPermission(permission);
+    }
+
+    @Override
+    public int checkCallingOrSelfPermission(String permission) {
+        return checkPermission(permission);
     }
 
     @Override
     public void startActivityAsUser(Intent intent, UserHandle userHandle) {
         spiedContext.startActivityAsUser(intent, userHandle);
     }
+
+    private int checkPermission(String permission) {
+        if (UserHandle.isSameApp(binder.getCallingUid(), SYSTEM_UID)) {
+            return PackageManager.PERMISSION_GRANTED; // Assume system has all permissions.
+        }
+        List<String> permissions = binder.callingPermissions.get(binder.getCallingUid());
+        if (permissions == null) {
+            permissions = callerPermissions;
+        }
+        if (permissions.contains(permission)) {
+            return PackageManager.PERMISSION_GRANTED;
+        } else {
+            return PackageManager.PERMISSION_DENIED;
+        }
+    }
+
 }
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDevicePlaybackTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDevicePlaybackTest.java
index 9b2f2b6..ef2365e 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDevicePlaybackTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDevicePlaybackTest.java
@@ -16,7 +16,6 @@
 package com.android.server.hdmi;
 
 import static com.android.server.hdmi.Constants.ADDR_BROADCAST;
-import static com.android.server.hdmi.Constants.ADDR_PLAYBACK_1;
 import static com.android.server.hdmi.Constants.ADDR_TV;
 import static com.android.server.hdmi.HdmiControlService.INITIATED_BY_ENABLE_CEC;
 
@@ -62,6 +61,7 @@
     private TestLooper mTestLooper = new TestLooper();
     private ArrayList<HdmiCecLocalDevice> mLocalDevices = new ArrayList<>();
     private int mPlaybackPhysicalAddress;
+    private int mPlaybackLogicalAddress;
     private boolean mWokenUp;
     private boolean mStandby;
 
@@ -129,6 +129,7 @@
         mPlaybackPhysicalAddress = 0x2000;
         mNativeWrapper.setPhysicalAddress(mPlaybackPhysicalAddress);
         mTestLooper.dispatchAll();
+        mPlaybackLogicalAddress = mHdmiCecLocalDevicePlayback.getDeviceInfo().getLogicalAddress();
         mNativeWrapper.clearResultMessages();
     }
 
@@ -144,7 +145,7 @@
                         mPlaybackPhysicalAddress);
 
         HdmiCecMessage expectedMessage =
-                HdmiCecMessageBuilder.buildActiveSource(ADDR_PLAYBACK_1,
+                HdmiCecMessageBuilder.buildActiveSource(mPlaybackLogicalAddress,
                         mPlaybackPhysicalAddress);
 
         assertThat(mHdmiCecLocalDevicePlayback.handleRoutingChange(message)).isTrue();
@@ -165,7 +166,7 @@
                         mPlaybackPhysicalAddress);
 
         HdmiCecMessage expectedMessage =
-                HdmiCecMessageBuilder.buildActiveSource(ADDR_PLAYBACK_1,
+                HdmiCecMessageBuilder.buildActiveSource(mPlaybackLogicalAddress,
                         mPlaybackPhysicalAddress);
 
         assertThat(mHdmiCecLocalDevicePlayback.handleRoutingInformation(message)).isTrue();
@@ -186,7 +187,7 @@
                         mPlaybackPhysicalAddress);
 
         HdmiCecMessage expectedMessage =
-                HdmiCecMessageBuilder.buildActiveSource(ADDR_PLAYBACK_1,
+                HdmiCecMessageBuilder.buildActiveSource(mPlaybackLogicalAddress,
                         mPlaybackPhysicalAddress);
 
         assertThat(mHdmiCecLocalDevicePlayback.handleRoutingChange(message)).isTrue();
@@ -207,7 +208,7 @@
                         mPlaybackPhysicalAddress);
 
         HdmiCecMessage expectedMessage =
-                HdmiCecMessageBuilder.buildActiveSource(ADDR_PLAYBACK_1,
+                HdmiCecMessageBuilder.buildActiveSource(mPlaybackLogicalAddress,
                         mPlaybackPhysicalAddress);
 
         assertThat(mHdmiCecLocalDevicePlayback.handleRoutingInformation(message)).isTrue();
@@ -230,7 +231,7 @@
                         mPlaybackPhysicalAddress);
 
         HdmiCecMessage expectedMessage =
-                HdmiCecMessageBuilder.buildActiveSource(ADDR_PLAYBACK_1,
+                HdmiCecMessageBuilder.buildActiveSource(mPlaybackLogicalAddress,
                         mPlaybackPhysicalAddress);
 
         assertThat(mHdmiCecLocalDevicePlayback.handleRoutingChange(message)).isTrue();
@@ -253,7 +254,7 @@
                         mPlaybackPhysicalAddress);
 
         HdmiCecMessage expectedMessage =
-                HdmiCecMessageBuilder.buildActiveSource(ADDR_PLAYBACK_1,
+                HdmiCecMessageBuilder.buildActiveSource(mPlaybackLogicalAddress,
                         mPlaybackPhysicalAddress);
 
         assertThat(mHdmiCecLocalDevicePlayback.handleRoutingInformation(message)).isTrue();
@@ -262,6 +263,112 @@
         assertThat(mNativeWrapper.getResultMessages()).contains(expectedMessage);
     }
 
+    @Test
+    public void handleRoutingChange_otherDevice_None() {
+        mHdmiCecLocalDevicePlayback.mPowerStateChangeOnActiveSourceLost =
+            HdmiProperties.power_state_change_on_active_source_lost_values.NONE;
+        mHdmiCecLocalDevicePlayback.setIsActiveSource(true);
+        mStandby = false;
+        HdmiCecMessage message =
+                HdmiCecMessageBuilder.buildRoutingChange(ADDR_TV, 0x0000, 0x5000);
+        assertThat(mHdmiCecLocalDevicePlayback.handleRoutingChange(message)).isTrue();
+        assertThat(mHdmiCecLocalDevicePlayback.mIsActiveSource).isFalse();
+        assertThat(mStandby).isFalse();
+    }
+
+    @Test
+    public void handleRoutingChange_otherDevice_StandbyNow() {
+        mHdmiCecLocalDevicePlayback.mPowerStateChangeOnActiveSourceLost =
+            HdmiProperties.power_state_change_on_active_source_lost_values.STANDBY_NOW;
+        mHdmiCecLocalDevicePlayback.setIsActiveSource(true);
+        mStandby = false;
+        HdmiCecMessage message =
+                HdmiCecMessageBuilder.buildRoutingChange(ADDR_TV, 0x0000, 0x5000);
+        assertThat(mHdmiCecLocalDevicePlayback.handleRoutingChange(message)).isTrue();
+        assertThat(mHdmiCecLocalDevicePlayback.mIsActiveSource).isFalse();
+        assertThat(mStandby).isTrue();
+    }
+
+    @Test
+    public void handleRoutingChange_otherDevice_StandbyNow_InactiveSource() {
+        mHdmiCecLocalDevicePlayback.mPowerStateChangeOnActiveSourceLost =
+            HdmiProperties.power_state_change_on_active_source_lost_values.STANDBY_NOW;
+        mHdmiCecLocalDevicePlayback.setIsActiveSource(false);
+        mStandby = false;
+        HdmiCecMessage message =
+                HdmiCecMessageBuilder.buildRoutingChange(ADDR_TV, 0x0000, 0x5000);
+        assertThat(mHdmiCecLocalDevicePlayback.handleRoutingChange(message)).isTrue();
+        assertThat(mHdmiCecLocalDevicePlayback.mIsActiveSource).isFalse();
+        assertThat(mStandby).isFalse();
+    }
+
+    @Test
+    public void handleRoutingChange_sameDevice_StandbyNow_ActiveSource() {
+        mHdmiCecLocalDevicePlayback.mPowerStateChangeOnActiveSourceLost =
+            HdmiProperties.power_state_change_on_active_source_lost_values.STANDBY_NOW;
+        mHdmiCecLocalDevicePlayback.setIsActiveSource(true);
+        mStandby = false;
+        HdmiCecMessage message =
+                HdmiCecMessageBuilder.buildRoutingChange(ADDR_TV, 0x0000,
+                        mPlaybackPhysicalAddress);
+        assertThat(mHdmiCecLocalDevicePlayback.handleRoutingChange(message)).isTrue();
+        assertThat(mHdmiCecLocalDevicePlayback.mIsActiveSource).isTrue();
+        assertThat(mStandby).isFalse();
+    }
+
+    @Test
+    public void handleRoutingInformation_otherDevice_None() {
+        mHdmiCecLocalDevicePlayback.mPowerStateChangeOnActiveSourceLost =
+            HdmiProperties.power_state_change_on_active_source_lost_values.NONE;
+        mHdmiCecLocalDevicePlayback.setIsActiveSource(true);
+        mStandby = false;
+        HdmiCecMessage message =
+                HdmiCecMessageBuilder.buildRoutingInformation(ADDR_TV, 0x5000);
+        assertThat(mHdmiCecLocalDevicePlayback.handleRoutingInformation(message)).isTrue();
+        assertThat(mHdmiCecLocalDevicePlayback.mIsActiveSource).isFalse();
+        assertThat(mStandby).isFalse();
+    }
+
+    @Test
+    public void handleRoutingInformation_otherDevice_StandbyNow() {
+        mHdmiCecLocalDevicePlayback.mPowerStateChangeOnActiveSourceLost =
+            HdmiProperties.power_state_change_on_active_source_lost_values.STANDBY_NOW;
+        mHdmiCecLocalDevicePlayback.setIsActiveSource(true);
+        mStandby = false;
+        HdmiCecMessage message =
+                HdmiCecMessageBuilder.buildRoutingInformation(ADDR_TV, 0x5000);
+        assertThat(mHdmiCecLocalDevicePlayback.handleRoutingInformation(message)).isTrue();
+        assertThat(mHdmiCecLocalDevicePlayback.mIsActiveSource).isFalse();
+        assertThat(mStandby).isTrue();
+    }
+
+    @Test
+    public void handleRoutingInformation_otherDevice_StandbyNow_InactiveSource() {
+        mHdmiCecLocalDevicePlayback.mPowerStateChangeOnActiveSourceLost =
+            HdmiProperties.power_state_change_on_active_source_lost_values.STANDBY_NOW;
+        mHdmiCecLocalDevicePlayback.setIsActiveSource(false);
+        mStandby = false;
+        HdmiCecMessage message =
+                HdmiCecMessageBuilder.buildRoutingInformation(ADDR_TV, 0x5000);
+        assertThat(mHdmiCecLocalDevicePlayback.handleRoutingInformation(message)).isTrue();
+        assertThat(mHdmiCecLocalDevicePlayback.mIsActiveSource).isFalse();
+        assertThat(mStandby).isFalse();
+    }
+
+    @Test
+    public void handleRoutingInformation_sameDevice_StandbyNow_ActiveSource() {
+        mHdmiCecLocalDevicePlayback.mPowerStateChangeOnActiveSourceLost =
+            HdmiProperties.power_state_change_on_active_source_lost_values.STANDBY_NOW;
+        mHdmiCecLocalDevicePlayback.setIsActiveSource(true);
+        mStandby = false;
+        HdmiCecMessage message =
+                HdmiCecMessageBuilder.buildRoutingInformation(ADDR_TV,
+                        mPlaybackPhysicalAddress);
+        assertThat(mHdmiCecLocalDevicePlayback.handleRoutingInformation(message)).isTrue();
+        assertThat(mHdmiCecLocalDevicePlayback.mIsActiveSource).isTrue();
+        assertThat(mStandby).isFalse();
+    }
+
     // Playback device does not handle routing control related feature right now
     @Ignore("b/120845532")
     @Test
@@ -275,7 +382,6 @@
     }
 
     @Test
-    @Ignore("b/120845532")
     public void handleSetSystemAudioModeOn_audioSystemBroadcast() {
         mHdmiControlService.setSystemAudioActivated(false);
         assertThat(mHdmiCecLocalDevicePlayback.mService.isSystemAudioActivated()).isFalse();
@@ -287,7 +393,6 @@
     }
 
     @Test
-    @Ignore("b/120845532")
     public void handleSetSystemAudioModeOff_audioSystemToPlayback() {
         mHdmiCecLocalDevicePlayback.mService.setSystemAudioActivated(true);
         assertThat(mHdmiCecLocalDevicePlayback.mService.isSystemAudioActivated()).isTrue();
@@ -301,8 +406,7 @@
     }
 
     @Test
-    @Ignore("b/120845532")
-    public void handleSystemAudioModeStatusOn_DirectltToLocalDeviceFromAudioSystem() {
+    public void handleSystemAudioModeStatusOn_DirectlyToLocalDeviceFromAudioSystem() {
         mHdmiControlService.setSystemAudioActivated(false);
         assertThat(mHdmiCecLocalDevicePlayback.mService.isSystemAudioActivated()).isFalse();
         HdmiCecMessage message =
@@ -445,7 +549,7 @@
         mHdmiCecLocalDevicePlayback.mPowerStateChangeOnActiveSourceLost =
             HdmiProperties.power_state_change_on_active_source_lost_values.NONE;
         mStandby = false;
-        HdmiCecMessage message = HdmiCecMessageBuilder.buildActiveSource(ADDR_PLAYBACK_1,
+        HdmiCecMessage message = HdmiCecMessageBuilder.buildActiveSource(mPlaybackLogicalAddress,
                                          mPlaybackPhysicalAddress);
         assertThat(mHdmiCecLocalDevicePlayback.handleActiveSource(message)).isTrue();
         mTestLooper.dispatchAll();
@@ -468,7 +572,7 @@
         mHdmiCecLocalDevicePlayback.mPowerStateChangeOnActiveSourceLost =
             HdmiProperties.power_state_change_on_active_source_lost_values.STANDBY_NOW;
         mStandby = false;
-        HdmiCecMessage message = HdmiCecMessageBuilder.buildActiveSource(ADDR_PLAYBACK_1,
+        HdmiCecMessage message = HdmiCecMessageBuilder.buildActiveSource(mPlaybackLogicalAddress,
                                          mPlaybackPhysicalAddress);
         assertThat(mHdmiCecLocalDevicePlayback.handleActiveSource(message)).isTrue();
         mTestLooper.dispatchAll();
@@ -632,4 +736,43 @@
                 mHdmiCecLocalDevicePlayback.getDeviceInfo().getLogicalAddress());
         assertThat(mHdmiCecLocalDevicePlayback.mIsActiveSource).isTrue();
     }
+
+    @Test
+    public void handleSetStreamPath_otherDevice_None() {
+        mHdmiCecLocalDevicePlayback.mPowerStateChangeOnActiveSourceLost =
+            HdmiProperties.power_state_change_on_active_source_lost_values.NONE;
+        mHdmiCecLocalDevicePlayback.setIsActiveSource(true);
+        mStandby = false;
+        HdmiCecMessage message =
+                HdmiCecMessageBuilder.buildSetStreamPath(ADDR_TV, 0x5000);
+        assertThat(mHdmiCecLocalDevicePlayback.handleSetStreamPath(message)).isTrue();
+        assertThat(mHdmiCecLocalDevicePlayback.mIsActiveSource).isFalse();
+        assertThat(mStandby).isFalse();
+    }
+
+    @Test
+    public void handleSetStreamPath_otherDevice_StandbyNow() {
+        mHdmiCecLocalDevicePlayback.mPowerStateChangeOnActiveSourceLost =
+            HdmiProperties.power_state_change_on_active_source_lost_values.STANDBY_NOW;
+        mHdmiCecLocalDevicePlayback.setIsActiveSource(true);
+        mStandby = false;
+        HdmiCecMessage message =
+                HdmiCecMessageBuilder.buildSetStreamPath(ADDR_TV, 0x5000);
+        assertThat(mHdmiCecLocalDevicePlayback.handleSetStreamPath(message)).isTrue();
+        assertThat(mHdmiCecLocalDevicePlayback.mIsActiveSource).isFalse();
+        assertThat(mStandby).isTrue();
+    }
+
+    @Test
+    public void handleSetStreamPath_otherDevice_StandbyNow_InactiveSource() {
+        mHdmiCecLocalDevicePlayback.mPowerStateChangeOnActiveSourceLost =
+            HdmiProperties.power_state_change_on_active_source_lost_values.STANDBY_NOW;
+        mHdmiCecLocalDevicePlayback.setIsActiveSource(false);
+        mStandby = false;
+        HdmiCecMessage message =
+                HdmiCecMessageBuilder.buildSetStreamPath(ADDR_TV, 0x5000);
+        assertThat(mHdmiCecLocalDevicePlayback.handleSetStreamPath(message)).isTrue();
+        assertThat(mHdmiCecLocalDevicePlayback.mIsActiveSource).isFalse();
+        assertThat(mStandby).isFalse();
+    }
 }
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 e4c9cc3..1d914ec 100644
--- a/services/tests/servicestests/src/com/android/server/inputmethod/InputMethodUtilsTest.java
+++ b/services/tests/servicestests/src/com/android/server/inputmethod/InputMethodUtilsTest.java
@@ -88,36 +88,36 @@
     public void testVoiceImes() throws Exception {
         // locale: en_US
         assertDefaultEnabledImes(getImesWithDefaultVoiceIme(), LOCALE_EN_US,
-                "DummyDefaultEnKeyboardIme", "DummyDefaultAutoVoiceIme");
+                "FakeDefaultEnKeyboardIme", "FakeDefaultAutoVoiceIme");
         assertDefaultEnabledImes(getImesWithoutDefaultVoiceIme(), LOCALE_EN_US,
-                "DummyDefaultEnKeyboardIme", "DummyNonDefaultAutoVoiceIme0",
-                "DummyNonDefaultAutoVoiceIme1");
+                "FakeDefaultEnKeyboardIme", "FakeNonDefaultAutoVoiceIme0",
+                "FakeNonDefaultAutoVoiceIme1");
         assertDefaultEnabledMinimumImes(getImesWithDefaultVoiceIme(), LOCALE_EN_US,
-                "DummyDefaultEnKeyboardIme");
+                "FakeDefaultEnKeyboardIme");
         assertDefaultEnabledMinimumImes(getImesWithoutDefaultVoiceIme(), LOCALE_EN_US,
-                "DummyDefaultEnKeyboardIme");
+                "FakeDefaultEnKeyboardIme");
 
         // locale: en_GB
         assertDefaultEnabledImes(getImesWithDefaultVoiceIme(), LOCALE_EN_GB,
-                "DummyDefaultEnKeyboardIme", "DummyDefaultAutoVoiceIme");
+                "FakeDefaultEnKeyboardIme", "FakeDefaultAutoVoiceIme");
         assertDefaultEnabledImes(getImesWithoutDefaultVoiceIme(), LOCALE_EN_GB,
-                "DummyDefaultEnKeyboardIme", "DummyNonDefaultAutoVoiceIme0",
-                "DummyNonDefaultAutoVoiceIme1");
+                "FakeDefaultEnKeyboardIme", "FakeNonDefaultAutoVoiceIme0",
+                "FakeNonDefaultAutoVoiceIme1");
         assertDefaultEnabledMinimumImes(getImesWithDefaultVoiceIme(), LOCALE_EN_GB,
-                "DummyDefaultEnKeyboardIme");
+                "FakeDefaultEnKeyboardIme");
         assertDefaultEnabledMinimumImes(getImesWithoutDefaultVoiceIme(), LOCALE_EN_GB,
-                "DummyDefaultEnKeyboardIme");
+                "FakeDefaultEnKeyboardIme");
 
         // locale: ja_JP
         assertDefaultEnabledImes(getImesWithDefaultVoiceIme(), LOCALE_JA_JP,
-                "DummyDefaultEnKeyboardIme", "DummyDefaultAutoVoiceIme");
+                "FakeDefaultEnKeyboardIme", "FakeDefaultAutoVoiceIme");
         assertDefaultEnabledImes(getImesWithoutDefaultVoiceIme(), LOCALE_JA_JP,
-                "DummyDefaultEnKeyboardIme", "DummyNonDefaultAutoVoiceIme0",
-                "DummyNonDefaultAutoVoiceIme1");
+                "FakeDefaultEnKeyboardIme", "FakeNonDefaultAutoVoiceIme0",
+                "FakeNonDefaultAutoVoiceIme1");
         assertDefaultEnabledMinimumImes(getImesWithDefaultVoiceIme(), LOCALE_JA_JP,
-                "DummyDefaultEnKeyboardIme");
+                "FakeDefaultEnKeyboardIme");
         assertDefaultEnabledMinimumImes(getImesWithoutDefaultVoiceIme(), LOCALE_JA_JP,
-                "DummyDefaultEnKeyboardIme");
+                "FakeDefaultEnKeyboardIme");
     }
 
     @Test
@@ -189,67 +189,67 @@
 
     @Test
     public void testGetImplicitlyApplicableSubtypesLocked() throws Exception {
-        final InputMethodSubtype nonAutoEnUS = createDummyInputMethodSubtype("en_US",
+        final InputMethodSubtype nonAutoEnUS = createFakeInputMethodSubtype("en_US",
                 SUBTYPE_MODE_KEYBOARD, !IS_AUX, !IS_OVERRIDES_IMPLICITLY_ENABLED_SUBTYPE,
                 IS_ASCII_CAPABLE, !IS_ENABLED_WHEN_DEFAULT_IS_NOT_ASCII_CAPABLE);
-        final InputMethodSubtype nonAutoEnGB = createDummyInputMethodSubtype("en_GB",
+        final InputMethodSubtype nonAutoEnGB = createFakeInputMethodSubtype("en_GB",
                 SUBTYPE_MODE_KEYBOARD, !IS_AUX, !IS_OVERRIDES_IMPLICITLY_ENABLED_SUBTYPE,
                 IS_ASCII_CAPABLE, IS_ENABLED_WHEN_DEFAULT_IS_NOT_ASCII_CAPABLE);
-        final InputMethodSubtype nonAutoEnIN = createDummyInputMethodSubtype("en_IN",
+        final InputMethodSubtype nonAutoEnIN = createFakeInputMethodSubtype("en_IN",
                 SUBTYPE_MODE_KEYBOARD, !IS_AUX, !IS_OVERRIDES_IMPLICITLY_ENABLED_SUBTYPE,
                 IS_ASCII_CAPABLE, IS_ENABLED_WHEN_DEFAULT_IS_NOT_ASCII_CAPABLE);
-        final InputMethodSubtype nonAutoFrCA = createDummyInputMethodSubtype("fr_CA",
+        final InputMethodSubtype nonAutoFrCA = createFakeInputMethodSubtype("fr_CA",
                 SUBTYPE_MODE_KEYBOARD, !IS_AUX, !IS_OVERRIDES_IMPLICITLY_ENABLED_SUBTYPE,
                 IS_ASCII_CAPABLE, IS_ENABLED_WHEN_DEFAULT_IS_NOT_ASCII_CAPABLE);
-        final InputMethodSubtype nonAutoFr = createDummyInputMethodSubtype("fr_CA",
+        final InputMethodSubtype nonAutoFr = createFakeInputMethodSubtype("fr_CA",
                 SUBTYPE_MODE_KEYBOARD, !IS_AUX, !IS_OVERRIDES_IMPLICITLY_ENABLED_SUBTYPE,
                 IS_ASCII_CAPABLE, IS_ENABLED_WHEN_DEFAULT_IS_NOT_ASCII_CAPABLE);
-        final InputMethodSubtype nonAutoFil = createDummyInputMethodSubtype("fil",
+        final InputMethodSubtype nonAutoFil = createFakeInputMethodSubtype("fil",
                 SUBTYPE_MODE_KEYBOARD, !IS_AUX, !IS_OVERRIDES_IMPLICITLY_ENABLED_SUBTYPE,
                 IS_ASCII_CAPABLE, !IS_ENABLED_WHEN_DEFAULT_IS_NOT_ASCII_CAPABLE);
-        final InputMethodSubtype nonAutoIn = createDummyInputMethodSubtype("in",
+        final InputMethodSubtype nonAutoIn = createFakeInputMethodSubtype("in",
                 SUBTYPE_MODE_KEYBOARD, !IS_AUX, !IS_OVERRIDES_IMPLICITLY_ENABLED_SUBTYPE,
                 IS_ASCII_CAPABLE, IS_ENABLED_WHEN_DEFAULT_IS_NOT_ASCII_CAPABLE);
-        final InputMethodSubtype nonAutoId = createDummyInputMethodSubtype("id",
+        final InputMethodSubtype nonAutoId = createFakeInputMethodSubtype("id",
                 SUBTYPE_MODE_KEYBOARD, !IS_AUX, !IS_OVERRIDES_IMPLICITLY_ENABLED_SUBTYPE,
                 IS_ASCII_CAPABLE, IS_ENABLED_WHEN_DEFAULT_IS_NOT_ASCII_CAPABLE);
-        final InputMethodSubtype autoSubtype = createDummyInputMethodSubtype("auto",
+        final InputMethodSubtype autoSubtype = createFakeInputMethodSubtype("auto",
                 SUBTYPE_MODE_KEYBOARD, !IS_AUX, IS_OVERRIDES_IMPLICITLY_ENABLED_SUBTYPE,
                 IS_ASCII_CAPABLE, !IS_ENABLED_WHEN_DEFAULT_IS_NOT_ASCII_CAPABLE);
-        final InputMethodSubtype nonAutoJa = createDummyInputMethodSubtype("ja",
+        final InputMethodSubtype nonAutoJa = createFakeInputMethodSubtype("ja",
                 SUBTYPE_MODE_KEYBOARD, !IS_AUX, !IS_OVERRIDES_IMPLICITLY_ENABLED_SUBTYPE,
                 !IS_ASCII_CAPABLE, !IS_ENABLED_WHEN_DEFAULT_IS_NOT_ASCII_CAPABLE);
-        final InputMethodSubtype nonAutoHi = createDummyInputMethodSubtype("hi",
+        final InputMethodSubtype nonAutoHi = createFakeInputMethodSubtype("hi",
                 SUBTYPE_MODE_KEYBOARD, !IS_AUX, !IS_OVERRIDES_IMPLICITLY_ENABLED_SUBTYPE,
                 !IS_ASCII_CAPABLE, !IS_ENABLED_WHEN_DEFAULT_IS_NOT_ASCII_CAPABLE);
-        final InputMethodSubtype nonAutoSrCyrl = createDummyInputMethodSubtype("sr",
+        final InputMethodSubtype nonAutoSrCyrl = createFakeInputMethodSubtype("sr",
                 "sr-Cyrl", SUBTYPE_MODE_KEYBOARD, !IS_AUX,
                 !IS_OVERRIDES_IMPLICITLY_ENABLED_SUBTYPE, !IS_ASCII_CAPABLE,
                 !IS_ENABLED_WHEN_DEFAULT_IS_NOT_ASCII_CAPABLE);
-        final InputMethodSubtype nonAutoSrLatn = createDummyInputMethodSubtype("sr_ZZ",
+        final InputMethodSubtype nonAutoSrLatn = createFakeInputMethodSubtype("sr_ZZ",
                 "sr-Latn", SUBTYPE_MODE_KEYBOARD, !IS_AUX,
                 !IS_OVERRIDES_IMPLICITLY_ENABLED_SUBTYPE, IS_ASCII_CAPABLE,
                 !IS_ENABLED_WHEN_DEFAULT_IS_NOT_ASCII_CAPABLE);
-        final InputMethodSubtype nonAutoHandwritingEn = createDummyInputMethodSubtype("en",
+        final InputMethodSubtype nonAutoHandwritingEn = createFakeInputMethodSubtype("en",
                 SUBTYPE_MODE_HANDWRITING, !IS_AUX, !IS_OVERRIDES_IMPLICITLY_ENABLED_SUBTYPE,
                 !IS_ASCII_CAPABLE, !IS_ENABLED_WHEN_DEFAULT_IS_NOT_ASCII_CAPABLE);
-        final InputMethodSubtype nonAutoHandwritingFr = createDummyInputMethodSubtype("fr",
+        final InputMethodSubtype nonAutoHandwritingFr = createFakeInputMethodSubtype("fr",
                 SUBTYPE_MODE_HANDWRITING, !IS_AUX, !IS_OVERRIDES_IMPLICITLY_ENABLED_SUBTYPE,
                 !IS_ASCII_CAPABLE, !IS_ENABLED_WHEN_DEFAULT_IS_NOT_ASCII_CAPABLE);
-        final InputMethodSubtype nonAutoHandwritingSrCyrl = createDummyInputMethodSubtype("sr",
+        final InputMethodSubtype nonAutoHandwritingSrCyrl = createFakeInputMethodSubtype("sr",
                 "sr-Cyrl", SUBTYPE_MODE_HANDWRITING, !IS_AUX,
                 !IS_OVERRIDES_IMPLICITLY_ENABLED_SUBTYPE, !IS_ASCII_CAPABLE,
                 !IS_ENABLED_WHEN_DEFAULT_IS_NOT_ASCII_CAPABLE);
-        final InputMethodSubtype nonAutoHandwritingSrLatn = createDummyInputMethodSubtype("sr_ZZ",
+        final InputMethodSubtype nonAutoHandwritingSrLatn = createFakeInputMethodSubtype("sr_ZZ",
                 "sr-Latn", SUBTYPE_MODE_HANDWRITING, !IS_AUX,
                 !IS_OVERRIDES_IMPLICITLY_ENABLED_SUBTYPE, !IS_ASCII_CAPABLE,
                 !IS_ENABLED_WHEN_DEFAULT_IS_NOT_ASCII_CAPABLE);
         final InputMethodSubtype nonAutoEnabledWhenDefaultIsNotAsciiCalableSubtype =
-                createDummyInputMethodSubtype("zz", SUBTYPE_MODE_KEYBOARD, !IS_AUX,
+                createFakeInputMethodSubtype("zz", SUBTYPE_MODE_KEYBOARD, !IS_AUX,
                         !IS_OVERRIDES_IMPLICITLY_ENABLED_SUBTYPE, !IS_ASCII_CAPABLE,
                         IS_ENABLED_WHEN_DEFAULT_IS_NOT_ASCII_CAPABLE);
         final InputMethodSubtype nonAutoEnabledWhenDefaultIsNotAsciiCalableSubtype2 =
-                createDummyInputMethodSubtype("zz", SUBTYPE_MODE_KEYBOARD, !IS_AUX,
+                createFakeInputMethodSubtype("zz", SUBTYPE_MODE_KEYBOARD, !IS_AUX,
                         !IS_OVERRIDES_IMPLICITLY_ENABLED_SUBTYPE, !IS_ASCII_CAPABLE,
                         IS_ENABLED_WHEN_DEFAULT_IS_NOT_ASCII_CAPABLE);
 
@@ -266,9 +266,9 @@
             subtypes.add(nonAutoEnabledWhenDefaultIsNotAsciiCalableSubtype2);
             subtypes.add(nonAutoHandwritingEn);
             subtypes.add(nonAutoHandwritingFr);
-            final InputMethodInfo imi = createDummyInputMethodInfo(
+            final InputMethodInfo imi = createFakeInputMethodInfo(
                     "com.android.apps.inputmethod.latin",
-                    "com.android.apps.inputmethod.latin", "DummyLatinIme", !IS_AUX, IS_DEFAULT,
+                    "com.android.apps.inputmethod.latin", "FakeLatinIme", !IS_AUX, IS_DEFAULT,
                     subtypes);
             final ArrayList<InputMethodSubtype> result =
                     InputMethodUtils.getImplicitlyApplicableSubtypesLocked(
@@ -290,9 +290,9 @@
             subtypes.add(nonAutoEnabledWhenDefaultIsNotAsciiCalableSubtype2);
             subtypes.add(nonAutoHandwritingEn);
             subtypes.add(nonAutoHandwritingFr);
-            final InputMethodInfo imi = createDummyInputMethodInfo(
+            final InputMethodInfo imi = createFakeInputMethodInfo(
                     "com.android.apps.inputmethod.latin",
-                    "com.android.apps.inputmethod.latin", "DummyLatinIme", !IS_AUX, IS_DEFAULT,
+                    "com.android.apps.inputmethod.latin", "FakeLatinIme", !IS_AUX, IS_DEFAULT,
                     subtypes);
             final ArrayList<InputMethodSubtype> result =
                     InputMethodUtils.getImplicitlyApplicableSubtypesLocked(
@@ -314,9 +314,9 @@
             subtypes.add(nonAutoEnabledWhenDefaultIsNotAsciiCalableSubtype);
             subtypes.add(nonAutoHandwritingEn);
             subtypes.add(nonAutoHandwritingFr);
-            final InputMethodInfo imi = createDummyInputMethodInfo(
+            final InputMethodInfo imi = createFakeInputMethodInfo(
                     "com.android.apps.inputmethod.latin",
-                    "com.android.apps.inputmethod.latin", "DummyLatinIme", !IS_AUX, IS_DEFAULT,
+                    "com.android.apps.inputmethod.latin", "FakeLatinIme", !IS_AUX, IS_DEFAULT,
                     subtypes);
             final ArrayList<InputMethodSubtype> result =
                     InputMethodUtils.getImplicitlyApplicableSubtypesLocked(
@@ -339,9 +339,9 @@
             subtypes.add(nonAutoEnabledWhenDefaultIsNotAsciiCalableSubtype2);
             subtypes.add(nonAutoHandwritingEn);
             subtypes.add(nonAutoHandwritingFr);
-            final InputMethodInfo imi = createDummyInputMethodInfo(
+            final InputMethodInfo imi = createFakeInputMethodInfo(
                     "com.android.apps.inputmethod.latin",
-                    "com.android.apps.inputmethod.latin", "DummyLatinIme", !IS_AUX, IS_DEFAULT,
+                    "com.android.apps.inputmethod.latin", "FakeLatinIme", !IS_AUX, IS_DEFAULT,
                     subtypes);
             final ArrayList<InputMethodSubtype> result =
                     InputMethodUtils.getImplicitlyApplicableSubtypesLocked(
@@ -360,9 +360,9 @@
             subtypes.add(nonAutoEnabledWhenDefaultIsNotAsciiCalableSubtype2);
             subtypes.add(nonAutoHandwritingEn);
             subtypes.add(nonAutoHandwritingFr);
-            final InputMethodInfo imi = createDummyInputMethodInfo(
+            final InputMethodInfo imi = createFakeInputMethodInfo(
                     "com.android.apps.inputmethod.latin",
-                    "com.android.apps.inputmethod.latin", "DummyLatinIme", !IS_AUX, IS_DEFAULT,
+                    "com.android.apps.inputmethod.latin", "FakeLatinIme", !IS_AUX, IS_DEFAULT,
                     subtypes);
             final ArrayList<InputMethodSubtype> result =
                     InputMethodUtils.getImplicitlyApplicableSubtypesLocked(
@@ -382,9 +382,9 @@
             subtypes.add(nonAutoEnabledWhenDefaultIsNotAsciiCalableSubtype2);
             subtypes.add(nonAutoHandwritingEn);
             subtypes.add(nonAutoHandwritingFr);
-            final InputMethodInfo imi = createDummyInputMethodInfo(
+            final InputMethodInfo imi = createFakeInputMethodInfo(
                     "com.android.apps.inputmethod.latin",
-                    "com.android.apps.inputmethod.latin", "DummyLatinIme", !IS_AUX, IS_DEFAULT,
+                    "com.android.apps.inputmethod.latin", "FakeLatinIme", !IS_AUX, IS_DEFAULT,
                     subtypes);
             final ArrayList<InputMethodSubtype> result =
                     InputMethodUtils.getImplicitlyApplicableSubtypesLocked(
@@ -404,9 +404,9 @@
             subtypes.add(nonAutoHandwritingEn);
             subtypes.add(nonAutoHandwritingFr);
             subtypes.add(nonAutoEnabledWhenDefaultIsNotAsciiCalableSubtype);
-            final InputMethodInfo imi = createDummyInputMethodInfo(
+            final InputMethodInfo imi = createFakeInputMethodInfo(
                     "com.android.apps.inputmethod.latin",
-                    "com.android.apps.inputmethod.latin", "DummyLatinIme", !IS_AUX, IS_DEFAULT,
+                    "com.android.apps.inputmethod.latin", "FakeLatinIme", !IS_AUX, IS_DEFAULT,
                     subtypes);
             final ArrayList<InputMethodSubtype> result =
                     InputMethodUtils.getImplicitlyApplicableSubtypesLocked(
@@ -421,9 +421,9 @@
             subtypes.add(nonAutoHandwritingEn);
             subtypes.add(nonAutoHandwritingFr);
             subtypes.add(nonAutoEnabledWhenDefaultIsNotAsciiCalableSubtype);
-            final InputMethodInfo imi = createDummyInputMethodInfo(
+            final InputMethodInfo imi = createFakeInputMethodInfo(
                     "com.android.apps.inputmethod.latin",
-                    "com.android.apps.inputmethod.latin", "DummyLatinIme", !IS_AUX, IS_DEFAULT,
+                    "com.android.apps.inputmethod.latin", "FakeLatinIme", !IS_AUX, IS_DEFAULT,
                     subtypes);
             final ArrayList<InputMethodSubtype> result =
                     InputMethodUtils.getImplicitlyApplicableSubtypesLocked(
@@ -438,9 +438,9 @@
             subtypes.add(nonAutoEnUS);
             subtypes.add(nonAutoHi);
             subtypes.add(nonAutoEnabledWhenDefaultIsNotAsciiCalableSubtype);
-            final InputMethodInfo imi = createDummyInputMethodInfo(
+            final InputMethodInfo imi = createFakeInputMethodInfo(
                     "com.android.apps.inputmethod.latin",
-                    "com.android.apps.inputmethod.latin", "DummyLatinIme", !IS_AUX, IS_DEFAULT,
+                    "com.android.apps.inputmethod.latin", "FakeLatinIme", !IS_AUX, IS_DEFAULT,
                     subtypes);
             final ArrayList<InputMethodSubtype> result =
                     InputMethodUtils.getImplicitlyApplicableSubtypesLocked(
@@ -460,9 +460,9 @@
             subtypes.add(nonAutoHandwritingFr);
             subtypes.add(nonAutoHandwritingSrCyrl);
             subtypes.add(nonAutoHandwritingSrLatn);
-            final InputMethodInfo imi = createDummyInputMethodInfo(
+            final InputMethodInfo imi = createFakeInputMethodInfo(
                     "com.android.apps.inputmethod.latin",
-                    "com.android.apps.inputmethod.latin", "DummyLatinIme", !IS_AUX, IS_DEFAULT,
+                    "com.android.apps.inputmethod.latin", "FakeLatinIme", !IS_AUX, IS_DEFAULT,
                     subtypes);
             final ArrayList<InputMethodSubtype> result =
                     InputMethodUtils.getImplicitlyApplicableSubtypesLocked(
@@ -480,9 +480,9 @@
             subtypes.add(nonAutoHandwritingFr);
             subtypes.add(nonAutoHandwritingSrCyrl);
             subtypes.add(nonAutoHandwritingSrLatn);
-            final InputMethodInfo imi = createDummyInputMethodInfo(
+            final InputMethodInfo imi = createFakeInputMethodInfo(
                     "com.android.apps.inputmethod.latin",
-                    "com.android.apps.inputmethod.latin", "DummyLatinIme", !IS_AUX, IS_DEFAULT,
+                    "com.android.apps.inputmethod.latin", "FakeLatinIme", !IS_AUX, IS_DEFAULT,
                     subtypes);
             final ArrayList<InputMethodSubtype> result =
                     InputMethodUtils.getImplicitlyApplicableSubtypesLocked(
@@ -506,9 +506,9 @@
             subtypes.add(nonAutoHandwritingFr);
             subtypes.add(nonAutoHandwritingSrCyrl);
             subtypes.add(nonAutoHandwritingSrLatn);
-            final InputMethodInfo imi = createDummyInputMethodInfo(
+            final InputMethodInfo imi = createFakeInputMethodInfo(
                     "com.android.apps.inputmethod.latin",
-                    "com.android.apps.inputmethod.latin", "DummyLatinIme", !IS_AUX, IS_DEFAULT,
+                    "com.android.apps.inputmethod.latin", "FakeLatinIme", !IS_AUX, IS_DEFAULT,
                     subtypes);
             final ArrayList<InputMethodSubtype> result =
                     InputMethodUtils.getImplicitlyApplicableSubtypesLocked(
@@ -533,9 +533,9 @@
             final ArrayList<InputMethodSubtype> subtypes = new ArrayList<>();
             subtypes.add(nonAutoEnUS);
             subtypes.add(nonAutoFil);
-            final InputMethodInfo imi = createDummyInputMethodInfo(
+            final InputMethodInfo imi = createFakeInputMethodInfo(
                     "com.android.apps.inputmethod.latin",
-                    "com.android.apps.inputmethod.latin", "DummyLatinIme", !IS_AUX, IS_DEFAULT,
+                    "com.android.apps.inputmethod.latin", "FakeLatinIme", !IS_AUX, IS_DEFAULT,
                     subtypes);
             final ArrayList<InputMethodSubtype> result =
                     InputMethodUtils.getImplicitlyApplicableSubtypesLocked(
@@ -551,9 +551,9 @@
             subtypes.add(nonAutoJa);
             subtypes.add(nonAutoEnUS);
             subtypes.add(nonAutoFil);
-            final InputMethodInfo imi = createDummyInputMethodInfo(
+            final InputMethodInfo imi = createFakeInputMethodInfo(
                     "com.android.apps.inputmethod.latin",
-                    "com.android.apps.inputmethod.latin", "DummyLatinIme", !IS_AUX, IS_DEFAULT,
+                    "com.android.apps.inputmethod.latin", "FakeLatinIme", !IS_AUX, IS_DEFAULT,
                     subtypes);
             final ArrayList<InputMethodSubtype> result =
                     InputMethodUtils.getImplicitlyApplicableSubtypesLocked(
@@ -567,9 +567,9 @@
             final ArrayList<InputMethodSubtype> subtypes = new ArrayList<>();
             subtypes.add(nonAutoIn);
             subtypes.add(nonAutoEnUS);
-            final InputMethodInfo imi = createDummyInputMethodInfo(
+            final InputMethodInfo imi = createFakeInputMethodInfo(
                     "com.android.apps.inputmethod.latin",
-                    "com.android.apps.inputmethod.latin", "DummyLatinIme", !IS_AUX, IS_DEFAULT,
+                    "com.android.apps.inputmethod.latin", "FakeLatinIme", !IS_AUX, IS_DEFAULT,
                     subtypes);
             final ArrayList<InputMethodSubtype> result =
                     InputMethodUtils.getImplicitlyApplicableSubtypesLocked(
@@ -581,9 +581,9 @@
             final ArrayList<InputMethodSubtype> subtypes = new ArrayList<>();
             subtypes.add(nonAutoIn);
             subtypes.add(nonAutoEnUS);
-            final InputMethodInfo imi = createDummyInputMethodInfo(
+            final InputMethodInfo imi = createFakeInputMethodInfo(
                     "com.android.apps.inputmethod.latin",
-                    "com.android.apps.inputmethod.latin", "DummyLatinIme", !IS_AUX, IS_DEFAULT,
+                    "com.android.apps.inputmethod.latin", "FakeLatinIme", !IS_AUX, IS_DEFAULT,
                     subtypes);
             final ArrayList<InputMethodSubtype> result =
                     InputMethodUtils.getImplicitlyApplicableSubtypesLocked(
@@ -595,9 +595,9 @@
             final ArrayList<InputMethodSubtype> subtypes = new ArrayList<>();
             subtypes.add(nonAutoId);
             subtypes.add(nonAutoEnUS);
-            final InputMethodInfo imi = createDummyInputMethodInfo(
+            final InputMethodInfo imi = createFakeInputMethodInfo(
                     "com.android.apps.inputmethod.latin",
-                    "com.android.apps.inputmethod.latin", "DummyLatinIme", !IS_AUX, IS_DEFAULT,
+                    "com.android.apps.inputmethod.latin", "FakeLatinIme", !IS_AUX, IS_DEFAULT,
                     subtypes);
             final ArrayList<InputMethodSubtype> result =
                     InputMethodUtils.getImplicitlyApplicableSubtypesLocked(
@@ -609,9 +609,9 @@
             final ArrayList<InputMethodSubtype> subtypes = new ArrayList<>();
             subtypes.add(nonAutoId);
             subtypes.add(nonAutoEnUS);
-            final InputMethodInfo imi = createDummyInputMethodInfo(
+            final InputMethodInfo imi = createFakeInputMethodInfo(
                     "com.android.apps.inputmethod.latin",
-                    "com.android.apps.inputmethod.latin", "DummyLatinIme", !IS_AUX, IS_DEFAULT,
+                    "com.android.apps.inputmethod.latin", "FakeLatinIme", !IS_AUX, IS_DEFAULT,
                     subtypes);
             final ArrayList<InputMethodSubtype> result =
                     InputMethodUtils.getImplicitlyApplicableSubtypesLocked(
@@ -631,9 +631,9 @@
             subtypes.add(nonAutoFil);
             subtypes.add(nonAutoEnabledWhenDefaultIsNotAsciiCalableSubtype);
             subtypes.add(nonAutoEnabledWhenDefaultIsNotAsciiCalableSubtype2);
-            final InputMethodInfo imi = createDummyInputMethodInfo(
+            final InputMethodInfo imi = createFakeInputMethodInfo(
                     "com.android.apps.inputmethod.latin",
-                    "com.android.apps.inputmethod.latin", "DummyLatinIme", !IS_AUX, IS_DEFAULT,
+                    "com.android.apps.inputmethod.latin", "FakeLatinIme", !IS_AUX, IS_DEFAULT,
                     subtypes);
             final ArrayList<InputMethodSubtype> result =
                     InputMethodUtils.getImplicitlyApplicableSubtypesLocked(
@@ -649,22 +649,22 @@
 
     @Test
     public void testContainsSubtypeOf() throws Exception {
-        final InputMethodSubtype nonAutoEnUS = createDummyInputMethodSubtype("en_US",
+        final InputMethodSubtype nonAutoEnUS = createFakeInputMethodSubtype("en_US",
                 SUBTYPE_MODE_KEYBOARD, !IS_AUX, !IS_OVERRIDES_IMPLICITLY_ENABLED_SUBTYPE,
                 IS_ASCII_CAPABLE, !IS_ENABLED_WHEN_DEFAULT_IS_NOT_ASCII_CAPABLE);
-        final InputMethodSubtype nonAutoEnGB = createDummyInputMethodSubtype("en_GB",
+        final InputMethodSubtype nonAutoEnGB = createFakeInputMethodSubtype("en_GB",
                 SUBTYPE_MODE_KEYBOARD, !IS_AUX, !IS_OVERRIDES_IMPLICITLY_ENABLED_SUBTYPE,
                 IS_ASCII_CAPABLE, IS_ENABLED_WHEN_DEFAULT_IS_NOT_ASCII_CAPABLE);
-        final InputMethodSubtype nonAutoFil = createDummyInputMethodSubtype("fil",
+        final InputMethodSubtype nonAutoFil = createFakeInputMethodSubtype("fil",
                 SUBTYPE_MODE_KEYBOARD, !IS_AUX, !IS_OVERRIDES_IMPLICITLY_ENABLED_SUBTYPE,
                 IS_ASCII_CAPABLE, !IS_ENABLED_WHEN_DEFAULT_IS_NOT_ASCII_CAPABLE);
-        final InputMethodSubtype nonAutoFilPH = createDummyInputMethodSubtype("fil_PH",
+        final InputMethodSubtype nonAutoFilPH = createFakeInputMethodSubtype("fil_PH",
                 SUBTYPE_MODE_KEYBOARD, !IS_AUX, !IS_OVERRIDES_IMPLICITLY_ENABLED_SUBTYPE,
                 IS_ASCII_CAPABLE, !IS_ENABLED_WHEN_DEFAULT_IS_NOT_ASCII_CAPABLE);
-        final InputMethodSubtype nonAutoIn = createDummyInputMethodSubtype("in",
+        final InputMethodSubtype nonAutoIn = createFakeInputMethodSubtype("in",
                 SUBTYPE_MODE_KEYBOARD, !IS_AUX, !IS_OVERRIDES_IMPLICITLY_ENABLED_SUBTYPE,
                 IS_ASCII_CAPABLE, IS_ENABLED_WHEN_DEFAULT_IS_NOT_ASCII_CAPABLE);
-        final InputMethodSubtype nonAutoId = createDummyInputMethodSubtype("id",
+        final InputMethodSubtype nonAutoId = createFakeInputMethodSubtype("id",
                 SUBTYPE_MODE_KEYBOARD, !IS_AUX, !IS_OVERRIDES_IMPLICITLY_ENABLED_SUBTYPE,
                 IS_ASCII_CAPABLE, IS_ENABLED_WHEN_DEFAULT_IS_NOT_ASCII_CAPABLE);
 
@@ -673,9 +673,9 @@
         {
             final ArrayList<InputMethodSubtype> subtypes = new ArrayList<>();
             subtypes.add(nonAutoEnUS);
-            final InputMethodInfo imi = createDummyInputMethodInfo(
+            final InputMethodInfo imi = createFakeInputMethodInfo(
                     "com.android.apps.inputmethod.latin",
-                    "com.android.apps.inputmethod.latin", "DummyLatinIme", !IS_AUX, IS_DEFAULT,
+                    "com.android.apps.inputmethod.latin", "FakeLatinIme", !IS_AUX, IS_DEFAULT,
                     subtypes);
 
             assertTrue(InputMethodUtils.containsSubtypeOf(imi, LOCALE_EN, !CHECK_COUNTRY,
@@ -705,9 +705,9 @@
         {
             final ArrayList<InputMethodSubtype> subtypes = new ArrayList<>();
             subtypes.add(nonAutoFil);
-            final InputMethodInfo imi = createDummyInputMethodInfo(
+            final InputMethodInfo imi = createFakeInputMethodInfo(
                     "com.android.apps.inputmethod.latin",
-                    "com.android.apps.inputmethod.latin", "DummyLatinIme", !IS_AUX, IS_DEFAULT,
+                    "com.android.apps.inputmethod.latin", "FakeLatinIme", !IS_AUX, IS_DEFAULT,
                     subtypes);
             assertTrue(InputMethodUtils.containsSubtypeOf(imi, LOCALE_FIL, !CHECK_COUNTRY,
                     SUBTYPE_MODE_KEYBOARD));
@@ -732,9 +732,9 @@
         {
             final ArrayList<InputMethodSubtype> subtypes = new ArrayList<>();
             subtypes.add(nonAutoFilPH);
-            final InputMethodInfo imi = createDummyInputMethodInfo(
+            final InputMethodInfo imi = createFakeInputMethodInfo(
                     "com.android.apps.inputmethod.latin",
-                    "com.android.apps.inputmethod.latin", "DummyLatinIme", !IS_AUX, IS_DEFAULT,
+                    "com.android.apps.inputmethod.latin", "FakeLatinIme", !IS_AUX, IS_DEFAULT,
                     subtypes);
             assertTrue(InputMethodUtils.containsSubtypeOf(imi, LOCALE_FIL, !CHECK_COUNTRY,
                     SUBTYPE_MODE_KEYBOARD));
@@ -760,9 +760,9 @@
             final ArrayList<InputMethodSubtype> subtypes = new ArrayList<>();
             subtypes.add(nonAutoIn);
             subtypes.add(nonAutoEnUS);
-            final InputMethodInfo imi = createDummyInputMethodInfo(
+            final InputMethodInfo imi = createFakeInputMethodInfo(
                     "com.android.apps.inputmethod.latin",
-                    "com.android.apps.inputmethod.latin", "DummyLatinIme", !IS_AUX, IS_DEFAULT,
+                    "com.android.apps.inputmethod.latin", "FakeLatinIme", !IS_AUX, IS_DEFAULT,
                     subtypes);
             assertTrue(InputMethodUtils.containsSubtypeOf(imi, LOCALE_IN, !CHECK_COUNTRY,
                     SUBTYPE_MODE_KEYBOARD));
@@ -779,9 +779,9 @@
             final ArrayList<InputMethodSubtype> subtypes = new ArrayList<>();
             subtypes.add(nonAutoId);
             subtypes.add(nonAutoEnUS);
-            final InputMethodInfo imi = createDummyInputMethodInfo(
+            final InputMethodInfo imi = createFakeInputMethodInfo(
                     "com.android.apps.inputmethod.latin",
-                    "com.android.apps.inputmethod.latin", "DummyLatinIme", !IS_AUX, IS_DEFAULT,
+                    "com.android.apps.inputmethod.latin", "FakeLatinIme", !IS_AUX, IS_DEFAULT,
                     subtypes);
             assertTrue(InputMethodUtils.containsSubtypeOf(imi, LOCALE_IN, !CHECK_COUNTRY,
                     SUBTYPE_MODE_KEYBOARD));
@@ -866,7 +866,7 @@
         assertEquals(expected.hashCode(), actual.hashCode());
     }
 
-    private static InputMethodInfo createDummyInputMethodInfo(String packageName, String name,
+    private static InputMethodInfo createFakeInputMethodInfo(String packageName, String name,
             CharSequence label, boolean isAuxIme, boolean isDefault,
             List<InputMethodSubtype> subtypes) {
         final ResolveInfo ri = new ResolveInfo();
@@ -885,15 +885,15 @@
         return new InputMethodInfo(ri, isAuxIme, "", subtypes, 1, isDefault);
     }
 
-    private static InputMethodSubtype createDummyInputMethodSubtype(String locale, String mode,
+    private static InputMethodSubtype createFakeInputMethodSubtype(String locale, String mode,
             boolean isAuxiliary, boolean overridesImplicitlyEnabledSubtype,
             boolean isAsciiCapable, boolean isEnabledWhenDefaultIsNotAsciiCapable) {
-        return createDummyInputMethodSubtype(locale, null /* languageTag */, mode, isAuxiliary,
+        return createFakeInputMethodSubtype(locale, null /* languageTag */, mode, isAuxiliary,
                 overridesImplicitlyEnabledSubtype, isAsciiCapable,
                 isEnabledWhenDefaultIsNotAsciiCapable);
     }
 
-    private static InputMethodSubtype createDummyInputMethodSubtype(String locale,
+    private static InputMethodSubtype createFakeInputMethodSubtype(String locale,
             String languageTag, String mode, boolean isAuxiliary,
             boolean overridesImplicitlyEnabledSubtype, boolean isAsciiCapable,
             boolean isEnabledWhenDefaultIsNotAsciiCapable) {
@@ -920,14 +920,14 @@
         ArrayList<InputMethodInfo> preinstalledImes = new ArrayList<>();
         {
             final ArrayList<InputMethodSubtype> subtypes = new ArrayList<>();
-            subtypes.add(createDummyInputMethodSubtype("auto", SUBTYPE_MODE_VOICE, IS_AUX,
+            subtypes.add(createFakeInputMethodSubtype("auto", SUBTYPE_MODE_VOICE, IS_AUX,
                     IS_OVERRIDES_IMPLICITLY_ENABLED_SUBTYPE, !IS_ASCII_CAPABLE,
                     !IS_ENABLED_WHEN_DEFAULT_IS_NOT_ASCII_CAPABLE));
-            subtypes.add(createDummyInputMethodSubtype("en_US", SUBTYPE_MODE_VOICE, IS_AUX,
+            subtypes.add(createFakeInputMethodSubtype("en_US", SUBTYPE_MODE_VOICE, IS_AUX,
                     !IS_OVERRIDES_IMPLICITLY_ENABLED_SUBTYPE, !IS_ASCII_CAPABLE,
                     !IS_ENABLED_WHEN_DEFAULT_IS_NOT_ASCII_CAPABLE));
-            preinstalledImes.add(createDummyInputMethodInfo("DummyDefaultAutoVoiceIme",
-                    "dummy.voice0", "DummyVoice0", IS_AUX, IS_DEFAULT, subtypes));
+            preinstalledImes.add(createFakeInputMethodInfo("FakeDefaultAutoVoiceIme",
+                    "fake.voice0", "FakeVoice0", IS_AUX, IS_DEFAULT, subtypes));
         }
         preinstalledImes.addAll(getImesWithoutDefaultVoiceIme());
         return preinstalledImes;
@@ -937,41 +937,41 @@
         ArrayList<InputMethodInfo> preinstalledImes = new ArrayList<>();
         {
             final ArrayList<InputMethodSubtype> subtypes = new ArrayList<>();
-            subtypes.add(createDummyInputMethodSubtype("auto", SUBTYPE_MODE_VOICE, IS_AUX,
+            subtypes.add(createFakeInputMethodSubtype("auto", SUBTYPE_MODE_VOICE, IS_AUX,
                     IS_OVERRIDES_IMPLICITLY_ENABLED_SUBTYPE, !IS_ASCII_CAPABLE,
                     !IS_ENABLED_WHEN_DEFAULT_IS_NOT_ASCII_CAPABLE));
-            subtypes.add(createDummyInputMethodSubtype("en_US", SUBTYPE_MODE_VOICE, IS_AUX,
+            subtypes.add(createFakeInputMethodSubtype("en_US", SUBTYPE_MODE_VOICE, IS_AUX,
                     !IS_OVERRIDES_IMPLICITLY_ENABLED_SUBTYPE, !IS_ASCII_CAPABLE,
                     !IS_ENABLED_WHEN_DEFAULT_IS_NOT_ASCII_CAPABLE));
-            preinstalledImes.add(createDummyInputMethodInfo("DummyNonDefaultAutoVoiceIme0",
-                    "dummy.voice1", "DummyVoice1", IS_AUX, !IS_DEFAULT, subtypes));
+            preinstalledImes.add(createFakeInputMethodInfo("FakeNonDefaultAutoVoiceIme0",
+                    "fake.voice1", "FakeVoice1", IS_AUX, !IS_DEFAULT, subtypes));
         }
         {
             final ArrayList<InputMethodSubtype> subtypes = new ArrayList<>();
-            subtypes.add(createDummyInputMethodSubtype("auto", SUBTYPE_MODE_VOICE, IS_AUX,
+            subtypes.add(createFakeInputMethodSubtype("auto", SUBTYPE_MODE_VOICE, IS_AUX,
                     IS_OVERRIDES_IMPLICITLY_ENABLED_SUBTYPE, !IS_ASCII_CAPABLE,
                     !IS_ENABLED_WHEN_DEFAULT_IS_NOT_ASCII_CAPABLE));
-            subtypes.add(createDummyInputMethodSubtype("en_US", SUBTYPE_MODE_VOICE, IS_AUX,
+            subtypes.add(createFakeInputMethodSubtype("en_US", SUBTYPE_MODE_VOICE, IS_AUX,
                     !IS_OVERRIDES_IMPLICITLY_ENABLED_SUBTYPE, !IS_ASCII_CAPABLE,
                     !IS_ENABLED_WHEN_DEFAULT_IS_NOT_ASCII_CAPABLE));
-            preinstalledImes.add(createDummyInputMethodInfo("DummyNonDefaultAutoVoiceIme1",
-                    "dummy.voice2", "DummyVoice2", IS_AUX, !IS_DEFAULT, subtypes));
+            preinstalledImes.add(createFakeInputMethodInfo("FakeNonDefaultAutoVoiceIme1",
+                    "fake.voice2", "FakeVoice2", IS_AUX, !IS_DEFAULT, subtypes));
         }
         {
             final ArrayList<InputMethodSubtype> subtypes = new ArrayList<>();
-            subtypes.add(createDummyInputMethodSubtype("en_US", SUBTYPE_MODE_VOICE, IS_AUX,
+            subtypes.add(createFakeInputMethodSubtype("en_US", SUBTYPE_MODE_VOICE, IS_AUX,
                     !IS_OVERRIDES_IMPLICITLY_ENABLED_SUBTYPE, !IS_ASCII_CAPABLE,
                     !IS_ENABLED_WHEN_DEFAULT_IS_NOT_ASCII_CAPABLE));
-            preinstalledImes.add(createDummyInputMethodInfo("DummyNonDefaultVoiceIme2",
-                    "dummy.voice3", "DummyVoice3", IS_AUX, !IS_DEFAULT, subtypes));
+            preinstalledImes.add(createFakeInputMethodInfo("FakeNonDefaultVoiceIme2",
+                    "fake.voice3", "FakeVoice3", IS_AUX, !IS_DEFAULT, subtypes));
         }
         {
             final ArrayList<InputMethodSubtype> subtypes = new ArrayList<>();
-            subtypes.add(createDummyInputMethodSubtype("en_US", SUBTYPE_MODE_KEYBOARD, !IS_AUX,
+            subtypes.add(createFakeInputMethodSubtype("en_US", SUBTYPE_MODE_KEYBOARD, !IS_AUX,
                     !IS_OVERRIDES_IMPLICITLY_ENABLED_SUBTYPE, IS_ASCII_CAPABLE,
                     !IS_ENABLED_WHEN_DEFAULT_IS_NOT_ASCII_CAPABLE));
-            preinstalledImes.add(createDummyInputMethodInfo("DummyDefaultEnKeyboardIme",
-                    "dummy.keyboard0", "DummyKeyboard0", !IS_AUX, IS_DEFAULT, subtypes));
+            preinstalledImes.add(createFakeInputMethodInfo("FakeDefaultEnKeyboardIme",
+                    "fake.keyboard0", "FakeKeyboard0", !IS_AUX, IS_DEFAULT, subtypes));
         }
         return preinstalledImes;
     }
@@ -991,91 +991,91 @@
     private static ArrayList<InputMethodInfo> getSamplePreinstalledImes(final String localeString) {
         ArrayList<InputMethodInfo> preinstalledImes = new ArrayList<>();
 
-        // a dummy Voice IME
+        // a fake Voice IME
         {
             final boolean isDefaultIme = false;
             final ArrayList<InputMethodSubtype> subtypes = new ArrayList<>();
-            subtypes.add(createDummyInputMethodSubtype("", SUBTYPE_MODE_VOICE, IS_AUX,
+            subtypes.add(createFakeInputMethodSubtype("", SUBTYPE_MODE_VOICE, IS_AUX,
                     IS_OVERRIDES_IMPLICITLY_ENABLED_SUBTYPE, !IS_ASCII_CAPABLE,
                     !IS_ENABLED_WHEN_DEFAULT_IS_NOT_ASCII_CAPABLE));
-            preinstalledImes.add(createDummyInputMethodInfo("com.android.apps.inputmethod.voice",
-                    "com.android.inputmethod.voice", "DummyVoiceIme", IS_AUX, isDefaultIme,
+            preinstalledImes.add(createFakeInputMethodInfo("com.android.apps.inputmethod.voice",
+                    "com.android.inputmethod.voice", "FakeVoiceIme", IS_AUX, isDefaultIme,
                     subtypes));
         }
-        // a dummy Hindi IME
+        // a fake Hindi IME
         {
             final boolean isDefaultIme = contains(new String[]{ "hi", "en-rIN" }, localeString);
             final ArrayList<InputMethodSubtype> subtypes = new ArrayList<>();
             // TODO: This subtype should be marked as IS_ASCII_CAPABLE
-            subtypes.add(createDummyInputMethodSubtype("en_IN", SUBTYPE_MODE_KEYBOARD, !IS_AUX,
+            subtypes.add(createFakeInputMethodSubtype("en_IN", SUBTYPE_MODE_KEYBOARD, !IS_AUX,
                     !IS_OVERRIDES_IMPLICITLY_ENABLED_SUBTYPE, !IS_ASCII_CAPABLE,
                     !IS_ENABLED_WHEN_DEFAULT_IS_NOT_ASCII_CAPABLE));
-            subtypes.add(createDummyInputMethodSubtype("hi", SUBTYPE_MODE_KEYBOARD, !IS_AUX,
+            subtypes.add(createFakeInputMethodSubtype("hi", SUBTYPE_MODE_KEYBOARD, !IS_AUX,
                     !IS_OVERRIDES_IMPLICITLY_ENABLED_SUBTYPE, !IS_ASCII_CAPABLE,
                     !IS_ENABLED_WHEN_DEFAULT_IS_NOT_ASCII_CAPABLE));
-            preinstalledImes.add(createDummyInputMethodInfo("com.android.apps.inputmethod.hindi",
-                    "com.android.inputmethod.hindi", "DummyHindiIme", !IS_AUX, isDefaultIme,
+            preinstalledImes.add(createFakeInputMethodInfo("com.android.apps.inputmethod.hindi",
+                    "com.android.inputmethod.hindi", "FakeHindiIme", !IS_AUX, isDefaultIme,
                     subtypes));
         }
 
-        // a dummy Pinyin IME
+        // a fake Pinyin IME
         {
             final boolean isDefaultIme = contains(new String[]{ "zh-rCN" }, localeString);
             final ArrayList<InputMethodSubtype> subtypes = new ArrayList<>();
-            subtypes.add(createDummyInputMethodSubtype("zh_CN", SUBTYPE_MODE_KEYBOARD, !IS_AUX,
+            subtypes.add(createFakeInputMethodSubtype("zh_CN", SUBTYPE_MODE_KEYBOARD, !IS_AUX,
                     !IS_OVERRIDES_IMPLICITLY_ENABLED_SUBTYPE, !IS_ASCII_CAPABLE,
                     !IS_ENABLED_WHEN_DEFAULT_IS_NOT_ASCII_CAPABLE));
-            preinstalledImes.add(createDummyInputMethodInfo("com.android.apps.inputmethod.pinyin",
-                    "com.android.apps.inputmethod.pinyin", "DummyPinyinIme", !IS_AUX, isDefaultIme,
+            preinstalledImes.add(createFakeInputMethodInfo("com.android.apps.inputmethod.pinyin",
+                    "com.android.apps.inputmethod.pinyin", "FakePinyinIme", !IS_AUX, isDefaultIme,
                     subtypes));
         }
 
-        // a dummy Korean IME
+        // a fake Korean IME
         {
             final boolean isDefaultIme = contains(new String[]{ "ko" }, localeString);
             final ArrayList<InputMethodSubtype> subtypes = new ArrayList<>();
-            subtypes.add(createDummyInputMethodSubtype("ko", SUBTYPE_MODE_KEYBOARD, !IS_AUX,
+            subtypes.add(createFakeInputMethodSubtype("ko", SUBTYPE_MODE_KEYBOARD, !IS_AUX,
                     !IS_OVERRIDES_IMPLICITLY_ENABLED_SUBTYPE, !IS_ASCII_CAPABLE,
                     !IS_ENABLED_WHEN_DEFAULT_IS_NOT_ASCII_CAPABLE));
-            preinstalledImes.add(createDummyInputMethodInfo("com.android.apps.inputmethod.korean",
-                    "com.android.apps.inputmethod.korean", "DummyKoreanIme", !IS_AUX, isDefaultIme,
+            preinstalledImes.add(createFakeInputMethodInfo("com.android.apps.inputmethod.korean",
+                    "com.android.apps.inputmethod.korean", "FakeKoreanIme", !IS_AUX, isDefaultIme,
                     subtypes));
         }
 
-        // a dummy Latin IME
+        // a fake Latin IME
         {
             final boolean isDefaultIme = contains(
                     new String[]{ "en-rUS", "en-rGB", "en-rIN", "en", "hi" }, localeString);
             final ArrayList<InputMethodSubtype> subtypes = new ArrayList<>();
-            subtypes.add(createDummyInputMethodSubtype("en_US", SUBTYPE_MODE_KEYBOARD, !IS_AUX,
+            subtypes.add(createFakeInputMethodSubtype("en_US", SUBTYPE_MODE_KEYBOARD, !IS_AUX,
                     !IS_OVERRIDES_IMPLICITLY_ENABLED_SUBTYPE, IS_ASCII_CAPABLE,
                     !IS_ENABLED_WHEN_DEFAULT_IS_NOT_ASCII_CAPABLE));
-            subtypes.add(createDummyInputMethodSubtype("en_GB", SUBTYPE_MODE_KEYBOARD, !IS_AUX,
+            subtypes.add(createFakeInputMethodSubtype("en_GB", SUBTYPE_MODE_KEYBOARD, !IS_AUX,
                     !IS_OVERRIDES_IMPLICITLY_ENABLED_SUBTYPE, IS_ASCII_CAPABLE,
                     !IS_ENABLED_WHEN_DEFAULT_IS_NOT_ASCII_CAPABLE));
-            subtypes.add(createDummyInputMethodSubtype("en_IN", SUBTYPE_MODE_KEYBOARD, !IS_AUX,
+            subtypes.add(createFakeInputMethodSubtype("en_IN", SUBTYPE_MODE_KEYBOARD, !IS_AUX,
                     !IS_OVERRIDES_IMPLICITLY_ENABLED_SUBTYPE, IS_ASCII_CAPABLE,
                     !IS_ENABLED_WHEN_DEFAULT_IS_NOT_ASCII_CAPABLE));
-            subtypes.add(createDummyInputMethodSubtype("hi", SUBTYPE_MODE_KEYBOARD, !IS_AUX,
+            subtypes.add(createFakeInputMethodSubtype("hi", SUBTYPE_MODE_KEYBOARD, !IS_AUX,
                     !IS_OVERRIDES_IMPLICITLY_ENABLED_SUBTYPE, IS_ASCII_CAPABLE,
                     !IS_ENABLED_WHEN_DEFAULT_IS_NOT_ASCII_CAPABLE));
-            preinstalledImes.add(createDummyInputMethodInfo("com.android.apps.inputmethod.latin",
-                    "com.android.apps.inputmethod.latin", "DummyLatinIme", !IS_AUX, isDefaultIme,
+            preinstalledImes.add(createFakeInputMethodInfo("com.android.apps.inputmethod.latin",
+                    "com.android.apps.inputmethod.latin", "FakeLatinIme", !IS_AUX, isDefaultIme,
                     subtypes));
         }
 
-        // a dummy Japanese IME
+        // a fake Japanese IME
         {
             final boolean isDefaultIme = contains(new String[]{ "ja", "ja-rJP" }, localeString);
             final ArrayList<InputMethodSubtype> subtypes = new ArrayList<>();
-            subtypes.add(createDummyInputMethodSubtype("ja", SUBTYPE_MODE_KEYBOARD, !IS_AUX,
+            subtypes.add(createFakeInputMethodSubtype("ja", SUBTYPE_MODE_KEYBOARD, !IS_AUX,
                     !IS_OVERRIDES_IMPLICITLY_ENABLED_SUBTYPE, !IS_ASCII_CAPABLE,
                     !IS_ENABLED_WHEN_DEFAULT_IS_NOT_ASCII_CAPABLE));
-            subtypes.add(createDummyInputMethodSubtype("emoji", SUBTYPE_MODE_KEYBOARD, !IS_AUX,
+            subtypes.add(createFakeInputMethodSubtype("emoji", SUBTYPE_MODE_KEYBOARD, !IS_AUX,
                     !IS_OVERRIDES_IMPLICITLY_ENABLED_SUBTYPE, !IS_ASCII_CAPABLE,
                     !IS_ENABLED_WHEN_DEFAULT_IS_NOT_ASCII_CAPABLE));
-            preinstalledImes.add(createDummyInputMethodInfo("com.android.apps.inputmethod.japanese",
-                    "com.android.apps.inputmethod.japanese", "DummyJapaneseIme", !IS_AUX,
+            preinstalledImes.add(createFakeInputMethodInfo("com.android.apps.inputmethod.japanese",
+                    "com.android.apps.inputmethod.japanese", "FakeJapaneseIme", !IS_AUX,
                     isDefaultIme, subtypes));
         }
 
diff --git a/services/tests/servicestests/src/com/android/server/job/MaxJobCountsTest.java b/services/tests/servicestests/src/com/android/server/job/MaxJobCountsTest.java
index 0219f22..4c36747 100644
--- a/services/tests/servicestests/src/com/android/server/job/MaxJobCountsTest.java
+++ b/services/tests/servicestests/src/com/android/server/job/MaxJobCountsTest.java
@@ -15,13 +15,15 @@
  */
 package com.android.server.job;
 
-import android.util.KeyValueListParser;
+import android.annotation.Nullable;
+import android.provider.DeviceConfig;
 
 import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
 
 import com.android.server.job.JobSchedulerService.MaxJobCounts;
 
+import org.junit.After;
 import org.junit.Assert;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -29,19 +31,32 @@
 @RunWith(AndroidJUnit4.class)
 @SmallTest
 public class MaxJobCountsTest {
+    @After
+    public void tearDown() throws Exception {
+        resetConfig();
+    }
 
-    private void check(String config,
+    private void resetConfig() {
+        // DeviceConfig.resetToDefaults() doesn't work here. Need to reset constants manually.
+        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_JOB_SCHEDULER, "total", "", false);
+        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_JOB_SCHEDULER, "maxbg", "", false);
+        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_JOB_SCHEDULER, "minbg", "", false);
+    }
+
+    private void check(@Nullable DeviceConfig.Properties config,
             int defaultTotal, int defaultMaxBg, int defaultMinBg,
-            int expectedTotal, int expectedMaxBg, int expectedMinBg) {
-        final KeyValueListParser parser = new KeyValueListParser(',');
-        parser.setString(config);
+            int expectedTotal, int expectedMaxBg, int expectedMinBg) throws Exception {
+        resetConfig();
+        if (config != null) {
+            DeviceConfig.setProperties(config);
+        }
 
         final MaxJobCounts counts = new JobSchedulerService.MaxJobCounts(
                 defaultTotal, "total",
                 defaultMaxBg, "maxbg",
                 defaultMinBg, "minbg");
 
-        counts.parse(parser);
+        counts.update();
 
         Assert.assertEquals(expectedTotal, counts.getMaxTotal());
         Assert.assertEquals(expectedMaxBg, counts.getMaxBg());
@@ -49,24 +64,35 @@
     }
 
     @Test
-    public void test() {
+    public void test() throws Exception {
         // Tests with various combinations.
-        check("", /*default*/ 5, 1, 0, /*expected*/ 5, 1, 0);
-        check("", /*default*/ 5, 0, 0, /*expected*/ 5, 1, 0);
-        check("", /*default*/ 0, 0, 0, /*expected*/ 1, 1, 0);
-        check("", /*default*/ -1, -1, -1, /*expected*/ 1, 1, 0);
-        check("", /*default*/ 5, 5, 5, /*expected*/ 5, 5, 4);
-        check("", /*default*/ 6, 5, 6, /*expected*/ 6, 5, 5);
-        check("", /*default*/ 4, 5, 6, /*expected*/ 4, 4, 3);
-        check("", /*default*/ 5, 1, 1, /*expected*/ 5, 1, 1);
-        check("", /*default*/ 15, 15, 15, /*expected*/ 15, 15, 14);
-        check("", /*default*/ 16, 16, 16, /*expected*/ 16, 16, 15);
-        check("", /*default*/ 20, 20, 20, /*expected*/ 16, 16, 15);
+        check(null, /*default*/ 5, 1, 0, /*expected*/ 5, 1, 0);
+        check(null, /*default*/ 5, 0, 0, /*expected*/ 5, 1, 0);
+        check(null, /*default*/ 0, 0, 0, /*expected*/ 1, 1, 0);
+        check(null, /*default*/ -1, -1, -1, /*expected*/ 1, 1, 0);
+        check(null, /*default*/ 5, 5, 5, /*expected*/ 5, 5, 4);
+        check(null, /*default*/ 6, 5, 6, /*expected*/ 6, 5, 5);
+        check(null, /*default*/ 4, 5, 6, /*expected*/ 4, 4, 3);
+        check(null, /*default*/ 5, 1, 1, /*expected*/ 5, 1, 1);
+        check(null, /*default*/ 15, 15, 15, /*expected*/ 15, 15, 14);
+        check(null, /*default*/ 16, 16, 16, /*expected*/ 16, 16, 15);
+        check(null, /*default*/ 20, 20, 20, /*expected*/ 16, 16, 15);
 
         // Test for overriding with a setting string.
-        check("total=5,maxbg=4,minbg=3", /*default*/ 9, 9, 9, /*expected*/ 5, 4, 3);
-        check("total=5", /*default*/ 9, 9, 9, /*expected*/ 5, 5, 4);
-        check("maxbg=4", /*default*/ 9, 9, 9, /*expected*/ 9, 4, 4);
-        check("minbg=3", /*default*/ 9, 9, 9, /*expected*/ 9, 9, 3);
+        check(new DeviceConfig.Properties.Builder(DeviceConfig.NAMESPACE_JOB_SCHEDULER)
+                        .setInt("total", 5)
+                        .setInt("maxbg", 4)
+                        .setInt("minbg", 3)
+                        .build(),
+                /*default*/ 9, 9, 9, /*expected*/ 5, 4, 3);
+        check(new DeviceConfig.Properties.Builder(DeviceConfig.NAMESPACE_JOB_SCHEDULER)
+                        .setInt("total", 5).build(),
+                /*default*/ 9, 9, 9, /*expected*/ 5, 5, 4);
+        check(new DeviceConfig.Properties.Builder(DeviceConfig.NAMESPACE_JOB_SCHEDULER)
+                        .setInt("maxbg", 4).build(),
+                /*default*/ 9, 9, 9, /*expected*/ 9, 4, 4);
+        check(new DeviceConfig.Properties.Builder(DeviceConfig.NAMESPACE_JOB_SCHEDULER)
+                        .setInt("minbg", 3).build(),
+                /*default*/ 9, 9, 9, /*expected*/ 9, 9, 3);
     }
 }
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/BaseLockSettingsServiceTests.java b/services/tests/servicestests/src/com/android/server/locksettings/BaseLockSettingsServiceTests.java
index 1b5c56a..d44d37e 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/BaseLockSettingsServiceTests.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/BaseLockSettingsServiceTests.java
@@ -19,7 +19,6 @@
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
 import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.anyBoolean;
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.doAnswer;
@@ -169,7 +168,7 @@
 
         final ArrayList<UserInfo> allUsers = new ArrayList<>(mPrimaryUserProfiles);
         allUsers.add(SECONDARY_USER_INFO);
-        when(mUserManager.getUsers(anyBoolean())).thenReturn(allUsers);
+        when(mUserManager.getUsers()).thenReturn(allUsers);
 
         when(mActivityManager.unlockUser(anyInt(), any(), any(), any())).thenAnswer(
                 new Answer<Boolean>() {
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsServiceTests.java b/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsServiceTests.java
index 12b144f..1f66c7c 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsServiceTests.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsServiceTests.java
@@ -129,7 +129,7 @@
         mGateKeeperService.clearAuthToken(TURNED_OFF_PROFILE_USER_ID);
         // verify credential
         assertEquals(VerifyCredentialResponse.RESPONSE_OK, mService.verifyCredential(
-                firstUnifiedPassword, 0, PRIMARY_USER_ID)
+                firstUnifiedPassword, PRIMARY_USER_ID, 0 /* flags */)
                 .getResponseCode());
 
         // Verify that we have a new auth token for the profile
@@ -186,13 +186,13 @@
         mGateKeeperService.clearAuthToken(MANAGED_PROFILE_USER_ID);
         // verify primary credential
         assertEquals(VerifyCredentialResponse.RESPONSE_OK, mService.verifyCredential(
-                primaryPassword, 0, PRIMARY_USER_ID)
+                primaryPassword, PRIMARY_USER_ID, 0 /* flags */)
                 .getResponseCode());
         assertNull(mGateKeeperService.getAuthToken(MANAGED_PROFILE_USER_ID));
 
         // verify profile credential
         assertEquals(VerifyCredentialResponse.RESPONSE_OK, mService.verifyCredential(
-                profilePassword, 0, MANAGED_PROFILE_USER_ID)
+                profilePassword, MANAGED_PROFILE_USER_ID, 0 /* flags */)
                 .getResponseCode());
         assertNotNull(mGateKeeperService.getAuthToken(MANAGED_PROFILE_USER_ID));
         assertEquals(profileSid, mGateKeeperService.getSecureUserId(MANAGED_PROFILE_USER_ID));
@@ -203,7 +203,7 @@
                 newPassword("pwd"), primaryPassword, PRIMARY_USER_ID));
         mStorageManager.setIgnoreBadUnlock(false);
         assertEquals(VerifyCredentialResponse.RESPONSE_OK, mService.verifyCredential(
-                profilePassword, 0, MANAGED_PROFILE_USER_ID)
+                profilePassword, MANAGED_PROFILE_USER_ID, 0 /* flags */)
                 .getResponseCode());
         assertEquals(profileSid, mGateKeeperService.getSecureUserId(MANAGED_PROFILE_USER_ID));
     }
@@ -389,7 +389,7 @@
         initializeStorageWithCredential(PRIMARY_USER_ID, password, 1234);
         reset(mRecoverableKeyStoreManager);
 
-        mService.verifyCredential(password, 1, PRIMARY_USER_ID);
+        mService.verifyCredential(password, PRIMARY_USER_ID, 0 /* flags */);
 
         verify(mRecoverableKeyStoreManager)
                 .lockScreenSecretAvailable(
@@ -406,7 +406,7 @@
                 MANAGED_PROFILE_USER_ID));
         reset(mRecoverableKeyStoreManager);
 
-        mService.verifyCredential(pattern, 1, MANAGED_PROFILE_USER_ID);
+        mService.verifyCredential(pattern, MANAGED_PROFILE_USER_ID, 0 /* flags */);
 
         verify(mRecoverableKeyStoreManager)
                 .lockScreenSecretAvailable(
@@ -421,7 +421,7 @@
         mService.setSeparateProfileChallengeEnabled(MANAGED_PROFILE_USER_ID, false, null);
         reset(mRecoverableKeyStoreManager);
 
-        mService.verifyCredential(pattern, 1, PRIMARY_USER_ID);
+        mService.verifyCredential(pattern, PRIMARY_USER_ID, 0 /* flags */);
 
         // Parent sends its credentials for both the parent and profile.
         verify(mRecoverableKeyStoreManager)
@@ -484,9 +484,8 @@
 
     private void assertVerifyCredentials(int userId, LockscreenCredential credential, long sid)
             throws RemoteException{
-        final long challenge = 54321;
-        VerifyCredentialResponse response = mService.verifyCredential(credential,
-                challenge, userId);
+        VerifyCredentialResponse response = mService.verifyCredential(credential, userId,
+                0 /* flags */);
 
         assertEquals(GateKeeperResponse.RESPONSE_OK, response.getResponseCode());
         if (sid != -1) assertEquals(sid, mGateKeeperService.getSecureUserId(userId));
@@ -508,7 +507,7 @@
             badCredential = LockscreenCredential.createPin("0");
         }
         assertEquals(GateKeeperResponse.RESPONSE_ERROR, mService.verifyCredential(
-                badCredential, challenge, userId).getResponseCode());
+                badCredential, userId, 0 /* flags */).getResponseCode());
     }
 
     private void initializeStorageWithCredential(int userId, LockscreenCredential credential,
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/LockscreenFrpTest.java b/services/tests/servicestests/src/com/android/server/locksettings/LockscreenFrpTest.java
index 4b3f7b5..9c0239b 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/LockscreenFrpTest.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/LockscreenFrpTest.java
@@ -52,7 +52,8 @@
 
         assertEquals(CREDENTIAL_TYPE_PIN, mService.getCredentialType(USER_FRP));
         assertEquals(VerifyCredentialResponse.RESPONSE_OK,
-                mService.verifyCredential(newPin("1234"), 0, USER_FRP).getResponseCode());
+                mService.verifyCredential(newPin("1234"), USER_FRP, 0 /* flags */)
+                        .getResponseCode());
     }
 
     @Test
@@ -61,7 +62,8 @@
 
         assertEquals(CREDENTIAL_TYPE_PATTERN, mService.getCredentialType(USER_FRP));
         assertEquals(VerifyCredentialResponse.RESPONSE_OK,
-                mService.verifyCredential(newPattern("4321"), 0, USER_FRP).getResponseCode());
+                mService.verifyCredential(newPattern("4321"), USER_FRP, 0 /* flags */)
+                        .getResponseCode());
     }
 
     @Test
@@ -70,7 +72,8 @@
 
         assertEquals(CREDENTIAL_TYPE_PASSWORD, mService.getCredentialType(USER_FRP));
         assertEquals(VerifyCredentialResponse.RESPONSE_OK,
-                mService.verifyCredential(newPassword("4321"), 0, USER_FRP).getResponseCode());
+                mService.verifyCredential(newPassword("4321"), USER_FRP, 0 /* flags */)
+                        .getResponseCode());
     }
 
     @Test
@@ -80,7 +83,8 @@
 
         assertEquals(CREDENTIAL_TYPE_PATTERN, mService.getCredentialType(USER_FRP));
         assertEquals(VerifyCredentialResponse.RESPONSE_OK,
-                mService.verifyCredential(newPattern("5678"), 0, USER_FRP).getResponseCode());
+                mService.verifyCredential(newPattern("5678"), USER_FRP, 0 /* flags */)
+                        .getResponseCode());
     }
 
     @Test
@@ -98,7 +102,8 @@
 
         mSettings.setDeviceProvisioned(true);
         assertEquals(VerifyCredentialResponse.RESPONSE_ERROR,
-                mService.verifyCredential(newPin("1234"), 0, USER_FRP).getResponseCode());
+                mService.verifyCredential(newPin("1234"), USER_FRP, 0 /* flags */)
+                        .getResponseCode());
     }
 
     @Test
@@ -113,7 +118,8 @@
 
         assertEquals(CREDENTIAL_TYPE_PIN, mService.getCredentialType(USER_FRP));
         assertEquals(VerifyCredentialResponse.RESPONSE_OK,
-                mService.verifyCredential(newPin("1234"), 0, USER_FRP).getResponseCode());
+                mService.verifyCredential(newPin("1234"), USER_FRP, 0 /* flags */)
+                        .getResponseCode());
 
     }
 
@@ -129,6 +135,7 @@
 
         assertEquals(CREDENTIAL_TYPE_PASSWORD, mService.getCredentialType(USER_FRP));
         assertEquals(VerifyCredentialResponse.RESPONSE_OK,
-                mService.verifyCredential(newPin("1234"), 0, USER_FRP).getResponseCode());
+                mService.verifyCredential(newPin("1234"), USER_FRP, 0 /* flags */)
+                        .getResponseCode());
     }
 }
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/SyntheticPasswordTests.java b/services/tests/servicestests/src/com/android/server/locksettings/SyntheticPasswordTests.java
index ba85199..2bd0e55 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/SyntheticPasswordTests.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/SyntheticPasswordTests.java
@@ -119,8 +119,7 @@
         long sid = mGateKeeperService.getSecureUserId(PRIMARY_USER_ID);
         mService.setLockCredential(newPassword, password, PRIMARY_USER_ID);
         assertEquals(VerifyCredentialResponse.RESPONSE_OK, mService.verifyCredential(
-                newPassword, 0, PRIMARY_USER_ID)
-                        .getResponseCode());
+                newPassword, PRIMARY_USER_ID, 0 /* flags */).getResponseCode());
         assertEquals(sid, mGateKeeperService.getSecureUserId(PRIMARY_USER_ID));
     }
 
@@ -131,12 +130,10 @@
 
         initializeCredentialUnderSP(password, PRIMARY_USER_ID);
         assertEquals(VerifyCredentialResponse.RESPONSE_OK, mService.verifyCredential(
-                password, 0, PRIMARY_USER_ID)
-                        .getResponseCode());
+                password, PRIMARY_USER_ID, 0 /* flags */).getResponseCode());
 
         assertEquals(VerifyCredentialResponse.RESPONSE_ERROR, mService.verifyCredential(
-                badPassword, 0, PRIMARY_USER_ID)
-                        .getResponseCode());
+                badPassword, PRIMARY_USER_ID, 0 /* flags */).getResponseCode());
     }
 
     @Test
@@ -153,8 +150,7 @@
         // set a new password
         mService.setLockCredential(badPassword, nonePassword(), PRIMARY_USER_ID);
         assertEquals(VerifyCredentialResponse.RESPONSE_OK, mService.verifyCredential(
-                badPassword, 0, PRIMARY_USER_ID)
-                        .getResponseCode());
+                badPassword, PRIMARY_USER_ID, 0 /* flags */).getResponseCode());
         assertNotEquals(sid, mGateKeeperService.getSecureUserId(PRIMARY_USER_ID));
     }
 
@@ -166,8 +162,7 @@
         initializeCredentialUnderSP(password, PRIMARY_USER_ID);
         mService.setLockCredential(badPassword, password, PRIMARY_USER_ID);
         assertEquals(VerifyCredentialResponse.RESPONSE_OK, mService.verifyCredential(
-                badPassword, 0, PRIMARY_USER_ID)
-                        .getResponseCode());
+                badPassword, PRIMARY_USER_ID, 0 /* flags */).getResponseCode());
 
         // Check the same secret was passed each time
         ArgumentCaptor<ArrayList<Byte>> secret = ArgumentCaptor.forClass(ArrayList.class);
@@ -183,8 +178,7 @@
         initializeCredentialUnderSP(password, PRIMARY_USER_ID);
         reset(mAuthSecretService);
         assertEquals(VerifyCredentialResponse.RESPONSE_OK, mService.verifyCredential(
-                password, 0, PRIMARY_USER_ID)
-                        .getResponseCode());
+                password, PRIMARY_USER_ID, 0 /* flags */).getResponseCode());
         verify(mAuthSecretService).primaryUserCredential(any(ArrayList.class));
     }
 
@@ -194,8 +188,7 @@
 
         initializeCredentialUnderSP(password, SECONDARY_USER_ID);
         assertEquals(VerifyCredentialResponse.RESPONSE_OK, mService.verifyCredential(
-                password, 0, SECONDARY_USER_ID)
-                        .getResponseCode());
+                password, SECONDARY_USER_ID, 0 /* flags */).getResponseCode());
         verify(mAuthSecretService, never()).primaryUserCredential(any(ArrayList.class));
     }
 
@@ -246,7 +239,8 @@
         assertFalse(mLocalService.isEscrowTokenActive(handle, PRIMARY_USER_ID));
         assertTrue(mService.hasPendingEscrowToken(PRIMARY_USER_ID));
 
-        mService.verifyCredential(password, 0, PRIMARY_USER_ID).getResponseCode();
+        mService.verifyCredential(password, PRIMARY_USER_ID, 0 /* flags */)
+                .getResponseCode();
         assertTrue(mLocalService.isEscrowTokenActive(handle, PRIMARY_USER_ID));
         assertFalse(mService.hasPendingEscrowToken(PRIMARY_USER_ID));
 
@@ -259,8 +253,7 @@
         verify(mDevicePolicyManager).reportPasswordChanged(PRIMARY_USER_ID);
 
         assertEquals(VerifyCredentialResponse.RESPONSE_OK, mService.verifyCredential(
-                pattern, 0, PRIMARY_USER_ID)
-                    .getResponseCode());
+                pattern, PRIMARY_USER_ID, 0 /* flags */).getResponseCode());
         assertArrayEquals(storageKey, mStorageManager.getUserUnlockToken(PRIMARY_USER_ID));
     }
 
@@ -275,7 +268,8 @@
         long handle = mLocalService.addEscrowToken(token, PRIMARY_USER_ID, null);
         assertFalse(mLocalService.isEscrowTokenActive(handle, PRIMARY_USER_ID));
 
-        mService.verifyCredential(password, 0, PRIMARY_USER_ID).getResponseCode();
+        mService.verifyCredential(password, PRIMARY_USER_ID, 0 /* flags */)
+                .getResponseCode();
         assertTrue(mLocalService.isEscrowTokenActive(handle, PRIMARY_USER_ID));
 
         mLocalService.setLockCredentialWithToken(nonePassword(), handle, token, PRIMARY_USER_ID);
@@ -284,8 +278,7 @@
                 PRIMARY_USER_ID);
 
         assertEquals(VerifyCredentialResponse.RESPONSE_OK, mService.verifyCredential(
-                pattern, 0, PRIMARY_USER_ID)
-                        .getResponseCode());
+                pattern, PRIMARY_USER_ID, 0 /* flags */).getResponseCode());
         assertArrayEquals(storageKey, mStorageManager.getUserUnlockToken(PRIMARY_USER_ID));
     }
 
@@ -301,7 +294,8 @@
         long handle = mLocalService.addEscrowToken(token, PRIMARY_USER_ID, null);
         assertFalse(mLocalService.isEscrowTokenActive(handle, PRIMARY_USER_ID));
 
-        mService.verifyCredential(password, 0, PRIMARY_USER_ID).getResponseCode();
+        mService.verifyCredential(password, PRIMARY_USER_ID, 0 /* flags */)
+                .getResponseCode();
         assertTrue(mLocalService.isEscrowTokenActive(handle, PRIMARY_USER_ID));
 
         mService.setLockCredential(pattern, password, PRIMARY_USER_ID);
@@ -309,8 +303,7 @@
         mLocalService.setLockCredentialWithToken(newPassword, handle, token, PRIMARY_USER_ID);
 
         assertEquals(VerifyCredentialResponse.RESPONSE_OK, mService.verifyCredential(
-                newPassword, 0, PRIMARY_USER_ID)
-                    .getResponseCode());
+                newPassword, PRIMARY_USER_ID, 0 /* flags */).getResponseCode());
         assertArrayEquals(storageKey, mStorageManager.getUserUnlockToken(PRIMARY_USER_ID));
     }
 
@@ -357,8 +350,7 @@
         assertFalse(mLocalService.isEscrowTokenActive(handle, PRIMARY_USER_ID));
         // Activate token (password gets migrated to SP at the same time)
         assertEquals(VerifyCredentialResponse.RESPONSE_OK, mService.verifyCredential(
-                password, 0, PRIMARY_USER_ID)
-                    .getResponseCode());
+                password, PRIMARY_USER_ID, 0 /* flags */).getResponseCode());
         // Verify token is activated
         assertTrue(mLocalService.isEscrowTokenActive(handle, PRIMARY_USER_ID));
     }
@@ -488,8 +480,7 @@
 
         initializeCredentialUnderSP(password, PRIMARY_USER_ID);
         assertEquals(VerifyCredentialResponse.RESPONSE_OK, mService.verifyCredential(
-                password, 0, PRIMARY_USER_ID)
-                        .getResponseCode());
+                password, PRIMARY_USER_ID, 0 /* flags */).getResponseCode());
         verify(mAuthSecretService, never()).primaryUserCredential(any(ArrayList.class));
     }
 
@@ -503,7 +494,8 @@
         reset(mDevicePolicyManager);
 
         long handle = mLocalService.addEscrowToken(token, PRIMARY_USER_ID, null);
-        mService.verifyCredential(password, 0, PRIMARY_USER_ID).getResponseCode();
+        mService.verifyCredential(password, PRIMARY_USER_ID, 0 /* flags */)
+                .getResponseCode();
         assertTrue(mLocalService.isEscrowTokenActive(handle, PRIMARY_USER_ID));
 
         mService.onCleanupUser(PRIMARY_USER_ID);
diff --git a/services/tests/servicestests/src/com/android/server/net/NetworkPolicyManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/net/NetworkPolicyManagerServiceTest.java
index 128177b..58b71d4 100644
--- a/services/tests/servicestests/src/com/android/server/net/NetworkPolicyManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/net/NetworkPolicyManagerServiceTest.java
@@ -462,7 +462,7 @@
 
     @Test
     public void testTurnRestrictBackgroundOn() throws Exception {
-        assertRestrictBackgroundOff(); // Sanity check.
+        assertRestrictBackgroundOff();
         final FutureIntent futureIntent = newRestrictBackgroundChangedFuture();
         setRestrictBackground(true);
         assertRestrictBackgroundChangedReceived(futureIntent, null);
@@ -471,7 +471,7 @@
     @Test
     @NetPolicyXml("restrict-background-on.xml")
     public void testTurnRestrictBackgroundOff() throws Exception {
-        assertRestrictBackgroundOn(); // Sanity check.
+        assertRestrictBackgroundOn();
         assertRestrictBackgroundChangedReceived(mFutureIntent, null);
         final FutureIntent futureIntent = newRestrictBackgroundChangedFuture();
         setRestrictBackground(false);
@@ -479,28 +479,27 @@
     }
 
     /**
-     * Adds whitelist when restrict background is on - app should receive an intent.
+     * Adds allowlist when restrict background is on - app should receive an intent.
      */
     @Test
     @NetPolicyXml("restrict-background-on.xml")
-    public void testAddRestrictBackgroundWhitelist_restrictBackgroundOn() throws Exception {
-        assertRestrictBackgroundOn(); // Sanity check.
+    public void testAddRestrictBackgroundAllowlist_restrictBackgroundOn() throws Exception {
+        assertRestrictBackgroundOn();
         assertRestrictBackgroundChangedReceived(mFutureIntent, null);
-        addRestrictBackgroundWhitelist(true);
+        addRestrictBackgroundAllowlist(true);
     }
 
     /**
-     * Adds whitelist when restrict background is off - app should not receive an intent.
+     * Adds allowlist when restrict background is off - app should not receive an intent.
      */
     @Test
-    public void testAddRestrictBackgroundWhitelist_restrictBackgroundOff() throws Exception {
-        assertRestrictBackgroundOff(); // Sanity check.
-        addRestrictBackgroundWhitelist(false);
+    public void testAddRestrictBackgroundAllowlist_restrictBackgroundOff() throws Exception {
+        assertRestrictBackgroundOff();
+        addRestrictBackgroundAllowlist(false);
     }
 
-    private void addRestrictBackgroundWhitelist(boolean expectIntent) throws Exception {
-        // Sanity checks.
-        assertWhitelistUids();
+    private void addRestrictBackgroundAllowlist(boolean expectIntent) throws Exception {
+        assertAllowlistUids();
         assertUidPolicy(UID_A, POLICY_NONE);
 
         final FutureIntent futureIntent = newRestrictBackgroundChangedFuture();
@@ -508,7 +507,7 @@
 
         mService.setUidPolicy(UID_A, POLICY_ALLOW_METERED_BACKGROUND);
 
-        assertWhitelistUids(UID_A);
+        assertAllowlistUids(UID_A);
         assertUidPolicy(UID_A, POLICY_ALLOW_METERED_BACKGROUND);
         mPolicyListener.waitAndVerify()
                 .onUidPoliciesChanged(APP_ID_A, POLICY_ALLOW_METERED_BACKGROUND);
@@ -520,24 +519,24 @@
     }
 
     /**
-     * Removes whitelist when restrict background is on - app should receive an intent.
+     * Removes allowlist when restrict background is on - app should receive an intent.
      */
     @Test
-    @NetPolicyXml("uidA-whitelisted-restrict-background-on.xml")
-    public void testRemoveRestrictBackgroundWhitelist_restrictBackgroundOn() throws Exception {
-        assertRestrictBackgroundOn(); // Sanity check.
+    @NetPolicyXml("uidA-allowlisted-restrict-background-on.xml")
+    public void testRemoveRestrictBackgroundAllowlist_restrictBackgroundOn() throws Exception {
+        assertRestrictBackgroundOn();
         assertRestrictBackgroundChangedReceived(mFutureIntent, null);
-        removeRestrictBackgroundWhitelist(true);
+        removeRestrictBackgroundAllowlist(true);
     }
 
     /**
-     * Removes whitelist when restrict background is off - app should not receive an intent.
+     * Removes allowlist when restrict background is off - app should not receive an intent.
      */
     @Test
-    @NetPolicyXml("uidA-whitelisted-restrict-background-off.xml")
-    public void testRemoveRestrictBackgroundWhitelist_restrictBackgroundOff() throws Exception {
-        assertRestrictBackgroundOff(); // Sanity check.
-        removeRestrictBackgroundWhitelist(false);
+    @NetPolicyXml("uidA-allowlisted-restrict-background-off.xml")
+    public void testRemoveRestrictBackgroundAllowlist_restrictBackgroundOff() throws Exception {
+        assertRestrictBackgroundOff();
+        removeRestrictBackgroundAllowlist(false);
     }
 
     @Test
@@ -688,9 +687,8 @@
         assertFalse(mService.getRestrictBackground());
     }
 
-    private void removeRestrictBackgroundWhitelist(boolean expectIntent) throws Exception {
-        // Sanity checks.
-        assertWhitelistUids(UID_A);
+    private void removeRestrictBackgroundAllowlist(boolean expectIntent) throws Exception {
+        assertAllowlistUids(UID_A);
         assertUidPolicy(UID_A, POLICY_ALLOW_METERED_BACKGROUND);
 
         final FutureIntent futureIntent = newRestrictBackgroundChangedFuture();
@@ -698,7 +696,7 @@
 
         mService.setUidPolicy(UID_A, POLICY_NONE);
 
-        assertWhitelistUids();
+        assertAllowlistUids();
         assertUidPolicy(UID_A, POLICY_NONE);
         mPolicyListener.waitAndVerify().onUidPoliciesChanged(APP_ID_A, POLICY_NONE);
         if (expectIntent) {
@@ -709,27 +707,27 @@
     }
 
     /**
-     * Adds blacklist when restrict background is on - app should not receive an intent.
+     * Adds denylist when restrict background is on - app should not receive an intent.
      */
     @Test
     @NetPolicyXml("restrict-background-on.xml")
-    public void testAddRestrictBackgroundBlacklist_restrictBackgroundOn() throws Exception {
-        assertRestrictBackgroundOn(); // Sanity check.
+    public void testAddRestrictBackgroundDenylist_restrictBackgroundOn() throws Exception {
+        assertRestrictBackgroundOn();
         assertRestrictBackgroundChangedReceived(mFutureIntent, null);
-        addRestrictBackgroundBlacklist(false);
+        addRestrictBackgroundDenylist(false);
     }
 
     /**
-     * Adds blacklist when restrict background is off - app should receive an intent.
+     * Adds denylist when restrict background is off - app should receive an intent.
      */
     @Test
-    public void testAddRestrictBackgroundBlacklist_restrictBackgroundOff() throws Exception {
-        assertRestrictBackgroundOff(); // Sanity check.
-        addRestrictBackgroundBlacklist(true);
+    public void testAddRestrictBackgroundDenylist_restrictBackgroundOff() throws Exception {
+        assertRestrictBackgroundOff();
+        addRestrictBackgroundDenylist(true);
     }
 
-    private void addRestrictBackgroundBlacklist(boolean expectIntent) throws Exception {
-        assertUidPolicy(UID_A, POLICY_NONE); // Sanity check.
+    private void addRestrictBackgroundDenylist(boolean expectIntent) throws Exception {
+        assertUidPolicy(UID_A, POLICY_NONE);
         final FutureIntent futureIntent = newRestrictBackgroundChangedFuture();
         mPolicyListener.expect().onUidPoliciesChanged(anyInt(), anyInt());
 
@@ -746,28 +744,28 @@
     }
 
     /**
-     * Removes blacklist when restrict background is on - app should not receive an intent.
+     * Removes denylist when restrict background is on - app should not receive an intent.
      */
     @Test
-    @NetPolicyXml("uidA-blacklisted-restrict-background-on.xml")
-    public void testRemoveRestrictBackgroundBlacklist_restrictBackgroundOn() throws Exception {
-        assertRestrictBackgroundOn(); // Sanity check.
+    @NetPolicyXml("uidA-denylisted-restrict-background-on.xml")
+    public void testRemoveRestrictBackgroundDenylist_restrictBackgroundOn() throws Exception {
+        assertRestrictBackgroundOn();
         assertRestrictBackgroundChangedReceived(mFutureIntent, null);
-        removeRestrictBackgroundBlacklist(false);
+        removeRestrictBackgroundDenylist(false);
     }
 
     /**
-     * Removes blacklist when restrict background is off - app should receive an intent.
+     * Removes denylist when restrict background is off - app should receive an intent.
      */
     @Test
-    @NetPolicyXml("uidA-blacklisted-restrict-background-off.xml")
-    public void testRemoveRestrictBackgroundBlacklist_restrictBackgroundOff() throws Exception {
-        assertRestrictBackgroundOff(); // Sanity check.
-        removeRestrictBackgroundBlacklist(true);
+    @NetPolicyXml("uidA-denylisted-restrict-background-off.xml")
+    public void testRemoveRestrictBackgroundDenylist_restrictBackgroundOff() throws Exception {
+        assertRestrictBackgroundOff();
+        removeRestrictBackgroundDenylist(true);
     }
 
-    private void removeRestrictBackgroundBlacklist(boolean expectIntent) throws Exception {
-        assertUidPolicy(UID_A, POLICY_REJECT_METERED_BACKGROUND); // Sanity check.
+    private void removeRestrictBackgroundDenylist(boolean expectIntent) throws Exception {
+        assertUidPolicy(UID_A, POLICY_REJECT_METERED_BACKGROUND);
         final FutureIntent futureIntent = newRestrictBackgroundChangedFuture();
         mPolicyListener.expect().onUidPoliciesChanged(anyInt(), anyInt());
 
@@ -784,9 +782,8 @@
     }
 
     @Test
-    @NetPolicyXml("uidA-blacklisted-restrict-background-on.xml")
-    public void testBlacklistedAppIsNotNotifiedWhenRestrictBackgroundIsOn() throws Exception {
-        // Sanity checks.
+    @NetPolicyXml("uidA-denylisted-restrict-background-on.xml")
+    public void testDenylistedAppIsNotNotifiedWhenRestrictBackgroundIsOn() throws Exception {
         assertRestrictBackgroundOn();
         assertRestrictBackgroundChangedReceived(mFutureIntent, null);
         assertUidPolicy(UID_A, POLICY_REJECT_METERED_BACKGROUND);
@@ -797,12 +794,11 @@
     }
 
     @Test
-    @NetPolicyXml("uidA-whitelisted-restrict-background-on.xml")
-    public void testWhitelistedAppIsNotNotifiedWhenRestrictBackgroundIsOn() throws Exception {
-        // Sanity checks.
+    @NetPolicyXml("uidA-allowlisted-restrict-background-on.xml")
+    public void testAllowlistedAppIsNotNotifiedWhenRestrictBackgroundIsOn() throws Exception {
         assertRestrictBackgroundOn();
         assertRestrictBackgroundChangedReceived(mFutureIntent, null);
-        assertWhitelistUids(UID_A);
+        assertAllowlistUids(UID_A);
 
         final FutureIntent futureIntent = newRestrictBackgroundChangedFuture();
         setRestrictBackground(true);
@@ -810,12 +806,11 @@
     }
 
     @Test
-    @NetPolicyXml("uidA-whitelisted-restrict-background-on.xml")
-    public void testWhitelistedAppIsNotifiedWhenBlacklisted() throws Exception {
-        // Sanity checks.
+    @NetPolicyXml("uidA-allowlisted-restrict-background-on.xml")
+    public void testAllowlistedAppIsNotifiedWhenDenylisted() throws Exception {
         assertRestrictBackgroundOn();
         assertRestrictBackgroundChangedReceived(mFutureIntent, null);
-        assertWhitelistUids(UID_A);
+        assertAllowlistUids(UID_A);
 
         final FutureIntent futureIntent = newRestrictBackgroundChangedFuture();
         mService.setUidPolicy(UID_A, POLICY_REJECT_METERED_BACKGROUND);
@@ -823,8 +818,8 @@
     }
 
     @Test
-    @NetPolicyXml("restrict-background-lists-whitelist-format.xml")
-    public void testRestrictBackgroundLists_whitelistFormat() throws Exception {
+    @NetPolicyXml("restrict-background-lists-allowlist-format.xml")
+    public void testRestrictBackgroundLists_allowlistFormat() throws Exception {
         restrictBackgroundListsTest();
     }
 
@@ -835,33 +830,33 @@
     }
 
     private void restrictBackgroundListsTest() throws Exception {
-        // UIds that are whitelisted.
-        assertWhitelistUids(UID_A, UID_B, UID_C);
+        // UIds that are allowlisted.
+        assertAllowlistUids(UID_A, UID_B, UID_C);
         assertUidPolicy(UID_A, POLICY_ALLOW_METERED_BACKGROUND);
         assertUidPolicy(UID_B, POLICY_ALLOW_METERED_BACKGROUND);
         assertUidPolicy(UID_C, POLICY_ALLOW_METERED_BACKGROUND);
 
-        // UIDs that are blacklisted.
+        // UIDs that are denylisted.
         assertUidPolicy(UID_D, POLICY_NONE);
         assertUidPolicy(UID_E, POLICY_REJECT_METERED_BACKGROUND);
 
         // UIDS that have legacy policies.
         assertUidPolicy(UID_F, 2); // POLICY_ALLOW_BACKGROUND_BATTERY_SAVE
 
-        // Remove whitelist.
+        // Remove allowlist.
         mService.setUidPolicy(UID_A, POLICY_NONE);
         assertUidPolicy(UID_A, POLICY_NONE);
-        assertWhitelistUids(UID_B, UID_C);
+        assertAllowlistUids(UID_B, UID_C);
 
-        // Add whitelist when blacklisted.
+        // Add allowlist when denylisted.
         mService.setUidPolicy(UID_E, POLICY_ALLOW_METERED_BACKGROUND);
         assertUidPolicy(UID_E, POLICY_ALLOW_METERED_BACKGROUND);
-        assertWhitelistUids(UID_B, UID_C, UID_E);
+        assertAllowlistUids(UID_B, UID_C, UID_E);
 
-        // Add blacklist when whitelisted.
+        // Add denylist when allowlisted.
         mService.setUidPolicy(UID_B, POLICY_REJECT_METERED_BACKGROUND);
         assertUidPolicy(UID_B, POLICY_REJECT_METERED_BACKGROUND);
-        assertWhitelistUids(UID_C, UID_E);
+        assertAllowlistUids(UID_C, UID_E);
     }
 
     /**
@@ -870,9 +865,9 @@
     @Test
     @NetPolicyXml("restrict-background-lists-mixed-format.xml")
     public void testRestrictBackgroundLists_mixedFormat() throws Exception {
-        assertWhitelistUids(UID_A, UID_C, UID_D);
+        assertAllowlistUids(UID_A, UID_C, UID_D);
         assertUidPolicy(UID_A, POLICY_ALLOW_METERED_BACKGROUND);
-        assertUidPolicy(UID_B, POLICY_REJECT_METERED_BACKGROUND); // Blacklist prevails.
+        assertUidPolicy(UID_B, POLICY_REJECT_METERED_BACKGROUND); // Denylist prevails.
         assertUidPolicy(UID_C, (POLICY_ALLOW_METERED_BACKGROUND | 2));
         assertUidPolicy(UID_D, POLICY_ALLOW_METERED_BACKGROUND);
     }
@@ -2045,7 +2040,7 @@
         }
     }
 
-    private void assertWhitelistUids(int... uids) {
+    private void assertAllowlistUids(int... uids) {
         assertContainsInAnyOrder(mService.getUidsWithPolicy(POLICY_ALLOW_METERED_BACKGROUND), uids);
     }
 
@@ -2133,7 +2128,6 @@
 
     private void setRestrictBackground(boolean flag) throws Exception {
         mService.setRestrictBackground(flag);
-        // Sanity check.
         assertEquals("restrictBackground not set", flag, mService.getRestrictBackground());
     }
 
diff --git a/services/tests/servicestests/src/com/android/server/om/OverlayActorEnforcerTests.kt b/services/tests/servicestests/src/com/android/server/om/OverlayActorEnforcerTests.kt
index e08eea2..0839273 100644
--- a/services/tests/servicestests/src/com/android/server/om/OverlayActorEnforcerTests.kt
+++ b/services/tests/servicestests/src/com/android/server/om/OverlayActorEnforcerTests.kt
@@ -160,7 +160,7 @@
         private val hasPermission: Boolean = false,
         private val overlayableInfo: OverlayableInfo? = null,
         private vararg val packageNames: String = arrayOf("com.test.actor.one")
-    ) : OverlayableInfoCallback {
+    ) : PackageManagerHelper {
 
         override fun getNamedActors() = if (isActor) {
             mapOf(NAMESPACE to mapOf(ACTOR_NAME to ACTOR_PKG_NAME))
@@ -195,6 +195,14 @@
             }
         }
 
+        override fun getConfigSignaturePackage(): String {
+            throw UnsupportedOperationException()
+        }
+
+        override fun getOverlayPackages(userId: Int): MutableList<PackageInfo> {
+            throw UnsupportedOperationException()
+        }
+
         override fun signaturesMatching(pkgName1: String, pkgName2: String, userId: Int): Boolean {
             throw UnsupportedOperationException()
         }
diff --git a/services/tests/servicestests/src/com/android/server/om/OverlayManagerServiceImplRebootTests.java b/services/tests/servicestests/src/com/android/server/om/OverlayManagerServiceImplRebootTests.java
index b7692f9..e281f2b 100644
--- a/services/tests/servicestests/src/com/android/server/om/OverlayManagerServiceImplRebootTests.java
+++ b/services/tests/servicestests/src/com/android/server/om/OverlayManagerServiceImplRebootTests.java
@@ -44,9 +44,9 @@
     @Test
     public void testUpdateOverlaysForUser() {
         final OverlayManagerServiceImpl impl = getImpl();
-        addSystemPackage(target(TARGET), USER);
-        addSystemPackage(target("some.other.target"), USER);;
-        addSystemPackage(overlay(OVERLAY, TARGET), USER);
+        addPackage(target(TARGET), USER);
+        addPackage(target("some.other.target"), USER);
+        addPackage(overlay(OVERLAY, TARGET), USER);
 
         // do nothing, expect no change
         final List<String> a = impl.updateOverlaysForUser(USER);
@@ -54,7 +54,7 @@
         assertTrue(a.contains(TARGET));
 
         // upgrade overlay, keep target
-        addSystemPackage(overlay(OVERLAY, TARGET), USER);
+        addPackage(overlay(OVERLAY, TARGET), USER);
 
         final List<String> b = impl.updateOverlaysForUser(USER);
         assertEquals(1, b.size());
@@ -66,7 +66,7 @@
         assertTrue(c.contains(TARGET));
 
         // upgrade overlay, switch to new target
-        addSystemPackage(overlay(OVERLAY, "some.other.target"), USER);
+        addPackage(overlay(OVERLAY, "some.other.target"), USER);
         final List<String> d = impl.updateOverlaysForUser(USER);
         assertEquals(2, d.size());
         assertTrue(d.containsAll(Arrays.asList(TARGET, "some.other.target")));
diff --git a/services/tests/servicestests/src/com/android/server/om/OverlayManagerServiceImplTests.java b/services/tests/servicestests/src/com/android/server/om/OverlayManagerServiceImplTests.java
index f4c5506..c1d862a 100644
--- a/services/tests/servicestests/src/com/android/server/om/OverlayManagerServiceImplTests.java
+++ b/services/tests/servicestests/src/com/android/server/om/OverlayManagerServiceImplTests.java
@@ -19,6 +19,7 @@
 import static android.content.om.OverlayInfo.STATE_DISABLED;
 import static android.content.om.OverlayInfo.STATE_ENABLED;
 import static android.content.om.OverlayInfo.STATE_MISSING_TARGET;
+import static android.os.OverlayablePolicy.CONFIG_SIGNATURE;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotNull;
@@ -49,6 +50,10 @@
     private static final String OVERLAY3 = OVERLAY + "3";
     private static final int USER3 = USER2 + 1;
 
+    private static final String CONFIG_SIGNATURE_REFERENCE_PKG = "com.dummy.ref";
+    private static final String CERT_CONFIG_OK = "config_certificate_ok";
+    private static final String CERT_CONFIG_NOK = "config_certificate_nok";
+
     @Test
     public void testGetOverlayInfo() {
         installNewPackage(overlay(OVERLAY, TARGET), USER);
@@ -204,4 +209,87 @@
         impl.setEnabled(OVERLAY, true, USER);
         assertEquals(0, listener.count);
     }
+
+    @Test
+    public void testConfigSignaturePolicyOk() {
+        setConfigSignaturePackageName(CONFIG_SIGNATURE_REFERENCE_PKG);
+        reinitializeImpl();
+
+        addPackage(target(CONFIG_SIGNATURE_REFERENCE_PKG).setCertificate(CERT_CONFIG_OK), USER);
+        installNewPackage(target(TARGET), USER);
+        installNewPackage(overlay(OVERLAY, TARGET).setCertificate(CERT_CONFIG_OK), USER);
+
+        final DummyIdmapDaemon idmapd = getIdmapd();
+        final DummyDeviceState state = getState();
+        String overlayPath = state.select(OVERLAY, USER).apkPath;
+        assertTrue(idmapd.idmapExists(overlayPath, USER));
+
+        DummyIdmapDaemon.IdmapHeader idmap = idmapd.getIdmap(overlayPath);
+        assertTrue((CONFIG_SIGNATURE & idmap.policies) == CONFIG_SIGNATURE);
+    }
+
+    @Test
+    public void testConfigSignaturePolicyCertNok() {
+        setConfigSignaturePackageName(CONFIG_SIGNATURE_REFERENCE_PKG);
+        reinitializeImpl();
+
+        addPackage(target(CONFIG_SIGNATURE_REFERENCE_PKG).setCertificate(CERT_CONFIG_OK), USER);
+        installNewPackage(target(TARGET), USER);
+        installNewPackage(overlay(OVERLAY, TARGET).setCertificate(CERT_CONFIG_NOK), USER);
+
+        final DummyIdmapDaemon idmapd = getIdmapd();
+        final DummyDeviceState state = getState();
+        String overlayPath = state.select(OVERLAY, USER).apkPath;
+        assertTrue(idmapd.idmapExists(overlayPath, USER));
+
+        DummyIdmapDaemon.IdmapHeader idmap = idmapd.getIdmap(overlayPath);
+        assertTrue((CONFIG_SIGNATURE & idmap.policies) == 0);
+    }
+
+    @Test
+    public void testConfigSignaturePolicyNoConfig() {
+        addPackage(target(CONFIG_SIGNATURE_REFERENCE_PKG).setCertificate(CERT_CONFIG_OK), USER);
+        installNewPackage(target(TARGET), USER);
+        installNewPackage(overlay(OVERLAY, TARGET).setCertificate(CERT_CONFIG_NOK), USER);
+
+        final DummyIdmapDaemon idmapd = getIdmapd();
+        final DummyDeviceState state = getState();
+        String overlayPath = state.select(OVERLAY, USER).apkPath;
+        assertTrue(idmapd.idmapExists(overlayPath, USER));
+
+        DummyIdmapDaemon.IdmapHeader idmap = idmapd.getIdmap(overlayPath);
+        assertTrue((CONFIG_SIGNATURE & idmap.policies) == 0);
+    }
+
+    @Test
+    public void testConfigSignaturePolicyNoRefPkg() {
+        installNewPackage(target(TARGET), USER);
+        installNewPackage(overlay(OVERLAY, TARGET).setCertificate(CERT_CONFIG_NOK), USER);
+
+        final DummyIdmapDaemon idmapd = getIdmapd();
+        final DummyDeviceState state = getState();
+        String overlayPath = state.select(OVERLAY, USER).apkPath;
+        assertTrue(idmapd.idmapExists(overlayPath, USER));
+
+        DummyIdmapDaemon.IdmapHeader idmap = idmapd.getIdmap(overlayPath);
+        assertTrue((CONFIG_SIGNATURE & idmap.policies) == 0);
+    }
+
+    @Test
+    public void testConfigSignaturePolicyRefPkgNotSystem() {
+        setConfigSignaturePackageName(CONFIG_SIGNATURE_REFERENCE_PKG);
+        reinitializeImpl();
+
+        addPackage(app(CONFIG_SIGNATURE_REFERENCE_PKG).setCertificate(CERT_CONFIG_OK), USER);
+        installNewPackage(target(TARGET), USER);
+        installNewPackage(overlay(OVERLAY, TARGET).setCertificate(CERT_CONFIG_NOK), USER);
+
+        final DummyIdmapDaemon idmapd = getIdmapd();
+        final DummyDeviceState state = getState();
+        String overlayPath = state.select(OVERLAY, USER).apkPath;
+        assertTrue(idmapd.idmapExists(overlayPath, USER));
+
+        DummyIdmapDaemon.IdmapHeader idmap = idmapd.getIdmap(overlayPath);
+        assertTrue((CONFIG_SIGNATURE & idmap.policies) == 0);
+    }
 }
diff --git a/services/tests/servicestests/src/com/android/server/om/OverlayManagerServiceImplTestsBase.java b/services/tests/servicestests/src/com/android/server/om/OverlayManagerServiceImplTestsBase.java
index 733310b..2faf29f 100644
--- a/services/tests/servicestests/src/com/android/server/om/OverlayManagerServiceImplTestsBase.java
+++ b/services/tests/servicestests/src/com/android/server/om/OverlayManagerServiceImplTestsBase.java
@@ -27,6 +27,7 @@
 import android.content.om.OverlayableInfo;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageInfo;
+import android.text.TextUtils;
 import android.util.ArrayMap;
 import android.util.ArraySet;
 
@@ -52,6 +53,7 @@
     private DummyPackageManagerHelper mPackageManager;
     private DummyIdmapDaemon mIdmapDaemon;
     private OverlayConfig mOverlayConfig;
+    private String mConfigSignaturePackageName;
 
     @Before
     public void setUp() {
@@ -83,6 +85,18 @@
         return mListener;
     }
 
+    DummyIdmapDaemon getIdmapd() {
+        return mIdmapDaemon;
+    }
+
+    DummyDeviceState getState() {
+        return mState;
+    }
+
+    void setConfigSignaturePackageName(String packageName) {
+        mConfigSignaturePackageName = packageName;
+    }
+
     void assertState(@State int expected, final String overlayPackageName, int userId) {
         final OverlayInfo info = mImpl.getOverlayInfo(overlayPackageName, userId);
         if (info == null) {
@@ -102,9 +116,14 @@
         assertEquals(expected, actual);
     }
 
+    DummyDeviceState.PackageBuilder app(String packageName) {
+        return new DummyDeviceState.PackageBuilder(packageName, null /* targetPackageName */,
+                null /* targetOverlayableName */, "data");
+    }
+
     DummyDeviceState.PackageBuilder target(String packageName) {
         return new DummyDeviceState.PackageBuilder(packageName, null /* targetPackageName */,
-                null /* targetOverlayableName */);
+                null /* targetOverlayableName */, "");
     }
 
     DummyDeviceState.PackageBuilder overlay(String packageName, String targetPackageName) {
@@ -114,10 +133,10 @@
     DummyDeviceState.PackageBuilder overlay(String packageName, String targetPackageName,
             String targetOverlayableName) {
         return new DummyDeviceState.PackageBuilder(packageName, targetPackageName,
-                targetOverlayableName);
+                targetOverlayableName, "");
     }
 
-    void addSystemPackage(DummyDeviceState.PackageBuilder pkg, int userId) {
+    void addPackage(DummyDeviceState.PackageBuilder pkg, int userId) {
         mState.add(pkg, userId);
     }
 
@@ -242,15 +261,17 @@
             private String packageName;
             private String targetPackage;
             private String certificate = "[default]";
+            private String partition;
             private int version = 0;
             private ArrayList<String> overlayableNames = new ArrayList<>();
             private String targetOverlayableName;
 
             private PackageBuilder(String packageName, String targetPackage,
-                    String targetOverlayableName) {
+                    String targetOverlayableName, String partition) {
                 this.packageName = packageName;
                 this.targetPackage = targetPackage;
                 this.targetOverlayableName = targetOverlayableName;
+                this.partition = partition;
             }
 
             PackageBuilder setCertificate(String certificate) {
@@ -269,9 +290,19 @@
             }
 
             Package build() {
-                final String apkPath = String.format("%s/%s/base.apk",
-                        targetPackage == null ? "/system/app/:" : "/vendor/overlay/:",
-                        packageName);
+                String path = "";
+                if (TextUtils.isEmpty(partition)) {
+                    if (targetPackage == null) {
+                        path = "/system/app";
+                    } else {
+                        path = "/vendor/overlay";
+                    }
+                } else {
+                    String type = targetPackage == null ? "app" : "overlay";
+                    path = String.format("%s/%s", partition, type);
+                }
+
+                final String apkPath = String.format("%s/%s/base.apk", path, packageName);
                 final Package newPackage = new Package(packageName, targetPackage,
                         targetOverlayableName, version, apkPath, certificate);
                 newPackage.overlayableNames.addAll(overlayableNames);
@@ -302,8 +333,7 @@
         }
     }
 
-    static final class DummyPackageManagerHelper implements PackageManagerHelper,
-            OverlayableInfoCallback {
+    final class DummyPackageManagerHelper implements PackageManagerHelper {
         private final DummyDeviceState mState;
 
         private DummyPackageManagerHelper(DummyDeviceState state) {
@@ -343,6 +373,11 @@
                     .collect(Collectors.toList());
         }
 
+        @Override
+        public @NonNull String getConfigSignaturePackage() {
+            return mConfigSignaturePackageName;
+        }
+
         @Nullable
         @Override
         public OverlayableInfo getOverlayableForTarget(@NonNull String packageName,
diff --git a/services/tests/servicestests/src/com/android/server/pm/AppsFilterTest.java b/services/tests/servicestests/src/com/android/server/pm/AppsFilterTest.java
index f991dff..b2512d3 100644
--- a/services/tests/servicestests/src/com/android/server/pm/AppsFilterTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/AppsFilterTest.java
@@ -69,6 +69,7 @@
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
+import java.util.concurrent.Executor;
 
 @Presubmit
 @RunWith(JUnit4.class)
@@ -88,6 +89,8 @@
     AppsFilter.FeatureConfig mFeatureConfigMock;
     @Mock
     AppsFilter.StateProvider mStateProvider;
+    @Mock
+    Executor mMockExecutor;
 
     private ArrayMap<String, PackageSetting> mExisting = new ArrayMap<>();
 
@@ -184,10 +187,15 @@
         doAnswer(invocation -> {
             ((AppsFilter.StateProvider.CurrentStateCallback) invocation.getArgument(0))
                     .currentState(mExisting, USER_INFO_LIST);
-            return null;
+            return new Object();
         }).when(mStateProvider)
                 .runWithState(any(AppsFilter.StateProvider.CurrentStateCallback.class));
 
+        doAnswer(invocation -> {
+            ((Runnable) invocation.getArgument(0)).run();
+            return new Object();
+        }).when(mMockExecutor).execute(any(Runnable.class));
+
         when(mFeatureConfigMock.isGloballyEnabled()).thenReturn(true);
         when(mFeatureConfigMock.packageIsEnabled(any(AndroidPackage.class))).thenAnswer(
                 (Answer<Boolean>) invocation ->
@@ -198,7 +206,8 @@
     @Test
     public void testSystemReadyPropogates() throws Exception {
         final AppsFilter appsFilter =
-                new AppsFilter(mStateProvider, mFeatureConfigMock, new String[]{}, false, null);
+                new AppsFilter(mStateProvider, mFeatureConfigMock, new String[]{}, false, null,
+                        mMockExecutor);
         appsFilter.onSystemReady();
         verify(mFeatureConfigMock).onSystemReady();
     }
@@ -206,7 +215,8 @@
     @Test
     public void testQueriesAction_FilterMatches() throws Exception {
         final AppsFilter appsFilter =
-                new AppsFilter(mStateProvider, mFeatureConfigMock, new String[]{}, false, null);
+                new AppsFilter(mStateProvider, mFeatureConfigMock, new String[]{}, false, null,
+                        mMockExecutor);
         simulateAddBasicAndroid(appsFilter);
         appsFilter.onSystemReady();
 
@@ -222,7 +232,8 @@
     @Test
     public void testQueriesProtectedAction_FilterDoesNotMatch() throws Exception {
         final AppsFilter appsFilter =
-                new AppsFilter(mStateProvider, mFeatureConfigMock, new String[]{}, false, null);
+                new AppsFilter(mStateProvider, mFeatureConfigMock, new String[]{}, false, null,
+                        mMockExecutor);
         final Signature frameworkSignature = Mockito.mock(Signature.class);
         final PackageParser.SigningDetails frameworkSigningDetails =
                 new PackageParser.SigningDetails(new Signature[]{frameworkSignature}, 1);
@@ -260,7 +271,8 @@
     @Test
     public void testQueriesProvider_FilterMatches() throws Exception {
         final AppsFilter appsFilter =
-                new AppsFilter(mStateProvider, mFeatureConfigMock, new String[]{}, false, null);
+                new AppsFilter(mStateProvider, mFeatureConfigMock, new String[]{}, false, null,
+                        mMockExecutor);
         simulateAddBasicAndroid(appsFilter);
         appsFilter.onSystemReady();
 
@@ -277,7 +289,8 @@
     @Test
     public void testQueriesDifferentProvider_Filters() throws Exception {
         final AppsFilter appsFilter =
-                new AppsFilter(mStateProvider, mFeatureConfigMock, new String[]{}, false, null);
+                new AppsFilter(mStateProvider, mFeatureConfigMock, new String[]{}, false, null,
+                        mMockExecutor);
         simulateAddBasicAndroid(appsFilter);
         appsFilter.onSystemReady();
 
@@ -294,7 +307,8 @@
     @Test
     public void testQueriesProviderWithSemiColon_FilterMatches() throws Exception {
         final AppsFilter appsFilter =
-                new AppsFilter(mStateProvider, mFeatureConfigMock, new String[]{}, false, null);
+                new AppsFilter(mStateProvider, mFeatureConfigMock, new String[]{}, false, null,
+                        mMockExecutor);
         simulateAddBasicAndroid(appsFilter);
         appsFilter.onSystemReady();
 
@@ -312,7 +326,8 @@
     @Test
     public void testQueriesAction_NoMatchingAction_Filters() throws Exception {
         final AppsFilter appsFilter =
-                new AppsFilter(mStateProvider, mFeatureConfigMock, new String[]{}, false, null);
+                new AppsFilter(mStateProvider, mFeatureConfigMock, new String[]{}, false, null,
+                        mMockExecutor);
         simulateAddBasicAndroid(appsFilter);
         appsFilter.onSystemReady();
 
@@ -328,7 +343,8 @@
     @Test
     public void testQueriesAction_NoMatchingActionFilterLowSdk_DoesntFilter() throws Exception {
         final AppsFilter appsFilter =
-                new AppsFilter(mStateProvider, mFeatureConfigMock, new String[]{}, false, null);
+                new AppsFilter(mStateProvider, mFeatureConfigMock, new String[]{}, false, null,
+                        mMockExecutor);
         simulateAddBasicAndroid(appsFilter);
         appsFilter.onSystemReady();
 
@@ -348,7 +364,8 @@
     @Test
     public void testNoQueries_Filters() throws Exception {
         final AppsFilter appsFilter =
-                new AppsFilter(mStateProvider, mFeatureConfigMock, new String[]{}, false, null);
+                new AppsFilter(mStateProvider, mFeatureConfigMock, new String[]{}, false, null,
+                        mMockExecutor);
         simulateAddBasicAndroid(appsFilter);
         appsFilter.onSystemReady();
 
@@ -364,7 +381,8 @@
     @Test
     public void testForceQueryable_DoesntFilter() throws Exception {
         final AppsFilter appsFilter =
-                new AppsFilter(mStateProvider, mFeatureConfigMock, new String[]{}, false, null);
+                new AppsFilter(mStateProvider, mFeatureConfigMock, new String[]{}, false, null,
+                        mMockExecutor);
         simulateAddBasicAndroid(appsFilter);
         appsFilter.onSystemReady();
 
@@ -381,7 +399,7 @@
     public void testForceQueryableByDevice_SystemCaller_DoesntFilter() throws Exception {
         final AppsFilter appsFilter =
                 new AppsFilter(mStateProvider, mFeatureConfigMock, new String[]{"com.some.package"},
-                        false, null);
+                        false, null, mMockExecutor);
         simulateAddBasicAndroid(appsFilter);
         appsFilter.onSystemReady();
 
@@ -399,7 +417,8 @@
     @Test
     public void testSystemSignedTarget_DoesntFilter() throws CertificateException {
         final AppsFilter appsFilter =
-                new AppsFilter(mStateProvider, mFeatureConfigMock, new String[]{}, false, null);
+                new AppsFilter(mStateProvider, mFeatureConfigMock, new String[]{}, false, null,
+                        mMockExecutor);
         appsFilter.onSystemReady();
 
         final Signature frameworkSignature = Mockito.mock(Signature.class);
@@ -428,7 +447,7 @@
     public void testForceQueryableByDevice_NonSystemCaller_Filters() throws Exception {
         final AppsFilter appsFilter =
                 new AppsFilter(mStateProvider, mFeatureConfigMock, new String[]{"com.some.package"},
-                        false, null);
+                        false, null, mMockExecutor);
         simulateAddBasicAndroid(appsFilter);
         appsFilter.onSystemReady();
 
@@ -446,7 +465,7 @@
     public void testSystemQueryable_DoesntFilter() throws Exception {
         final AppsFilter appsFilter =
                 new AppsFilter(mStateProvider, mFeatureConfigMock, new String[]{},
-                        true /* system force queryable */, null);
+                        true /* system force queryable */, null, mMockExecutor);
         simulateAddBasicAndroid(appsFilter);
         appsFilter.onSystemReady();
 
@@ -463,7 +482,8 @@
     @Test
     public void testQueriesPackage_DoesntFilter() throws Exception {
         final AppsFilter appsFilter =
-                new AppsFilter(mStateProvider, mFeatureConfigMock, new String[]{}, false, null);
+                new AppsFilter(mStateProvider, mFeatureConfigMock, new String[]{}, false, null,
+                        mMockExecutor);
         simulateAddBasicAndroid(appsFilter);
         appsFilter.onSystemReady();
 
@@ -481,7 +501,8 @@
         when(mFeatureConfigMock.packageIsEnabled(any(AndroidPackage.class)))
                 .thenReturn(false);
         final AppsFilter appsFilter =
-                new AppsFilter(mStateProvider, mFeatureConfigMock, new String[]{}, false, null);
+                new AppsFilter(mStateProvider, mFeatureConfigMock, new String[]{}, false, null,
+                        mMockExecutor);
         simulateAddBasicAndroid(appsFilter);
         appsFilter.onSystemReady();
 
@@ -497,7 +518,8 @@
     @Test
     public void testSystemUid_DoesntFilter() throws Exception {
         final AppsFilter appsFilter =
-                new AppsFilter(mStateProvider, mFeatureConfigMock, new String[]{}, false, null);
+                new AppsFilter(mStateProvider, mFeatureConfigMock, new String[]{}, false, null,
+                        mMockExecutor);
         simulateAddBasicAndroid(appsFilter);
         appsFilter.onSystemReady();
 
@@ -512,7 +534,8 @@
     @Test
     public void testSystemUidSecondaryUser_DoesntFilter() throws Exception {
         final AppsFilter appsFilter =
-                new AppsFilter(mStateProvider, mFeatureConfigMock, new String[]{}, false, null);
+                new AppsFilter(mStateProvider, mFeatureConfigMock, new String[]{}, false, null,
+                        mMockExecutor);
         simulateAddBasicAndroid(appsFilter);
         appsFilter.onSystemReady();
 
@@ -528,7 +551,8 @@
     @Test
     public void testNonSystemUid_NoCallingSetting_Filters() throws Exception {
         final AppsFilter appsFilter =
-                new AppsFilter(mStateProvider, mFeatureConfigMock, new String[]{}, false, null);
+                new AppsFilter(mStateProvider, mFeatureConfigMock, new String[]{}, false, null,
+                        mMockExecutor);
         simulateAddBasicAndroid(appsFilter);
         appsFilter.onSystemReady();
 
@@ -542,7 +566,8 @@
     @Test
     public void testNoTargetPackage_filters() throws Exception {
         final AppsFilter appsFilter =
-                new AppsFilter(mStateProvider, mFeatureConfigMock, new String[]{}, false, null);
+                new AppsFilter(mStateProvider, mFeatureConfigMock, new String[]{}, false, null,
+                        mMockExecutor);
         simulateAddBasicAndroid(appsFilter);
         appsFilter.onSystemReady();
 
@@ -550,7 +575,6 @@
                 .setAppId(DUMMY_TARGET_APPID)
                 .setName("com.some.package")
                 .setCodePath("/")
-                .setResourcePath("/")
                 .setPVersionCode(1L)
                 .build();
         PackageSetting calling = simulateAddPackage(appsFilter,
@@ -600,7 +624,8 @@
                         }
                         return Collections.emptyMap();
                     }
-                });
+                },
+                mMockExecutor);
         simulateAddBasicAndroid(appsFilter);
         appsFilter.onSystemReady();
 
@@ -672,7 +697,8 @@
                         }
                         return Collections.emptyMap();
                     }
-                });
+                },
+                mMockExecutor);
         simulateAddBasicAndroid(appsFilter);
         appsFilter.onSystemReady();
 
@@ -697,7 +723,8 @@
     @Test
     public void testInitiatingApp_DoesntFilter() throws Exception {
         final AppsFilter appsFilter =
-                new AppsFilter(mStateProvider, mFeatureConfigMock, new String[]{}, false, null);
+                new AppsFilter(mStateProvider, mFeatureConfigMock, new String[]{}, false, null,
+                        mMockExecutor);
         simulateAddBasicAndroid(appsFilter);
         appsFilter.onSystemReady();
 
@@ -713,7 +740,8 @@
     @Test
     public void testUninstalledInitiatingApp_Filters() throws Exception {
         final AppsFilter appsFilter =
-                new AppsFilter(mStateProvider, mFeatureConfigMock, new String[]{}, false, null);
+                new AppsFilter(mStateProvider, mFeatureConfigMock, new String[]{}, false, null,
+                        mMockExecutor);
         simulateAddBasicAndroid(appsFilter);
         appsFilter.onSystemReady();
 
@@ -729,7 +757,8 @@
     @Test
     public void testOriginatingApp_Filters() throws Exception {
         final AppsFilter appsFilter =
-                new AppsFilter(mStateProvider, mFeatureConfigMock, new String[]{}, false, null);
+                new AppsFilter(mStateProvider, mFeatureConfigMock, new String[]{}, false, null,
+                        mMockExecutor);
         simulateAddBasicAndroid(appsFilter);
         appsFilter.onSystemReady();
 
@@ -745,7 +774,8 @@
     @Test
     public void testInstallingApp_DoesntFilter() throws Exception {
         final AppsFilter appsFilter =
-                new AppsFilter(mStateProvider, mFeatureConfigMock, new String[]{}, false, null);
+                new AppsFilter(mStateProvider, mFeatureConfigMock, new String[]{}, false, null,
+                        mMockExecutor);
         simulateAddBasicAndroid(appsFilter);
         appsFilter.onSystemReady();
 
@@ -761,7 +791,8 @@
     @Test
     public void testInstrumentation_DoesntFilter() throws Exception {
         final AppsFilter appsFilter =
-                new AppsFilter(mStateProvider, mFeatureConfigMock, new String[]{}, false, null);
+                new AppsFilter(mStateProvider, mFeatureConfigMock, new String[]{}, false, null,
+                        mMockExecutor);
         simulateAddBasicAndroid(appsFilter);
         appsFilter.onSystemReady();
 
@@ -783,7 +814,8 @@
     @Test
     public void testWhoCanSee() throws Exception {
         final AppsFilter appsFilter =
-                new AppsFilter(mStateProvider, mFeatureConfigMock, new String[]{}, false, null);
+                new AppsFilter(mStateProvider, mFeatureConfigMock, new String[]{}, false, null,
+                        mMockExecutor);
         simulateAddBasicAndroid(appsFilter);
         appsFilter.onSystemReady();
 
@@ -874,7 +906,6 @@
                 .setAppId(appId)
                 .setName(newPkg.getPackageName())
                 .setCodePath("/")
-                .setResourcePath("/")
                 .setPVersionCode(1L);
         final PackageSetting setting =
                 (action == null ? settingBuilder : action.withBuilder(settingBuilder)).build();
diff --git a/services/tests/servicestests/src/com/android/server/pm/KeySetManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/pm/KeySetManagerServiceTest.java
index 164bd72..90edaef 100644
--- a/services/tests/servicestests/src/com/android/server/pm/KeySetManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/KeySetManagerServiceTest.java
@@ -38,8 +38,7 @@
 
     public PackageSetting generateFakePackageSetting(String name) {
         return new PackageSetting(name, name, new File(mContext.getCacheDir(), "fakeCodePath"),
-                new File(mContext.getCacheDir(), "fakeResPath"), "", "", "",
-                "", 1, 0, 0, 0 /*sharedUserId*/, null /*usesStaticLibraries*/,
+                "", "", "", "", 1, 0, 0, 0 /*sharedUserId*/, null /*usesStaticLibraries*/,
                 null /*usesStaticLibrariesVersions*/, null /*mimeGroups*/);
     }
 
diff --git a/services/tests/servicestests/src/com/android/server/pm/PackageManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/pm/PackageManagerServiceTest.java
index c4e25b5..35d6f47 100644
--- a/services/tests/servicestests/src/com/android/server/pm/PackageManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/PackageManagerServiceTest.java
@@ -48,7 +48,7 @@
             public void sendPackageBroadcast(final String action, final String pkg,
                     final Bundle extras, final int flags, final String targetPkg,
                     final IIntentReceiver finishedReceiver, final int[] userIds,
-                    int[] instantUserIds, SparseArray<int[]> broadcastWhitelist) {
+                    int[] instantUserIds, SparseArray<int[]> broadcastAllowList) {
             }
 
             public void sendPackageAddedForNewUsers(String packageName,
@@ -86,8 +86,7 @@
         // Create a real (non-null) PackageSetting and confirm that the removed
         // users are copied properly
         setting = new PackageSetting("name", "realName", new File("codePath"),
-                new File("resourcePath"), "legacyNativeLibraryPathString",
-                "primaryCpuAbiString", "secondaryCpuAbiString",
+                "legacyNativeLibraryPathString", "primaryCpuAbiString", "secondaryCpuAbiString",
                 "cpuAbiOverrideString", 0, 0, 0, 0,
                 null, null, null);
         pri.populateUsers(new int[] {
diff --git a/services/tests/servicestests/src/com/android/server/pm/PackageManagerSettingsTests.java b/services/tests/servicestests/src/com/android/server/pm/PackageManagerSettingsTests.java
index aa92ba4..0bf06bb 100644
--- a/services/tests/servicestests/src/com/android/server/pm/PackageManagerSettingsTests.java
+++ b/services/tests/servicestests/src/com/android/server/pm/PackageManagerSettingsTests.java
@@ -439,7 +439,6 @@
                 PACKAGE_NAME,
                 REAL_PACKAGE_NAME,
                 INITIAL_CODE_PATH /*codePath*/,
-                INITIAL_CODE_PATH /*resourcePath*/,
                 null /*legacyNativeLibraryPathString*/,
                 "x86_64" /*primaryCpuAbiString*/,
                 "x86" /*secondaryCpuAbiString*/,
@@ -461,7 +460,6 @@
                 PACKAGE_NAME /*pkgName*/,
                 REAL_PACKAGE_NAME /*realPkgName*/,
                 INITIAL_CODE_PATH /*codePath*/,
-                INITIAL_CODE_PATH /*resourcePath*/,
                 null /*legacyNativeLibraryPathString*/,
                 "x86_64" /*primaryCpuAbiString*/,
                 "x86" /*secondaryCpuAbiString*/,
@@ -477,7 +475,6 @@
                 PACKAGE_NAME /*pkgName*/,
                 REAL_PACKAGE_NAME /*realPkgName*/,
                 UPDATED_CODE_PATH /*codePath*/,
-                UPDATED_CODE_PATH /*resourcePath*/,
                 null /*legacyNativeLibraryPathString*/,
                 null /*primaryCpuAbiString*/,
                 null /*secondaryCpuAbiString*/,
@@ -507,7 +504,6 @@
                 null /*disabledPkg*/,
                 null /*sharedUser*/,
                 UPDATED_CODE_PATH /*codePath*/,
-                UPDATED_CODE_PATH /*resourcePath*/,
                 null /*legacyNativeLibraryPath*/,
                 "arm64-v8a" /*primaryCpuAbi*/,
                 "armeabi" /*secondaryCpuAbi*/,
@@ -541,7 +537,6 @@
                 null /*disabledPkg*/,
                 null /*sharedUser*/,
                 UPDATED_CODE_PATH /*codePath*/,
-                UPDATED_CODE_PATH /*resourcePath*/,
                 null /*legacyNativeLibraryPath*/,
                 "arm64-v8a" /*primaryCpuAbi*/,
                 "armeabi" /*secondaryCpuAbi*/,
@@ -581,7 +576,6 @@
                     null /*disabledPkg*/,
                     testUserSetting01 /*sharedUser*/,
                     UPDATED_CODE_PATH /*codePath*/,
-                    null /*resourcePath*/,
                     null /*legacyNativeLibraryPath*/,
                     "arm64-v8a" /*primaryCpuAbi*/,
                     "armeabi" /*secondaryCpuAbi*/,
@@ -609,7 +603,6 @@
                 null /*realPkgName*/,
                 null /*sharedUser*/,
                 UPDATED_CODE_PATH /*codePath*/,
-                UPDATED_CODE_PATH /*resourcePath*/,
                 null /*legacyNativeLibraryPath*/,
                 "arm64-v8a" /*primaryCpuAbi*/,
                 "armeabi" /*secondaryCpuAbi*/,
@@ -624,12 +617,11 @@
                 null /*usesStaticLibraries*/,
                 null /*usesStaticLibrariesVersions*/,
                 null /*mimeGroups*/);
-        assertThat(testPkgSetting01.codePath, is(UPDATED_CODE_PATH));
+        assertThat(testPkgSetting01.getCodePath(), is(UPDATED_CODE_PATH));
         assertThat(testPkgSetting01.name, is(PACKAGE_NAME));
         assertThat(testPkgSetting01.pkgFlags, is(ApplicationInfo.FLAG_SYSTEM));
         assertThat(testPkgSetting01.pkgPrivateFlags, is(ApplicationInfo.PRIVATE_FLAG_PRIVILEGED));
         assertThat(testPkgSetting01.primaryCpuAbiString, is("arm64-v8a"));
-        assertThat(testPkgSetting01.resourcePath, is(UPDATED_CODE_PATH));
         assertThat(testPkgSetting01.secondaryCpuAbiString, is("armeabi"));
         // signatures object must be different
         assertNotSame(testPkgSetting01.signatures, originalSignatures);
@@ -649,7 +641,6 @@
                 null /*realPkgName*/,
                 null /*sharedUser*/,
                 INITIAL_CODE_PATH /*codePath*/,
-                INITIAL_CODE_PATH /*resourcePath*/,
                 null /*legacyNativeLibraryPath*/,
                 "x86_64" /*primaryCpuAbiString*/,
                 "x86" /*secondaryCpuAbiString*/,
@@ -665,12 +656,11 @@
                 null /*usesStaticLibrariesVersions*/,
                 null /*mimeGroups*/);
         assertThat(testPkgSetting01.appId, is(0));
-        assertThat(testPkgSetting01.codePath, is(INITIAL_CODE_PATH));
+        assertThat(testPkgSetting01.getCodePath(), is(INITIAL_CODE_PATH));
         assertThat(testPkgSetting01.name, is(PACKAGE_NAME));
         assertThat(testPkgSetting01.pkgFlags, is(0));
         assertThat(testPkgSetting01.pkgPrivateFlags, is(0));
         assertThat(testPkgSetting01.primaryCpuAbiString, is("x86_64"));
-        assertThat(testPkgSetting01.resourcePath, is(INITIAL_CODE_PATH));
         assertThat(testPkgSetting01.secondaryCpuAbiString, is("x86"));
         assertThat(testPkgSetting01.versionCode, is(INITIAL_VERSION_CODE));
         // by default, the package is considered stopped
@@ -695,7 +685,6 @@
                 null /*realPkgName*/,
                 testUserSetting01 /*sharedUser*/,
                 INITIAL_CODE_PATH /*codePath*/,
-                INITIAL_CODE_PATH /*resourcePath*/,
                 null /*legacyNativeLibraryPath*/,
                 "x86_64" /*primaryCpuAbiString*/,
                 "x86" /*secondaryCpuAbiString*/,
@@ -711,12 +700,11 @@
                 null /*usesStaticLibrariesVersions*/,
                 null /*mimeGroups*/);
         assertThat(testPkgSetting01.appId, is(10064));
-        assertThat(testPkgSetting01.codePath, is(INITIAL_CODE_PATH));
+        assertThat(testPkgSetting01.getCodePath(), is(INITIAL_CODE_PATH));
         assertThat(testPkgSetting01.name, is(PACKAGE_NAME));
         assertThat(testPkgSetting01.pkgFlags, is(0));
         assertThat(testPkgSetting01.pkgPrivateFlags, is(0));
         assertThat(testPkgSetting01.primaryCpuAbiString, is("x86_64"));
-        assertThat(testPkgSetting01.resourcePath, is(INITIAL_CODE_PATH));
         assertThat(testPkgSetting01.secondaryCpuAbiString, is("x86"));
         assertThat(testPkgSetting01.versionCode, is(INITIAL_VERSION_CODE));
         final PackageUserState userState = testPkgSetting01.readUserState(0);
@@ -738,7 +726,6 @@
                 null /*realPkgName*/,
                 null /*sharedUser*/,
                 UPDATED_CODE_PATH /*codePath*/,
-                UPDATED_CODE_PATH /*resourcePath*/,
                 null /*legacyNativeLibraryPath*/,
                 "arm64-v8a" /*primaryCpuAbi*/,
                 "armeabi" /*secondaryCpuAbi*/,
@@ -754,12 +741,11 @@
                 null /*usesStaticLibrariesVersions*/,
                 null /*mimeGroups*/);
         assertThat(testPkgSetting01.appId, is(10064));
-        assertThat(testPkgSetting01.codePath, is(UPDATED_CODE_PATH));
+        assertThat(testPkgSetting01.getCodePath(), is(UPDATED_CODE_PATH));
         assertThat(testPkgSetting01.name, is(PACKAGE_NAME));
         assertThat(testPkgSetting01.pkgFlags, is(0));
         assertThat(testPkgSetting01.pkgPrivateFlags, is(0));
         assertThat(testPkgSetting01.primaryCpuAbiString, is("arm64-v8a"));
-        assertThat(testPkgSetting01.resourcePath, is(UPDATED_CODE_PATH));
         assertThat(testPkgSetting01.secondaryCpuAbiString, is("armeabi"));
         assertNotSame(testPkgSetting01.signatures, disabledSignatures);
         assertThat(testPkgSetting01.versionCode, is(UPDATED_VERSION_CODE));
@@ -806,10 +792,10 @@
     private void verifySettingCopy(PackageSetting origPkgSetting, PackageSetting testPkgSetting) {
         assertThat(origPkgSetting, is(not(testPkgSetting)));
         assertThat(origPkgSetting.appId, is(testPkgSetting.appId));
-        assertSame(origPkgSetting.codePath, testPkgSetting.codePath);
-        assertThat(origPkgSetting.codePath, is(testPkgSetting.codePath));
-        assertSame(origPkgSetting.codePathString, testPkgSetting.codePathString);
-        assertThat(origPkgSetting.codePathString, is(testPkgSetting.codePathString));
+        assertSame(origPkgSetting.getCodePath(), testPkgSetting.getCodePath());
+        assertThat(origPkgSetting.getCodePath(), is(testPkgSetting.getCodePath()));
+        assertSame(origPkgSetting.getCodePathString(), testPkgSetting.getCodePathString());
+        assertThat(origPkgSetting.getCodePathString(), is(testPkgSetting.getCodePathString()));
         assertSame(origPkgSetting.cpuAbiOverrideString, testPkgSetting.cpuAbiOverrideString);
         assertThat(origPkgSetting.cpuAbiOverrideString, is(testPkgSetting.cpuAbiOverrideString));
         assertThat(origPkgSetting.firstInstallTime, is(testPkgSetting.firstInstallTime));
@@ -823,7 +809,9 @@
                 testPkgSetting.legacyNativeLibraryPathString);
         assertThat(origPkgSetting.legacyNativeLibraryPathString,
                 is(testPkgSetting.legacyNativeLibraryPathString));
-        assertNotSame(origPkgSetting.mimeGroups, testPkgSetting.mimeGroups);
+        if (origPkgSetting.mimeGroups != null) {
+            assertNotSame(origPkgSetting.mimeGroups, testPkgSetting.mimeGroups);
+        }
         assertThat(origPkgSetting.mimeGroups, is(testPkgSetting.mimeGroups));
         assertNotSame(origPkgSetting.mPermissionsState, testPkgSetting.mPermissionsState);
         assertThat(origPkgSetting.mPermissionsState, is(testPkgSetting.mPermissionsState));
@@ -839,10 +827,6 @@
         assertSame(origPkgSetting.primaryCpuAbiString, testPkgSetting.primaryCpuAbiString);
         assertThat(origPkgSetting.primaryCpuAbiString, is(testPkgSetting.primaryCpuAbiString));
         assertThat(origPkgSetting.realName, is(testPkgSetting.realName));
-        assertSame(origPkgSetting.resourcePath, testPkgSetting.resourcePath);
-        assertThat(origPkgSetting.resourcePath, is(testPkgSetting.resourcePath));
-        assertSame(origPkgSetting.resourcePathString, testPkgSetting.resourcePathString);
-        assertThat(origPkgSetting.resourcePathString, is(testPkgSetting.resourcePathString));
         assertSame(origPkgSetting.secondaryCpuAbiString, testPkgSetting.secondaryCpuAbiString);
         assertThat(origPkgSetting.secondaryCpuAbiString, is(testPkgSetting.secondaryCpuAbiString));
         assertSame(origPkgSetting.sharedUser, testPkgSetting.sharedUser);
@@ -874,7 +858,6 @@
                 PACKAGE_NAME,
                 REAL_PACKAGE_NAME,
                 INITIAL_CODE_PATH /*codePath*/,
-                INITIAL_CODE_PATH /*resourcePath*/,
                 null /*legacyNativeLibraryPathString*/,
                 "x86_64" /*primaryCpuAbiString*/,
                 "x86" /*secondaryCpuAbiString*/,
@@ -893,7 +876,6 @@
                 packageName,
                 packageName,
                 INITIAL_CODE_PATH /*codePath*/,
-                INITIAL_CODE_PATH /*resourcePath*/,
                 null /*legacyNativeLibraryPathString*/,
                 "x86_64" /*primaryCpuAbiString*/,
                 "x86" /*secondaryCpuAbiString*/,
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 b0b5386..2651cfa 100644
--- a/services/tests/servicestests/src/com/android/server/pm/PackageParserTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/PackageParserTest.java
@@ -312,8 +312,7 @@
 
     private static PackageSetting mockPkgSetting(AndroidPackage pkg) {
         return new PackageSetting(pkg.getPackageName(), pkg.getRealPackage(),
-                new File(pkg.getCodePath()), new File(pkg.getCodePath()), null,
-                pkg.getPrimaryCpuAbi(), pkg.getSecondaryCpuAbi(),
+                new File(pkg.getCodePath()), null, pkg.getPrimaryCpuAbi(), pkg.getSecondaryCpuAbi(),
                 null, pkg.getVersionCode(),
                 PackageInfoUtils.appInfoFlags(pkg, null),
                 PackageInfoUtils.appInfoPrivateFlags(pkg, null),
diff --git a/services/tests/servicestests/src/com/android/server/pm/PackageSettingBuilder.java b/services/tests/servicestests/src/com/android/server/pm/PackageSettingBuilder.java
index d12ea894..f8e92ad 100644
--- a/services/tests/servicestests/src/com/android/server/pm/PackageSettingBuilder.java
+++ b/services/tests/servicestests/src/com/android/server/pm/PackageSettingBuilder.java
@@ -30,7 +30,6 @@
     private String mName;
     private String mRealName;
     private String mCodePath;
-    private String mResourcePath;
     private String mLegacyNativeLibraryPathString;
     private String mPrimaryCpuAbiString;
     private String mSecondaryCpuAbiString;
@@ -74,11 +73,6 @@
         return this;
     }
 
-    public PackageSettingBuilder setResourcePath(String resourcePath) {
-        this.mResourcePath = resourcePath;
-        return this;
-    }
-
     public PackageSettingBuilder setLegacyNativeLibraryPathString(
             String legacyNativeLibraryPathString) {
         this.mLegacyNativeLibraryPathString = legacyNativeLibraryPathString;
@@ -162,10 +156,10 @@
 
     public PackageSetting build() {
         final PackageSetting packageSetting = new PackageSetting(mName, mRealName,
-                new File(mCodePath), new File(mResourcePath),
-                mLegacyNativeLibraryPathString, mPrimaryCpuAbiString, mSecondaryCpuAbiString,
-                mCpuAbiOverrideString, mPVersionCode, mPkgFlags, mPrivateFlags, mSharedUserId,
-                mUsesStaticLibraries, mUsesStaticLibrariesVersions, mMimeGroups);
+                new File(mCodePath), mLegacyNativeLibraryPathString, mPrimaryCpuAbiString,
+                mSecondaryCpuAbiString, mCpuAbiOverrideString, mPVersionCode, mPkgFlags,
+                mPrivateFlags, mSharedUserId, mUsesStaticLibraries, mUsesStaticLibrariesVersions,
+                mMimeGroups);
         packageSetting.signatures = mSigningDetails != null
                 ? new PackageSignatures(mSigningDetails)
                 : new PackageSignatures();
diff --git a/services/tests/servicestests/src/com/android/server/pm/PackageSignaturesTest.java b/services/tests/servicestests/src/com/android/server/pm/PackageSignaturesTest.java
index 55bc681..7108490 100644
--- a/services/tests/servicestests/src/com/android/server/pm/PackageSignaturesTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/PackageSignaturesTest.java
@@ -465,9 +465,9 @@
         // Generic PackageSetting object with values from a test app installed on a device to be
         // used to test the methods under the PackageSignatures signatures data member.
         File appPath = new File("/data/app/app");
-        PackageSetting result = new PackageSetting("test.app", null, appPath, appPath,
-                "/data/app/app", null, null, null,
-                1, 940097092, 0, 0 /*userId*/, null, null, null /*mimeGroups*/);
+        PackageSetting result = new PackageSetting("test.app", null, appPath,
+                "/data/app/app", null, null, null, 1, 940097092, 0, 0 /*userId*/, null, null,
+                null /*mimeGroups*/);
         return result;
     }
 }
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 e7eff00..56dddb0 100644
--- a/services/tests/servicestests/src/com/android/server/pm/ScanTests.java
+++ b/services/tests/servicestests/src/com/android/server/pm/ScanTests.java
@@ -484,8 +484,7 @@
     private static PackageSettingBuilder createBasicPackageSettingBuilder(String packageName) {
         return new PackageSettingBuilder()
                 .setName(packageName)
-                .setCodePath(createCodePath(packageName))
-                .setResourcePath(createCodePath(packageName));
+                .setCodePath(createCodePath(packageName));
     }
 
     private static ScanRequestBuilder createBasicScanRequestBuilder(ParsingPackage pkg) {
@@ -534,8 +533,7 @@
                 arrayContaining("some.static.library", "some.other.static.library"));
         assertThat(pkgSetting.usesStaticLibrariesVersions, is(new long[]{234L, 456L}));
         assertThat(pkgSetting.pkg, is(scanResult.request.parsedPackage));
-        assertThat(pkgSetting.codePath, is(new File(createCodePath(packageName))));
-        assertThat(pkgSetting.resourcePath, is(new File(createCodePath(packageName))));
+        assertThat(pkgSetting.getCodePath(), is(new File(createCodePath(packageName))));
         assertThat(pkgSetting.versionCode, is(PackageInfo.composeLongVersionCode(1, 2345)));
     }
 
diff --git a/services/tests/servicestests/src/com/android/server/pm/parsing/AndroidPackageInfoFlagBehaviorTest.kt b/services/tests/servicestests/src/com/android/server/pm/parsing/AndroidPackageInfoFlagBehaviorTest.kt
index 946f27e..d36dcce 100644
--- a/services/tests/servicestests/src/com/android/server/pm/parsing/AndroidPackageInfoFlagBehaviorTest.kt
+++ b/services/tests/servicestests/src/com/android/server/pm/parsing/AndroidPackageInfoFlagBehaviorTest.kt
@@ -20,7 +20,7 @@
 import android.content.pm.PackageInfo
 import android.content.pm.PackageManager
 import android.content.pm.PackageParser
-import android.platform.test.annotations.Presubmit
+import android.platform.test.annotations.Postsubmit
 import com.android.server.pm.parsing.AndroidPackageInfoFlagBehaviorTest.Companion.Param.Companion.appInfo
 import com.android.server.pm.parsing.AndroidPackageInfoFlagBehaviorTest.Companion.Param.Companion.pkgInfo
 import com.android.server.pm.parsing.pkg.AndroidPackage
@@ -38,7 +38,7 @@
  * This test has to be updated manually whenever the info generation behavior changes, since
  * there's no single place where flag -> field is defined besides this test.
  */
-@Presubmit
+@Postsubmit
 @RunWith(Parameterized::class)
 class AndroidPackageInfoFlagBehaviorTest : AndroidPackageParsingTestBase() {
 
diff --git a/services/tests/servicestests/src/com/android/server/pm/parsing/AndroidPackageParsingEquivalenceTest.kt b/services/tests/servicestests/src/com/android/server/pm/parsing/AndroidPackageParsingEquivalenceTest.kt
index 9f9ec31..574921c 100644
--- a/services/tests/servicestests/src/com/android/server/pm/parsing/AndroidPackageParsingEquivalenceTest.kt
+++ b/services/tests/servicestests/src/com/android/server/pm/parsing/AndroidPackageParsingEquivalenceTest.kt
@@ -17,10 +17,9 @@
 package com.android.server.pm.parsing
 
 import android.content.pm.PackageManager
-import android.platform.test.annotations.Presubmit
+import android.platform.test.annotations.Postsubmit
 import androidx.test.filters.LargeTest
 import com.google.common.truth.Expect
-
 import org.junit.Rule
 import org.junit.Test
 
@@ -28,7 +27,7 @@
  * Collects APKs from the device and verifies that the new parsing behavior outputs
  * the same exposed Info object as the old parsing logic.
  */
-@Presubmit
+@Postsubmit
 class AndroidPackageParsingEquivalenceTest : AndroidPackageParsingTestBase() {
 
     @get:Rule
diff --git a/services/tests/servicestests/src/com/android/server/power/ThermalManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/power/ThermalManagerServiceTest.java
index 083df28..aaa74dd 100644
--- a/services/tests/servicestests/src/com/android/server/power/ThermalManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/power/ThermalManagerServiceTest.java
@@ -284,28 +284,29 @@
     @Test
     public void testNotify() throws RemoteException {
         int status = Temperature.THROTTLING_SEVERE;
+        // Should only notify event not status
         Temperature newBattery = new Temperature(50, Temperature.TYPE_BATTERY, "batt", status);
         mFakeHal.mCallback.onValues(newBattery);
         verify(mEventListener1, timeout(CALLBACK_TIMEOUT_MILLI_SEC)
                 .times(1)).notifyThrottling(newBattery);
         verify(mStatusListener1, timeout(CALLBACK_TIMEOUT_MILLI_SEC)
-                .times(1)).onStatusChange(status);
+                .times(0)).onStatusChange(anyInt());
         verify(mEventListener2, timeout(CALLBACK_TIMEOUT_MILLI_SEC)
                 .times(0)).notifyThrottling(newBattery);
         verify(mStatusListener2, timeout(CALLBACK_TIMEOUT_MILLI_SEC)
-                .times(1)).onStatusChange(status);
+                .times(0)).onStatusChange(anyInt());
         resetListenerMock();
-        // Should only notify event not status
+        // Notify both event and status
         Temperature newSkin = new Temperature(50, Temperature.TYPE_SKIN, "skin1", status);
         mFakeHal.mCallback.onValues(newSkin);
         verify(mEventListener1, timeout(CALLBACK_TIMEOUT_MILLI_SEC)
                 .times(1)).notifyThrottling(newSkin);
         verify(mStatusListener1, timeout(CALLBACK_TIMEOUT_MILLI_SEC)
-                .times(0)).onStatusChange(anyInt());
+                .times(1)).onStatusChange(status);
         verify(mEventListener2, timeout(CALLBACK_TIMEOUT_MILLI_SEC)
                 .times(1)).notifyThrottling(newSkin);
         verify(mStatusListener2, timeout(CALLBACK_TIMEOUT_MILLI_SEC)
-                .times(0)).onStatusChange(anyInt());
+                .times(1)).onStatusChange(status);
         resetListenerMock();
         // Back to None, should only notify event not status
         status = Temperature.THROTTLING_NONE;
@@ -345,10 +346,13 @@
 
     @Test
     public void testGetCurrentStatus() throws RemoteException {
-        int status = Temperature.THROTTLING_EMERGENCY;
+        int status = Temperature.THROTTLING_SEVERE;
         Temperature newSkin = new Temperature(100, Temperature.TYPE_SKIN, "skin1", status);
         mFakeHal.mCallback.onValues(newSkin);
         assertEquals(status, mService.mService.getCurrentThermalStatus());
+        int battStatus = Temperature.THROTTLING_EMERGENCY;
+        Temperature newBattery = new Temperature(60, Temperature.TYPE_BATTERY, "batt", battStatus);
+        assertEquals(status, mService.mService.getCurrentThermalStatus());
     }
 
     @Test
diff --git a/services/tests/servicestests/src/com/android/server/timezonedetector/FakeTimeZoneDetectorStrategy.java b/services/tests/servicestests/src/com/android/server/timezonedetector/FakeTimeZoneDetectorStrategy.java
index dcf3190..e5e9311 100644
--- a/services/tests/servicestests/src/com/android/server/timezonedetector/FakeTimeZoneDetectorStrategy.java
+++ b/services/tests/servicestests/src/com/android/server/timezonedetector/FakeTimeZoneDetectorStrategy.java
@@ -27,6 +27,9 @@
 import android.app.timezonedetector.TimeZoneConfiguration;
 import android.util.IndentingPrintWriter;
 
+import java.util.ArrayList;
+import java.util.List;
+
 class FakeTimeZoneDetectorStrategy implements TimeZoneDetectorStrategy {
 
     private StrategyListener mListener;
@@ -41,6 +44,7 @@
     private TelephonyTimeZoneSuggestion mLastTelephonySuggestion;
     private boolean mHandleAutoTimeZoneConfigChangedCalled;
     private boolean mDumpCalled;
+    private final List<Dumpable> mDumpables = new ArrayList<>();
 
     @Override
     public void setStrategyListener(@NonNull StrategyListener listener) {
@@ -105,7 +109,7 @@
 
     @Override
     public void addDumpable(Dumpable dumpable) {
-        // Stubbed
+        mDumpables.add(dumpable);
     }
 
     @Override
@@ -149,4 +153,8 @@
     void verifyDumpCalled() {
         assertTrue(mDumpCalled);
     }
+
+    void verifyHasDumpable(Dumpable expected) {
+        assertTrue(mDumpables.contains(expected));
+    }
 }
diff --git a/services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorInternalImplTest.java b/services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorInternalImplTest.java
index 0e2c227..e9d57e5 100644
--- a/services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorInternalImplTest.java
+++ b/services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorInternalImplTest.java
@@ -75,6 +75,16 @@
         mFakeTimeZoneDetectorStrategy.verifySuggestGeolocationTimeZoneCalled(timeZoneSuggestion);
     }
 
+    @Test
+    public void testAddDumpable() throws Exception {
+        Dumpable stubbedDumpable = mock(Dumpable.class);
+
+        mTimeZoneDetectorInternal.addDumpable(stubbedDumpable);
+        mTestHandler.assertTotalMessagesEnqueued(0);
+
+        mFakeTimeZoneDetectorStrategy.verifyHasDumpable(stubbedDumpable);
+    }
+
     private static GeolocationTimeZoneSuggestion createGeolocationTimeZoneSuggestion() {
         return new GeolocationTimeZoneSuggestion(ARBITRARY_ZONE_IDS);
     }
diff --git a/services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorServiceTest.java b/services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorServiceTest.java
index 1536345..3a1ec4f 100644
--- a/services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorServiceTest.java
@@ -52,6 +52,7 @@
 
 import java.io.PrintWriter;
 import java.io.StringWriter;
+import java.util.Arrays;
 
 @RunWith(AndroidJUnit4.class)
 public class TimeZoneDetectorServiceTest {
@@ -170,7 +171,7 @@
         ITimeZoneConfigurationListener mockListener = mock(ITimeZoneConfigurationListener.class);
         try {
             mTimeZoneDetectorService.removeConfigurationListener(mockListener);
-            fail();
+            fail("Expected a SecurityException");
         } finally {
             verify(mMockContext).enforceCallingPermission(
                     eq(android.Manifest.permission.WRITE_SECURE_SETTINGS),
@@ -253,6 +254,39 @@
     }
 
     @Test(expected = SecurityException.class)
+    public void testSuggestGeolocationTimeZone_withoutPermission() {
+        doThrow(new SecurityException("Mock"))
+                .when(mMockContext).enforceCallingOrSelfPermission(anyString(), any());
+        GeolocationTimeZoneSuggestion timeZoneSuggestion = createGeolocationTimeZoneSuggestion();
+
+        try {
+            mTimeZoneDetectorService.suggestGeolocationTimeZone(timeZoneSuggestion);
+            fail();
+        } finally {
+            verify(mMockContext).enforceCallingOrSelfPermission(
+                    eq(android.Manifest.permission.SET_TIME_ZONE),
+                    anyString());
+        }
+    }
+
+    @Test
+    public void testSuggestGeolocationTimeZone() throws Exception {
+        doNothing().when(mMockContext).enforceCallingOrSelfPermission(anyString(), any());
+
+        GeolocationTimeZoneSuggestion timeZoneSuggestion = createGeolocationTimeZoneSuggestion();
+
+        mTimeZoneDetectorService.suggestGeolocationTimeZone(timeZoneSuggestion);
+        mTestHandler.assertTotalMessagesEnqueued(1);
+
+        verify(mMockContext).enforceCallingOrSelfPermission(
+                eq(android.Manifest.permission.SET_TIME_ZONE),
+                anyString());
+
+        mTestHandler.waitForMessagesToBeProcessed();
+        mFakeTimeZoneDetectorStrategy.verifySuggestGeolocationTimeZoneCalled(timeZoneSuggestion);
+    }
+
+    @Test(expected = SecurityException.class)
     public void testSuggestManualTimeZone_withoutPermission() {
         doThrow(new SecurityException("Mock"))
                 .when(mMockContext).enforceCallingOrSelfPermission(anyString(), any());
@@ -346,7 +380,7 @@
     }
 
     @Test
-    public void testAutoTimeZoneDetectionChanged() throws Exception {
+    public void testHandleAutoTimeZoneConfigChanged() throws Exception {
         mTimeZoneDetectorService.handleAutoTimeZoneConfigChanged();
         mTestHandler.assertTotalMessagesEnqueued(1);
         mTestHandler.waitForMessagesToBeProcessed();
@@ -370,10 +404,15 @@
     private static TimeZoneCapabilities createTimeZoneCapabilities() {
         return new TimeZoneCapabilities.Builder(ARBITRARY_USER_ID)
                 .setConfigureAutoDetectionEnabled(CAPABILITY_POSSESSED)
+                .setConfigureGeoDetectionEnabled(CAPABILITY_POSSESSED)
                 .setSuggestManualTimeZone(CAPABILITY_POSSESSED)
                 .build();
     }
 
+    private static GeolocationTimeZoneSuggestion createGeolocationTimeZoneSuggestion() {
+        return new GeolocationTimeZoneSuggestion(Arrays.asList("TestZoneId"));
+    }
+
     private static ManualTimeZoneSuggestion createManualTimeZoneSuggestion() {
         return new ManualTimeZoneSuggestion("TestZoneId");
     }
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 6855445..a6caa42 100644
--- a/services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorStrategyImplTest.java
+++ b/services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorStrategyImplTest.java
@@ -41,6 +41,7 @@
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
 
+import android.annotation.Nullable;
 import android.annotation.UserIdInt;
 import android.app.timezonedetector.ManualTimeZoneSuggestion;
 import android.app.timezonedetector.TelephonyTimeZoneSuggestion;
@@ -56,10 +57,10 @@
 import org.junit.Test;
 
 import java.io.StringWriter;
-import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collections;
 import java.util.LinkedList;
+import java.util.List;
 import java.util.Objects;
 import java.util.concurrent.atomic.AtomicBoolean;
 
@@ -93,16 +94,26 @@
                     TELEPHONY_SCORE_HIGHEST),
     };
 
-    private static final TimeZoneConfiguration CONFIG_AUTO_TIME_ZONE_DETECTION_ENABLED =
+    private static final TimeZoneConfiguration CONFIG_AUTO_ENABLED =
             new TimeZoneConfiguration.Builder()
                     .setAutoDetectionEnabled(true)
                     .build();
 
-    private static final TimeZoneConfiguration CONFIG_AUTO_TIME_ZONE_DETECTION_DISABLED =
+    private static final TimeZoneConfiguration CONFIG_AUTO_DISABLED =
             new TimeZoneConfiguration.Builder()
                     .setAutoDetectionEnabled(false)
                     .build();
 
+    private static final TimeZoneConfiguration CONFIG_GEO_DETECTION_DISABLED =
+            new TimeZoneConfiguration.Builder()
+                    .setGeoDetectionEnabled(false)
+                    .build();
+
+    private static final TimeZoneConfiguration CONFIG_GEO_DETECTION_ENABLED =
+            new TimeZoneConfiguration.Builder()
+                    .setGeoDetectionEnabled(true)
+                    .build();
+
     private TimeZoneDetectorStrategyImpl mTimeZoneDetectorStrategy;
     private FakeCallback mFakeCallback;
     private MockStrategyListener mMockStrategyListener;
@@ -120,7 +131,7 @@
     public void testGetCapabilities() {
         new Script()
                 .initializeUser(USER_ID, UserCase.UNRESTRICTED,
-                        CONFIG_AUTO_TIME_ZONE_DETECTION_ENABLED);
+                        CONFIG_AUTO_ENABLED.with(CONFIG_GEO_DETECTION_DISABLED));
         TimeZoneCapabilities expectedCapabilities = mFakeCallback.getCapabilities(USER_ID);
         assertEquals(expectedCapabilities, mTimeZoneDetectorStrategy.getCapabilities(USER_ID));
     }
@@ -129,7 +140,7 @@
     public void testGetConfiguration() {
         new Script()
                 .initializeUser(USER_ID, UserCase.UNRESTRICTED,
-                        CONFIG_AUTO_TIME_ZONE_DETECTION_ENABLED);
+                        CONFIG_AUTO_ENABLED.with(CONFIG_GEO_DETECTION_DISABLED));
         TimeZoneConfiguration expectedConfiguration = mFakeCallback.getConfiguration(USER_ID);
         assertTrue(expectedConfiguration.isComplete());
         assertEquals(expectedConfiguration, mTimeZoneDetectorStrategy.getConfiguration(USER_ID));
@@ -140,20 +151,22 @@
         Script script = new Script();
 
         script.initializeUser(USER_ID, UserCase.UNRESTRICTED,
-                CONFIG_AUTO_TIME_ZONE_DETECTION_ENABLED);
+                CONFIG_AUTO_ENABLED.with(CONFIG_GEO_DETECTION_DISABLED));
         {
             // Check the fake test infra is doing what is expected.
             TimeZoneCapabilities capabilities = mFakeCallback.getCapabilities(USER_ID);
             assertEquals(CAPABILITY_POSSESSED, capabilities.getConfigureAutoDetectionEnabled());
+            assertEquals(CAPABILITY_POSSESSED, capabilities.getConfigureGeoDetectionEnabled());
             assertEquals(CAPABILITY_NOT_APPLICABLE, capabilities.getSuggestManualTimeZone());
         }
 
         script.initializeUser(USER_ID, UserCase.UNRESTRICTED,
-                CONFIG_AUTO_TIME_ZONE_DETECTION_DISABLED);
+                CONFIG_AUTO_DISABLED.with(CONFIG_GEO_DETECTION_DISABLED));
         {
             // Check the fake test infra is doing what is expected.
             TimeZoneCapabilities capabilities = mFakeCallback.getCapabilities(USER_ID);
             assertEquals(CAPABILITY_POSSESSED, capabilities.getConfigureAutoDetectionEnabled());
+            assertEquals(CAPABILITY_POSSESSED, capabilities.getConfigureGeoDetectionEnabled());
             assertEquals(CAPABILITY_POSSESSED, capabilities.getSuggestManualTimeZone());
         }
     }
@@ -163,20 +176,22 @@
         Script script = new Script();
 
         script.initializeUser(USER_ID, UserCase.RESTRICTED,
-                CONFIG_AUTO_TIME_ZONE_DETECTION_ENABLED);
+                CONFIG_AUTO_ENABLED.with(CONFIG_GEO_DETECTION_DISABLED));
         {
             // Check the fake test infra is doing what is expected.
             TimeZoneCapabilities capabilities = mFakeCallback.getCapabilities(USER_ID);
             assertEquals(CAPABILITY_NOT_ALLOWED, capabilities.getConfigureAutoDetectionEnabled());
+            assertEquals(CAPABILITY_NOT_ALLOWED, capabilities.getConfigureGeoDetectionEnabled());
             assertEquals(CAPABILITY_NOT_ALLOWED, capabilities.getSuggestManualTimeZone());
         }
 
         script.initializeUser(USER_ID, UserCase.RESTRICTED,
-                CONFIG_AUTO_TIME_ZONE_DETECTION_DISABLED);
+                CONFIG_AUTO_DISABLED.with(CONFIG_GEO_DETECTION_DISABLED));
         {
             // Check the fake test infra is doing what is expected.
             TimeZoneCapabilities capabilities = mFakeCallback.getCapabilities(USER_ID);
             assertEquals(CAPABILITY_NOT_ALLOWED, capabilities.getConfigureAutoDetectionEnabled());
+            assertEquals(CAPABILITY_NOT_ALLOWED, capabilities.getConfigureGeoDetectionEnabled());
             assertEquals(CAPABILITY_NOT_ALLOWED, capabilities.getSuggestManualTimeZone());
         }
     }
@@ -186,20 +201,22 @@
         Script script = new Script();
 
         script.initializeUser(USER_ID, UserCase.AUTO_DETECT_NOT_SUPPORTED,
-                CONFIG_AUTO_TIME_ZONE_DETECTION_ENABLED);
+                CONFIG_AUTO_ENABLED.with(CONFIG_GEO_DETECTION_DISABLED));
         {
             // Check the fake test infra is doing what is expected.
             TimeZoneCapabilities capabilities = mFakeCallback.getCapabilities(USER_ID);
             assertEquals(CAPABILITY_NOT_SUPPORTED, capabilities.getConfigureAutoDetectionEnabled());
+            assertEquals(CAPABILITY_NOT_SUPPORTED, capabilities.getConfigureGeoDetectionEnabled());
             assertEquals(CAPABILITY_POSSESSED, capabilities.getSuggestManualTimeZone());
         }
 
         script.initializeUser(USER_ID, UserCase.AUTO_DETECT_NOT_SUPPORTED,
-                CONFIG_AUTO_TIME_ZONE_DETECTION_DISABLED);
+                CONFIG_AUTO_DISABLED.with(CONFIG_GEO_DETECTION_DISABLED));
         {
             // Check the fake test infra is doing what is expected.
             TimeZoneCapabilities capabilities = mFakeCallback.getCapabilities(USER_ID);
             assertEquals(CAPABILITY_NOT_SUPPORTED, capabilities.getConfigureAutoDetectionEnabled());
+            assertEquals(CAPABILITY_NOT_SUPPORTED, capabilities.getConfigureGeoDetectionEnabled());
             assertEquals(CAPABILITY_POSSESSED, capabilities.getSuggestManualTimeZone());
         }
     }
@@ -208,42 +225,61 @@
     public void testUpdateConfiguration_unrestricted() {
         Script script = new Script()
                 .initializeUser(USER_ID, UserCase.UNRESTRICTED,
-                        CONFIG_AUTO_TIME_ZONE_DETECTION_ENABLED);
+                        CONFIG_AUTO_ENABLED.with(CONFIG_GEO_DETECTION_DISABLED));
 
         // Set the configuration with auto detection enabled.
-        script.simulateUpdateConfiguration(USER_ID, CONFIG_AUTO_TIME_ZONE_DETECTION_ENABLED);
+        script.simulateUpdateConfiguration(USER_ID, CONFIG_AUTO_ENABLED, true /* expectedResult */);
 
         // Nothing should have happened: it was initialized in this state.
         script.verifyConfigurationNotChanged();
 
         // Update the configuration with auto detection disabled.
-        script.simulateUpdateConfiguration(USER_ID, CONFIG_AUTO_TIME_ZONE_DETECTION_DISABLED);
+        script.simulateUpdateConfiguration(
+                USER_ID, CONFIG_AUTO_DISABLED, true /* expectedResult */);
 
         // The settings should have been changed and the StrategyListener onChange() called.
-        script.verifyConfigurationChangedAndReset(
-                USER_ID, CONFIG_AUTO_TIME_ZONE_DETECTION_DISABLED);
+        script.verifyConfigurationChangedAndReset(USER_ID,
+                CONFIG_AUTO_DISABLED.with(CONFIG_GEO_DETECTION_DISABLED));
 
         // Update the configuration with auto detection enabled.
-        script.simulateUpdateConfiguration(USER_ID, CONFIG_AUTO_TIME_ZONE_DETECTION_ENABLED);
+        script.simulateUpdateConfiguration(USER_ID, CONFIG_AUTO_ENABLED, true /* expectedResult */);
 
         // The settings should have been changed and the StrategyListener onChange() called.
-        script.verifyConfigurationChangedAndReset(USER_ID, CONFIG_AUTO_TIME_ZONE_DETECTION_ENABLED);
+        script.verifyConfigurationChangedAndReset(USER_ID,
+                CONFIG_AUTO_ENABLED.with(CONFIG_GEO_DETECTION_DISABLED));
+
+        // Update the configuration to enable geolocation time zone detection.
+        script.simulateUpdateConfiguration(
+                USER_ID, CONFIG_GEO_DETECTION_ENABLED,  true /* expectedResult */);
+
+        // The settings should have been changed and the StrategyListener onChange() called.
+        script.verifyConfigurationChangedAndReset(USER_ID,
+                CONFIG_AUTO_ENABLED.with(CONFIG_GEO_DETECTION_ENABLED));
     }
 
     @Test
     public void testUpdateConfiguration_restricted() {
         Script script = new Script()
                 .initializeUser(USER_ID, UserCase.RESTRICTED,
-                        CONFIG_AUTO_TIME_ZONE_DETECTION_ENABLED);
+                        CONFIG_AUTO_ENABLED.with(CONFIG_GEO_DETECTION_DISABLED));
 
         // Try to update the configuration with auto detection disabled.
-        script.simulateUpdateConfiguration(USER_ID, CONFIG_AUTO_TIME_ZONE_DETECTION_DISABLED);
+        script.simulateUpdateConfiguration(
+                USER_ID, CONFIG_AUTO_DISABLED, false /* expectedResult */);
 
         // The settings should not have been changed: user shouldn't have the capabilities.
         script.verifyConfigurationNotChanged();
 
         // Update the configuration with auto detection enabled.
-        script.simulateUpdateConfiguration(USER_ID, CONFIG_AUTO_TIME_ZONE_DETECTION_ENABLED);
+        script.simulateUpdateConfiguration(
+                USER_ID, CONFIG_AUTO_ENABLED,  false /* expectedResult */);
+
+        // The settings should not have been changed: user shouldn't have the capabilities.
+        script.verifyConfigurationNotChanged();
+
+        // Update the configuration to enable geolocation time zone detection.
+        script.simulateUpdateConfiguration(
+                USER_ID, CONFIG_GEO_DETECTION_ENABLED,  false /* expectedResult */);
 
         // The settings should not have been changed: user shouldn't have the capabilities.
         script.verifyConfigurationNotChanged();
@@ -253,16 +289,18 @@
     public void testUpdateConfiguration_autoDetectNotSupported() {
         Script script = new Script()
                 .initializeUser(USER_ID, UserCase.AUTO_DETECT_NOT_SUPPORTED,
-                        CONFIG_AUTO_TIME_ZONE_DETECTION_ENABLED);
+                        CONFIG_AUTO_ENABLED.with(CONFIG_GEO_DETECTION_DISABLED));
 
         // Try to update the configuration with auto detection disabled.
-        script.simulateUpdateConfiguration(USER_ID, CONFIG_AUTO_TIME_ZONE_DETECTION_DISABLED);
+        script.simulateUpdateConfiguration(
+                USER_ID, CONFIG_AUTO_DISABLED, false /* expectedResult */);
 
         // The settings should not have been changed: user shouldn't have the capabilities.
         script.verifyConfigurationNotChanged();
 
         // Update the configuration with auto detection enabled.
-        script.simulateUpdateConfiguration(USER_ID, CONFIG_AUTO_TIME_ZONE_DETECTION_ENABLED);
+        script.simulateUpdateConfiguration(
+                USER_ID, CONFIG_AUTO_ENABLED, false /* expectedResult */);
 
         // The settings should not have been changed: user shouldn't have the capabilities.
         script.verifyConfigurationNotChanged();
@@ -276,7 +314,7 @@
                 createEmptySlotIndex2Suggestion();
         Script script = new Script()
                 .initializeUser(USER_ID, UserCase.UNRESTRICTED,
-                        CONFIG_AUTO_TIME_ZONE_DETECTION_ENABLED)
+                        CONFIG_AUTO_ENABLED.with(CONFIG_GEO_DETECTION_DISABLED))
                 .initializeTimeZoneSetting(ARBITRARY_TIME_ZONE_ID);
 
         script.simulateTelephonyTimeZoneSuggestion(slotIndex1TimeZoneSuggestion)
@@ -308,6 +346,10 @@
                 mTimeZoneDetectorStrategy.findBestTelephonySuggestionForTests());
     }
 
+    /**
+     * Telephony suggestions have quality metadata. Ordinarily, low scoring suggestions are not
+     * used, but this is not true if the device's time zone setting is uninitialized.
+     */
     @Test
     public void testTelephonySuggestionsWhenTimeZoneUninitialized() {
         assertTrue(TELEPHONY_SCORE_LOW < TELEPHONY_SCORE_USAGE_THRESHOLD);
@@ -319,7 +361,7 @@
 
         Script script = new Script()
                 .initializeUser(USER_ID, UserCase.UNRESTRICTED,
-                        CONFIG_AUTO_TIME_ZONE_DETECTION_ENABLED);
+                        CONFIG_AUTO_ENABLED.with(CONFIG_GEO_DETECTION_DISABLED));
 
         // A low quality suggestions will not be taken: The device time zone setting is left
         // uninitialized.
@@ -376,16 +418,16 @@
 
     /**
      * Confirms that toggling the auto time zone detection setting has the expected behavior when
-     * the strategy is "opinionated".
+     * the strategy is "opinionated" when using telephony auto detection.
      */
     @Test
-    public void testTogglingAutoTimeZoneDetection() {
+    public void testTogglingAutoDetection_autoTelephony() {
         Script script = new Script();
 
         for (TelephonyTestCase testCase : TELEPHONY_TEST_CASES) {
             // Start with the device in a known state.
             script.initializeUser(USER_ID, UserCase.UNRESTRICTED,
-                    CONFIG_AUTO_TIME_ZONE_DETECTION_DISABLED)
+                    CONFIG_AUTO_DISABLED.with(CONFIG_GEO_DETECTION_DISABLED))
                     .initializeTimeZoneSetting(ARBITRARY_TIME_ZONE_ID);
 
             TelephonyTimeZoneSuggestion suggestion =
@@ -405,7 +447,8 @@
                     mTimeZoneDetectorStrategy.findBestTelephonySuggestionForTests());
 
             // Toggling the time zone setting on should cause the device setting to be set.
-            script.simulateAutoTimeZoneDetectionEnabled(USER_ID, true);
+            script.simulateUpdateConfiguration(USER_ID, CONFIG_AUTO_ENABLED,
+                    true /* expectedResult */);
 
             // When time zone detection is already enabled the suggestion (if it scores highly
             // enough) should be set immediately.
@@ -422,7 +465,8 @@
                     mTimeZoneDetectorStrategy.findBestTelephonySuggestionForTests());
 
             // Toggling the time zone setting should off should do nothing.
-            script.simulateAutoTimeZoneDetectionEnabled(USER_ID, false)
+            script.simulateUpdateConfiguration(
+                    USER_ID, CONFIG_AUTO_DISABLED, true /* expectedResult */)
                     .verifyTimeZoneNotChanged();
 
             // Assert internal service state.
@@ -437,7 +481,7 @@
     public void testTelephonySuggestionsSingleSlotId() {
         Script script = new Script()
                 .initializeUser(USER_ID, UserCase.UNRESTRICTED,
-                        CONFIG_AUTO_TIME_ZONE_DETECTION_ENABLED)
+                        CONFIG_AUTO_ENABLED.with(CONFIG_GEO_DETECTION_DISABLED))
                 .initializeTimeZoneSetting(ARBITRARY_TIME_ZONE_ID);
 
         for (TelephonyTestCase testCase : TELEPHONY_TEST_CASES) {
@@ -451,8 +495,7 @@
          */
 
         // Each test case will have the same or lower score than the last.
-        ArrayList<TelephonyTestCase> descendingCasesByScore =
-                new ArrayList<>(Arrays.asList(TELEPHONY_TEST_CASES));
+        List<TelephonyTestCase> descendingCasesByScore = list(TELEPHONY_TEST_CASES);
         Collections.reverse(descendingCasesByScore);
 
         for (TelephonyTestCase testCase : descendingCasesByScore) {
@@ -504,7 +547,7 @@
 
         Script script = new Script()
                 .initializeUser(USER_ID, UserCase.UNRESTRICTED,
-                        CONFIG_AUTO_TIME_ZONE_DETECTION_ENABLED)
+                        CONFIG_AUTO_ENABLED.with(CONFIG_GEO_DETECTION_DISABLED))
                 .initializeTimeZoneSetting(ARBITRARY_TIME_ZONE_ID)
                 // Initialize the latest suggestions as empty so we don't need to worry about nulls
                 // below for the first loop.
@@ -583,15 +626,15 @@
     }
 
     /**
-     * The {@link TimeZoneDetectorStrategyImpl.Callback} is left to detect whether changing
-     * the time zone is actually necessary. This test proves that the service doesn't assume it
-     * knows the current setting.
+     * The {@link TimeZoneDetectorStrategyImpl.Callback} is left to detect whether changing the time
+     * zone is actually necessary. This test proves that the strategy doesn't assume it knows the
+     * current settings.
      */
     @Test
-    public void testTelephonySuggestionTimeZoneDetectorStrategyDoesNotAssumeCurrentSetting() {
+    public void testTelephonySuggestionStrategyDoesNotAssumeCurrentSetting_autoTelephony() {
         Script script = new Script()
                 .initializeUser(USER_ID, UserCase.UNRESTRICTED,
-                        CONFIG_AUTO_TIME_ZONE_DETECTION_ENABLED);
+                        CONFIG_AUTO_ENABLED.with(CONFIG_GEO_DETECTION_DISABLED));
 
         TelephonyTestCase testCase = newTelephonyTestCase(
                 MATCH_TYPE_NETWORK_COUNTRY_AND_OFFSET, QUALITY_SINGLE_ZONE, TELEPHONY_SCORE_HIGH);
@@ -609,26 +652,40 @@
 
         // Toggling time zone detection should set the device time zone only if the current setting
         // value is different from the most recent telephony suggestion.
-        script.simulateAutoTimeZoneDetectionEnabled(USER_ID, false)
+        script.simulateUpdateConfiguration(USER_ID, CONFIG_AUTO_DISABLED, true /* expectedResult */)
                 .verifyTimeZoneNotChanged()
-                .simulateAutoTimeZoneDetectionEnabled(USER_ID, true)
+                .simulateUpdateConfiguration(
+                        USER_ID, CONFIG_AUTO_ENABLED, true /* expectedResult */)
                 .verifyTimeZoneNotChanged();
 
         // Simulate a user turning auto detection off, a new suggestion being made while auto
         // detection is off, and the user turning it on again.
-        script.simulateAutoTimeZoneDetectionEnabled(USER_ID, false)
+        script.simulateUpdateConfiguration(USER_ID, CONFIG_AUTO_DISABLED, true /* expectedResult */)
                 .simulateTelephonyTimeZoneSuggestion(newYorkSuggestion)
                 .verifyTimeZoneNotChanged();
         // Latest suggestion should be used.
-        script.simulateAutoTimeZoneDetectionEnabled(USER_ID, true)
+        script.simulateUpdateConfiguration(USER_ID, CONFIG_AUTO_ENABLED, true /* expectedResult */)
                 .verifyTimeZoneChangedAndReset(newYorkSuggestion);
     }
 
     @Test
-    public void testManualSuggestion_unrestricted_simulateAutoTimeZoneEnabled() {
+    public void testManualSuggestion_autoDetectionEnabled_autoTelephony() {
+        checkManualSuggestion_autoDetectionEnabled(false /* geoDetectionEnabled */);
+    }
+
+    @Test
+    public void testManualSuggestion_autoDetectionEnabled_autoGeo() {
+        checkManualSuggestion_autoDetectionEnabled(true /* geoDetectionEnabled */);
+    }
+
+    private void checkManualSuggestion_autoDetectionEnabled(boolean geoDetectionEnabled) {
+        TimeZoneConfiguration geoTzEnabledConfig =
+                new TimeZoneConfiguration.Builder()
+                        .setGeoDetectionEnabled(geoDetectionEnabled)
+                        .build();
         Script script = new Script()
                 .initializeUser(USER_ID, UserCase.UNRESTRICTED,
-                        CONFIG_AUTO_TIME_ZONE_DETECTION_ENABLED)
+                        CONFIG_AUTO_ENABLED.with(geoTzEnabledConfig))
                 .initializeTimeZoneSetting(ARBITRARY_TIME_ZONE_ID);
 
         // Auto time zone detection is enabled so the manual suggestion should be ignored.
@@ -641,7 +698,7 @@
     public void testManualSuggestion_restricted_simulateAutoTimeZoneEnabled() {
         Script script = new Script()
                 .initializeUser(USER_ID, UserCase.RESTRICTED,
-                        CONFIG_AUTO_TIME_ZONE_DETECTION_ENABLED)
+                        CONFIG_AUTO_ENABLED.with(CONFIG_GEO_DETECTION_DISABLED))
                 .initializeTimeZoneSetting(ARBITRARY_TIME_ZONE_ID);
 
         // Auto time zone detection is enabled so the manual suggestion should be ignored.
@@ -654,7 +711,7 @@
     public void testManualSuggestion_autoDetectNotSupported_simulateAutoTimeZoneEnabled() {
         Script script = new Script()
                 .initializeUser(USER_ID, UserCase.AUTO_DETECT_NOT_SUPPORTED,
-                        CONFIG_AUTO_TIME_ZONE_DETECTION_ENABLED)
+                        CONFIG_AUTO_ENABLED.with(CONFIG_GEO_DETECTION_DISABLED))
                 .initializeTimeZoneSetting(ARBITRARY_TIME_ZONE_ID);
 
         // Auto time zone detection is enabled so the manual suggestion should be ignored.
@@ -668,7 +725,7 @@
     public void testManualSuggestion_unrestricted_autoTimeZoneDetectionDisabled() {
         Script script = new Script()
                 .initializeUser(USER_ID, UserCase.UNRESTRICTED,
-                        CONFIG_AUTO_TIME_ZONE_DETECTION_DISABLED)
+                        CONFIG_AUTO_DISABLED.with(CONFIG_GEO_DETECTION_DISABLED))
                 .initializeTimeZoneSetting(ARBITRARY_TIME_ZONE_ID);
 
         // Auto time zone detection is disabled so the manual suggestion should be used.
@@ -682,7 +739,7 @@
     public void testManualSuggestion_restricted_autoTimeZoneDetectionDisabled() {
         Script script = new Script()
                 .initializeUser(USER_ID, UserCase.RESTRICTED,
-                        CONFIG_AUTO_TIME_ZONE_DETECTION_DISABLED)
+                        CONFIG_AUTO_DISABLED.with(CONFIG_GEO_DETECTION_DISABLED))
                 .initializeTimeZoneSetting(ARBITRARY_TIME_ZONE_ID);
 
         // Restricted users do not have the capability.
@@ -696,7 +753,7 @@
     public void testManualSuggestion_autoDetectNotSupported_autoTimeZoneDetectionDisabled() {
         Script script = new Script()
                 .initializeUser(USER_ID, UserCase.AUTO_DETECT_NOT_SUPPORTED,
-                        CONFIG_AUTO_TIME_ZONE_DETECTION_DISABLED)
+                        CONFIG_AUTO_DISABLED.with(CONFIG_GEO_DETECTION_DISABLED))
                 .initializeTimeZoneSetting(ARBITRARY_TIME_ZONE_ID);
 
         // Unrestricted users have the capability.
@@ -707,10 +764,261 @@
     }
 
     @Test
+    public void testGeoSuggestion_uncertain() {
+        Script script = new Script()
+                .initializeUser(USER_ID, UserCase.UNRESTRICTED,
+                        CONFIG_AUTO_ENABLED.with(CONFIG_GEO_DETECTION_ENABLED))
+                .initializeTimeZoneSetting(ARBITRARY_TIME_ZONE_ID);
+
+        GeolocationTimeZoneSuggestion uncertainSuggestion = createUncertainGeoLocationSuggestion();
+
+        script.simulateGeolocationTimeZoneSuggestion(uncertainSuggestion)
+                .verifyTimeZoneNotChanged();
+
+        // Assert internal service state.
+        assertEquals(uncertainSuggestion,
+                mTimeZoneDetectorStrategy.getLatestGeolocationSuggestion());
+    }
+
+    @Test
+    public void testGeoSuggestion_noZones() {
+        Script script = new Script()
+                .initializeUser(USER_ID, UserCase.UNRESTRICTED,
+                        CONFIG_AUTO_ENABLED.with(CONFIG_GEO_DETECTION_ENABLED))
+                .initializeTimeZoneSetting(ARBITRARY_TIME_ZONE_ID);
+
+        GeolocationTimeZoneSuggestion noZonesSuggestion = createGeoLocationSuggestion(list());
+
+        script.simulateGeolocationTimeZoneSuggestion(noZonesSuggestion)
+                .verifyTimeZoneNotChanged();
+
+        // Assert internal service state.
+        assertEquals(noZonesSuggestion, mTimeZoneDetectorStrategy.getLatestGeolocationSuggestion());
+    }
+
+    @Test
+    public void testGeoSuggestion_oneZone() {
+        GeolocationTimeZoneSuggestion suggestion =
+                createGeoLocationSuggestion(list("Europe/London"));
+
+        Script script = new Script()
+                .initializeUser(USER_ID, UserCase.UNRESTRICTED,
+                        CONFIG_AUTO_ENABLED.with(CONFIG_GEO_DETECTION_ENABLED))
+                .initializeTimeZoneSetting(ARBITRARY_TIME_ZONE_ID);
+
+        script.simulateGeolocationTimeZoneSuggestion(suggestion)
+                .verifyTimeZoneChangedAndReset("Europe/London");
+
+        // Assert internal service state.
+        assertEquals(suggestion, mTimeZoneDetectorStrategy.getLatestGeolocationSuggestion());
+    }
+
+    /**
+     * In the current implementation, the first zone ID is always used unless the device is set to
+     * one of the other options. This is "stickiness" - the device favors the zone it is currently
+     * set to until that unambiguously can't be correct.
+     */
+    @Test
+    public void testGeoSuggestion_multiZone() {
+        GeolocationTimeZoneSuggestion londonOnlySuggestion =
+                createGeoLocationSuggestion(list("Europe/London"));
+        GeolocationTimeZoneSuggestion londonOrParisSuggestion =
+                createGeoLocationSuggestion(list("Europe/Paris", "Europe/London"));
+        GeolocationTimeZoneSuggestion parisOnlySuggestion =
+                createGeoLocationSuggestion(list("Europe/Paris"));
+
+        Script script = new Script()
+                .initializeUser(USER_ID, UserCase.UNRESTRICTED,
+                        CONFIG_AUTO_ENABLED.with(CONFIG_GEO_DETECTION_ENABLED))
+                .initializeTimeZoneSetting(ARBITRARY_TIME_ZONE_ID);
+
+        script.simulateGeolocationTimeZoneSuggestion(londonOnlySuggestion)
+                .verifyTimeZoneChangedAndReset("Europe/London");
+        assertEquals(londonOnlySuggestion,
+                mTimeZoneDetectorStrategy.getLatestGeolocationSuggestion());
+
+        // Confirm bias towards the current device zone when there's multiple zones to choose from.
+        script.simulateGeolocationTimeZoneSuggestion(londonOrParisSuggestion)
+                .verifyTimeZoneNotChanged();
+        assertEquals(londonOrParisSuggestion,
+                mTimeZoneDetectorStrategy.getLatestGeolocationSuggestion());
+
+        script.simulateGeolocationTimeZoneSuggestion(parisOnlySuggestion)
+                .verifyTimeZoneChangedAndReset("Europe/Paris");
+        assertEquals(parisOnlySuggestion,
+                mTimeZoneDetectorStrategy.getLatestGeolocationSuggestion());
+
+        // Now the suggestion that previously left the device on Europe/London will leave the device
+        // on Europe/Paris.
+        script.simulateGeolocationTimeZoneSuggestion(londonOrParisSuggestion)
+                .verifyTimeZoneNotChanged();
+        assertEquals(londonOrParisSuggestion,
+                mTimeZoneDetectorStrategy.getLatestGeolocationSuggestion());
+    }
+
+    /**
+     * Confirms that toggling the auto time zone detection enabled setting has the expected behavior
+     * when the strategy is "opinionated" and "un-opinionated" when in geolocation detection is
+     * enabled.
+     */
+    @Test
+    public void testTogglingAutoDetectionEnabled_autoGeo() {
+        GeolocationTimeZoneSuggestion geolocationSuggestion =
+                createGeoLocationSuggestion(list("Europe/London"));
+        GeolocationTimeZoneSuggestion uncertainGeolocationSuggestion =
+                createUncertainGeoLocationSuggestion();
+        ManualTimeZoneSuggestion manualSuggestion = createManualSuggestion("Europe/Paris");
+
+        Script script = new Script()
+                .initializeUser(USER_ID, UserCase.UNRESTRICTED,
+                        CONFIG_AUTO_DISABLED.with(CONFIG_GEO_DETECTION_ENABLED))
+                .initializeTimeZoneSetting(ARBITRARY_TIME_ZONE_ID);
+
+        script.simulateGeolocationTimeZoneSuggestion(geolocationSuggestion);
+
+        // When time zone detection is not enabled, the time zone suggestion will not be set.
+        script.verifyTimeZoneNotChanged();
+
+        // Assert internal service state.
+        assertEquals(geolocationSuggestion,
+                mTimeZoneDetectorStrategy.getLatestGeolocationSuggestion());
+
+        // Toggling the time zone setting on should cause the device setting to be set.
+        script.simulateUpdateConfiguration(USER_ID, CONFIG_AUTO_ENABLED, true /* expectedResult */)
+                .verifyTimeZoneChangedAndReset("Europe/London");
+
+        // Toggling the time zone setting should off should do nothing because the device is now
+        // set to that time zone.
+        script.simulateUpdateConfiguration(USER_ID, CONFIG_AUTO_DISABLED, true /* expectedResult */)
+                .verifyTimeZoneNotChanged()
+                .simulateUpdateConfiguration(
+                        USER_ID, CONFIG_AUTO_ENABLED, true /* expectedResult */)
+                .verifyTimeZoneNotChanged();
+
+        // Now toggle auto time zone setting, and confirm it is opinionated.
+        script.simulateUpdateConfiguration(USER_ID, CONFIG_AUTO_DISABLED, true /* expectedResult */)
+                .simulateManualTimeZoneSuggestion(
+                        USER_ID, manualSuggestion, true /* expectedResult */)
+                .verifyTimeZoneChangedAndReset(manualSuggestion)
+                .simulateUpdateConfiguration(
+                        USER_ID, CONFIG_AUTO_ENABLED, true /* expectedResult */)
+                .verifyTimeZoneChangedAndReset("Europe/London");
+
+        // Now withdraw the geolocation suggestion, and assert the strategy is no longer
+        // opinionated.
+        /* expectedResult */
+        script.simulateGeolocationTimeZoneSuggestion(uncertainGeolocationSuggestion)
+                .verifyTimeZoneNotChanged()
+                .simulateUpdateConfiguration(
+                        USER_ID, CONFIG_AUTO_DISABLED, true /* expectedResult */)
+                .verifyTimeZoneNotChanged()
+                .simulateManualTimeZoneSuggestion(
+                        USER_ID, manualSuggestion, true /* expectedResult */)
+                .verifyTimeZoneChangedAndReset(manualSuggestion)
+                .simulateUpdateConfiguration(
+                        USER_ID, CONFIG_AUTO_ENABLED, true /* expectedResult */)
+                .verifyTimeZoneNotChanged();
+
+        // Assert internal service state.
+        assertEquals(uncertainGeolocationSuggestion,
+                mTimeZoneDetectorStrategy.getLatestGeolocationSuggestion());
+    }
+
+    /**
+     * Confirms that changing the geolocation time zone detection enabled setting has the expected
+     * behavior, i.e. immediately recompute the detected time zone using different signals.
+     */
+    @Test
+    public void testChangingGeoDetectionEnabled() {
+        GeolocationTimeZoneSuggestion geolocationSuggestion =
+                createGeoLocationSuggestion(list("Europe/London"));
+        TelephonyTimeZoneSuggestion telephonySuggestion = createTelephonySuggestion(
+                SLOT_INDEX1, MATCH_TYPE_NETWORK_COUNTRY_AND_OFFSET, QUALITY_SINGLE_ZONE,
+                "Europe/Paris");
+
+        Script script = new Script()
+                .initializeUser(USER_ID, UserCase.UNRESTRICTED,
+                        CONFIG_AUTO_DISABLED.with(CONFIG_GEO_DETECTION_DISABLED))
+                .initializeTimeZoneSetting(ARBITRARY_TIME_ZONE_ID);
+
+        // Add suggestions. Nothing should happen as time zone detection is disabled.
+        script.simulateGeolocationTimeZoneSuggestion(geolocationSuggestion)
+                .verifyTimeZoneNotChanged();
+        script.simulateTelephonyTimeZoneSuggestion(telephonySuggestion)
+                .verifyTimeZoneNotChanged();
+
+        // Assert internal service state.
+        assertEquals(geolocationSuggestion,
+                mTimeZoneDetectorStrategy.getLatestGeolocationSuggestion());
+        assertEquals(telephonySuggestion,
+                mTimeZoneDetectorStrategy.getLatestTelephonySuggestion(SLOT_INDEX1).suggestion);
+
+        // Toggling the time zone detection enabled setting on should cause the device setting to be
+        // set from the telephony signal, as we've started with geolocation time zone detection
+        // disabled.
+        script.simulateUpdateConfiguration(USER_ID, CONFIG_AUTO_ENABLED, true /* expectedResult */)
+                .verifyTimeZoneChangedAndReset(telephonySuggestion);
+
+        // Changing the detection to enable geo detection should cause the device tz setting to
+        // change to the geo suggestion.
+        script.simulateUpdateConfiguration(
+                USER_ID, CONFIG_GEO_DETECTION_ENABLED, true /* expectedResult */)
+                .verifyTimeZoneChangedAndReset(geolocationSuggestion.getZoneIds().get(0));
+
+        // Changing the detection to disable geo detection should cause the device tz setting to
+        // change to the telephony suggestion.
+        script.simulateUpdateConfiguration(
+                USER_ID, CONFIG_GEO_DETECTION_DISABLED, true /* expectedResult */)
+                .verifyTimeZoneChangedAndReset(telephonySuggestion);
+    }
+
+    /**
+     * The {@link TimeZoneDetectorStrategyImpl.Callback} is left to detect whether changing the time
+     * zone is actually necessary. This test proves that the strategy doesn't assume it knows the
+     * current setting.
+     */
+    @Test
+    public void testTimeZoneDetectorStrategyDoesNotAssumeCurrentSetting_autoGeo() {
+        GeolocationTimeZoneSuggestion losAngelesSuggestion =
+                createGeoLocationSuggestion(list("America/Los_Angeles"));
+        GeolocationTimeZoneSuggestion newYorkSuggestion =
+                createGeoLocationSuggestion(list("America/New_York"));
+
+        Script script = new Script()
+                .initializeUser(USER_ID, UserCase.UNRESTRICTED,
+                        CONFIG_AUTO_ENABLED.with(CONFIG_GEO_DETECTION_ENABLED));
+
+        // Initialization.
+        script.simulateGeolocationTimeZoneSuggestion(losAngelesSuggestion)
+                .verifyTimeZoneChangedAndReset("America/Los_Angeles");
+        // Suggest it again - it should not be set because it is already set.
+        script.simulateGeolocationTimeZoneSuggestion(losAngelesSuggestion)
+                .verifyTimeZoneNotChanged();
+
+        // Toggling time zone detection should set the device time zone only if the current setting
+        // value is different from the most recent telephony suggestion.
+        /* expectedResult */
+        script.simulateUpdateConfiguration(USER_ID, CONFIG_AUTO_DISABLED, true /* expectedResult */)
+                .verifyTimeZoneNotChanged()
+                .simulateUpdateConfiguration(
+                        USER_ID, CONFIG_AUTO_ENABLED, true /* expectedResult */)
+                .verifyTimeZoneNotChanged();
+
+        // Simulate a user turning auto detection off, a new suggestion being made while auto
+        // detection is off, and the user turning it on again.
+        script.simulateUpdateConfiguration(USER_ID, CONFIG_AUTO_DISABLED, true /* expectedResult */)
+                .simulateGeolocationTimeZoneSuggestion(newYorkSuggestion)
+                .verifyTimeZoneNotChanged();
+        // Latest suggestion should be used.
+        script.simulateUpdateConfiguration(USER_ID, CONFIG_AUTO_ENABLED, true /* expectedResult */)
+                .verifyTimeZoneChangedAndReset("America/New_York");
+    }
+
+    @Test
     public void testAddDumpable() {
         new Script()
                 .initializeUser(USER_ID, UserCase.UNRESTRICTED,
-                        CONFIG_AUTO_TIME_ZONE_DETECTION_DISABLED)
+                        CONFIG_AUTO_DISABLED.with(CONFIG_GEO_DETECTION_DISABLED))
                 .initializeTimeZoneSetting(ARBITRARY_TIME_ZONE_ID);
 
         AtomicBoolean dumpCalled = new AtomicBoolean(false);
@@ -733,6 +1041,15 @@
         return new ManualTimeZoneSuggestion(zoneId);
     }
 
+    private static TelephonyTimeZoneSuggestion createTelephonySuggestion(
+            int slotIndex, @MatchType int matchType, @Quality int quality, String zoneId) {
+        return new TelephonyTimeZoneSuggestion.Builder(slotIndex)
+                .setMatchType(matchType)
+                .setQuality(quality)
+                .setZoneId(zoneId)
+                .build();
+    }
+
     private static TelephonyTimeZoneSuggestion createEmptySlotIndex1Suggestion() {
         return new TelephonyTimeZoneSuggestion.Builder(SLOT_INDEX1).build();
     }
@@ -741,6 +1058,17 @@
         return new TelephonyTimeZoneSuggestion.Builder(SLOT_INDEX2).build();
     }
 
+    private static GeolocationTimeZoneSuggestion createUncertainGeoLocationSuggestion() {
+        return createGeoLocationSuggestion(null);
+    }
+
+    private static GeolocationTimeZoneSuggestion createGeoLocationSuggestion(
+            @Nullable List<String> zoneIds) {
+        GeolocationTimeZoneSuggestion suggestion = new GeolocationTimeZoneSuggestion(zoneIds);
+        suggestion.addDebugInfo("Test suggestion");
+        return suggestion;
+    }
+
     static class FakeCallback implements TimeZoneDetectorStrategyImpl.Callback {
 
         private TimeZoneCapabilities mCapabilities;
@@ -757,7 +1085,8 @@
                 TimeZoneConfiguration configuration) {
             assertEquals(userId, capabilities.getUserId());
             mCapabilities = capabilities;
-            assertTrue(configuration.isComplete());
+            assertTrue("Configuration must be complete when initializing, config=" + configuration,
+                    configuration.isComplete());
             mConfiguration.init(new UserConfiguration(userId, configuration));
         }
 
@@ -790,11 +1119,8 @@
             mConfiguration.set(new UserConfiguration(userId, newConfig));
 
             if (!newConfig.equals(oldConfig)) {
-                if (oldConfig.isAutoDetectionEnabled() != newConfig.isAutoDetectionEnabled()) {
-                    // Simulate what happens when the auto detection enabled configuration is
-                    // changed.
-                    mStrategy.handleAutoTimeZoneConfigChanged();
-                }
+                // Simulate what happens when the auto detection configuration is changed.
+                mStrategy.handleAutoTimeZoneConfigChanged();
             }
         }
 
@@ -804,6 +1130,11 @@
         }
 
         @Override
+        public boolean isGeoDetectionEnabled() {
+            return mConfiguration.getLatest().configuration.isGeoDetectionEnabled();
+        }
+
+        @Override
         public boolean isDeviceTimeZoneInitialized() {
             return mTimeZoneId.getLatest() != null;
         }
@@ -942,32 +1273,33 @@
      * supplied configuration.
      */
     private static TimeZoneCapabilities createCapabilities(
-            int userId, UserCase userRole, TimeZoneConfiguration configuration) {
-        switch (userRole) {
+            int userId, UserCase userCase, TimeZoneConfiguration configuration) {
+        switch (userCase) {
             case UNRESTRICTED: {
                 int suggestManualTimeZoneCapability = configuration.isAutoDetectionEnabled()
                         ? CAPABILITY_NOT_APPLICABLE : CAPABILITY_POSSESSED;
                 return new TimeZoneCapabilities.Builder(userId)
                         .setConfigureAutoDetectionEnabled(CAPABILITY_POSSESSED)
+                        .setConfigureGeoDetectionEnabled(CAPABILITY_POSSESSED)
                         .setSuggestManualTimeZone(suggestManualTimeZoneCapability)
                         .build();
             }
             case RESTRICTED: {
                 return new TimeZoneCapabilities.Builder(userId)
                         .setConfigureAutoDetectionEnabled(CAPABILITY_NOT_ALLOWED)
+                        .setConfigureGeoDetectionEnabled(CAPABILITY_NOT_ALLOWED)
                         .setSuggestManualTimeZone(CAPABILITY_NOT_ALLOWED)
                         .build();
-
             }
             case AUTO_DETECT_NOT_SUPPORTED: {
                 return new TimeZoneCapabilities.Builder(userId)
                         .setConfigureAutoDetectionEnabled(CAPABILITY_NOT_SUPPORTED)
+                        .setConfigureGeoDetectionEnabled(CAPABILITY_NOT_SUPPORTED)
                         .setSuggestManualTimeZone(CAPABILITY_POSSESSED)
                         .build();
-
             }
             default:
-                throw new AssertionError(userRole + " not recognized");
+                throw new AssertionError(userCase + " not recognized");
         }
     }
 
@@ -978,8 +1310,8 @@
     private class Script {
 
         Script initializeUser(
-                @UserIdInt int userId, UserCase userRole, TimeZoneConfiguration configuration) {
-            TimeZoneCapabilities capabilities = createCapabilities(userId, userRole, configuration);
+                @UserIdInt int userId, UserCase userCase, TimeZoneConfiguration configuration) {
+            TimeZoneCapabilities capabilities = createCapabilities(userId, userCase, configuration);
             mFakeCallback.initializeUser(userId, capabilities, configuration);
             return this;
         }
@@ -989,27 +1321,24 @@
             return this;
         }
 
-        Script simulateAutoTimeZoneDetectionEnabled(@UserIdInt int userId, boolean enabled) {
-            TimeZoneConfiguration configuration = new TimeZoneConfiguration.Builder()
-                    .setAutoDetectionEnabled(enabled)
-                    .build();
-            return simulateUpdateConfiguration(userId, configuration);
-        }
-
         /**
-         * Simulates the time zone detection strategy receiving an updated configuration.
+         * Simulates the time zone detection strategy receiving an updated configuration and checks
+         * the return value.
          */
         Script simulateUpdateConfiguration(
-                @UserIdInt int userId, TimeZoneConfiguration configuration) {
-            mTimeZoneDetectorStrategy.updateConfiguration(userId, configuration);
+                @UserIdInt int userId, TimeZoneConfiguration configuration,
+                boolean expectedResult) {
+            assertEquals(expectedResult,
+                    mTimeZoneDetectorStrategy.updateConfiguration(userId, configuration));
             return this;
         }
 
         /**
-         * Simulates the time zone detection strategy receiving a telephony-originated suggestion.
+         * Simulates the time zone detection strategy receiving a geolocation-originated
+         * suggestion.
          */
-        Script simulateTelephonyTimeZoneSuggestion(TelephonyTimeZoneSuggestion timeZoneSuggestion) {
-            mTimeZoneDetectorStrategy.suggestTelephonyTimeZone(timeZoneSuggestion);
+        Script simulateGeolocationTimeZoneSuggestion(GeolocationTimeZoneSuggestion suggestion) {
+            mTimeZoneDetectorStrategy.suggestGeolocationTimeZone(suggestion);
             return this;
         }
 
@@ -1024,13 +1353,26 @@
             return this;
         }
 
+        /**
+         * Simulates the time zone detection strategy receiving a telephony-originated suggestion.
+         */
+        Script simulateTelephonyTimeZoneSuggestion(TelephonyTimeZoneSuggestion timeZoneSuggestion) {
+            mTimeZoneDetectorStrategy.suggestTelephonyTimeZone(timeZoneSuggestion);
+            return this;
+        }
+
+        /**
+         * Confirms that the device's time zone has not been set by previous actions since the test
+         * state was last reset.
+         */
         Script verifyTimeZoneNotChanged() {
             mFakeCallback.assertTimeZoneNotChanged();
             return this;
         }
 
-        Script verifyTimeZoneChangedAndReset(TelephonyTimeZoneSuggestion suggestion) {
-            mFakeCallback.assertTimeZoneChangedTo(suggestion.getZoneId());
+        /** Verifies the device's time zone has been set and clears change tracking history. */
+        Script verifyTimeZoneChangedAndReset(String zoneId) {
+            mFakeCallback.assertTimeZoneChangedTo(zoneId);
             mFakeCallback.commitAllChanges();
             return this;
         }
@@ -1041,6 +1383,12 @@
             return this;
         }
 
+        Script verifyTimeZoneChangedAndReset(TelephonyTimeZoneSuggestion suggestion) {
+            mFakeCallback.assertTimeZoneChangedTo(suggestion.getZoneId());
+            mFakeCallback.commitAllChanges();
+            return this;
+        }
+
         /**
          * Verifies that the configuration has been changed to the expected value.
          */
@@ -1120,4 +1468,8 @@
             mOnConfigurationChangedCalled = false;
         }
     }
+
+    private static <T> List<T> list(T... values) {
+        return Arrays.asList(values);
+    }
 }
diff --git a/services/tests/servicestests/src/com/android/server/tv/tunerresourcemanager/TunerResourceManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/tv/tunerresourcemanager/TunerResourceManagerServiceTest.java
index f934323..7b07102 100644
--- a/services/tests/servicestests/src/com/android/server/tv/tunerresourcemanager/TunerResourceManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/tv/tunerresourcemanager/TunerResourceManagerServiceTest.java
@@ -74,7 +74,7 @@
             mReclaimed = true;
         }
 
-        public boolean isRelaimed() {
+        public boolean isReclaimed() {
             return mReclaimed;
         }
     }
@@ -387,13 +387,13 @@
                 new TunerFrontendRequest(clientId1[0] /*clientId*/, FrontendSettings.TYPE_DVBT);
         assertThat(mTunerResourceManagerService
                 .requestFrontendInternal(request, frontendHandle)).isFalse();
-        assertThat(listener.isRelaimed()).isFalse();
+        assertThat(listener.isReclaimed()).isFalse();
 
         request =
                 new TunerFrontendRequest(clientId1[0] /*clientId*/, FrontendSettings.TYPE_DVBS);
         assertThat(mTunerResourceManagerService
                 .requestFrontendInternal(request, frontendHandle)).isFalse();
-        assertThat(listener.isRelaimed()).isFalse();
+        assertThat(listener.isReclaimed()).isFalse();
     }
 
     @Test
@@ -452,7 +452,7 @@
                 .getOwnerClientId()).isEqualTo(clientId1[0]);
         assertThat(mTunerResourceManagerService.getFrontendResource(infos[1].getId())
                 .getOwnerClientId()).isEqualTo(clientId1[0]);
-        assertThat(listener.isRelaimed()).isTrue();
+        assertThat(listener.isReclaimed()).isTrue();
     }
 
     @Test
@@ -486,7 +486,7 @@
 
         // Release frontend
         mTunerResourceManagerService.releaseFrontendInternal(mTunerResourceManagerService
-                .getFrontendResource(frontendId));
+                .getFrontendResource(frontendId), clientId[0]);
         assertThat(mTunerResourceManagerService
                 .getFrontendResource(frontendId).isInUse()).isFalse();
         assertThat(mTunerResourceManagerService
@@ -548,7 +548,7 @@
         assertThat(mTunerResourceManagerService.getCasResource(1)
                 .getOwnerClientIds()).isEqualTo(new HashSet<Integer>(Arrays.asList(clientId1[0])));
         assertThat(mTunerResourceManagerService.getCasResource(1).isFullyUsed()).isFalse();
-        assertThat(listener.isRelaimed()).isTrue();
+        assertThat(listener.isReclaimed()).isTrue();
     }
 
     @Test
@@ -633,7 +633,7 @@
                 .isInUse()).isTrue();
         assertThat(mTunerResourceManagerService.getLnbResource(lnbIds[0])
                 .getOwnerClientId()).isEqualTo(clientId1[0]);
-        assertThat(listener.isRelaimed()).isTrue();
+        assertThat(listener.isReclaimed()).isTrue();
         assertThat(mTunerResourceManagerService.getClientProfile(clientId0[0])
                 .getInUseLnbIds().size()).isEqualTo(0);
     }
@@ -761,4 +761,293 @@
                 backgroundRecordProfile)).isEqualTo(
                         (backgroundPlaybackPriority > backgroundRecordPriority));
     }
+
+    @Test
+    public void shareFrontendTest_FrontendWithExclusiveGroupReadyToShare() {
+        /**** Register Clients and Set Priority ****/
+
+        // Int array to save the returned client ids
+        int[] ownerClientId0 = new int[1];
+        int[] ownerClientId1 = new int[1];
+        int[] shareClientId0 = new int[1];
+        int[] shareClientId1 = new int[1];
+
+        // Predefined client profiles
+        ResourceClientProfile[] ownerProfiles = new ResourceClientProfile[2];
+        ResourceClientProfile[] shareProfiles = new ResourceClientProfile[2];
+        ownerProfiles[0] = new ResourceClientProfile(
+                "0" /*sessionId*/,
+                TvInputService.PRIORITY_HINT_USE_CASE_TYPE_LIVE);
+        ownerProfiles[1] = new ResourceClientProfile(
+                "1" /*sessionId*/,
+                TvInputService.PRIORITY_HINT_USE_CASE_TYPE_LIVE);
+        shareProfiles[0] = new ResourceClientProfile(
+                "2" /*sessionId*/,
+                TvInputService.PRIORITY_HINT_USE_CASE_TYPE_RECORD);
+        shareProfiles[1] = new ResourceClientProfile(
+                "3" /*sessionId*/,
+                TvInputService.PRIORITY_HINT_USE_CASE_TYPE_RECORD);
+
+        // Predefined client reclaim listeners
+        TestResourcesReclaimListener ownerListener0 = new TestResourcesReclaimListener();
+        TestResourcesReclaimListener shareListener0 = new TestResourcesReclaimListener();
+        TestResourcesReclaimListener ownerListener1 = new TestResourcesReclaimListener();
+        TestResourcesReclaimListener shareListener1 = new TestResourcesReclaimListener();
+        // Register clients and validate the returned client ids
+        mTunerResourceManagerService
+                .registerClientProfileInternal(ownerProfiles[0], ownerListener0, ownerClientId0);
+        mTunerResourceManagerService
+                .registerClientProfileInternal(shareProfiles[0], shareListener0, shareClientId0);
+        mTunerResourceManagerService
+                .registerClientProfileInternal(ownerProfiles[1], ownerListener1, ownerClientId1);
+        mTunerResourceManagerService
+                .registerClientProfileInternal(shareProfiles[1], shareListener1, shareClientId1);
+        assertThat(ownerClientId0[0]).isNotEqualTo(TunerResourceManagerService.INVALID_CLIENT_ID);
+        assertThat(shareClientId0[0]).isNotEqualTo(TunerResourceManagerService.INVALID_CLIENT_ID);
+        assertThat(ownerClientId1[0]).isNotEqualTo(TunerResourceManagerService.INVALID_CLIENT_ID);
+        assertThat(shareClientId1[0]).isNotEqualTo(TunerResourceManagerService.INVALID_CLIENT_ID);
+
+        mTunerResourceManagerService.updateClientPriorityInternal(
+                ownerClientId0[0],
+                100/*priority*/,
+                0/*niceValue*/);
+        mTunerResourceManagerService.updateClientPriorityInternal(
+                shareClientId0[0],
+                200/*priority*/,
+                0/*niceValue*/);
+        mTunerResourceManagerService.updateClientPriorityInternal(
+                ownerClientId1[0],
+                300/*priority*/,
+                0/*niceValue*/);
+        mTunerResourceManagerService.updateClientPriorityInternal(
+                shareClientId1[0],
+                400/*priority*/,
+                0/*niceValue*/);
+
+        /**** Init Frontend Resources ****/
+
+        // Predefined frontend info
+        TunerFrontendInfo[] infos = new TunerFrontendInfo[2];
+        infos[0] = new TunerFrontendInfo(
+                0 /*id*/,
+                FrontendSettings.TYPE_DVBT,
+                1 /*exclusiveGroupId*/);
+        infos[1] = new TunerFrontendInfo(
+                1 /*id*/,
+                FrontendSettings.TYPE_DVBS,
+                1 /*exclusiveGroupId*/);
+
+        /**** Init Lnb Resources ****/
+        int[] lnbIds = {1};
+        mTunerResourceManagerService.setLnbInfoListInternal(lnbIds);
+
+        // Update frontend list in TRM
+        mTunerResourceManagerService.setFrontendInfoListInternal(infos);
+
+        /**** Request Frontend ****/
+
+        // Predefined frontend request and array to save returned frontend handle
+        int[] frontendHandle = new int[1];
+        TunerFrontendRequest request = new TunerFrontendRequest(
+                ownerClientId0[0] /*clientId*/,
+                FrontendSettings.TYPE_DVBT);
+
+        // Request call and validate granted resource and internal mapping
+        assertThat(mTunerResourceManagerService
+                .requestFrontendInternal(request, frontendHandle))
+                .isTrue();
+        assertThat(mTunerResourceManagerService
+                .getResourceIdFromHandle(frontendHandle[0]))
+                .isEqualTo(infos[0].getId());
+        assertThat(mTunerResourceManagerService
+                .getClientProfile(ownerClientId0[0])
+                .getInUseFrontendIds())
+                .isEqualTo(new HashSet<Integer>(Arrays.asList(
+                        infos[0].getId(),
+                        infos[1].getId())));
+
+        /**** Share Frontend ****/
+
+        // Share frontend call and validate the internal mapping
+        mTunerResourceManagerService.shareFrontendInternal(
+                shareClientId0[0]/*selfClientId*/,
+                ownerClientId0[0]/*targetClientId*/);
+        mTunerResourceManagerService.shareFrontendInternal(
+                shareClientId1[0]/*selfClientId*/,
+                ownerClientId0[0]/*targetClientId*/);
+        // Verify fe in use status
+        assertThat(mTunerResourceManagerService.getFrontendResource(infos[0].getId())
+                .isInUse()).isTrue();
+        assertThat(mTunerResourceManagerService.getFrontendResource(infos[1].getId())
+                .isInUse()).isTrue();
+        // Verify fe owner status
+        assertThat(mTunerResourceManagerService.getFrontendResource(infos[0].getId())
+                .getOwnerClientId()).isEqualTo(ownerClientId0[0]);
+        assertThat(mTunerResourceManagerService.getFrontendResource(infos[1].getId())
+                .getOwnerClientId()).isEqualTo(ownerClientId0[0]);
+        // Verify share fe client status in the primary owner client
+        assertThat(mTunerResourceManagerService.getClientProfile(ownerClientId0[0])
+                .getShareFeClientIds())
+                .isEqualTo(new HashSet<Integer>(Arrays.asList(
+                        shareClientId0[0],
+                        shareClientId1[0])));
+        // Verify in use frontend list in all the primary owner and share owner clients
+        assertThat(mTunerResourceManagerService
+                .getClientProfile(ownerClientId0[0])
+                .getInUseFrontendIds())
+                .isEqualTo(new HashSet<Integer>(Arrays.asList(
+                        infos[0].getId(),
+                        infos[1].getId())));
+        assertThat(mTunerResourceManagerService
+                .getClientProfile(shareClientId0[0])
+                .getInUseFrontendIds())
+                .isEqualTo(new HashSet<Integer>(Arrays.asList(
+                        infos[0].getId(),
+                        infos[1].getId())));
+        assertThat(mTunerResourceManagerService
+                .getClientProfile(shareClientId1[0])
+                .getInUseFrontendIds())
+                .isEqualTo(new HashSet<Integer>(Arrays.asList(
+                        infos[0].getId(),
+                        infos[1].getId())));
+
+        /**** Remove Frontend Share Owner ****/
+
+        // Unregister the second share fe client
+        mTunerResourceManagerService.unregisterClientProfileInternal(shareClientId1[0]);
+
+        // Validate the internal mapping
+        assertThat(mTunerResourceManagerService.getClientProfile(ownerClientId0[0])
+                .getShareFeClientIds())
+                .isEqualTo(new HashSet<Integer>(Arrays.asList(
+                        shareClientId0[0])));
+        assertThat(mTunerResourceManagerService
+                .getClientProfile(ownerClientId0[0])
+                .getInUseFrontendIds())
+                .isEqualTo(new HashSet<Integer>(Arrays.asList(
+                        infos[0].getId(),
+                        infos[1].getId())));
+        assertThat(mTunerResourceManagerService
+                .getClientProfile(shareClientId0[0])
+                .getInUseFrontendIds())
+                .isEqualTo(new HashSet<Integer>(Arrays.asList(
+                        infos[0].getId(),
+                        infos[1].getId())));
+
+        /**** Request Shared Frontend with Higher Priority Client ****/
+
+        // Predefined second frontend request
+        request = new TunerFrontendRequest(
+                ownerClientId1[0] /*clientId*/,
+                FrontendSettings.TYPE_DVBT);
+
+        // Second request call
+        assertThat(mTunerResourceManagerService
+                .requestFrontendInternal(request, frontendHandle))
+                .isTrue();
+
+        // Validate granted resource and internal mapping
+        assertThat(mTunerResourceManagerService
+                .getResourceIdFromHandle(frontendHandle[0]))
+                .isEqualTo(infos[0].getId());
+        assertThat(mTunerResourceManagerService.getFrontendResource(infos[0].getId())
+                .getOwnerClientId()).isEqualTo(ownerClientId1[0]);
+        assertThat(mTunerResourceManagerService.getFrontendResource(infos[1].getId())
+                .getOwnerClientId()).isEqualTo(ownerClientId1[0]);
+        assertThat(mTunerResourceManagerService
+                .getClientProfile(ownerClientId1[0])
+                .getInUseFrontendIds())
+                .isEqualTo(new HashSet<Integer>(Arrays.asList(
+                        infos[0].getId(),
+                        infos[1].getId())));
+        assertThat(mTunerResourceManagerService
+                .getClientProfile(ownerClientId0[0])
+                .getInUseFrontendIds()
+                .isEmpty())
+                .isTrue();
+        assertThat(mTunerResourceManagerService
+                .getClientProfile(shareClientId0[0])
+                .getInUseFrontendIds()
+                .isEmpty())
+                .isTrue();
+        assertThat(mTunerResourceManagerService
+                .getClientProfile(ownerClientId0[0])
+                .getShareFeClientIds()
+                .isEmpty())
+                .isTrue();
+        assertThat(ownerListener0.isReclaimed()).isTrue();
+        assertThat(shareListener0.isReclaimed()).isTrue();
+
+        /**** Release Frontend Resource From Primary Owner ****/
+
+        // Reshare the frontend
+        mTunerResourceManagerService.shareFrontendInternal(
+                shareClientId0[0]/*selfClientId*/,
+                ownerClientId1[0]/*targetClientId*/);
+
+        // Release the frontend resource from the primary owner
+        mTunerResourceManagerService.releaseFrontendInternal(mTunerResourceManagerService
+                .getFrontendResource(infos[0].getId()), ownerClientId1[0]);
+
+        // Validate the internal mapping
+        assertThat(mTunerResourceManagerService.getFrontendResource(infos[0].getId())
+                .isInUse()).isFalse();
+        assertThat(mTunerResourceManagerService.getFrontendResource(infos[1].getId())
+                .isInUse()).isFalse();
+        // Verify client status
+        assertThat(mTunerResourceManagerService
+                .getClientProfile(ownerClientId1[0])
+                .getInUseFrontendIds()
+                .isEmpty())
+                .isTrue();
+        assertThat(mTunerResourceManagerService
+                .getClientProfile(shareClientId0[0])
+                .getInUseFrontendIds()
+                .isEmpty())
+                .isTrue();
+        assertThat(mTunerResourceManagerService
+                .getClientProfile(ownerClientId1[0])
+                .getShareFeClientIds()
+                .isEmpty())
+                .isTrue();
+
+        /**** Unregister Primary Owner when the Share owner owns an Lnb ****/
+
+        // Predefined Lnb request and handle array
+        TunerLnbRequest requestLnb = new TunerLnbRequest(shareClientId0[0]);
+        int[] lnbHandle = new int[1];
+
+        // Request for an Lnb
+        assertThat(mTunerResourceManagerService
+                .requestLnbInternal(requestLnb, lnbHandle))
+                .isTrue();
+
+        // Request and share the frontend resource again
+        assertThat(mTunerResourceManagerService
+                .requestFrontendInternal(request, frontendHandle))
+                .isTrue();
+        mTunerResourceManagerService.shareFrontendInternal(
+                shareClientId0[0]/*selfClientId*/,
+                ownerClientId1[0]/*targetClientId*/);
+
+        // Unregister the primary owner of the shared frontend
+        mTunerResourceManagerService.unregisterClientProfileInternal(ownerClientId1[0]);
+
+        // Validate the internal mapping
+        assertThat(mTunerResourceManagerService.getFrontendResource(infos[0].getId())
+                .isInUse()).isFalse();
+        assertThat(mTunerResourceManagerService.getFrontendResource(infos[1].getId())
+                .isInUse()).isFalse();
+        // Verify client status
+        assertThat(mTunerResourceManagerService
+                .getClientProfile(shareClientId0[0])
+                .getInUseFrontendIds()
+                .isEmpty())
+                .isTrue();
+        assertThat(mTunerResourceManagerService
+                .getClientProfile(shareClientId0[0])
+                .getInUseLnbIds())
+                .isEqualTo(new HashSet<Integer>(Arrays.asList(
+                        lnbIds[0])));
+    }
 }
diff --git a/services/tests/servicestests/src/com/android/server/usage/AppStandbyControllerTests.java b/services/tests/servicestests/src/com/android/server/usage/AppStandbyControllerTests.java
index 4dec7a1..7c7b1a2 100644
--- a/services/tests/servicestests/src/com/android/server/usage/AppStandbyControllerTests.java
+++ b/services/tests/servicestests/src/com/android/server/usage/AppStandbyControllerTests.java
@@ -1383,7 +1383,7 @@
                 getStandbyBucket(mController, PACKAGE_1));
 
         mController.postReportSyncScheduled(PACKAGE_1, USER_ID, false);
-        mStateChangedLatch.await(100, TimeUnit.MILLISECONDS);
+        mStateChangedLatch.await(1000, TimeUnit.MILLISECONDS);
         assertEquals("Unexempted sync scheduled should bring the package out of the Never bucket",
                 STANDBY_BUCKET_WORKING_SET, getStandbyBucket(mController, PACKAGE_1));
 
@@ -1391,7 +1391,7 @@
 
         mStateChangedLatch = new CountDownLatch(1);
         mController.postReportSyncScheduled(PACKAGE_1, USER_ID, false);
-        mStateChangedLatch.await(100, TimeUnit.MILLISECONDS);
+        mStateChangedLatch.await(1000, TimeUnit.MILLISECONDS);
         assertEquals("Unexempted sync scheduled should not elevate a non Never bucket",
                 STANDBY_BUCKET_RARE, getStandbyBucket(mController, PACKAGE_1));
     }
@@ -1402,7 +1402,7 @@
         mInjector.mDeviceIdleMode = true;
         mStateChangedLatch = new CountDownLatch(1);
         mController.postReportSyncScheduled(PACKAGE_1, USER_ID, true);
-        mStateChangedLatch.await(100, TimeUnit.MILLISECONDS);
+        mStateChangedLatch.await(1000, TimeUnit.MILLISECONDS);
         assertEquals("Exempted sync scheduled in doze should set bucket to working set",
                 STANDBY_BUCKET_WORKING_SET, getStandbyBucket(mController, PACKAGE_1));
 
@@ -1410,7 +1410,7 @@
         mInjector.mDeviceIdleMode = false;
         mStateChangedLatch = new CountDownLatch(1);
         mController.postReportSyncScheduled(PACKAGE_1, USER_ID, true);
-        mStateChangedLatch.await(100, TimeUnit.MILLISECONDS);
+        mStateChangedLatch.await(1000, TimeUnit.MILLISECONDS);
         assertEquals("Exempted sync scheduled while not in doze should set bucket to active",
                 STANDBY_BUCKET_ACTIVE, getStandbyBucket(mController, PACKAGE_1));
     }
diff --git a/services/tests/uiservicestests/src/com/android/server/UiModeManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/UiModeManagerServiceTest.java
index 1d75967..88b1d19 100644
--- a/services/tests/uiservicestests/src/com/android/server/UiModeManagerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/UiModeManagerServiceTest.java
@@ -16,6 +16,7 @@
 
 package com.android.server;
 
+import android.Manifest;
 import android.app.AlarmManager;
 import android.app.IUiModeManager;
 import android.content.BroadcastReceiver;
@@ -24,6 +25,7 @@
 import android.content.Intent;
 import android.content.IntentFilter;
 import android.content.pm.PackageManager;
+import android.content.pm.UserInfo;
 import android.content.res.Configuration;
 import android.content.res.Resources;
 import android.os.Handler;
@@ -67,6 +69,7 @@
 import static org.mockito.Mockito.atLeastOnce;
 import static org.mockito.Mockito.doAnswer;
 import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
@@ -230,6 +233,17 @@
     }
 
     @Test
+    public void setNightModeActivated_permissiontoChangeOtherUsers() throws RemoteException {
+        SystemService.TargetUser user = mock(SystemService.TargetUser.class);
+        doReturn(9).when(user).getUserIdentifier();
+        mUiManagerService.onUserSwitching(user, user);
+        when(mContext.checkCallingOrSelfPermission(
+                eq(Manifest.permission.INTERACT_ACROSS_USERS)))
+                .thenReturn(PackageManager.PERMISSION_DENIED);
+        assertFalse(mService.setNightModeActivated(true));
+    }
+
+    @Test
     public void autoNightModeSwitch_batterySaverOn() throws RemoteException {
         mService.setNightMode(MODE_NIGHT_NO);
         when(mTwilightState.isNight()).thenReturn(false);
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
index 70006b4..9319bea 100755
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
@@ -166,6 +166,7 @@
 import com.android.server.DeviceIdleInternal;
 import com.android.server.LocalServices;
 import com.android.server.SystemService;
+import com.android.server.SystemService.TargetUser;
 import com.android.server.UiServiceTestCase;
 import com.android.server.lights.LightsManager;
 import com.android.server.lights.LogicalLight;
@@ -3885,7 +3886,7 @@
         mService.updateUriPermissions(recordB, recordA, mContext.getPackageName(),
                 USER_SYSTEM);
         verify(mUgmInternal, times(1)).revokeUriPermissionFromOwner(any(),
-                eq(message1.getDataUri()), anyInt(), anyInt());
+                eq(message1.getDataUri()), anyInt(), anyInt(), eq(null), eq(-1));
 
         // Update back means we grant access to first again
         reset(mUgm);
@@ -4855,6 +4856,70 @@
     }
 
     @Test
+    public void backgroundSystemCustomToast_callsSetProcessImportantAsForegroundForToast() throws
+            Exception {
+        final String testPackage = "testPackageName";
+        assertEquals(0, mService.mToastQueue.size());
+        mService.isSystemUid = true;
+
+        // package is not suspended
+        when(mPackageManager.isPackageSuspendedForUser(testPackage, UserHandle.getUserId(mUid)))
+                .thenReturn(false);
+
+        // notifications from this package are blocked by the user
+        mService.setPreferencesHelper(mPreferencesHelper);
+        when(mPreferencesHelper.getImportance(testPackage, mUid)).thenReturn(IMPORTANCE_NONE);
+
+        setAppInForegroundForToasts(mUid, false);
+
+        // enqueue toast -> toast should still enqueue
+        ((INotificationManager) mService.mService).enqueueToast(testPackage, new Binder(),
+                new TestableToastCallback(), 2000, 0);
+        assertEquals(1, mService.mToastQueue.size());
+        verify(mAm).setProcessImportant(any(), anyInt(), eq(true), any());
+    }
+
+    @Test
+    public void foregroundTextToast_callsSetProcessImportantAsNotForegroundForToast() throws
+            Exception {
+        final String testPackage = "testPackageName";
+        assertEquals(0, mService.mToastQueue.size());
+        mService.isSystemUid = false;
+
+        // package is not suspended
+        when(mPackageManager.isPackageSuspendedForUser(testPackage, UserHandle.getUserId(mUid)))
+                .thenReturn(false);
+
+        setAppInForegroundForToasts(mUid, true);
+
+        // enqueue toast -> toast should still enqueue
+        ((INotificationManager) mService.mService).enqueueTextToast(testPackage, new Binder(),
+                "Text", 2000, 0, null);
+        assertEquals(1, mService.mToastQueue.size());
+        verify(mAm).setProcessImportant(any(), anyInt(), eq(false), any());
+    }
+
+    @Test
+    public void backgroundTextToast_callsSetProcessImportantAsNotForegroundForToast() throws
+            Exception {
+        final String testPackage = "testPackageName";
+        assertEquals(0, mService.mToastQueue.size());
+        mService.isSystemUid = false;
+
+        // package is not suspended
+        when(mPackageManager.isPackageSuspendedForUser(testPackage, UserHandle.getUserId(mUid)))
+                .thenReturn(false);
+
+        setAppInForegroundForToasts(mUid, false);
+
+        // enqueue toast -> toast should still enqueue
+        ((INotificationManager) mService.mService).enqueueTextToast(testPackage, new Binder(),
+                "Text", 2000, 0, null);
+        assertEquals(1, mService.mToastQueue.size());
+        verify(mAm).setProcessImportant(any(), anyInt(), eq(false), any());
+    }
+
+    @Test
     public void testTextToastsCallStatusBar() throws Exception {
         final String testPackage = "testPackageName";
         assertEquals(0, mService.mToastQueue.size());
@@ -6454,7 +6519,7 @@
     public void testOnUnlockUser() {
         UserInfo ui = new UserInfo();
         ui.id = 10;
-        mService.onUnlockUser(ui);
+        mService.onUserUnlocking(new TargetUser(ui));
         waitForIdle();
 
         verify(mHistoryManager, timeout(MAX_POST_DELAY).times(1)).onUserUnlocked(ui.id);
@@ -6464,7 +6529,7 @@
     public void testOnStopUser() {
         UserInfo ui = new UserInfo();
         ui.id = 10;
-        mService.onStopUser(ui);
+        mService.onUserStopping(new TargetUser(ui));
         waitForIdle();
 
         verify(mHistoryManager, timeout(MAX_POST_DELAY).times(1)).onUserStopped(ui.id);
diff --git a/services/tests/uiservicestests/src/com/android/server/slice/PackageMatchingCacheTest.java b/services/tests/uiservicestests/src/com/android/server/slice/PackageMatchingCacheTest.java
deleted file mode 100644
index f6c854e..0000000
--- a/services/tests/uiservicestests/src/com/android/server/slice/PackageMatchingCacheTest.java
+++ /dev/null
@@ -1,76 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
- * except in compliance with the License. You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software distributed under the
- * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the specific language governing
- * permissions and limitations under the License.
- */
-
-package com.android.server.slice;
-
-import static org.junit.Assert.assertTrue;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.times;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
-import android.testing.AndroidTestingRunner;
-import android.testing.TestableLooper.RunWithLooper;
-
-import androidx.test.filters.SmallTest;
-
-import com.android.server.UiServiceTestCase;
-import com.android.server.slice.SliceManagerService.PackageMatchingCache;
-
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import java.util.function.Supplier;
-
-@SmallTest
-@RunWith(AndroidTestingRunner.class)
-@RunWithLooper
-public class PackageMatchingCacheTest extends UiServiceTestCase {
-
-    private final Supplier<String> supplier = mock(Supplier.class);
-    private final PackageMatchingCache cache = new PackageMatchingCache(supplier);
-
-    @Test
-    public void testNulls() {
-        // Doesn't get for a null input
-        cache.matches(null);
-        verify(supplier, never()).get();
-
-        // Gets once valid input in sent.
-        cache.matches("");
-        verify(supplier).get();
-    }
-
-    @Test
-    public void testCaching() {
-        when(supplier.get()).thenReturn("ret.pkg");
-
-        assertTrue(cache.matches("ret.pkg"));
-        assertTrue(cache.matches("ret.pkg"));
-        assertTrue(cache.matches("ret.pkg"));
-
-        verify(supplier, times(1)).get();
-    }
-
-    @Test
-    public void testGetOnFailure() {
-        when(supplier.get()).thenReturn("ret.pkg");
-        assertTrue(cache.matches("ret.pkg"));
-
-        when(supplier.get()).thenReturn("other.pkg");
-        assertTrue(cache.matches("other.pkg"));
-        verify(supplier, times(2)).get();
-    }
-}
diff --git a/services/tests/uiservicestests/src/com/android/server/slice/SliceManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/slice/SliceManagerServiceTest.java
index a443695..cf1c36c 100644
--- a/services/tests/uiservicestests/src/com/android/server/slice/SliceManagerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/slice/SliceManagerServiceTest.java
@@ -90,8 +90,6 @@
 
     @Test
     public void testAddPinCreatesPinned() throws RemoteException {
-        doReturn("pkg").when(mService).getDefaultHome(anyInt());
-
         mService.pinSlice("pkg", TEST_URI, EMPTY_SPECS, mToken);
         mService.pinSlice("pkg", TEST_URI, EMPTY_SPECS, mToken);
         verify(mService, times(1)).createPinnedSlice(eq(maybeAddUserId(TEST_URI, 0)), anyString());
@@ -99,8 +97,6 @@
 
     @Test
     public void testRemovePinDestroysPinned() throws RemoteException {
-        doReturn("pkg").when(mService).getDefaultHome(anyInt());
-
         mService.pinSlice("pkg", TEST_URI, EMPTY_SPECS, mToken);
 
         when(mCreatedSliceState.unpin(eq("pkg"), eq(mToken))).thenReturn(false);
diff --git a/services/tests/wmtests/Android.bp b/services/tests/wmtests/Android.bp
index b3d75d3..4ca5b9e 100644
--- a/services/tests/wmtests/Android.bp
+++ b/services/tests/wmtests/Android.bp
@@ -2,15 +2,37 @@
 // Build WmTests package
 //########################################################################
 
+// Include all test java files.
+filegroup {
+    name: "wmtests-sources",
+    srcs: [
+        "src/**/*.java",
+    ],
+}
+
+genrule {
+    name: "wmtests.protologsrc",
+    srcs: [
+        ":protolog-groups",
+        ":wmtests-sources",
+    ],
+    tools: ["protologtool"],
+    cmd: "$(location protologtool) transform-protolog-calls " +
+      "--protolog-class com.android.internal.protolog.common.ProtoLog " +
+      "--protolog-impl-class com.android.internal.protolog.ProtoLogImpl " +
+      "--protolog-cache-class 'com.android.server.wm.ProtoLogCache' " +
+      "--loggroups-class com.android.internal.protolog.ProtoLogGroup " +
+      "--loggroups-jar $(location :protolog-groups) " +
+      "--output-srcjar $(out) " +
+      "$(locations :wmtests-sources)",
+    out: ["wmtests.protolog.srcjar"],
+}
+
 android_test {
     name: "WmTests",
 
     // We only want this apk build for tests.
-
-    // Include all test java files.
-    srcs: [
-        "src/**/*.java",
-    ],
+    srcs: [":wmtests.protologsrc"],
 
     static_libs: [
         "frameworks-base-testutils",
diff --git a/services/tests/wmtests/AndroidManifest.xml b/services/tests/wmtests/AndroidManifest.xml
index 4040fa6..e3c795d 100644
--- a/services/tests/wmtests/AndroidManifest.xml
+++ b/services/tests/wmtests/AndroidManifest.xml
@@ -38,6 +38,7 @@
     <uses-permission android:name="android.permission.REORDER_TASKS" />
     <uses-permission android:name="android.permission.READ_DEVICE_CONFIG" />
     <uses-permission android:name="android.permission.STATUS_BAR" />
+    <uses-permission android:name="android.permission.CAPTURE_VIDEO_OUTPUT" />
 
     <!-- TODO: Remove largeHeap hack when memory leak is fixed (b/123984854) -->
     <application android:debuggable="true"
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityDisplayTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityDisplayTests.java
index f69d7c3..1eb45d5 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityDisplayTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityDisplayTests.java
@@ -59,7 +59,7 @@
 @Presubmit
 @RunWith(WindowTestRunner.class)
 // TODO(b/144248496): Merge to DisplayContentTests
-public class ActivityDisplayTests extends ActivityTestsBase {
+public class ActivityDisplayTests extends WindowTestsBase {
 
     @Test
     public void testLastFocusedStackIsUpdatedWhenMovingStack() {
@@ -89,9 +89,9 @@
         // Create a pinned stack and move to front.
         final Task pinnedStack = mRootWindowContainer.getDefaultTaskDisplayArea()
                 .createStack(WINDOWING_MODE_PINNED, ACTIVITY_TYPE_STANDARD, ON_TOP);
-        final Task pinnedTask = new TaskBuilder(mService.mStackSupervisor)
+        final Task pinnedTask = new TaskBuilder(mAtm.mStackSupervisor)
                 .setStack(pinnedStack).build();
-        new ActivityBuilder(mService).setActivityFlags(FLAG_ALWAYS_FOCUSABLE)
+        new ActivityBuilder(mAtm).setActivityFlags(FLAG_ALWAYS_FOCUSABLE)
                 .setTask(pinnedTask).build();
         pinnedStack.moveToFront("movePinnedStackToFront");
 
@@ -140,7 +140,7 @@
         // Create a display which supports system decoration and allows reparenting stacks to
         // another display when the display is removed.
         final DisplayContent display = new TestDisplayContent.Builder(
-                mService, 1000, 1500).setSystemDecorations(true).build();
+                mAtm, 1000, 1500).setSystemDecorations(true).build();
         doReturn(false).when(display).shouldDestroyContentOnRemove();
 
         // Put home stack on the display.
@@ -162,9 +162,9 @@
     private Task createFullscreenStackWithSimpleActivityAt(DisplayContent display) {
         final Task fullscreenStack = display.getDefaultTaskDisplayArea().createStack(
                 WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, ON_TOP);
-        final Task fullscreenTask = new TaskBuilder(mService.mStackSupervisor)
+        final Task fullscreenTask = new TaskBuilder(mAtm.mStackSupervisor)
                 .setStack(fullscreenStack).build();
-        new ActivityBuilder(mService).setTask(fullscreenTask).build();
+        new ActivityBuilder(mAtm).setTask(fullscreenTask).build();
         return fullscreenStack;
     }
 
@@ -197,7 +197,7 @@
         assertNull(display.topRunningActivity(true /* considerKeyguardState */));
 
         // Add activity that should be shown on the keyguard.
-        final ActivityRecord showWhenLockedActivity = new ActivityBuilder(mService)
+        final ActivityRecord showWhenLockedActivity = new ActivityBuilder(mAtm)
                 .setCreateTask(true)
                 .setStack(stack)
                 .setActivityFlags(FLAG_SHOW_WHEN_LOCKED)
@@ -226,7 +226,7 @@
         final TaskDisplayArea taskDisplayArea = mRootWindowContainer.getDefaultTaskDisplayArea();
         final Task alwaysOnTopStack = taskDisplayArea.createStack(WINDOWING_MODE_FREEFORM,
                 ACTIVITY_TYPE_STANDARD, true /* onTop */);
-        final ActivityRecord activity = new ActivityBuilder(mService).setCreateTask(true)
+        final ActivityRecord activity = new ActivityBuilder(mAtm).setCreateTask(true)
                 .setStack(alwaysOnTopStack).build();
         alwaysOnTopStack.setAlwaysOnTop(true);
         taskDisplayArea.positionChildAt(POSITION_TOP, alwaysOnTopStack,
@@ -322,10 +322,10 @@
                 ACTIVITY_TYPE_STANDARD, ON_TOP);
         final Task stack4 = taskDisplayArea.createStack(WINDOWING_MODE_FULLSCREEN,
                 ACTIVITY_TYPE_STANDARD, ON_TOP);
-        final Task task1 = new TaskBuilder(mService.mStackSupervisor).setStack(stack1).build();
-        final Task task2 = new TaskBuilder(mService.mStackSupervisor).setStack(stack2).build();
-        final Task task3 = new TaskBuilder(mService.mStackSupervisor).setStack(stack3).build();
-        final Task task4 = new TaskBuilder(mService.mStackSupervisor).setStack(stack4).build();
+        final Task task1 = new TaskBuilder(mAtm.mStackSupervisor).setStack(stack1).build();
+        final Task task2 = new TaskBuilder(mAtm.mStackSupervisor).setStack(stack2).build();
+        final Task task3 = new TaskBuilder(mAtm.mStackSupervisor).setStack(stack3).build();
+        final Task task4 = new TaskBuilder(mAtm.mStackSupervisor).setStack(stack4).build();
 
         // Reordering stacks while removing stacks.
         doAnswer(invocation -> {
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityMetricsLaunchObserverTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityMetricsLaunchObserverTests.java
index feac6db..eb78172 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityMetricsLaunchObserverTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityMetricsLaunchObserverTests.java
@@ -20,6 +20,7 @@
 import static android.app.ActivityManager.START_TASK_TO_FRONT;
 import static android.content.ComponentName.createRelative;
 
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.mock;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.verifyNoMoreInteractions;
@@ -59,7 +60,7 @@
 @SmallTest
 @Presubmit
 @RunWith(WindowTestRunner.class)
-public class ActivityMetricsLaunchObserverTests extends ActivityTestsBase {
+public class ActivityMetricsLaunchObserverTests extends WindowTestsBase {
     private ActivityMetricsLogger mActivityMetricsLogger;
     private ActivityMetricsLogger.LaunchingState mLaunchingState;
     private ActivityMetricsLaunchObserver mLaunchObserver;
@@ -81,11 +82,11 @@
 
         // Sometimes we need an ActivityRecord for ActivityMetricsLogger to do anything useful.
         // This seems to be the easiest way to create an ActivityRecord.
-        mTrampolineActivity = new ActivityBuilder(mService)
+        mTrampolineActivity = new ActivityBuilder(mAtm)
                 .setCreateTask(true)
                 .setComponent(createRelative(DEFAULT_COMPONENT_PACKAGE_NAME, "TrampolineActivity"))
                 .build();
-        mTopActivity = new ActivityBuilder(mService)
+        mTopActivity = new ActivityBuilder(mAtm)
                 .setTask(mTrampolineActivity.getTask())
                 .setComponent(createRelative(DEFAULT_COMPONENT_PACKAGE_NAME, "TopActivity"))
                 .build();
@@ -121,7 +122,7 @@
     private <T> T verifyAsync(T mock) {
         // With WindowTestRunner, all test methods are inside WM lock, so we have to unblock any
         // messages that are waiting for the lock.
-        waitHandlerIdle(mService.mH);
+        waitHandlerIdle(mAtm.mH);
         // AMLO callbacks happen on a separate thread than AML calls, so we need to use a timeout.
         return verify(mock, timeout(TimeUnit.SECONDS.toMillis(5)));
     }
@@ -176,7 +177,8 @@
     public void testOnActivityLaunchCancelled_hasDrawn() {
         onActivityLaunched(mTopActivity);
 
-        mTopActivity.mVisibleRequested = mTopActivity.mDrawn = true;
+        mTopActivity.mVisibleRequested = true;
+        doReturn(true).when(mTopActivity).isReportedDrawn();
 
         // Cannot time already-visible activities.
         notifyActivityLaunched(START_TASK_TO_FRONT, mTopActivity);
@@ -187,16 +189,14 @@
 
     @Test
     public void testOnActivityLaunchCancelled_finishedBeforeDrawn() {
-        mTopActivity.mVisibleRequested = mTopActivity.mDrawn = true;
+        mTopActivity.mVisibleRequested = true;
+        doReturn(true).when(mTopActivity).isReportedDrawn();
 
-        // Suppress resume when creating the record because we want to notify logger manually.
-        mSupervisor.beginDeferResume();
         // Create an activity with different process that meets process switch.
-        final ActivityRecord noDrawnActivity = new ActivityBuilder(mService)
+        final ActivityRecord noDrawnActivity = new ActivityBuilder(mAtm)
                 .setTask(mTopActivity.getTask())
                 .setProcessName("other")
                 .build();
-        mSupervisor.readyToResume();
 
         notifyActivityLaunching(noDrawnActivity.intent);
         notifyActivityLaunched(START_SUCCESS, noDrawnActivity);
@@ -294,7 +294,7 @@
     public void testOnActivityLaunchCancelledTrampoline() {
         onActivityLaunchedTrampoline();
 
-        mTopActivity.mDrawn = true;
+        doReturn(true).when(mTopActivity).isReportedDrawn();
 
         // Cannot time already-visible activities.
         notifyActivityLaunched(START_TASK_TO_FRONT, mTopActivity);
@@ -321,7 +321,7 @@
         onActivityLaunched(mTopActivity);
         final ActivityMetricsLogger.LaunchingState previousState = mLaunchingState;
 
-        final ActivityRecord otherActivity = new ActivityBuilder(mService)
+        final ActivityRecord otherActivity = new ActivityBuilder(mAtm)
                 .setComponent(createRelative(DEFAULT_COMPONENT_PACKAGE_NAME, "OtherActivity"))
                 .setCreateTask(true)
                 .build();
@@ -345,7 +345,7 @@
                 .setDisplay(addNewDisplayContentAt(DisplayContent.POSITION_BOTTOM))
                 .setCreateActivity(false)
                 .build();
-        final ActivityRecord activityOnNewDisplay = new ActivityBuilder(mService)
+        final ActivityRecord activityOnNewDisplay = new ActivityBuilder(mAtm)
                 .setStack(stack)
                 .setCreateTask(true)
                 .setProcessName("new")
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 e45ced6..708d802 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
@@ -22,6 +22,10 @@
 import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
 import static android.content.pm.ActivityInfo.CONFIG_ORIENTATION;
 import static android.content.pm.ActivityInfo.CONFIG_SCREEN_LAYOUT;
+import static android.content.pm.ActivityInfo.LOCK_TASK_LAUNCH_MODE_ALWAYS;
+import static android.content.pm.ActivityInfo.LOCK_TASK_LAUNCH_MODE_DEFAULT;
+import static android.content.pm.ActivityInfo.LOCK_TASK_LAUNCH_MODE_IF_ALLOWLISTED;
+import static android.content.pm.ActivityInfo.LOCK_TASK_LAUNCH_MODE_NEVER;
 import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE;
 import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_PORTRAIT;
 import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
@@ -67,13 +71,10 @@
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
-import static org.mockito.ArgumentMatchers.anyBoolean;
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.anyString;
-import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.ArgumentMatchers.same;
 import static org.mockito.Mockito.clearInvocations;
-import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.never;
 
 import android.app.ActivityManager.TaskSnapshot;
@@ -85,6 +86,7 @@
 import android.content.ComponentName;
 import android.content.Intent;
 import android.content.pm.ActivityInfo;
+import android.content.pm.ApplicationInfo;
 import android.content.res.Configuration;
 import android.content.res.Resources;
 import android.graphics.Rect;
@@ -122,7 +124,7 @@
 @MediumTest
 @Presubmit
 @RunWith(WindowTestRunner.class)
-public class ActivityRecordTests extends ActivityTestsBase {
+public class ActivityRecordTests extends WindowTestsBase {
     private Task mStack;
     private Task mTask;
     private ActivityRecord mActivity;
@@ -133,7 +135,7 @@
         mTask = mStack.getBottomMostTask();
         mActivity = mTask.getTopNonFinishingActivity();
 
-        setBooted(mService);
+        setBooted(mAtm);
     }
 
     @Test
@@ -152,16 +154,16 @@
     public void testStackCleanupOnTaskRemoval() {
         mStack.removeChild(mTask, null /*reason*/);
         // Stack should be gone on task removal.
-        assertNull(mService.mRootWindowContainer.getStack(mStack.mTaskId));
+        assertNull(mAtm.mRootWindowContainer.getStack(mStack.mTaskId));
     }
 
     @Test
     public void testRemoveChildWithOverlayActivity() {
         final ActivityRecord overlayActivity =
-                new ActivityBuilder(mService).setTask(mTask).build();
+                new ActivityBuilder(mAtm).setTask(mTask).build();
         overlayActivity.setTaskOverlay(true);
         final ActivityRecord overlayActivity2 =
-                new ActivityBuilder(mService).setTask(mTask).build();
+                new ActivityBuilder(mAtm).setTask(mTask).build();
         overlayActivity2.setTaskOverlay(true);
 
         mTask.removeChild(overlayActivity2, "test");
@@ -170,7 +172,7 @@
 
     @Test
     public void testNoCleanupMovingActivityInSameStack() {
-        final Task newTask = new TaskBuilder(mService.mStackSupervisor).setStack(mStack).build();
+        final Task newTask = new TaskBuilder(mAtm.mStackSupervisor).setStack(mStack).build();
         mActivity.reparent(newTask, 0, null /*reason*/);
         verify(mStack, times(0)).cleanUpActivityReferences(any());
     }
@@ -213,7 +215,7 @@
         // Make sure the state does not change if we are not the current top activity.
         mActivity.setState(STOPPED, "testPausingWhenVisibleFromStopped behind");
 
-        final ActivityRecord topActivity = new ActivityBuilder(mService).setTask(mTask).build();
+        final ActivityRecord topActivity = new ActivityBuilder(mAtm).setTask(mTask).build();
         mStack.mTranslucentActivityWaiting = topActivity;
         mActivity.makeVisibleIfNeeded(null /* starting */, true /* reportToClient */);
         assertTrue(mActivity.isState(STARTED));
@@ -231,8 +233,8 @@
 
     @Test
     public void testCanBeLaunchedOnDisplay() {
-        mService.mSupportsMultiWindow = true;
-        final ActivityRecord activity = new ActivityBuilder(mService).build();
+        mAtm.mSupportsMultiWindow = true;
+        final ActivityRecord activity = new ActivityBuilder(mAtm).build();
 
         // An activity can be launched on default display.
         assertTrue(activity.canBeLaunchedOnDisplay(DEFAULT_DISPLAY));
@@ -251,7 +253,7 @@
 
         // Set options for two ActivityRecords in same Task. Apply one ActivityRecord options.
         // Pending options should be cleared for both ActivityRecords
-        ActivityRecord activity2 = new ActivityBuilder(mService).setTask(mTask).build();
+        ActivityRecord activity2 = new ActivityBuilder(mAtm).setTask(mTask).build();
         activity2.updateOptionsLocked(activityOptions);
         mActivity.updateOptionsLocked(activityOptions);
         mActivity.applyOptionsLocked();
@@ -260,8 +262,8 @@
 
         // Set options for two ActivityRecords in separate Tasks. Apply one ActivityRecord options.
         // Pending options should be cleared for only ActivityRecord that was applied
-        Task task2 = new TaskBuilder(mService.mStackSupervisor).setStack(mStack).build();
-        activity2 = new ActivityBuilder(mService).setTask(task2).build();
+        Task task2 = new TaskBuilder(mAtm.mStackSupervisor).setStack(mStack).build();
+        activity2 = new ActivityBuilder(mAtm).setTask(task2).build();
         activity2.updateOptionsLocked(activityOptions);
         mActivity.updateOptionsLocked(activityOptions);
         mActivity.applyOptionsLocked();
@@ -362,7 +364,7 @@
 
     @Test
     public void testSetRequestedOrientationUpdatesConfiguration() throws Exception {
-        mActivity = new ActivityBuilder(mService)
+        mActivity = new ActivityBuilder(mAtm)
                 .setTask(mTask)
                 .setConfigChanges(CONFIG_ORIENTATION | CONFIG_SCREEN_LAYOUT)
                 .build();
@@ -405,7 +407,7 @@
 
         final ActivityConfigurationChangeItem expected =
                 ActivityConfigurationChangeItem.obtain(newConfig);
-        verify(mService.getLifecycleManager()).scheduleTransaction(eq(mActivity.app.getThread()),
+        verify(mAtm.getLifecycleManager()).scheduleTransaction(eq(mActivity.app.getThread()),
                 eq(mActivity.appToken), eq(expected));
     }
 
@@ -500,9 +502,9 @@
 
     @Test
     public void testShouldMakeActive_nonTopVisible() {
-        ActivityRecord finishingActivity = new ActivityBuilder(mService).setTask(mTask).build();
+        ActivityRecord finishingActivity = new ActivityBuilder(mAtm).setTask(mTask).build();
         finishingActivity.finishing = true;
-        ActivityRecord topActivity = new ActivityBuilder(mService).setTask(mTask).build();
+        ActivityRecord topActivity = new ActivityBuilder(mAtm).setTask(mTask).build();
         mActivity.setState(Task.ActivityState.STOPPED, "Testing");
 
         assertEquals(false, mActivity.shouldMakeActive(null /* activeActivity */));
@@ -528,7 +530,7 @@
         mActivity.setState(Task.ActivityState.STOPPED, "Testing");
         spyOn(mStack);
 
-        ActivityRecord topActivity = new ActivityBuilder(mService).setTask(mTask).build();
+        ActivityRecord topActivity = new ActivityBuilder(mAtm).setTask(mTask).build();
         mActivity.addResultLocked(topActivity, "resultWho", 0, 0, new Intent());
         topActivity.finishing = true;
 
@@ -539,10 +541,10 @@
 
     @Test
     public void testPushConfigurationWhenLaunchTaskBehind() throws Exception {
-        mActivity = new ActivityBuilder(mService)
+        mActivity = new ActivityBuilder(mAtm)
                 .setTask(mTask)
                 .setLaunchTaskBehind(true)
-                .setConfigChanges(CONFIG_ORIENTATION)
+                .setConfigChanges(CONFIG_ORIENTATION | CONFIG_SCREEN_LAYOUT)
                 .build();
         mActivity.setState(Task.ActivityState.STOPPED, "Testing");
 
@@ -574,7 +576,7 @@
 
             final ActivityConfigurationChangeItem expected =
                     ActivityConfigurationChangeItem.obtain(newConfig);
-            verify(mService.getLifecycleManager()).scheduleTransaction(
+            verify(mAtm.getLifecycleManager()).scheduleTransaction(
                     eq(mActivity.app.getThread()), eq(mActivity.appToken), eq(expected));
         } finally {
             stack.getDisplayArea().removeChild(stack);
@@ -583,7 +585,7 @@
 
     @Test
     public void testShouldStartWhenMakeClientActive() {
-        ActivityRecord topActivity = new ActivityBuilder(mService).setTask(mTask).build();
+        ActivityRecord topActivity = new ActivityBuilder(mAtm).setTask(mTask).build();
         topActivity.setOccludesParent(false);
         mActivity.setState(Task.ActivityState.STOPPED, "Testing");
         mActivity.setVisibility(true);
@@ -621,7 +623,7 @@
     public void testCanLaunchHomeActivityFromChooser() {
         ComponentName chooserComponent = ComponentName.unflattenFromString(
                 Resources.getSystem().getString(R.string.config_chooserActivity));
-        ActivityRecord chooserActivity = new ActivityBuilder(mService).setComponent(
+        ActivityRecord chooserActivity = new ActivityBuilder(mAtm).setComponent(
                 chooserComponent).build();
         assertThat(mActivity.canLaunchHomeActivity(NOBODY_UID, chooserActivity)).isTrue();
     }
@@ -736,7 +738,7 @@
 
         // Put a visible activity on top, so the finishing activity doesn't have to wait until the
         // next activity reports idle to destroy it.
-        final ActivityRecord topActivity = new ActivityBuilder(mService).setTask(mTask).build();
+        final ActivityRecord topActivity = new ActivityBuilder(mAtm).setTask(mTask).build();
         topActivity.mVisibleRequested = true;
         topActivity.nowVisible = true;
         topActivity.setState(RESUMED, "test");
@@ -914,7 +916,7 @@
     public void testFinishActivityIfPossible_nonVisibleNoAppTransition() {
         // Put an activity on top of test activity to make it invisible and prevent us from
         // accidentally resuming the topmost one again.
-        new ActivityBuilder(mService).build();
+        new ActivityBuilder(mAtm).build();
         mActivity.mVisibleRequested = false;
         mActivity.setState(STOPPED, "test");
 
@@ -992,7 +994,7 @@
      */
     @Test
     public void testCompleteFinishing_waitForNextVisible() {
-        final ActivityRecord topActivity = new ActivityBuilder(mService).setTask(mTask).build();
+        final ActivityRecord topActivity = new ActivityBuilder(mAtm).setTask(mTask).build();
         topActivity.mVisibleRequested = true;
         topActivity.nowVisible = true;
         topActivity.finishing = true;
@@ -1017,7 +1019,7 @@
      */
     @Test
     public void testCompleteFinishing_noWaitForNextVisible_alreadyInvisible() {
-        final ActivityRecord topActivity = new ActivityBuilder(mService).setTask(mTask).build();
+        final ActivityRecord topActivity = new ActivityBuilder(mAtm).setTask(mTask).build();
         topActivity.mVisibleRequested = false;
         topActivity.nowVisible = false;
         topActivity.finishing = true;
@@ -1039,7 +1041,7 @@
      */
     @Test
     public void testCompleteFinishing_waitForIdle() {
-        final ActivityRecord topActivity = new ActivityBuilder(mService).setTask(mTask).build();
+        final ActivityRecord topActivity = new ActivityBuilder(mAtm).setTask(mTask).build();
         topActivity.mVisibleRequested = true;
         topActivity.nowVisible = true;
         topActivity.finishing = true;
@@ -1060,7 +1062,7 @@
      */
     @Test
     public void testCompleteFinishing_noWaitForNextVisible_stopped() {
-        final ActivityRecord topActivity = new ActivityBuilder(mService).setTask(mTask).build();
+        final ActivityRecord topActivity = new ActivityBuilder(mAtm).setTask(mTask).build();
         topActivity.mVisibleRequested = false;
         topActivity.nowVisible = false;
         topActivity.finishing = true;
@@ -1081,7 +1083,7 @@
      */
     @Test
     public void testCompleteFinishing_noWaitForNextVisible_nonFocusedStack() {
-        final ActivityRecord topActivity = new ActivityBuilder(mService).setTask(mTask).build();
+        final ActivityRecord topActivity = new ActivityBuilder(mAtm).setTask(mTask).build();
         topActivity.mVisibleRequested = true;
         topActivity.nowVisible = true;
         topActivity.finishing = true;
@@ -1114,7 +1116,7 @@
         // Make keyguard locked and set the top activity show-when-locked.
         KeyguardController keyguardController = mActivity.mStackSupervisor.getKeyguardController();
         doReturn(true).when(keyguardController).isKeyguardLocked();
-        final ActivityRecord topActivity = new ActivityBuilder(mService).setTask(mTask).build();
+        final ActivityRecord topActivity = new ActivityBuilder(mAtm).setTask(mTask).build();
         topActivity.mVisibleRequested = true;
         topActivity.nowVisible = true;
         topActivity.setState(RESUMED, "true");
@@ -1143,18 +1145,18 @@
      */
     @Test
     public void testCompleteFinishing_ensureActivitiesVisible() {
-        final ActivityRecord firstActivity = new ActivityBuilder(mService).setTask(mTask).build();
+        final ActivityRecord firstActivity = new ActivityBuilder(mAtm).setTask(mTask).build();
         firstActivity.mVisibleRequested = false;
         firstActivity.nowVisible = false;
         firstActivity.setState(STOPPED, "true");
 
-        final ActivityRecord secondActivity = new ActivityBuilder(mService).setTask(mTask).build();
+        final ActivityRecord secondActivity = new ActivityBuilder(mAtm).setTask(mTask).build();
         secondActivity.mVisibleRequested = true;
         secondActivity.nowVisible = true;
         secondActivity.setState(PAUSED, "true");
 
         final ActivityRecord translucentActivity =
-                new ActivityBuilder(mService).setTask(mTask).build();
+                new ActivityBuilder(mAtm).setTask(mTask).build();
         translucentActivity.mVisibleRequested = true;
         translucentActivity.nowVisible = true;
         translucentActivity.setState(RESUMED, "true");
@@ -1327,12 +1329,15 @@
     public void testRemoveFromHistory() {
         final Task stack = mActivity.getRootTask();
         final Task task = mActivity.getTask();
+        final WindowProcessController wpc = mActivity.app;
+        assertTrue(wpc.hasActivities());
 
         mActivity.removeFromHistory("test");
 
         assertEquals(DESTROYED, mActivity.getState());
         assertNull(mActivity.app);
         assertNull(mActivity.getTask());
+        assertFalse(wpc.hasActivities());
         assertEquals(0, task.getChildCount());
         assertEquals(task.getRootTask(), task);
         assertEquals(0, stack.getChildCount());
@@ -1393,7 +1398,7 @@
 
         final Task firstTaskRecord = mActivity.getTask();
         final ActivityRecord secondActivityRecord =
-                new ActivityBuilder(mService).setTask(firstTaskRecord).setUseProcess(wpc).build();
+                new ActivityBuilder(mAtm).setTask(firstTaskRecord).setUseProcess(wpc).build();
 
         assertTrue(wpc.registeredForActivityConfigChanges());
         assertEquals(0, secondActivityRecord.getMergedOverrideConfiguration()
@@ -1406,7 +1411,7 @@
         assertTrue(wpc.registeredForActivityConfigChanges());
 
         final ActivityRecord secondActivityRecord =
-                new ActivityBuilder(mService).setTask(mTask).setUseProcess(wpc).build();
+                new ActivityBuilder(mAtm).setTask(mTask).setUseProcess(wpc).build();
 
         assertTrue(wpc.registeredForActivityConfigChanges());
         assertEquals(0, secondActivityRecord.getMergedOverrideConfiguration()
@@ -1501,8 +1506,8 @@
         final WindowManager.LayoutParams params = new WindowManager.LayoutParams(
                 WindowManager.LayoutParams.TYPE_APPLICATION_STARTING);
         params.width = params.height = WindowManager.LayoutParams.MATCH_PARENT;
-        final WindowTestUtils.TestWindowState w = new WindowTestUtils.TestWindowState(
-                mService.mWindowManager, mock(Session.class), new TestIWindow(), params, mActivity);
+        final TestWindowState w = new TestWindowState(
+                mAtm.mWindowManager, mock(Session.class), new TestIWindow(), params, mActivity);
         mActivity.addWindow(w);
 
         // Assume the activity is launching in different rotation, and there was an available
@@ -1523,7 +1528,7 @@
                     any() /* outContentInsets */, any() /* outStableInsets */,
                     any() /* outDisplayCutout */, any() /* outInputChannel */,
                     any() /* outInsetsState */, any() /* outActiveControls */);
-            TaskSnapshotSurface.create(mService.mWindowManager, mActivity, snapshot);
+            TaskSnapshotSurface.create(mAtm.mWindowManager, mActivity, snapshot);
         } catch (RemoteException ignored) {
         } finally {
             reset(session);
@@ -1599,7 +1604,7 @@
         final Configuration initialConf =
                 new Configuration(mActivity.getMergedOverrideConfiguration());
         final Task initialTask = mActivity.getTask();
-        final ActivityRecord secondActivity = new ActivityBuilder(mService).setTask(initialTask)
+        final ActivityRecord secondActivity = new ActivityBuilder(mAtm).setTask(initialTask)
                 .setUseProcess(wpc).build();
 
         assertTrue(wpc.registeredForActivityConfigChanges());
@@ -1683,6 +1688,32 @@
         assertFalse(mActivity.canTurnScreenOn());
     }
 
+    @Test
+    public void testGetLockTaskLaunchMode() {
+        final ActivityOptions options = ActivityOptions.makeBasic().setLockTaskEnabled(true);
+        mActivity.info.lockTaskLaunchMode = LOCK_TASK_LAUNCH_MODE_DEFAULT;
+        assertEquals(LOCK_TASK_LAUNCH_MODE_IF_ALLOWLISTED,
+                ActivityRecord.getLockTaskLaunchMode(mActivity.info, options));
+
+        mActivity.info.lockTaskLaunchMode = LOCK_TASK_LAUNCH_MODE_ALWAYS;
+        assertEquals(LOCK_TASK_LAUNCH_MODE_DEFAULT,
+                ActivityRecord.getLockTaskLaunchMode(mActivity.info, null /*options*/));
+
+        mActivity.info.lockTaskLaunchMode = LOCK_TASK_LAUNCH_MODE_NEVER;
+        assertEquals(LOCK_TASK_LAUNCH_MODE_DEFAULT,
+                ActivityRecord.getLockTaskLaunchMode(mActivity.info, null /*options*/));
+
+        mActivity.info.applicationInfo.privateFlags |= ApplicationInfo.PRIVATE_FLAG_PRIVILEGED;
+        mActivity.info.lockTaskLaunchMode = LOCK_TASK_LAUNCH_MODE_ALWAYS;
+        assertEquals(LOCK_TASK_LAUNCH_MODE_ALWAYS,
+                ActivityRecord.getLockTaskLaunchMode(mActivity.info, null /*options*/));
+
+        mActivity.info.lockTaskLaunchMode = LOCK_TASK_LAUNCH_MODE_NEVER;
+        assertEquals(LOCK_TASK_LAUNCH_MODE_NEVER,
+                ActivityRecord.getLockTaskLaunchMode(mActivity.info, null /*options*/));
+
+    }
+
     /**
      * Creates an activity on display. For non-default display request it will also create a new
      * display with custom DisplayInfo.
@@ -1693,12 +1724,12 @@
         if (defaultDisplay) {
             display = mRootWindowContainer.getDefaultDisplay();
         } else {
-            display = new TestDisplayContent.Builder(mService, 2000, 1000).setDensityDpi(300)
+            display = new TestDisplayContent.Builder(mAtm, 2000, 1000).setDensityDpi(300)
                     .setPosition(DisplayContent.POSITION_TOP).build();
         }
         final Task stack = display.getDefaultTaskDisplayArea()
                 .createStack(WINDOWING_MODE_UNDEFINED, ACTIVITY_TYPE_STANDARD, true /* onTop */);
         final Task task = new TaskBuilder(mSupervisor).setStack(stack).build();
-        return new ActivityBuilder(mService).setTask(task).setUseProcess(process).build();
+        return new ActivityBuilder(mAtm).setTask(task).setUseProcess(process).build();
     }
 }
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityStackSupervisorTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityStackSupervisorTests.java
index 197c89a..27e2d13 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityStackSupervisorTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityStackSupervisorTests.java
@@ -22,6 +22,7 @@
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
 import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
 
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.never;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.reset;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
@@ -39,6 +40,7 @@
 import android.app.WaitResult;
 import android.content.pm.ActivityInfo;
 import android.platform.test.annotations.Presubmit;
+import android.view.Display;
 
 import androidx.test.filters.MediumTest;
 
@@ -55,7 +57,7 @@
 @MediumTest
 @Presubmit
 @RunWith(WindowTestRunner.class)
-public class ActivityStackSupervisorTests extends ActivityTestsBase {
+public class ActivityStackSupervisorTests extends WindowTestsBase {
     private Task mFullscreenStack;
 
     @Before
@@ -69,7 +71,7 @@
      */
     @Test
     public void testStoppingActivityRemovedWhenResumed() {
-        final ActivityRecord firstActivity = new ActivityBuilder(mService).setCreateTask(true)
+        final ActivityRecord firstActivity = new ActivityBuilder(mAtm).setCreateTask(true)
                 .setStack(mFullscreenStack).build();
         mSupervisor.mStoppingActivities.add(firstActivity);
 
@@ -83,7 +85,7 @@
      */
     @Test
     public void testReportWaitingActivityLaunchedIfNeeded() {
-        final ActivityRecord firstActivity = new ActivityBuilder(mService).setCreateTask(true)
+        final ActivityRecord firstActivity = new ActivityBuilder(mAtm).setCreateTask(true)
                 .setStack(mFullscreenStack).build();
 
         final WaitResult taskToFrontWait = new WaitResult();
@@ -121,7 +123,7 @@
         task.setResizeMode(unresizableActivity.info.resizeMode);
 
         final TaskChangeNotificationController taskChangeNotifier =
-                mService.getTaskChangeNotificationController();
+                mAtm.getTaskChangeNotificationController();
         spyOn(taskChangeNotifier);
 
         mSupervisor.handleNonResizableTaskIfNeeded(task, newDisplay.getWindowingMode(),
@@ -133,7 +135,7 @@
         reset(taskChangeNotifier);
 
         // Put a resizable activity on top of the unresizable task.
-        final ActivityRecord resizableActivity = new ActivityBuilder(mService)
+        final ActivityRecord resizableActivity = new ActivityBuilder(mAtm)
                 .setTask(task).build();
         resizableActivity.info.resizeMode = ActivityInfo.RESIZE_MODE_RESIZEABLE;
 
@@ -150,27 +152,52 @@
      */
     @Test
     public void testNotifyTaskFocusChanged() {
-        final ActivityRecord fullScreenActivityA = new ActivityBuilder(mService).setCreateTask(true)
+        final ActivityRecord fullScreenActivityA = new ActivityBuilder(mAtm).setCreateTask(true)
                 .setStack(mFullscreenStack).build();
         final Task taskA = fullScreenActivityA.getTask();
 
         final TaskChangeNotificationController taskChangeNotifier =
-                mService.getTaskChangeNotificationController();
+                mAtm.getTaskChangeNotificationController();
         spyOn(taskChangeNotifier);
 
-        mService.setResumedActivityUncheckLocked(fullScreenActivityA, "resumeA");
+        mAtm.setResumedActivityUncheckLocked(fullScreenActivityA, "resumeA");
         verify(taskChangeNotifier).notifyTaskFocusChanged(eq(taskA.mTaskId) /* taskId */,
                 eq(true) /* focused */);
         reset(taskChangeNotifier);
 
-        final ActivityRecord fullScreenActivityB = new ActivityBuilder(mService).setCreateTask(true)
+        final ActivityRecord fullScreenActivityB = new ActivityBuilder(mAtm).setCreateTask(true)
                 .setStack(mFullscreenStack).build();
         final Task taskB = fullScreenActivityB.getTask();
 
-        mService.setResumedActivityUncheckLocked(fullScreenActivityB, "resumeB");
+        mAtm.setResumedActivityUncheckLocked(fullScreenActivityB, "resumeB");
         verify(taskChangeNotifier).notifyTaskFocusChanged(eq(taskA.mTaskId) /* taskId */,
                 eq(false) /* focused */);
         verify(taskChangeNotifier).notifyTaskFocusChanged(eq(taskB.mTaskId) /* taskId */,
                 eq(true) /* focused */);
     }
+
+    /**
+     * Ensures that a trusted display can launch arbitrary activity and an untrusted display can't.
+     */
+    @Test
+    public void testDisplayCanLaunchActivities() {
+        final Display display = mDisplayContent.mDisplay;
+        // An empty info without FLAG_ALLOW_EMBEDDED.
+        final ActivityInfo activityInfo = new ActivityInfo();
+        final int callingPid = 12345;
+        final int callingUid = 12345;
+        spyOn(display);
+
+        doReturn(true).when(display).isTrusted();
+        final boolean allowedOnTrusted = mSupervisor.isCallerAllowedToLaunchOnDisplay(callingPid,
+                callingUid, display.getDisplayId(), activityInfo);
+
+        assertThat(allowedOnTrusted).isTrue();
+
+        doReturn(false).when(display).isTrusted();
+        final boolean allowedOnUntrusted = mSupervisor.isCallerAllowedToLaunchOnDisplay(callingPid,
+                callingUid, display.getDisplayId(), activityInfo);
+
+        assertThat(allowedOnUntrusted).isFalse();
+    }
 }
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityStackTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityStackTests.java
index 775df74..e2948a7 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityStackTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityStackTests.java
@@ -89,7 +89,7 @@
 @SmallTest
 @Presubmit
 @RunWith(WindowTestRunner.class)
-public class ActivityStackTests extends ActivityTestsBase {
+public class ActivityStackTests extends WindowTestsBase {
     private TaskDisplayArea mDefaultTaskDisplayArea;
     private Task mStack;
     private Task mTask;
@@ -105,7 +105,7 @@
 
     @Test
     public void testResumedActivity() {
-        final ActivityRecord r = new ActivityBuilder(mService).setTask(mTask).build();
+        final ActivityRecord r = new ActivityBuilder(mAtm).setTask(mTask).build();
         assertNull(mStack.getResumedActivity());
         r.setState(RESUMED, "testResumedActivity");
         assertEquals(r, mStack.getResumedActivity());
@@ -115,7 +115,7 @@
 
     @Test
     public void testResumedActivityFromTaskReparenting() {
-        final ActivityRecord r = new ActivityBuilder(mService).setTask(mTask).build();
+        final ActivityRecord r = new ActivityBuilder(mAtm).setTask(mTask).build();
         // Ensure moving task between two stacks updates resumed activity
         r.setState(RESUMED, "testResumedActivityFromTaskReparenting");
         assertEquals(r, mStack.getResumedActivity());
@@ -133,7 +133,7 @@
 
     @Test
     public void testResumedActivityFromActivityReparenting() {
-        final ActivityRecord r = new ActivityBuilder(mService).setTask(mTask).build();
+        final ActivityRecord r = new ActivityBuilder(mAtm).setTask(mTask).build();
         // Ensure moving task between two stacks updates resumed activity
         r.setState(RESUMED, "testResumedActivityFromActivityReparenting");
         assertEquals(r, mStack.getResumedActivity());
@@ -149,7 +149,7 @@
 
     @Test
     public void testPrimarySplitScreenMoveToBack() {
-        TestSplitOrganizer organizer = new TestSplitOrganizer(mService);
+        TestSplitOrganizer organizer = new TestSplitOrganizer(mAtm);
         // We're testing an edge case here where we have primary + fullscreen rather than secondary.
         organizer.setMoveToSecondaryOnEnter(false);
 
@@ -177,7 +177,7 @@
 
     @Test
     public void testMoveToPrimarySplitScreenThenMoveToBack() {
-        TestSplitOrganizer organizer = new TestSplitOrganizer(mService);
+        TestSplitOrganizer organizer = new TestSplitOrganizer(mAtm);
         // This time, start with a fullscreen activitystack
         final Task primarySplitScreen = mDefaultTaskDisplayArea.createStack(
                 WINDOWING_MODE_UNDEFINED, ACTIVITY_TYPE_STANDARD, true /* onTop */);
@@ -202,7 +202,7 @@
 
     @Test
     public void testSplitScreenMoveToBack() {
-        TestSplitOrganizer organizer = new TestSplitOrganizer(mService);
+        TestSplitOrganizer organizer = new TestSplitOrganizer(mAtm);
         // Set up split-screen with primary on top and secondary containing the home task below
         // another stack.
         final Task primaryTask = mDefaultTaskDisplayArea.createStack(
@@ -241,12 +241,12 @@
     @Test
     public void testRemoveOrganizedTask_UpdateStackReference() {
         final Task rootHomeTask = mDefaultTaskDisplayArea.getRootHomeTask();
-        final ActivityRecord homeActivity = new ActivityBuilder(mService)
+        final ActivityRecord homeActivity = new ActivityBuilder(mAtm)
                 .setStack(rootHomeTask)
                 .setCreateTask(true)
                 .build();
         final Task secondaryStack = (Task) WindowContainer.fromBinder(
-                mService.mTaskOrganizerController.createRootTask(rootHomeTask.getDisplayId(),
+                mAtm.mTaskOrganizerController.createRootTask(rootHomeTask.getDisplayId(),
                         WINDOWING_MODE_SPLIT_SCREEN_SECONDARY).token.asBinder());
 
         rootHomeTask.reparent(secondaryStack, POSITION_TOP);
@@ -292,7 +292,7 @@
 
     @Test
     public void testStopActivityWhenActivityDestroyed() {
-        final ActivityRecord r = new ActivityBuilder(mService).setTask(mTask).build();
+        final ActivityRecord r = new ActivityBuilder(mAtm).setTask(mTask).build();
         r.info.flags |= ActivityInfo.FLAG_NO_HISTORY;
         mStack.moveToFront("testStopActivityWithDestroy");
         r.stopIfPossible();
@@ -302,14 +302,14 @@
 
     @Test
     public void testFindTaskWithOverlay() {
-        final ActivityRecord r = new ActivityBuilder(mService)
+        final ActivityRecord r = new ActivityBuilder(mAtm)
                 .setCreateTask(true)
                 .setStack(mStack)
                 .setUid(0)
                 .build();
         final Task task = r.getTask();
         // Overlay must be for a different user to prevent recognizing a matching top activity
-        final ActivityRecord taskOverlay = new ActivityBuilder(mService).setTask(task)
+        final ActivityRecord taskOverlay = new ActivityBuilder(mAtm).setTask(task)
                 .setUid(UserHandle.PER_USER_RANGE * 2).build();
         taskOverlay.setTaskOverlay(true);
 
@@ -330,21 +330,21 @@
                 targetActivity);
         final ComponentName alias = new ComponentName(DEFAULT_COMPONENT_PACKAGE_NAME,
                 aliasActivity);
-        final Task task = new TaskBuilder(mService.mStackSupervisor).setStack(mStack).build();
+        final Task task = new TaskBuilder(mAtm.mStackSupervisor).setStack(mStack).build();
         task.origActivity = alias;
         task.realActivity = target;
-        new ActivityBuilder(mService).setComponent(target).setTask(task).setTargetActivity(
+        new ActivityBuilder(mAtm).setComponent(target).setTask(task).setTargetActivity(
                 targetActivity).build();
 
         // Using target activity to find task.
-        final ActivityRecord r1 = new ActivityBuilder(mService).setComponent(
+        final ActivityRecord r1 = new ActivityBuilder(mAtm).setComponent(
                 target).setTargetActivity(targetActivity).build();
         RootWindowContainer.FindTaskResult result = new RootWindowContainer.FindTaskResult();
         result.process(r1, mStack);
         assertThat(result.mRecord).isNotNull();
 
         // Using alias activity to find task.
-        final ActivityRecord r2 = new ActivityBuilder(mService).setComponent(
+        final ActivityRecord r2 = new ActivityBuilder(mAtm).setComponent(
                 alias).setTargetActivity(targetActivity).build();
         result = new RootWindowContainer.FindTaskResult();
         result.process(r2, mStack);
@@ -377,7 +377,7 @@
         final Task pinnedStack = createStackForShouldBeVisibleTest(mDefaultTaskDisplayArea,
                 WINDOWING_MODE_PINNED, ACTIVITY_TYPE_STANDARD, true /* onTop */);
         // Add an activity to the pinned stack so it isn't considered empty for visibility check.
-        final ActivityRecord pinnedActivity = new ActivityBuilder(mService)
+        final ActivityRecord pinnedActivity = new ActivityBuilder(mAtm)
                 .setCreateTask(true)
                 .setStack(pinnedStack)
                 .build();
@@ -676,7 +676,7 @@
         assertEquals(STACK_VISIBILITY_VISIBLE,
                 translucentStack.getVisibility(null /* starting */));
         // Add an activity to the pinned stack so it isn't considered empty for visibility check.
-        final ActivityRecord pinnedActivity = new ActivityBuilder(mService)
+        final ActivityRecord pinnedActivity = new ActivityBuilder(mAtm)
                 .setCreateTask(true)
                 .setStack(pinnedStack)
                 .build();
@@ -689,7 +689,7 @@
                 WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_HOME, true /* onTop */);
         ActivityRecord topRunningHomeActivity = homeStack.topRunningActivity();
         if (topRunningHomeActivity == null) {
-            topRunningHomeActivity = new ActivityBuilder(mService)
+            topRunningHomeActivity = new ActivityBuilder(mAtm)
                     .setStack(homeStack)
                     .setCreateTask(true)
                     .build();
@@ -721,12 +721,12 @@
         final Task homeStack = createStackForShouldBeVisibleTest(mDefaultTaskDisplayArea,
                 WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_HOME, true /* onTop */);
 
-        final ActivityRecord firstActivity = new ActivityBuilder(mService)
+        final ActivityRecord firstActivity = new ActivityBuilder(mAtm)
                     .setStack(homeStack)
                     .setCreateTask(true)
                     .build();
         final Task task = firstActivity.getTask();
-        final ActivityRecord secondActivity = new ActivityBuilder(mService)
+        final ActivityRecord secondActivity = new ActivityBuilder(mAtm)
                 .setTask(task)
                 .build();
 
@@ -991,7 +991,6 @@
             TaskDisplayArea taskDisplayArea, int windowingMode, int activityType, boolean onTop) {
         final Task task;
         if (activityType == ACTIVITY_TYPE_HOME) {
-            // Home stack and activity are created in ActivityTestsBase#setupActivityManagerService
             task = mDefaultTaskDisplayArea.getStack(WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_HOME);
             mDefaultTaskDisplayArea.positionChildAt(onTop ? POSITION_TOP : POSITION_BOTTOM, task,
                     false /* includingParents */);
@@ -1009,8 +1008,8 @@
 
     @Test
     public void testFinishDisabledPackageActivities_FinishAliveActivities() {
-        final ActivityRecord firstActivity = new ActivityBuilder(mService).setTask(mTask).build();
-        final ActivityRecord secondActivity = new ActivityBuilder(mService).setTask(mTask).build();
+        final ActivityRecord firstActivity = new ActivityBuilder(mAtm).setTask(mTask).build();
+        final ActivityRecord secondActivity = new ActivityBuilder(mAtm).setTask(mTask).build();
         firstActivity.setState(STOPPED, "testFinishDisabledPackageActivities");
         secondActivity.setState(RESUMED, "testFinishDisabledPackageActivities");
         mStack.mResumedActivity = secondActivity;
@@ -1029,10 +1028,10 @@
 
     @Test
     public void testFinishDisabledPackageActivities_RemoveNonAliveActivities() {
-        final ActivityRecord activity = new ActivityBuilder(mService).setTask(mTask).build();
+        final ActivityRecord activity = new ActivityBuilder(mAtm).setTask(mTask).build();
 
         // The overlay activity is not in the disabled package but it is in the same task.
-        final ActivityRecord overlayActivity = new ActivityBuilder(mService).setTask(mTask)
+        final ActivityRecord overlayActivity = new ActivityBuilder(mAtm).setTask(mTask)
                 .setComponent(new ComponentName("package.overlay", ".OverlayActivity")).build();
         // If the task only remains overlay activity, the task should also be removed.
         // See {@link ActivityStack#removeFromHistory}.
@@ -1058,8 +1057,8 @@
 
     @Test
     public void testHandleAppDied() {
-        final ActivityRecord firstActivity = new ActivityBuilder(mService).setTask(mTask).build();
-        final ActivityRecord secondActivity = new ActivityBuilder(mService).setTask(mTask).build();
+        final ActivityRecord firstActivity = new ActivityBuilder(mAtm).setTask(mTask).build();
+        final ActivityRecord secondActivity = new ActivityBuilder(mAtm).setTask(mTask).build();
 
         // Making the first activity a task overlay means it will be removed from the task's
         // activities as well once second activity is removed as handleAppDied processes the
@@ -1072,7 +1071,7 @@
 
         assertEquals(2, mTask.getChildCount());
 
-        mRootWindowContainer.handleAppDied(secondActivity.app);
+        secondActivity.app.handleAppDied();
 
         assertFalse(mTask.hasChild());
         assertFalse(mStack.hasChild());
@@ -1080,13 +1079,13 @@
 
     @Test
     public void testHandleAppDied_RelaunchesAfterCrashDuringWindowingModeResize() {
-        final ActivityRecord activity = new ActivityBuilder(mService).setTask(mTask).build();
+        final ActivityRecord activity = new ActivityBuilder(mAtm).setTask(mTask).build();
 
         activity.mRelaunchReason = RELAUNCH_REASON_WINDOWING_MODE_RESIZE;
         activity.launchCount = 1;
         activity.setSavedState(null /* savedState */);
 
-        mRootWindowContainer.handleAppDied(activity.app);
+        activity.app.handleAppDied();
 
         assertEquals(1, mTask.getChildCount());
         assertEquals(1, mStack.getChildCount());
@@ -1094,13 +1093,13 @@
 
     @Test
     public void testHandleAppDied_NotRelaunchAfterThreeCrashesDuringWindowingModeResize() {
-        final ActivityRecord activity = new ActivityBuilder(mService).setTask(mTask).build();
+        final ActivityRecord activity = new ActivityBuilder(mAtm).setTask(mTask).build();
 
         activity.mRelaunchReason = RELAUNCH_REASON_WINDOWING_MODE_RESIZE;
         activity.launchCount = 3;
         activity.setSavedState(null /* savedState */);
 
-        mRootWindowContainer.handleAppDied(activity.app);
+        activity.app.handleAppDied();
 
         assertFalse(mTask.hasChild());
         assertFalse(mStack.hasChild());
@@ -1108,13 +1107,13 @@
 
     @Test
     public void testHandleAppDied_RelaunchesAfterCrashDuringFreeResize() {
-        final ActivityRecord activity = new ActivityBuilder(mService).setTask(mTask).build();
+        final ActivityRecord activity = new ActivityBuilder(mAtm).setTask(mTask).build();
 
         activity.mRelaunchReason = RELAUNCH_REASON_FREE_RESIZE;
         activity.launchCount = 1;
         activity.setSavedState(null /* savedState */);
 
-        mRootWindowContainer.handleAppDied(activity.app);
+        activity.app.handleAppDied();
 
         assertEquals(1, mTask.getChildCount());
         assertEquals(1, mStack.getChildCount());
@@ -1122,13 +1121,13 @@
 
     @Test
     public void testHandleAppDied_NotRelaunchAfterThreeCrashesDuringFreeResize() {
-        final ActivityRecord activity = new ActivityBuilder(mService).setTask(mTask).build();
+        final ActivityRecord activity = new ActivityBuilder(mAtm).setTask(mTask).build();
 
         activity.mRelaunchReason = RELAUNCH_REASON_FREE_RESIZE;
         activity.launchCount = 3;
         activity.setSavedState(null /* savedState */);
 
-        mRootWindowContainer.handleAppDied(activity.app);
+        activity.app.handleAppDied();
 
         assertFalse(mTask.hasChild());
         assertFalse(mStack.hasChild());
@@ -1136,11 +1135,11 @@
 
     @Test
     public void testCompletePauseOnResumeWhilePausingActivity() {
-        final ActivityRecord bottomActivity = new ActivityBuilder(mService).setTask(mTask).build();
+        final ActivityRecord bottomActivity = new ActivityBuilder(mAtm).setTask(mTask).build();
         doReturn(true).when(bottomActivity).attachedToProcess();
         mStack.mPausingActivity = null;
         mStack.mResumedActivity = bottomActivity;
-        final ActivityRecord topActivity = new ActivityBuilder(mService).setTask(mTask).build();
+        final ActivityRecord topActivity = new ActivityBuilder(mAtm).setTask(mTask).build();
         topActivity.info.flags |= FLAG_RESUME_WHILE_PAUSING;
 
         mStack.startPausingLocked(false /* userLeaving */, false /* uiSleeping */, topActivity);
@@ -1154,7 +1153,7 @@
 
         ActivityRecord activity = homeStack.topRunningActivity();
         if (activity == null) {
-            activity = new ActivityBuilder(mService)
+            activity = new ActivityBuilder(mAtm)
                     .setStack(homeStack)
                     .setCreateTask(true)
                     .build();
@@ -1265,13 +1264,13 @@
     public void testNavigateUpTo() {
         final ActivityStartController controller = mock(ActivityStartController.class);
         final ActivityStarter starter = new ActivityStarter(controller,
-                mService, mService.mStackSupervisor, mock(ActivityStartInterceptor.class));
-        doReturn(controller).when(mService).getActivityStartController();
+                mAtm, mAtm.mStackSupervisor, mock(ActivityStartInterceptor.class));
+        doReturn(controller).when(mAtm).getActivityStartController();
         spyOn(starter);
         doReturn(ActivityManager.START_SUCCESS).when(starter).execute();
 
-        final ActivityRecord firstActivity = new ActivityBuilder(mService).setTask(mTask).build();
-        final ActivityRecord secondActivity = new ActivityBuilder(mService).setTask(mTask)
+        final ActivityRecord firstActivity = new ActivityBuilder(mAtm).setTask(mTask).build();
+        final ActivityRecord secondActivity = new ActivityBuilder(mAtm).setTask(mTask)
                 .setUid(firstActivity.getUid() + 1).build();
         doReturn(starter).when(controller).obtainStarter(eq(firstActivity.intent), anyString());
 
@@ -1297,7 +1296,7 @@
     @Test
     public void testShouldUpRecreateTaskLockedWithCorrectAffinityFormat() {
         final String affinity = "affinity";
-        final ActivityRecord activity = new ActivityBuilder(mService).setAffinity(affinity)
+        final ActivityRecord activity = new ActivityBuilder(mAtm).setAffinity(affinity)
                 .setUid(Binder.getCallingUid()).setCreateTask(true).build();
         activity.getTask().affinity = activity.taskAffinity;
 
@@ -1307,7 +1306,7 @@
     @Test
     public void testShouldUpRecreateTaskLockedWithWrongAffinityFormat() {
         final String affinity = "affinity";
-        final ActivityRecord activity = new ActivityBuilder(mService).setAffinity(affinity)
+        final ActivityRecord activity = new ActivityBuilder(mAtm).setAffinity(affinity)
                 .setUid(Binder.getCallingUid()).setCreateTask(true).build();
         activity.getTask().affinity = activity.taskAffinity;
         final String fakeAffinity = activity.getUid() + activity.taskAffinity;
@@ -1318,12 +1317,12 @@
     @Test
     public void testResetTaskWithFinishingActivities() {
         final ActivityRecord taskTop =
-                new ActivityBuilder(mService).setStack(mStack).setCreateTask(true).build();
+                new ActivityBuilder(mAtm).setStack(mStack).setCreateTask(true).build();
         // Make all activities in the task are finishing to simulate Task#getTopActivity
         // returns null.
         taskTop.finishing = true;
 
-        final ActivityRecord newR = new ActivityBuilder(mService).build();
+        final ActivityRecord newR = new ActivityBuilder(mAtm).build();
         final ActivityRecord result = mStack.resetTaskIfNeeded(taskTop, newR);
         assertThat(result).isEqualTo(taskTop);
     }
@@ -1333,9 +1332,9 @@
         final ArrayList<ActivityRecord> occludedActivities = new ArrayList<>();
         final Consumer<ActivityRecord> handleOccludedActivity = occludedActivities::add;
         final ActivityRecord bottomActivity =
-                new ActivityBuilder(mService).setStack(mStack).setTask(mTask).build();
+                new ActivityBuilder(mAtm).setStack(mStack).setTask(mTask).build();
         final ActivityRecord topActivity =
-                new ActivityBuilder(mService).setStack(mStack).setTask(mTask).build();
+                new ActivityBuilder(mAtm).setStack(mStack).setTask(mTask).build();
         // Top activity occludes bottom activity.
         doReturn(true).when(mStack).shouldBeVisible(any());
         assertTrue(topActivity.shouldBeVisible());
@@ -1354,7 +1353,7 @@
 
         // A finishing activity should not occlude other activities behind.
         final ActivityRecord finishingActivity =
-                new ActivityBuilder(mService).setStack(mStack).setTask(mTask).build();
+                new ActivityBuilder(mAtm).setStack(mStack).setTask(mTask).build();
         finishingActivity.finishing = true;
         doCallRealMethod().when(finishingActivity).occludesParent();
         assertTrue(topActivity.shouldBeVisible());
@@ -1376,9 +1375,9 @@
         final ActivityRecord[] activities = new ActivityRecord[2];
         mSupervisor.beginDeferResume();
         for (int i = 0; i < activities.length; i++) {
-            final ActivityRecord r = new ActivityBuilder(mService).setTask(mTask).build();
+            final ActivityRecord r = new ActivityBuilder(mAtm).setTask(mTask).build();
             activities[i] = r;
-            doReturn(null).when(mService).getProcessController(
+            doReturn(null).when(mAtm).getProcessController(
                     eq(r.processName), eq(r.info.applicationInfo.uid));
             r.setState(Task.ActivityState.INITIALIZING, "test");
             // Ensure precondition that the activity is opaque.
@@ -1388,7 +1387,7 @@
         }
         mSupervisor.endDeferResume();
 
-        setBooted(mService);
+        setBooted(mAtm);
         // 2 activities are started while keyguard is locked, so they are waiting to be resolved.
         assertFalse(unknownAppVisibilityController.allResolved());
 
@@ -1405,8 +1404,8 @@
     @Test
     public void testNonTopVisibleActivityNotResume() {
         final ActivityRecord nonTopVisibleActivity =
-                new ActivityBuilder(mService).setTask(mTask).build();
-        new ActivityBuilder(mService).setTask(mTask).build();
+                new ActivityBuilder(mAtm).setTask(mTask).build();
+        new ActivityBuilder(mAtm).setTask(mTask).build();
         doReturn(false).when(nonTopVisibleActivity).attachedToProcess();
         doReturn(true).when(nonTopVisibleActivity).shouldBeVisible(anyBoolean(), anyBoolean());
         doNothing().when(mSupervisor).startSpecificActivity(any(), anyBoolean(),
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityStartControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityStartControllerTests.java
index c9a9279..55afc70 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityStartControllerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityStartControllerTests.java
@@ -51,7 +51,7 @@
 @SmallTest
 @Presubmit
 @RunWith(WindowTestRunner.class)
-public class ActivityStartControllerTests extends ActivityTestsBase {
+public class ActivityStartControllerTests extends WindowTestsBase {
     private ActivityStartController mController;
     private Factory mFactory;
     private ActivityStarter mStarter;
@@ -59,9 +59,9 @@
     @Before
     public void setUp() throws Exception {
         mFactory = mock(Factory.class);
-        mController = new ActivityStartController(mService, mService.mStackSupervisor, mFactory);
-        mStarter = spy(new ActivityStarter(mController, mService,
-                mService.mStackSupervisor, mock(ActivityStartInterceptor.class)));
+        mController = new ActivityStartController(mAtm, mAtm.mStackSupervisor, mFactory);
+        mStarter = spy(new ActivityStarter(mController, mAtm,
+                mAtm.mStackSupervisor, mock(ActivityStartInterceptor.class)));
         doReturn(mStarter).when(mFactory).obtain();
     }
 
@@ -72,15 +72,15 @@
     public void testPendingActivityLaunches() {
         final Random random = new Random();
 
-        final ActivityRecord activity = new ActivityBuilder(mService).build();
-        final ActivityRecord source = new ActivityBuilder(mService)
+        final ActivityRecord activity = new ActivityBuilder(mAtm).build();
+        final ActivityRecord source = new ActivityBuilder(mAtm)
                 .setCreateTask(true)
                 .build();
         final int startFlags = random.nextInt();
-        final Task stack = mService.mRootWindowContainer.getDefaultTaskDisplayArea()
+        final Task stack = mAtm.mRootWindowContainer.getDefaultTaskDisplayArea()
                 .createStack(WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, true /* onTop */);
-        final WindowProcessController wpc = new WindowProcessController(mService,
-                mService.mContext.getApplicationInfo(), "name", 12345,
+        final WindowProcessController wpc = new WindowProcessController(mAtm,
+                mAtm.mContext.getApplicationInfo(), "name", 12345,
                 UserHandle.getUserId(12345), mock(Object.class),
                 mock(WindowProcessListener.class));
         wpc.setThread(mock(IApplicationThread.class));
@@ -101,8 +101,8 @@
     @Test
     public void testRecycling() {
         final Intent intent = new Intent();
-        final ActivityStarter optionStarter = new ActivityStarter(mController, mService,
-                mService.mStackSupervisor, mock(ActivityStartInterceptor.class));
+        final ActivityStarter optionStarter = new ActivityStarter(mController, mAtm,
+                mAtm.mStackSupervisor, mock(ActivityStartInterceptor.class));
         optionStarter
                 .setIntent(intent)
                 .setReason("Test")
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java
index d07000f..e5c9ecc 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java
@@ -100,7 +100,7 @@
 @SmallTest
 @Presubmit
 @RunWith(WindowTestRunner.class)
-public class ActivityStarterTests extends ActivityTestsBase {
+public class ActivityStarterTests extends WindowTestsBase {
     private ActivityStartController mController;
     private ActivityMetricsLogger mActivityMetricsLogger;
     private PackageManagerInternal mMockPackageManager;
@@ -187,7 +187,7 @@
      */
     private void verifyStartActivityPreconditionsUntracked(int preconditions, int launchFlags,
             int expectedResult) {
-        final ActivityTaskManagerService service = mService;
+        final ActivityTaskManagerService service = mAtm;
         final IPackageManager packageManager = mock(IPackageManager.class);
         final ActivityStartController controller = mock(ActivityStartController.class);
 
@@ -283,8 +283,8 @@
 
         // Ensure that {@link ActivityOptions} are aborted with unsuccessful result.
         if (expectedResult != START_SUCCESS) {
-            final ActivityStarter optionStarter = new ActivityStarter(mController, mService,
-                    mService.mStackSupervisor, mock(ActivityStartInterceptor.class));
+            final ActivityStarter optionStarter = new ActivityStarter(mController, mAtm,
+                    mAtm.mStackSupervisor, mock(ActivityStartInterceptor.class));
             final ActivityOptions options = spy(ActivityOptions.makeBasic());
 
             final int optionResult = optionStarter.setCaller(caller)
@@ -338,7 +338,7 @@
                 invocation -> {
                     throw new RuntimeException("Not stubbed");
                 });
-        doReturn(mMockPackageManager).when(mService).getPackageManagerInternalLocked();
+        doReturn(mMockPackageManager).when(mAtm).getPackageManagerInternalLocked();
         doReturn(false).when(mMockPackageManager).isInstantAppInstallerComponent(any());
         doReturn(null).when(mMockPackageManager).resolveIntent(any(), any(), anyInt(), anyInt(),
                 anyInt(), anyBoolean(), anyInt());
@@ -359,8 +359,8 @@
         info.applicationInfo = new ApplicationInfo();
         info.applicationInfo.packageName = ActivityBuilder.getDefaultComponent().getPackageName();
 
-        return new ActivityStarter(mController, mService,
-                mService.mStackSupervisor, mock(ActivityStartInterceptor.class))
+        return new ActivityStarter(mController, mAtm,
+                mAtm.mStackSupervisor, mock(ActivityStartInterceptor.class))
                 .setIntent(intent)
                 .setActivityInfo(info);
     }
@@ -373,7 +373,7 @@
     public void testCreateTaskLayout() {
         // modifier for validating passed values.
         final LaunchParamsModifier modifier = mock(LaunchParamsModifier.class);
-        mService.mStackSupervisor.getLaunchParamsController().registerModifier(modifier);
+        mAtm.mStackSupervisor.getLaunchParamsController().registerModifier(modifier);
 
         // add custom values to activity info to make unique.
         final ActivityInfo info = new ActivityInfo();
@@ -414,9 +414,9 @@
         final ActivityStarter starter = prepareStarter(
                 FLAG_ACTIVITY_RESET_TASK_IF_NEEDED | FLAG_ACTIVITY_SINGLE_TOP, false);
         final ActivityRecord splitPrimaryFocusActivity =
-                new ActivityBuilder(mService).setCreateTask(true).build();
+                new ActivityBuilder(mAtm).setCreateTask(true).build();
         final ActivityRecord splitSecondReusableActivity =
-                new ActivityBuilder(mService).setCreateTask(true).build();
+                new ActivityBuilder(mAtm).setCreateTask(true).build();
         splitPrimaryFocusActivity.getRootTask()
                 .setWindowingMode(WINDOWING_MODE_SPLIT_SCREEN_PRIMARY);
         splitSecondReusableActivity.getRootTask()
@@ -443,11 +443,11 @@
         final ActivityStarter starter = prepareStarter(
                 FLAG_ACTIVITY_RESET_TASK_IF_NEEDED | FLAG_ACTIVITY_SINGLE_TOP, false);
         final ActivityRecord splitSecondReusableActivity =
-                new ActivityBuilder(mService).setCreateTask(true).build();
+                new ActivityBuilder(mAtm).setCreateTask(true).build();
         final ActivityRecord splitSecondTopActivity =
-                new ActivityBuilder(mService).setCreateTask(true).build();
+                new ActivityBuilder(mAtm).setCreateTask(true).build();
         final ActivityRecord splitPrimaryFocusActivity =
-                new ActivityBuilder(mService).setCreateTask(true).build();
+                new ActivityBuilder(mAtm).setCreateTask(true).build();
         splitPrimaryFocusActivity.getRootTask()
                 .setWindowingMode(WINDOWING_MODE_SPLIT_SCREEN_PRIMARY);
         splitSecondReusableActivity.getRootTask()
@@ -475,13 +475,13 @@
      */
     @Test
     public void testTaskModeViolation() {
-        final DisplayContent display = mService.mRootWindowContainer.getDefaultDisplay();
+        final DisplayContent display = mAtm.mRootWindowContainer.getDefaultDisplay();
         display.removeAllTasks();
         assertNoTasks(display);
 
         final ActivityStarter starter = prepareStarter(0);
 
-        final LockTaskController lockTaskController = mService.getLockTaskController();
+        final LockTaskController lockTaskController = mAtm.getLockTaskController();
         doReturn(true).when(lockTaskController).isLockTaskModeViolation(any());
 
         final int result = starter.setReason("testTaskModeViolation").execute();
@@ -504,8 +504,8 @@
      */
     @Test
     public void testActivityStartsLogging_noLoggingWhenDisabled() {
-        doReturn(false).when(mService).isActivityStartsLoggingEnabled();
-        doReturn(mActivityMetricsLogger).when(mService.mStackSupervisor).getActivityMetricsLogger();
+        doReturn(false).when(mAtm).isActivityStartsLoggingEnabled();
+        doReturn(mActivityMetricsLogger).when(mAtm.mStackSupervisor).getActivityMetricsLogger();
 
         ActivityStarter starter = prepareStarter(FLAG_ACTIVITY_NEW_TASK);
         starter.setReason("testActivityStartsLogging_noLoggingWhenDisabled").execute();
@@ -521,8 +521,8 @@
     @Test
     public void testActivityStartsLogging_logsWhenEnabled() {
         // note: conveniently this package doesn't have any activity visible
-        doReturn(true).when(mService).isActivityStartsLoggingEnabled();
-        doReturn(mActivityMetricsLogger).when(mService.mStackSupervisor).getActivityMetricsLogger();
+        doReturn(true).when(mAtm).isActivityStartsLoggingEnabled();
+        doReturn(mActivityMetricsLogger).when(mAtm.mStackSupervisor).getActivityMetricsLogger();
 
         ActivityStarter starter = prepareStarter(FLAG_ACTIVITY_NEW_TASK)
                 .setCallingUid(FAKE_CALLING_UID)
@@ -544,7 +544,7 @@
      */
     @Test
     public void testBackgroundActivityStartsAllowed_noStartsAborted() {
-        doReturn(true).when(mService).isBackgroundActivityStartsEnabled();
+        doReturn(true).when(mAtm).isBackgroundActivityStartsEnabled();
 
         runAndVerifyBackgroundActivityStartsSubtest("allowed_noStartsAborted", false,
                 UNIMPORTANT_UID, false, PROCESS_STATE_TOP + 1,
@@ -558,7 +558,7 @@
      */
     @Test
     public void testBackgroundActivityStartsDisallowed_unsupportedStartsAborted() {
-        doReturn(false).when(mService).isBackgroundActivityStartsEnabled();
+        doReturn(false).when(mAtm).isBackgroundActivityStartsEnabled();
 
         runAndVerifyBackgroundActivityStartsSubtest(
                 "disallowed_unsupportedUsecase_aborted", true,
@@ -589,7 +589,7 @@
      */
     @Test
     public void testBackgroundActivityStartsDisallowed_supportedStartsNotAborted() {
-        doReturn(false).when(mService).isBackgroundActivityStartsEnabled();
+        doReturn(false).when(mAtm).isBackgroundActivityStartsEnabled();
 
         runAndVerifyBackgroundActivityStartsSubtest("disallowed_rootUid_notAborted", false,
                 Process.ROOT_UID, false, PROCESS_STATE_TOP + 1,
@@ -644,13 +644,13 @@
             boolean callerIsInstrumentingWithBackgroundActivityStartPrivileges,
             boolean isCallingUidDeviceOwner) {
         // window visibility
-        doReturn(callingUidHasVisibleWindow).when(mService.mWindowManager.mRoot)
+        doReturn(callingUidHasVisibleWindow).when(mAtm.mWindowManager.mRoot)
                 .isAnyNonToastWindowVisibleForUid(callingUid);
-        doReturn(realCallingUidHasVisibleWindow).when(mService.mWindowManager.mRoot)
+        doReturn(realCallingUidHasVisibleWindow).when(mAtm.mWindowManager.mRoot)
                 .isAnyNonToastWindowVisibleForUid(realCallingUid);
         // process importance
-        doReturn(callingUidProcState).when(mService).getUidState(callingUid);
-        doReturn(realCallingUidProcState).when(mService).getUidState(realCallingUid);
+        doReturn(callingUidProcState).when(mAtm).getUidState(callingUid);
+        doReturn(realCallingUidProcState).when(mAtm).getUidState(realCallingUid);
         // foreground activities
         final IApplicationThread caller = mock(IApplicationThread.class);
         final WindowProcessListener listener = mock(WindowProcessListener.class);
@@ -658,12 +658,12 @@
         ai.uid = callingUid;
         ai.packageName = "com.android.test.package";
         final WindowProcessController callerApp =
-                new WindowProcessController(mService, ai, null, callingUid, -1, null, listener);
+                new WindowProcessController(mAtm, ai, null, callingUid, -1, null, listener);
         callerApp.setHasForegroundActivities(hasForegroundActivities);
-        doReturn(callerApp).when(mService).getProcessController(caller);
+        doReturn(callerApp).when(mAtm).getProcessController(caller);
         // caller is recents
         RecentTasks recentTasks = mock(RecentTasks.class);
-        mService.mStackSupervisor.setRecentTasks(recentTasks);
+        mAtm.mStackSupervisor.setRecentTasks(recentTasks);
         doReturn(callerIsRecents).when(recentTasks).isCallerRecents(callingUid);
         // caller is temp allowed
         if (callerIsTempAllowed) {
@@ -673,7 +673,7 @@
         callerApp.setInstrumenting(callerIsInstrumentingWithBackgroundActivityStartPrivileges,
                 callerIsInstrumentingWithBackgroundActivityStartPrivileges);
         // callingUid is the device owner
-        doReturn(isCallingUidDeviceOwner).when(mService).isDeviceOwner(callingUid);
+        doReturn(isCallingUidDeviceOwner).when(mAtm).isDeviceOwner(callingUid);
 
         final ActivityOptions options = spy(ActivityOptions.makeBasic());
         ActivityRecord[] outActivity = new ActivityRecord[1];
@@ -706,14 +706,14 @@
     @Test
     public void testBringTaskToFrontWhenFocusedStackIsFinising() {
         // Put 2 tasks in the same stack (simulate the behavior of home stack).
-        final ActivityRecord activity = new ActivityBuilder(mService)
+        final ActivityRecord activity = new ActivityBuilder(mAtm)
                 .setCreateTask(true).build();
-        new ActivityBuilder(mService)
+        new ActivityBuilder(mAtm)
                 .setStack(activity.getRootTask())
                 .setCreateTask(true).build();
 
         // Create a top finishing activity.
-        final ActivityRecord finishingTopActivity = new ActivityBuilder(mService)
+        final ActivityRecord finishingTopActivity = new ActivityBuilder(mAtm)
                 .setCreateTask(true).build();
         finishingTopActivity.getRootTask().moveToFront("finishingTopActivity");
 
@@ -741,7 +741,7 @@
 
         // Create a secondary display at bottom.
         final TestDisplayContent secondaryDisplay =
-                new TestDisplayContent.Builder(mService, 1000, 1500)
+                new TestDisplayContent.Builder(mAtm, 1000, 1500)
                         .setPosition(POSITION_BOTTOM).build();
         final TaskDisplayArea secondaryTaskContainer = secondaryDisplay.getDefaultTaskDisplayArea();
         final Task stack = secondaryTaskContainer.createStack(
@@ -751,7 +751,7 @@
         final ActivityRecord topActivityOnSecondaryDisplay = createSingleTaskActivityOn(stack);
 
         // Put an activity on default display as the top focused activity.
-        new ActivityBuilder(mService).setCreateTask(true).build();
+        new ActivityBuilder(mAtm).setCreateTask(true).build();
 
         // Start activity with the same intent as {@code topActivityOnSecondaryDisplay}
         // on secondary display.
@@ -781,7 +781,7 @@
 
         // Create a secondary display with an activity.
         final TestDisplayContent secondaryDisplay =
-                new TestDisplayContent.Builder(mService, 1000, 1500).build();
+                new TestDisplayContent.Builder(mAtm, 1000, 1500).build();
         mRootWindowContainer.positionChildAt(POSITION_TOP, secondaryDisplay,
                 false /* includingParents */);
         final TaskDisplayArea secondaryTaskContainer = secondaryDisplay.getDefaultTaskDisplayArea();
@@ -793,7 +793,7 @@
         final Task topStack = secondaryTaskContainer.createStack(WINDOWING_MODE_FULLSCREEN,
                 ACTIVITY_TYPE_STANDARD, true /* onTop */);
         final Task topTask = new TaskBuilder(mSupervisor).setStack(topStack).build();
-        new ActivityBuilder(mService).setTask(topTask).build();
+        new ActivityBuilder(mAtm).setTask(topTask).build();
 
         // Start activity with the same intent as {@code singleTaskActivity} on secondary display.
         final ActivityOptions options = ActivityOptions.makeBasic()
@@ -815,16 +815,16 @@
         final ActivityStarter starter = prepareStarter(
                 FLAG_ACTIVITY_RESET_TASK_IF_NEEDED | FLAG_ACTIVITY_SINGLE_TOP, false);
         final ActivityRecord reusableActivity =
-                new ActivityBuilder(mService).setCreateTask(true).build();
+                new ActivityBuilder(mAtm).setCreateTask(true).build();
         final ActivityRecord topActivity =
-                new ActivityBuilder(mService).setCreateTask(true).build();
+                new ActivityBuilder(mAtm).setCreateTask(true).build();
 
         // Make sure topActivity is on top
         topActivity.getRootTask().moveToFront("testWasVisibleInRestartAttempt");
         reusableActivity.setVisible(false);
 
         final TaskChangeNotificationController taskChangeNotifier =
-                mService.getTaskChangeNotificationController();
+                mAtm.getTaskChangeNotificationController();
         spyOn(taskChangeNotifier);
 
         Task task = topActivity.getTask();
@@ -853,7 +853,7 @@
                 .setComponent(componentName)
                 .setStack(stack)
                 .build();
-        return new ActivityBuilder(mService)
+        return new ActivityBuilder(mAtm)
                 .setComponent(componentName)
                 .setLaunchMode(LAUNCH_SINGLE_TASK)
                 .setTask(task)
@@ -876,7 +876,7 @@
                 true /* onTop */);
 
         // Put an activity on default display as the top focused activity.
-        final ActivityRecord topActivity = new ActivityBuilder(mService)
+        final ActivityRecord topActivity = new ActivityBuilder(mAtm)
                 .setCreateTask(true)
                 .setLaunchMode(LAUNCH_SINGLE_TASK)
                 .build();
@@ -900,7 +900,7 @@
     @Test
     public void testFreezeTaskListActivityOption() {
         RecentTasks recentTasks = mock(RecentTasks.class);
-        mService.mStackSupervisor.setRecentTasks(recentTasks);
+        mAtm.mStackSupervisor.setRecentTasks(recentTasks);
         doReturn(true).when(recentTasks).isCallerRecents(anyInt());
 
         final ActivityStarter starter = prepareStarter(0 /* flags */);
@@ -922,7 +922,7 @@
     @Test
     public void testFreezeTaskListActivityOptionFailedStart_expectResetFreezeTaskList() {
         RecentTasks recentTasks = mock(RecentTasks.class);
-        mService.mStackSupervisor.setRecentTasks(recentTasks);
+        mAtm.mStackSupervisor.setRecentTasks(recentTasks);
         doReturn(true).when(recentTasks).isCallerRecents(anyInt());
 
         final ActivityStarter starter = prepareStarter(0 /* flags */);
@@ -959,7 +959,7 @@
         intent.setComponent(ActivityBuilder.getDefaultComponent());
 
         doReturn(true).when(mMockPackageManager).isInstantAppInstallerComponent(any());
-        starter.setIntent(intent).mRequest.resolveActivity(mService.mStackSupervisor);
+        starter.setIntent(intent).mRequest.resolveActivity(mAtm.mStackSupervisor);
 
         // Make sure the client intent won't be modified.
         assertThat(intent.getComponent()).isNotNull();
@@ -985,9 +985,9 @@
     @Test
     public void testRecycleTaskFromAnotherUser() {
         final ActivityStarter starter = prepareStarter(0 /* flags */);
-        starter.mStartActivity = new ActivityBuilder(mService).build();
-        final Task task = new TaskBuilder(mService.mStackSupervisor)
-                .setStack(mService.mRootWindowContainer.getDefaultTaskDisplayArea().createStack(
+        starter.mStartActivity = new ActivityBuilder(mAtm).build();
+        final Task task = new TaskBuilder(mAtm.mStackSupervisor)
+                .setStack(mAtm.mRootWindowContainer.getDefaultTaskDisplayArea().createStack(
                         WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, true /* onTop */))
                 .setUserId(10)
                 .build();
@@ -1001,7 +1001,7 @@
     public void testTargetStackInSplitScreen() {
         final ActivityStarter starter =
                 prepareStarter(FLAG_ACTIVITY_LAUNCH_ADJACENT, false /* mockGetLaunchStack */);
-        final ActivityRecord top = new ActivityBuilder(mService).setCreateTask(true).build();
+        final ActivityRecord top = new ActivityBuilder(mAtm).setCreateTask(true).build();
         final ActivityOptions options = ActivityOptions.makeBasic();
         final ActivityRecord[] outActivity = new ActivityRecord[1];
 
@@ -1012,7 +1012,7 @@
         assertThat(outActivity[0].inSplitScreenWindowingMode()).isFalse();
 
         // Move activity to split-screen-primary stack and make sure it has the focus.
-        TestSplitOrganizer splitOrg = new TestSplitOrganizer(mService, top.getDisplayId());
+        TestSplitOrganizer splitOrg = new TestSplitOrganizer(mAtm, top.getDisplayId());
         top.getRootTask().reparent(splitOrg.mPrimary, POSITION_BOTTOM);
         top.getRootTask().moveToFront("testWindowingModeOptionsLaunchAdjacent");
 
@@ -1026,7 +1026,7 @@
     @Test
     public void testActivityStart_expectAddedToRecentTask() {
         RecentTasks recentTasks = mock(RecentTasks.class);
-        mService.mStackSupervisor.setRecentTasks(recentTasks);
+        mAtm.mStackSupervisor.setRecentTasks(recentTasks);
         doReturn(true).when(recentTasks).isCallerRecents(anyInt());
 
         final ActivityStarter starter = prepareStarter(0 /* flags */);
@@ -1044,10 +1044,10 @@
 
         starter.setReason("testAllSplitScreenPrimaryActivitiesAreResumed");
 
-        final ActivityRecord targetRecord = new ActivityBuilder(mService).build();
+        final ActivityRecord targetRecord = new ActivityBuilder(mAtm).build();
         targetRecord.setFocusable(false);
         targetRecord.setVisibility(false);
-        final ActivityRecord sourceRecord = new ActivityBuilder(mService).build();
+        final ActivityRecord sourceRecord = new ActivityBuilder(mAtm).build();
 
         final Task stack = spy(
                 mRootWindowContainer.getDefaultTaskDisplayArea()
@@ -1059,7 +1059,7 @@
         doReturn(stack).when(mRootWindowContainer)
                 .getLaunchStack(any(), any(), any(), anyBoolean(), any(), anyInt(), anyInt());
 
-        starter.mStartActivity = new ActivityBuilder(mService).build();
+        starter.mStartActivity = new ActivityBuilder(mAtm).build();
 
         // When
         starter.startActivityInner(
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityTaskManagerServiceTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityTaskManagerServiceTests.java
index f8faae6..5676100 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityTaskManagerServiceTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityTaskManagerServiceTests.java
@@ -60,14 +60,14 @@
 @Presubmit
 @MediumTest
 @RunWith(WindowTestRunner.class)
-public class ActivityTaskManagerServiceTests extends ActivityTestsBase {
+public class ActivityTaskManagerServiceTests extends WindowTestsBase {
 
     private final ArgumentCaptor<ClientTransaction> mClientTransactionCaptor =
             ArgumentCaptor.forClass(ClientTransaction.class);
 
     @Before
     public void setUp() throws Exception {
-        setBooted(mService);
+        setBooted(mAtm);
     }
 
     /** Verify that activity is finished correctly upon request. */
@@ -75,13 +75,13 @@
     public void testActivityFinish() {
         final Task stack = new StackBuilder(mRootWindowContainer).build();
         final ActivityRecord activity = stack.getBottomMostTask().getTopNonFinishingActivity();
-        assertTrue("Activity must be finished", mService.finishActivity(activity.appToken,
+        assertTrue("Activity must be finished", mAtm.finishActivity(activity.appToken,
                 0 /* resultCode */, null /* resultData */,
                 Activity.DONT_FINISH_TASK_WITH_ACTIVITY));
         assertTrue(activity.finishing);
 
         assertTrue("Duplicate activity finish request must also return 'true'",
-                mService.finishActivity(activity.appToken, 0 /* resultCode */,
+                mAtm.finishActivity(activity.appToken, 0 /* resultCode */,
                         null /* resultData */, Activity.DONT_FINISH_TASK_WITH_ACTIVITY));
     }
 
@@ -90,10 +90,10 @@
         final Task stack = new StackBuilder(mRootWindowContainer).build();
         final ActivityRecord activity = stack.getBottomMostTask().getTopNonFinishingActivity();
         final ClientLifecycleManager mockLifecycleManager = mock(ClientLifecycleManager.class);
-        doReturn(mockLifecycleManager).when(mService).getLifecycleManager();
+        doReturn(mockLifecycleManager).when(mAtm).getLifecycleManager();
         doReturn(true).when(activity).checkEnterPictureInPictureState(anyString(), anyBoolean());
 
-        mService.requestPictureInPictureMode(activity.token);
+        mAtm.requestPictureInPictureMode(activity.token);
 
         verify(mockLifecycleManager).scheduleTransaction(mClientTransactionCaptor.capture());
         final ClientTransaction transaction = mClientTransactionCaptor.getValue();
@@ -108,11 +108,11 @@
     public void testOnPictureInPictureRequested_cannotEnterPip() throws RemoteException {
         final Task stack = new StackBuilder(mRootWindowContainer).build();
         final ActivityRecord activity = stack.getBottomMostTask().getTopNonFinishingActivity();
-        ClientLifecycleManager lifecycleManager = mService.getLifecycleManager();
+        ClientLifecycleManager lifecycleManager = mAtm.getLifecycleManager();
         doReturn(false).when(activity).inPinnedWindowingMode();
         doReturn(false).when(activity).checkEnterPictureInPictureState(anyString(), anyBoolean());
 
-        mService.requestPictureInPictureMode(activity.token);
+        mAtm.requestPictureInPictureMode(activity.token);
 
         // Check enter no transactions with enter pip requests are made.
         verify(lifecycleManager, times(0)).scheduleTransaction(any());
@@ -122,10 +122,10 @@
     public void testOnPictureInPictureRequested_alreadyInPIPMode() throws RemoteException {
         final Task stack = new StackBuilder(mRootWindowContainer).build();
         final ActivityRecord activity = stack.getBottomMostTask().getTopNonFinishingActivity();
-        ClientLifecycleManager lifecycleManager = mService.getLifecycleManager();
+        ClientLifecycleManager lifecycleManager = mAtm.getLifecycleManager();
         doReturn(true).when(activity).inPinnedWindowingMode();
 
-        mService.requestPictureInPictureMode(activity.token);
+        mAtm.requestPictureInPictureMode(activity.token);
 
         // Check that no transactions with enter pip requests are made.
         verify(lifecycleManager, times(0)).scheduleTransaction(any());
@@ -158,14 +158,14 @@
             @Override
             public void onFixedRotationFinished(int displayId) {}
         };
-        mService.mWindowManager.registerDisplayWindowListener(listener);
+        mAtm.mWindowManager.registerDisplayWindowListener(listener);
         // Check that existing displays call added
         assertEquals(1, added.size());
         assertEquals(0, changed.size());
         assertEquals(0, removed.size());
         added.clear();
         // Check adding a display
-        DisplayContent newDisp1 = new TestDisplayContent.Builder(mService, 600, 800).build();
+        DisplayContent newDisp1 = new TestDisplayContent.Builder(mAtm, 600, 800).build();
         assertEquals(1, added.size());
         assertEquals(0, changed.size());
         assertEquals(0, removed.size());
@@ -174,7 +174,7 @@
         Configuration c = new Configuration(newDisp1.getRequestedOverrideConfiguration());
         c.windowConfiguration.setBounds(new Rect(0, 0, 1000, 1300));
         newDisp1.onRequestedOverrideConfigurationChanged(c);
-        mService.mRootWindowContainer.ensureVisibilityAndConfig(null /* starting */,
+        mAtm.mRootWindowContainer.ensureVisibilityAndConfig(null /* starting */,
                 newDisp1.mDisplayId, false /* markFrozenIfConfigChanged */,
                 false /* deferResume */);
         assertEquals(0, added.size());
@@ -214,16 +214,38 @@
         //mock other operations
         doReturn(true).when(record)
                 .checkEnterPictureInPictureState("enterPictureInPictureMode", false);
-        doReturn(false).when(mService).isInPictureInPictureMode(any());
-        doReturn(false).when(mService).isKeyguardLocked();
+        doReturn(false).when(mAtm).isInPictureInPictureMode(any());
+        doReturn(false).when(mAtm).isKeyguardLocked();
 
         //to simulate NPE
         doReturn(null).when(record).getParent();
 
-        mService.enterPictureInPictureMode(token, params);
+        mAtm.enterPictureInPictureMode(token, params);
         //if record's null parent is not handled gracefully, test will fail with NPE
 
         mockSession.finishMocking();
     }
+
+    @Test
+    public void testResumeNextActivityOnCrashedAppDied() {
+        mSupervisor.beginDeferResume();
+        final ActivityRecord homeActivity = new ActivityBuilder(mAtm)
+                .setTask(mRootWindowContainer.getDefaultTaskDisplayArea().getOrCreateRootHomeTask())
+                .build();
+        final ActivityRecord activity = new ActivityBuilder(mAtm).setCreateTask(true).build();
+        mSupervisor.endDeferResume();
+        // Assume the activity is finishing and hidden because it was crashed.
+        activity.finishing = true;
+        activity.mVisibleRequested = false;
+        activity.setVisible(false);
+        activity.getRootTask().mPausingActivity = activity;
+        homeActivity.setState(Task.ActivityState.PAUSED, "test");
+
+        // Even the visibility states are invisible, the next activity should be resumed because
+        // the crashed activity was pausing.
+        mAtm.mInternal.handleAppDied(activity.app, false /* restarting */,
+                null /* finishInstrumentationCallback */);
+        assertEquals(Task.ActivityState.RESUMED, homeActivity.getState());
+    }
 }
 
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityTestsBase.java b/services/tests/wmtests/src/com/android/server/wm/ActivityTestsBase.java
deleted file mode 100644
index 5be2f04..0000000
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityTestsBase.java
+++ /dev/null
@@ -1,607 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package com.android.server.wm;
-
-import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
-import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
-import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
-import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY;
-import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_SECONDARY;
-import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
-import static android.content.pm.ActivityInfo.RESIZE_MODE_RESIZEABLE;
-import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
-
-import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
-
-import static com.android.dx.mockito.inline.extended.ExtendedMockito.any;
-import static com.android.dx.mockito.inline.extended.ExtendedMockito.anyBoolean;
-import static com.android.dx.mockito.inline.extended.ExtendedMockito.doNothing;
-import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
-import static com.android.dx.mockito.inline.extended.ExtendedMockito.mock;
-import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
-import static com.android.server.wm.WindowContainer.POSITION_BOTTOM;
-
-import android.app.ActivityManager;
-import android.app.ActivityOptions;
-import android.app.IApplicationThread;
-import android.app.WindowConfiguration;
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.Intent;
-import android.content.pm.ActivityInfo;
-import android.content.pm.ApplicationInfo;
-import android.content.res.Configuration;
-import android.os.Build;
-import android.os.Bundle;
-import android.os.UserHandle;
-import android.service.voice.IVoiceInteractionSession;
-import android.view.SurfaceControl;
-import android.window.ITaskOrganizer;
-import android.window.WindowContainerToken;
-
-import com.android.server.AttributeCache;
-
-import org.junit.Before;
-import org.junit.BeforeClass;
-
-/**
- * A base class to handle common operations in activity related unit tests.
- */
-class ActivityTestsBase extends SystemServiceTestsBase {
-    final Context mContext = getInstrumentation().getTargetContext();
-
-    ActivityTaskManagerService mService;
-    RootWindowContainer mRootWindowContainer;
-    ActivityStackSupervisor mSupervisor;
-
-    // Default package name
-    static final String DEFAULT_COMPONENT_PACKAGE_NAME = "com.foo";
-
-    // Default base activity name
-    private static final String DEFAULT_COMPONENT_CLASS_NAME = ".BarActivity";
-
-    @BeforeClass
-    public static void setUpOnceBase() {
-        AttributeCache.init(getInstrumentation().getTargetContext());
-    }
-
-    @Before
-    public void setUpBase() {
-        mService = mSystemServicesTestRule.getActivityTaskManagerService();
-        mSupervisor = mService.mStackSupervisor;
-        mRootWindowContainer = mService.mRootWindowContainer;
-    }
-
-    /** Creates and adds a {@link TestDisplayContent} to supervisor at the given position. */
-    TestDisplayContent addNewDisplayContentAt(int position) {
-        return new TestDisplayContent.Builder(mService, 1000, 1500).setPosition(position).build();
-    }
-
-    /** Sets the default minimum task size to 1 so that tests can use small task sizes */
-    public void removeGlobalMinSizeRestriction() {
-        mService.mRootWindowContainer.mDefaultMinSizeOfResizeableTaskDp = 1;
-    }
-
-    /**
-     * Builder for creating new activities.
-     */
-    protected static class ActivityBuilder {
-        // An id appended to the end of the component name to make it unique
-        private static int sCurrentActivityId = 0;
-
-        private final ActivityTaskManagerService mService;
-
-        private ComponentName mComponent;
-        private String mTargetActivity;
-        private Task mTask;
-        private String mProcessName = "name";
-        private String mAffinity;
-        private int mUid = 12345;
-        private boolean mCreateTask;
-        private Task mStack;
-        private int mActivityFlags;
-        private int mLaunchMode;
-        private int mResizeMode = RESIZE_MODE_RESIZEABLE;
-        private float mMaxAspectRatio;
-        private int mScreenOrientation = SCREEN_ORIENTATION_UNSPECIFIED;
-        private boolean mLaunchTaskBehind;
-        private int mConfigChanges;
-        private int mLaunchedFromPid;
-        private int mLaunchedFromUid;
-        private WindowProcessController mWpc;
-        private Bundle mIntentExtras;
-
-        ActivityBuilder(ActivityTaskManagerService service) {
-            mService = service;
-        }
-
-        ActivityBuilder setComponent(ComponentName component) {
-            mComponent = component;
-            return this;
-        }
-
-        ActivityBuilder setTargetActivity(String targetActivity) {
-            mTargetActivity = targetActivity;
-            return this;
-        }
-
-        ActivityBuilder setIntentExtras(Bundle extras) {
-            mIntentExtras = extras;
-            return this;
-        }
-
-        static ComponentName getDefaultComponent() {
-            return ComponentName.createRelative(DEFAULT_COMPONENT_PACKAGE_NAME,
-                    DEFAULT_COMPONENT_PACKAGE_NAME);
-        }
-
-        ActivityBuilder setTask(Task task) {
-            mTask = task;
-            return this;
-        }
-
-        ActivityBuilder setActivityFlags(int flags) {
-            mActivityFlags = flags;
-            return this;
-        }
-
-        ActivityBuilder setLaunchMode(int launchMode) {
-            mLaunchMode = launchMode;
-            return this;
-        }
-
-        ActivityBuilder setStack(Task stack) {
-            mStack = stack;
-            return this;
-        }
-
-        ActivityBuilder setCreateTask(boolean createTask) {
-            mCreateTask = createTask;
-            return this;
-        }
-
-        ActivityBuilder setProcessName(String name) {
-            mProcessName = name;
-            return this;
-        }
-
-        ActivityBuilder setUid(int uid) {
-            mUid = uid;
-            return this;
-        }
-
-        ActivityBuilder setResizeMode(int resizeMode) {
-            mResizeMode = resizeMode;
-            return this;
-        }
-
-        ActivityBuilder setMaxAspectRatio(float maxAspectRatio) {
-            mMaxAspectRatio = maxAspectRatio;
-            return this;
-        }
-
-        ActivityBuilder setScreenOrientation(int screenOrientation) {
-            mScreenOrientation = screenOrientation;
-            return this;
-        }
-
-        ActivityBuilder setLaunchTaskBehind(boolean launchTaskBehind) {
-            mLaunchTaskBehind = launchTaskBehind;
-            return this;
-        }
-
-        ActivityBuilder setConfigChanges(int configChanges) {
-            mConfigChanges = configChanges;
-            return this;
-        }
-
-        ActivityBuilder setLaunchedFromPid(int pid) {
-            mLaunchedFromPid = pid;
-            return this;
-        }
-
-        ActivityBuilder setLaunchedFromUid(int uid) {
-            mLaunchedFromUid = uid;
-            return this;
-        }
-
-        ActivityBuilder setUseProcess(WindowProcessController wpc) {
-            mWpc = wpc;
-            return this;
-        }
-
-        ActivityBuilder setAffinity(String affinity) {
-            mAffinity = affinity;
-            return this;
-        }
-
-        ActivityRecord build() {
-            SystemServicesTestRule.checkHoldsLock(mService.mGlobalLock);
-            try {
-                mService.deferWindowLayout();
-                return buildInner();
-            } finally {
-                mService.continueWindowLayout();
-            }
-        }
-
-        ActivityRecord buildInner() {
-            if (mComponent == null) {
-                final int id = sCurrentActivityId++;
-                mComponent = ComponentName.createRelative(DEFAULT_COMPONENT_PACKAGE_NAME,
-                        DEFAULT_COMPONENT_CLASS_NAME + id);
-            }
-
-            if (mCreateTask) {
-                mTask = new TaskBuilder(mService.mStackSupervisor)
-                        .setComponent(mComponent)
-                        .setStack(mStack).build();
-            } else if (mTask == null && mStack != null && DisplayContent.alwaysCreateStack(
-                    mStack.getWindowingMode(), mStack.getActivityType())) {
-                // The stack can be the task root.
-                mTask = mStack;
-            }
-
-            Intent intent = new Intent();
-            intent.setComponent(mComponent);
-            if (mIntentExtras != null) {
-                intent.putExtras(mIntentExtras);
-            }
-            final ActivityInfo aInfo = new ActivityInfo();
-            aInfo.applicationInfo = new ApplicationInfo();
-            aInfo.applicationInfo.targetSdkVersion = Build.VERSION_CODES.CUR_DEVELOPMENT;
-            aInfo.applicationInfo.packageName = mComponent.getPackageName();
-            aInfo.applicationInfo.uid = mUid;
-            aInfo.processName = mProcessName;
-            aInfo.packageName = mComponent.getPackageName();
-            aInfo.name = mComponent.getClassName();
-            if (mTargetActivity != null) {
-                aInfo.targetActivity = mTargetActivity;
-            }
-            aInfo.flags |= mActivityFlags;
-            aInfo.launchMode = mLaunchMode;
-            aInfo.resizeMode = mResizeMode;
-            aInfo.maxAspectRatio = mMaxAspectRatio;
-            aInfo.screenOrientation = mScreenOrientation;
-            aInfo.configChanges |= mConfigChanges;
-            aInfo.taskAffinity = mAffinity;
-
-            ActivityOptions options = null;
-            if (mLaunchTaskBehind) {
-                options = ActivityOptions.makeTaskLaunchBehind();
-            }
-
-            final ActivityRecord activity = new ActivityRecord(mService, null /* caller */,
-                    mLaunchedFromPid /* launchedFromPid */, mLaunchedFromUid /* launchedFromUid */,
-                    null, null, intent, null, aInfo /*aInfo*/, new Configuration(),
-                    null /* resultTo */, null /* resultWho */, 0 /* reqCode */,
-                    false /*componentSpecified*/, false /* rootVoiceInteraction */,
-                    mService.mStackSupervisor, options, null /* sourceRecord */);
-            spyOn(activity);
-            if (mTask != null) {
-                // fullscreen value is normally read from resources in ctor, so for testing we need
-                // to set it somewhere else since we can't mock resources.
-                doReturn(true).when(activity).occludesParent();
-                doReturn(true).when(activity).fillsParent();
-                mTask.addChild(activity);
-                // Make visible by default...
-                activity.setVisible(true);
-            }
-
-            final WindowProcessController wpc;
-            if (mWpc != null) {
-                wpc = mWpc;
-            } else {
-                wpc = new WindowProcessController(mService,
-                        aInfo.applicationInfo, mProcessName, mUid,
-                        UserHandle.getUserId(12345), mock(Object.class),
-                        mock(WindowProcessListener.class));
-                wpc.setThread(mock(IApplicationThread.class));
-            }
-            wpc.setThread(mock(IApplicationThread.class));
-            activity.setProcess(wpc);
-            doReturn(wpc).when(mService).getProcessController(
-                    activity.processName, activity.info.applicationInfo.uid);
-
-            // Resume top activities to make sure all other signals in the system are connected.
-            mService.mRootWindowContainer.resumeFocusedStacksTopActivities();
-            return activity;
-        }
-    }
-
-    /**
-     * Builder for creating new tasks.
-     */
-    protected static class TaskBuilder {
-        private final ActivityStackSupervisor mSupervisor;
-
-        private ComponentName mComponent;
-        private String mPackage;
-        private int mFlags = 0;
-        // Task id 0 is reserved in ARC for the home app.
-        private int mTaskId = SystemServicesTestRule.sNextTaskId++;
-        private int mUserId = 0;
-        private IVoiceInteractionSession mVoiceSession;
-        private boolean mCreateStack = true;
-
-        private Task mStack;
-        private TaskDisplayArea mTaskDisplayArea;
-
-        TaskBuilder(ActivityStackSupervisor supervisor) {
-            mSupervisor = supervisor;
-        }
-
-        TaskBuilder setComponent(ComponentName component) {
-            mComponent = component;
-            return this;
-        }
-
-        TaskBuilder setPackage(String packageName) {
-            mPackage = packageName;
-            return this;
-        }
-
-        /**
-         * Set to {@code true} by default, set to {@code false} to prevent the task from
-         * automatically creating a parent stack.
-         */
-        TaskBuilder setCreateStack(boolean createStack) {
-            mCreateStack = createStack;
-            return this;
-        }
-
-        TaskBuilder setVoiceSession(IVoiceInteractionSession session) {
-            mVoiceSession = session;
-            return this;
-        }
-
-        TaskBuilder setFlags(int flags) {
-            mFlags = flags;
-            return this;
-        }
-
-        TaskBuilder setTaskId(int taskId) {
-            mTaskId = taskId;
-            return this;
-        }
-
-        TaskBuilder setUserId(int userId) {
-            mUserId = userId;
-            return this;
-        }
-
-        TaskBuilder setStack(Task stack) {
-            mStack = stack;
-            return this;
-        }
-
-        TaskBuilder setDisplay(DisplayContent display) {
-            mTaskDisplayArea = display.getDefaultTaskDisplayArea();
-            return this;
-        }
-
-        Task build() {
-            SystemServicesTestRule.checkHoldsLock(mSupervisor.mService.mGlobalLock);
-
-            if (mStack == null && mCreateStack) {
-                TaskDisplayArea displayArea = mTaskDisplayArea != null ? mTaskDisplayArea
-                        : mSupervisor.mRootWindowContainer.getDefaultTaskDisplayArea();
-                mStack = displayArea.createStack(
-                        WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, true /* onTop */);
-                spyOn(mStack);
-            }
-
-            final ActivityInfo aInfo = new ActivityInfo();
-            aInfo.applicationInfo = new ApplicationInfo();
-            aInfo.applicationInfo.packageName = mPackage;
-
-            Intent intent = new Intent();
-            if (mComponent == null) {
-                mComponent = ComponentName.createRelative(DEFAULT_COMPONENT_PACKAGE_NAME,
-                        DEFAULT_COMPONENT_CLASS_NAME);
-            }
-
-            intent.setComponent(mComponent);
-            intent.setFlags(mFlags);
-
-            final Task task = new Task(mSupervisor.mService, mTaskId, aInfo,
-                    intent /*intent*/, mVoiceSession, null /*_voiceInteractor*/,
-                    null /*taskDescription*/, mStack);
-            spyOn(task);
-            task.mUserId = mUserId;
-
-            if (mStack != null) {
-                mStack.moveToFront("test");
-                mStack.addChild(task, true, true);
-            }
-
-            return task;
-        }
-    }
-
-    static class StackBuilder {
-        private final RootWindowContainer mRootWindowContainer;
-        private DisplayContent mDisplay;
-        private TaskDisplayArea mTaskDisplayArea;
-        private int mStackId = -1;
-        private int mWindowingMode = WINDOWING_MODE_UNDEFINED;
-        private int mActivityType = ACTIVITY_TYPE_STANDARD;
-        private boolean mOnTop = true;
-        private boolean mCreateActivity = true;
-        private ActivityInfo mInfo;
-        private Intent mIntent;
-
-        StackBuilder(RootWindowContainer root) {
-            mRootWindowContainer = root;
-            mDisplay = mRootWindowContainer.getDefaultDisplay();
-            mTaskDisplayArea = mDisplay.getDefaultTaskDisplayArea();
-        }
-
-        StackBuilder setWindowingMode(int windowingMode) {
-            mWindowingMode = windowingMode;
-            return this;
-        }
-
-        StackBuilder setActivityType(int activityType) {
-            mActivityType = activityType;
-            return this;
-        }
-
-        StackBuilder setStackId(int stackId) {
-            mStackId = stackId;
-            return this;
-        }
-
-        /**
-         * Set the parent {@link DisplayContent} and use the default task display area. Overrides
-         * the task display area, if was set before.
-         */
-        StackBuilder setDisplay(DisplayContent display) {
-            mDisplay = display;
-            mTaskDisplayArea = mDisplay.getDefaultTaskDisplayArea();
-            return this;
-        }
-
-        /** Set the parent {@link TaskDisplayArea}. Overrides the display, if was set before. */
-        StackBuilder setTaskDisplayArea(TaskDisplayArea taskDisplayArea) {
-            mTaskDisplayArea = taskDisplayArea;
-            mDisplay = mTaskDisplayArea.mDisplayContent;
-            return this;
-        }
-
-        StackBuilder setOnTop(boolean onTop) {
-            mOnTop = onTop;
-            return this;
-        }
-
-        StackBuilder setCreateActivity(boolean createActivity) {
-            mCreateActivity = createActivity;
-            return this;
-        }
-
-        StackBuilder setActivityInfo(ActivityInfo info) {
-            mInfo = info;
-            return this;
-        }
-
-        StackBuilder setIntent(Intent intent) {
-            mIntent = intent;
-            return this;
-        }
-
-        Task build() {
-            SystemServicesTestRule.checkHoldsLock(mRootWindowContainer.mWmService.mGlobalLock);
-
-            final int stackId = mStackId >= 0 ? mStackId : mTaskDisplayArea.getNextStackId();
-            final Task stack = mTaskDisplayArea.createStackUnchecked(
-                    mWindowingMode, mActivityType, stackId, mOnTop, mInfo, mIntent,
-                    false /* createdByOrganizer */);
-            final ActivityStackSupervisor supervisor = mRootWindowContainer.mStackSupervisor;
-
-            if (mCreateActivity) {
-                new ActivityBuilder(supervisor.mService)
-                        .setCreateTask(true)
-                        .setStack(stack)
-                        .build();
-                if (mOnTop) {
-                    // We move the task to front again in order to regain focus after activity
-                    // added to the stack. Or {@link DisplayContent#mPreferredTopFocusableStack}
-                    // could be other stacks (e.g. home stack).
-                    stack.moveToFront("createActivityStack");
-                } else {
-                    stack.moveToBack("createActivityStack", null);
-                }
-            }
-            spyOn(stack);
-
-            doNothing().when(stack).startActivityLocked(
-                    any(), any(), anyBoolean(), anyBoolean(), any());
-
-            return stack;
-        }
-
-    }
-
-    static class TestSplitOrganizer extends ITaskOrganizer.Stub {
-        final ActivityTaskManagerService mService;
-        Task mPrimary;
-        Task mSecondary;
-        boolean mInSplit = false;
-        // moves everything to secondary. Most tests expect this since sysui usually does it.
-        boolean mMoveToSecondaryOnEnter = true;
-        int mDisplayId;
-        TestSplitOrganizer(ActivityTaskManagerService service, int displayId) {
-            mService = service;
-            mDisplayId = displayId;
-            mService.mTaskOrganizerController.registerTaskOrganizer(this,
-                    WINDOWING_MODE_SPLIT_SCREEN_PRIMARY);
-            mService.mTaskOrganizerController.registerTaskOrganizer(this,
-                    WINDOWING_MODE_SPLIT_SCREEN_SECONDARY);
-            WindowContainerToken primary = mService.mTaskOrganizerController.createRootTask(
-                    displayId, WINDOWING_MODE_SPLIT_SCREEN_PRIMARY).token;
-            mPrimary = WindowContainer.fromBinder(primary.asBinder()).asTask();
-            WindowContainerToken secondary = mService.mTaskOrganizerController.createRootTask(
-                    displayId, WINDOWING_MODE_SPLIT_SCREEN_SECONDARY).token;
-            mSecondary = WindowContainer.fromBinder(secondary.asBinder()).asTask();
-        }
-        TestSplitOrganizer(ActivityTaskManagerService service) {
-            this(service,
-                    service.mStackSupervisor.mRootWindowContainer.getDefaultDisplay().mDisplayId);
-        }
-        public void setMoveToSecondaryOnEnter(boolean move) {
-            mMoveToSecondaryOnEnter = move;
-        }
-        @Override
-        public void onTaskAppeared(ActivityManager.RunningTaskInfo info, SurfaceControl leash) {
-        }
-        @Override
-        public void onTaskVanished(ActivityManager.RunningTaskInfo info) {
-        }
-        @Override
-        public void onTaskInfoChanged(ActivityManager.RunningTaskInfo info) {
-            if (mInSplit) {
-                return;
-            }
-            if (info.topActivityType == ACTIVITY_TYPE_UNDEFINED) {
-                // Not populated
-                return;
-            }
-            if (info.configuration.windowConfiguration.getWindowingMode()
-                    != WINDOWING_MODE_SPLIT_SCREEN_PRIMARY) {
-                return;
-            }
-            mInSplit = true;
-            if (!mMoveToSecondaryOnEnter) {
-                return;
-            }
-            mService.mTaskOrganizerController.setLaunchRoot(mDisplayId,
-                    mSecondary.mRemoteToken.toWindowContainerToken());
-            DisplayContent dc = mService.mRootWindowContainer.getDisplayContent(mDisplayId);
-            dc.forAllTaskDisplayAreas(taskDisplayArea -> {
-                for (int sNdx = taskDisplayArea.getStackCount() - 1; sNdx >= 0; --sNdx) {
-                    final Task stack = taskDisplayArea.getStackAt(sNdx);
-                    if (!WindowConfiguration.isSplitScreenWindowingMode(stack.getWindowingMode())) {
-                        stack.reparent(mSecondary, POSITION_BOTTOM);
-                    }
-                }
-            });
-        }
-        @Override
-        public void onBackPressedOnTaskRoot(ActivityManager.RunningTaskInfo taskInfo) {
-        }
-    };
-}
diff --git a/services/tests/wmtests/src/com/android/server/wm/AppTransitionControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/AppTransitionControllerTest.java
index 7adcead..1b21920 100644
--- a/services/tests/wmtests/src/com/android/server/wm/AppTransitionControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/AppTransitionControllerTest.java
@@ -133,10 +133,10 @@
         // [DisplayContent] -+- [TaskStack1] - [Task1] - [ActivityRecord1] (opening, visible)
         //                   +- [TaskStack2] - [Task2] - [ActivityRecord2] (closing, invisible)
         final Task stack1 = createTaskStackOnDisplay(mDisplayContent);
-        final ActivityRecord activity1 = WindowTestUtils.createTestActivityRecord(stack1);
+        final ActivityRecord activity1 = createTestActivityRecord(stack1);
 
         final Task stack2 = createTaskStackOnDisplay(mDisplayContent);
-        final ActivityRecord activity2 = WindowTestUtils.createTestActivityRecord(stack2);
+        final ActivityRecord activity2 = createTestActivityRecord(stack2);
         activity2.setVisible(false);
         activity2.mVisibleRequested = false;
 
@@ -162,13 +162,13 @@
         // [DisplayContent] -+- [TaskStack1] - [Task1] - [ActivityRecord1] (closing, invisible)
         //                   +- [TaskStack2] - [Task2] - [ActivityRecord2] (opening, visible)
         final Task stack1 = createTaskStackOnDisplay(mDisplayContent);
-        final ActivityRecord activity1 = WindowTestUtils.createTestActivityRecord(stack1);
+        final ActivityRecord activity1 = createTestActivityRecord(stack1);
         activity1.setVisible(true);
         activity1.mVisibleRequested = true;
         activity1.mRequestForceTransition = true;
 
         final Task stack2 = createTaskStackOnDisplay(mDisplayContent);
-        final ActivityRecord activity2 = WindowTestUtils.createTestActivityRecord(stack2);
+        final ActivityRecord activity2 = createTestActivityRecord(stack2);
         activity2.setVisible(false);
         activity2.mVisibleRequested = false;
         activity2.mRequestForceTransition = true;
@@ -193,10 +193,10 @@
     @Test
     public void testGetAnimationTargets_exitingBeforeTransition() {
         // Create another non-empty task so the animation target won't promote to task display area.
-        WindowTestUtils.createTestActivityRecord(
+        createTestActivityRecord(
                 mDisplayContent.getDefaultTaskDisplayArea().getOrCreateRootHomeTask());
         final Task stack = createTaskStackOnDisplay(mDisplayContent);
-        final ActivityRecord activity = WindowTestUtils.createTestActivityRecord(stack);
+        final ActivityRecord activity = createTestActivityRecord(stack);
         activity.setVisible(false);
         activity.mIsExiting = true;
 
@@ -218,20 +218,20 @@
         //                   +- [TaskStack2] - [Task2] - [ActivityRecord2] (closing, invisible)
         //                                                      +- [AppWindow2] (being-replaced)
         final Task stack1 = createTaskStackOnDisplay(mDisplayContent);
-        final ActivityRecord activity1 = WindowTestUtils.createTestActivityRecord(stack1);
+        final ActivityRecord activity1 = createTestActivityRecord(stack1);
         final WindowManager.LayoutParams attrs = new WindowManager.LayoutParams(
                 TYPE_BASE_APPLICATION);
         attrs.setTitle("AppWindow1");
-        final WindowTestUtils.TestWindowState appWindow1 = createWindowState(attrs, activity1);
+        final TestWindowState appWindow1 = createWindowState(attrs, activity1);
         appWindow1.mWillReplaceWindow = true;
         activity1.addWindow(appWindow1);
 
         final Task stack2 = createTaskStackOnDisplay(mDisplayContent);
-        final ActivityRecord activity2 = WindowTestUtils.createTestActivityRecord(stack2);
+        final ActivityRecord activity2 = createTestActivityRecord(stack2);
         activity2.setVisible(false);
         activity2.mVisibleRequested = false;
         attrs.setTitle("AppWindow2");
-        final WindowTestUtils.TestWindowState appWindow2 = createWindowState(attrs, activity2);
+        final TestWindowState appWindow2 = createWindowState(attrs, activity2);
         appWindow2.mWillReplaceWindow = true;
         activity2.addWindow(appWindow2);
 
@@ -262,21 +262,17 @@
         //                                              +- [ActivityRecord4] (invisible)
         final Task stack1 = createTaskStackOnDisplay(mDisplayContent);
         final Task task1 = createTaskInStack(stack1, 0 /* userId */);
-        final ActivityRecord activity1 = WindowTestUtils.createActivityRecordInTask(
-                mDisplayContent, task1);
+        final ActivityRecord activity1 = createActivityRecordInTask(mDisplayContent, task1);
         activity1.setVisible(false);
         activity1.mVisibleRequested = true;
-        final ActivityRecord activity2 = WindowTestUtils.createActivityRecordInTask(
-                mDisplayContent, task1);
+        final ActivityRecord activity2 = createActivityRecordInTask(mDisplayContent, task1);
         activity2.setVisible(false);
         activity2.mVisibleRequested = false;
 
         final Task stack2 = createTaskStackOnDisplay(mDisplayContent);
         final Task task2 = createTaskInStack(stack2, 0 /* userId */);
-        final ActivityRecord activity3 = WindowTestUtils.createActivityRecordInTask(
-                mDisplayContent, task2);
-        final ActivityRecord activity4 = WindowTestUtils.createActivityRecordInTask(
-                mDisplayContent, task2);
+        final ActivityRecord activity3 = createActivityRecordInTask(mDisplayContent, task2);
+        final ActivityRecord activity4 = createActivityRecordInTask(mDisplayContent, task2);
         activity4.setVisible(false);
         activity4.mVisibleRequested = false;
 
@@ -303,12 +299,10 @@
         //                                          +- [ActivityRecord2] (closing, visible)
         final Task stack = createTaskStackOnDisplay(mDisplayContent);
         final Task task = createTaskInStack(stack, 0 /* userId */);
-        final ActivityRecord activity1 = WindowTestUtils.createActivityRecordInTask(
-                mDisplayContent, task);
+        final ActivityRecord activity1 = createActivityRecordInTask(mDisplayContent, task);
         activity1.setVisible(false);
         activity1.mVisibleRequested = true;
-        final ActivityRecord activity2 = WindowTestUtils.createActivityRecordInTask(
-                mDisplayContent, task);
+        final ActivityRecord activity2 = createActivityRecordInTask(mDisplayContent, task);
 
         final ArraySet<ActivityRecord> opening = new ArraySet<>();
         opening.add(activity1);
@@ -337,22 +331,18 @@
 
         final Task stack1 = createTaskStackOnDisplay(mDisplayContent);
         final Task task1 = createTaskInStack(stack1, 0 /* userId */);
-        final ActivityRecord activity1 = WindowTestUtils.createActivityRecordInTask(
-                mDisplayContent, task1);
+        final ActivityRecord activity1 = createActivityRecordInTask(mDisplayContent, task1);
         activity1.setVisible(false);
         activity1.mVisibleRequested = true;
         activity1.setOccludesParent(false);
 
-        final ActivityRecord activity2 = WindowTestUtils.createActivityRecordInTask(
-                mDisplayContent, task1);
+        final ActivityRecord activity2 = createActivityRecordInTask(mDisplayContent, task1);
 
         final Task stack2 = createTaskStackOnDisplay(mDisplayContent);
         final Task task2 = createTaskInStack(stack2, 0 /* userId */);
-        final ActivityRecord activity3 = WindowTestUtils.createActivityRecordInTask(
-                mDisplayContent, task2);
+        final ActivityRecord activity3 = createActivityRecordInTask(mDisplayContent, task2);
         activity3.setOccludesParent(false);
-        final ActivityRecord activity4 = WindowTestUtils.createActivityRecordInTask(
-                mDisplayContent, task2);
+        final ActivityRecord activity4 = createActivityRecordInTask(mDisplayContent, task2);
 
         final ArraySet<ActivityRecord> opening = new ArraySet<>();
         opening.add(activity1);
@@ -381,24 +371,20 @@
 
         final Task stack1 = createTaskStackOnDisplay(mDisplayContent);
         final Task task1 = createTaskInStack(stack1, 0 /* userId */);
-        final ActivityRecord activity1 = WindowTestUtils.createActivityRecordInTask(
-                mDisplayContent, task1);
+        final ActivityRecord activity1 = createActivityRecordInTask(mDisplayContent, task1);
         activity1.setVisible(false);
         activity1.mVisibleRequested = true;
         activity1.setOccludesParent(false);
 
-        final ActivityRecord activity2 = WindowTestUtils.createActivityRecordInTask(
-                mDisplayContent, task1);
+        final ActivityRecord activity2 = createActivityRecordInTask(mDisplayContent, task1);
         activity2.setVisible(false);
         activity2.mVisibleRequested = true;
 
         final Task stack2 = createTaskStackOnDisplay(mDisplayContent);
         final Task task2 = createTaskInStack(stack2, 0 /* userId */);
-        final ActivityRecord activity3 = WindowTestUtils.createActivityRecordInTask(
-                mDisplayContent, task2);
+        final ActivityRecord activity3 = createActivityRecordInTask(mDisplayContent, task2);
         activity3.setOccludesParent(false);
-        final ActivityRecord activity4 = WindowTestUtils.createActivityRecordInTask(
-                mDisplayContent, task2);
+        final ActivityRecord activity4 = createActivityRecordInTask(mDisplayContent, task2);
 
         final ArraySet<ActivityRecord> opening = new ArraySet<>();
         opening.add(activity1);
@@ -425,13 +411,11 @@
         //                                 +- [Task2] - [ActivityRecord2] (closing, visible)
         final Task stack = createTaskStackOnDisplay(mDisplayContent);
         final Task task1 = createTaskInStack(stack, 0 /* userId */);
-        final ActivityRecord activity1 = WindowTestUtils.createActivityRecordInTask(
-                mDisplayContent, task1);
+        final ActivityRecord activity1 = createActivityRecordInTask(mDisplayContent, task1);
         activity1.setVisible(false);
         activity1.mVisibleRequested = true;
         final Task task2 = createTaskInStack(stack, 0 /* userId */);
-        final ActivityRecord activity2 = WindowTestUtils.createActivityRecordInTask(
-                mDisplayContent, task2);
+        final ActivityRecord activity2 = createActivityRecordInTask(mDisplayContent, task2);
 
         final ArraySet<ActivityRecord> opening = new ArraySet<>();
         opening.add(activity1);
diff --git a/services/tests/wmtests/src/com/android/server/wm/AppTransitionTests.java b/services/tests/wmtests/src/com/android/server/wm/AppTransitionTests.java
index 17914e7..ee030af 100644
--- a/services/tests/wmtests/src/com/android/server/wm/AppTransitionTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/AppTransitionTests.java
@@ -151,8 +151,7 @@
 
         final Task stack1 = createTaskStackOnDisplay(dc1);
         final Task task1 = createTaskInStack(stack1, 0 /* userId */);
-        final ActivityRecord activity1 =
-                WindowTestUtils.createTestActivityRecord(dc1);
+        final ActivityRecord activity1 = createTestActivityRecord(dc1);
         task1.addChild(activity1, 0);
 
         // Simulate same app is during opening / closing transition set stage.
diff --git a/services/tests/wmtests/src/com/android/server/wm/AppWindowTokenTests.java b/services/tests/wmtests/src/com/android/server/wm/AppWindowTokenTests.java
index 97a2ebe..085b8de 100644
--- a/services/tests/wmtests/src/com/android/server/wm/AppWindowTokenTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/AppWindowTokenTests.java
@@ -92,7 +92,7 @@
     public void setUp() throws Exception {
         mStack = createTaskStackOnDisplay(mDisplayContent);
         mTask = createTaskInStack(mStack, 0 /* userId */);
-        mActivity = WindowTestUtils.createTestActivityRecord(mDisplayContent);
+        mActivity = createTestActivityRecord(mDisplayContent);
 
         mTask.addChild(mActivity, 0);
     }
@@ -165,7 +165,7 @@
         final WindowManager.LayoutParams attrs = new WindowManager.LayoutParams(
                 TYPE_BASE_APPLICATION);
         attrs.setTitle("AppWindow");
-        final WindowTestUtils.TestWindowState appWindow = createWindowState(attrs, mActivity);
+        final TestWindowState appWindow = createWindowState(attrs, mActivity);
         mActivity.addWindow(appWindow);
 
         // Set initial orientation and update.
@@ -198,7 +198,7 @@
         final WindowManager.LayoutParams attrs = new WindowManager.LayoutParams(
                 TYPE_BASE_APPLICATION);
         attrs.setTitle("RotationByPolicy");
-        final WindowTestUtils.TestWindowState appWindow = createWindowState(attrs, mActivity);
+        final TestWindowState appWindow = createWindowState(attrs, mActivity);
         mActivity.addWindow(appWindow);
 
         // Set initial orientation and update.
@@ -244,7 +244,7 @@
                 TYPE_BASE_APPLICATION);
         attrs.flags |= FLAG_SHOW_WHEN_LOCKED | FLAG_DISMISS_KEYGUARD;
         attrs.setTitle("AppWindow");
-        final WindowTestUtils.TestWindowState appWindow = createWindowState(attrs, mActivity);
+        final TestWindowState appWindow = createWindowState(attrs, mActivity);
 
         // Add window with show when locked flag
         mActivity.addWindow(appWindow);
@@ -307,7 +307,7 @@
         assertEquals(Configuration.ORIENTATION_PORTRAIT, displayConfig.orientation);
         assertEquals(Configuration.ORIENTATION_PORTRAIT, activityConfig.orientation);
 
-        final ActivityRecord topActivity = WindowTestUtils.createTestActivityRecord(mStack);
+        final ActivityRecord topActivity = createTestActivityRecord(mStack);
         topActivity.setOrientation(SCREEN_ORIENTATION_LANDSCAPE);
 
         assertEquals(Configuration.ORIENTATION_LANDSCAPE, displayConfig.orientation);
@@ -431,7 +431,7 @@
         doCallRealMethod().when(mStack).startActivityLocked(
                 any(), any(), anyBoolean(), anyBoolean(), any());
         // Make mVisibleSetFromTransferredStartingWindow true.
-        final ActivityRecord middle = new ActivityTestsBase.ActivityBuilder(mWm.mAtmService)
+        final ActivityRecord middle = new ActivityBuilder(mWm.mAtmService)
                 .setTask(mTask).build();
         mStack.startActivityLocked(middle, null /* focusedTopActivity */,
                 false /* newTask */, false /* keepCurTransition */, null /* options */);
@@ -440,7 +440,7 @@
         assertNull(mActivity.startingWindow);
         assertHasStartingWindow(middle);
 
-        final ActivityRecord top = new ActivityTestsBase.ActivityBuilder(mWm.mAtmService)
+        final ActivityRecord top = new ActivityBuilder(mWm.mAtmService)
                 .setTask(mTask).build();
         // Expect the visibility should be updated to true when transferring starting window from
         // a visible activity.
@@ -490,8 +490,7 @@
     }
 
     private ActivityRecord createTestActivityRecordForGivenTask(Task task) {
-        final ActivityRecord activity =
-                WindowTestUtils.createTestActivityRecord(mDisplayContent);
+        final ActivityRecord activity = createTestActivityRecord(mDisplayContent);
         task.addChild(activity, 0);
         waitUntilHandlersIdle();
         return activity;
@@ -562,7 +561,7 @@
     public void testHasStartingWindow() {
         final WindowManager.LayoutParams attrs =
                 new WindowManager.LayoutParams(TYPE_APPLICATION_STARTING);
-        final WindowTestUtils.TestWindowState startingWindow = createWindowState(attrs, mActivity);
+        final TestWindowState startingWindow = createWindowState(attrs, mActivity);
         mActivity.startingDisplayed = true;
         mActivity.addWindow(startingWindow);
         assertTrue("Starting window should be present", mActivity.hasStartingWindow());
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
index edf1536..d54b4a0 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
@@ -326,7 +326,7 @@
         assertEquals(dc, stack.getDisplayContent());
 
         final Task task = createTaskInStack(stack, 0 /* userId */);
-        final ActivityRecord activity = WindowTestUtils.createTestActivityRecord(dc);
+        final ActivityRecord activity = createTestActivityRecord(dc);
         task.addChild(activity, 0);
         assertEquals(dc, task.getDisplayContent());
         assertEquals(dc, activity.getDisplayContent());
@@ -397,16 +397,14 @@
         // Add stack with activity.
         final Task stack0 = createTaskStackOnDisplay(dc0);
         final Task task0 = createTaskInStack(stack0, 0 /* userId */);
-        final ActivityRecord activity =
-                WindowTestUtils.createTestActivityRecord(dc0);
+        final ActivityRecord activity = createTestActivityRecord(dc0);
         task0.addChild(activity, 0);
         dc0.configureDisplayPolicy();
         assertNotNull(dc0.mTapDetector);
 
         final Task stack1 = createTaskStackOnDisplay(dc1);
         final Task task1 = createTaskInStack(stack1, 0 /* userId */);
-        final ActivityRecord activity1 =
-                WindowTestUtils.createTestActivityRecord(dc0);
+        final ActivityRecord activity1 = createTestActivityRecord(dc0);
         task1.addChild(activity1, 0);
         dc1.configureDisplayPolicy();
         assertNotNull(dc1.mTapDetector);
@@ -850,13 +848,13 @@
                 IWindowManager.FIXED_TO_USER_ROTATION_DISABLED);
 
         final Task stack =
-                new ActivityTestsBase.StackBuilder(mWm.mAtmService.mRootWindowContainer)
+                new StackBuilder(mWm.mAtmService.mRootWindowContainer)
                         .setDisplay(dc)
                         .build();
         doReturn(true).when(stack).isVisible();
 
         final Task freeformStack =
-                new ActivityTestsBase.StackBuilder(mWm.mAtmService.mRootWindowContainer)
+                new StackBuilder(mWm.mAtmService.mRootWindowContainer)
                         .setDisplay(dc)
                         .setWindowingMode(WINDOWING_MODE_FREEFORM)
                         .build();
@@ -881,9 +879,8 @@
                 IWindowManager.FIXED_TO_USER_ROTATION_DISABLED);
         final int newOrientation = getRotatedOrientation(dc);
 
-        final Task stack =
-                new ActivityTestsBase.StackBuilder(mWm.mAtmService.mRootWindowContainer)
-                        .setDisplay(dc).build();
+        final Task stack = new StackBuilder(mWm.mAtmService.mRootWindowContainer)
+                .setDisplay(dc).build();
         final ActivityRecord activity = stack.getTopMostTask().getTopNonFinishingActivity();
 
         activity.setRequestedOrientation(newOrientation);
@@ -901,9 +898,8 @@
                 IWindowManager.FIXED_TO_USER_ROTATION_ENABLED);
         final int newOrientation = getRotatedOrientation(dc);
 
-        final Task stack =
-                new ActivityTestsBase.StackBuilder(mWm.mAtmService.mRootWindowContainer)
-                        .setDisplay(dc).build();
+        final Task stack = new StackBuilder(mWm.mAtmService.mRootWindowContainer)
+                .setDisplay(dc).build();
         final ActivityRecord activity = stack.getTopMostTask().getTopNonFinishingActivity();
 
         activity.setRequestedOrientation(newOrientation);
@@ -1213,7 +1209,7 @@
         verify(t, never()).setPosition(any(), eq(0), eq(0));
 
         // Launch another activity before the transition is finished.
-        final ActivityRecord app2 = new ActivityTestsBase.StackBuilder(mWm.mRoot)
+        final ActivityRecord app2 = new StackBuilder(mWm.mRoot)
                 .setDisplay(mDisplayContent).build().getTopMostActivity();
         app2.setVisible(false);
         mDisplayContent.mOpeningApps.add(app2);
@@ -1247,8 +1243,7 @@
         final ActivityRecord app = createActivityRecord(mDisplayContent, WINDOWING_MODE_FULLSCREEN,
                 ACTIVITY_TYPE_STANDARD);
         final Task task = app.getTask();
-        final ActivityRecord app2 = new ActivityTestsBase.ActivityBuilder(mWm.mAtmService)
-                .setTask(task).build();
+        final ActivityRecord app2 = new ActivityBuilder(mWm.mAtmService).setTask(task).build();
         mDisplayContent.setFixedRotationLaunchingApp(app2, (mDisplayContent.getRotation() + 1) % 4);
         doReturn(true).when(task).isAppTransitioning();
         // If the task is animating transition, this should be no-op.
@@ -1299,7 +1294,7 @@
         final ActivityRecord pinnedActivity = createActivityRecord(displayContent,
                 WINDOWING_MODE_PINNED, ACTIVITY_TYPE_STANDARD);
         final Task pinnedTask = pinnedActivity.getRootTask();
-        final ActivityRecord homeActivity = WindowTestUtils.createTestActivityRecord(
+        final ActivityRecord homeActivity = createTestActivityRecord(
                 displayContent.getDefaultTaskDisplayArea().getOrCreateRootHomeTask());
         if (displayConfig.orientation == Configuration.ORIENTATION_PORTRAIT) {
             homeActivity.setOrientation(SCREEN_ORIENTATION_PORTRAIT);
@@ -1513,8 +1508,7 @@
     @Test
     public void testSetWindowingModeAtomicallyUpdatesWindoingModeAndDisplayWindowingMode() {
         final DisplayContent dc = createNewDisplay();
-        final Task stack =
-                new ActivityTestsBase.StackBuilder(mWm.mAtmService.mRootWindowContainer)
+        final Task stack = new StackBuilder(mWm.mAtmService.mRootWindowContainer)
                 .setDisplay(dc)
                 .build();
         doAnswer(invocation -> {
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyLayoutTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyLayoutTests.java
index 4ea5b97..0675c6d 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyLayoutTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyLayoutTests.java
@@ -48,12 +48,14 @@
 import static android.view.WindowManagerPolicyConstants.ALT_BAR_RIGHT;
 import static android.view.WindowManagerPolicyConstants.ALT_BAR_TOP;
 
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
 
 import static org.hamcrest.Matchers.is;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotEquals;
 import static org.junit.Assert.assertThat;
+import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.doNothing;
 import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.spy;
@@ -874,6 +876,19 @@
     }
 
     @Test
+    public void testFixedRotationInsetsSourceFrame() {
+        mDisplayPolicy.beginLayoutLw(mFrames, mDisplayContent.getConfiguration().uiMode);
+        doReturn((mDisplayContent.getRotation() + 1) % 4).when(mDisplayContent)
+                .rotationForActivityInDifferentOrientation(eq(mWindow.mActivityRecord));
+        final Rect frame = mWindow.getInsetsState().getSource(ITYPE_STATUS_BAR).getFrame();
+        mDisplayContent.rotateInDifferentOrientationIfNeeded(mWindow.mActivityRecord);
+        final Rect rotatedFrame = mWindow.getInsetsState().getSource(ITYPE_STATUS_BAR).getFrame();
+
+        assertEquals(DISPLAY_WIDTH, frame.width());
+        assertEquals(DISPLAY_HEIGHT, rotatedFrame.width());
+    }
+
+    @Test
     public void testScreenDecorWindows() {
         final WindowState decorWindow = spy(
                 createWindow(null, TYPE_APPLICATION_OVERLAY, "decorWindow"));
diff --git a/services/tests/wmtests/src/com/android/server/wm/DragDropControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/DragDropControllerTests.java
index e18d93d..4536997 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DragDropControllerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DragDropControllerTests.java
@@ -95,8 +95,7 @@
      * Creates a window state which can be used as a drop target.
      */
     private WindowState createDropTargetWindow(String name, int ownerId) {
-        final ActivityRecord activity = WindowTestUtils.createTestActivityRecord(
-                mDisplayContent);
+        final ActivityRecord activity = createTestActivityRecord(mDisplayContent);
         final Task stack = createTaskStackOnDisplay(
                 WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, mDisplayContent);
         final Task task = createTaskInStack(stack, ownerId);
diff --git a/services/tests/wmtests/src/com/android/server/wm/InsetsPolicyTest.java b/services/tests/wmtests/src/com/android/server/wm/InsetsPolicyTest.java
index 555906d..608305c 100644
--- a/services/tests/wmtests/src/com/android/server/wm/InsetsPolicyTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/InsetsPolicyTest.java
@@ -43,7 +43,6 @@
 import static org.mockito.Mockito.spy;
 
 import android.platform.test.annotations.Presubmit;
-import android.util.IntArray;
 import android.view.InsetsSourceControl;
 import android.view.InsetsState;
 import android.view.test.InsetsModeSession;
@@ -242,8 +241,7 @@
         }).when(policy).startAnimation(anyBoolean(), any(), any());
 
         policy.updateBarControlTarget(mAppWindow);
-        policy.showTransient(
-                IntArray.wrap(new int[]{ITYPE_STATUS_BAR, ITYPE_NAVIGATION_BAR}));
+        policy.showTransient(new int[]{ITYPE_STATUS_BAR, ITYPE_NAVIGATION_BAR});
         waitUntilWindowAnimatorIdle();
         final InsetsSourceControl[] controls =
                 mDisplayContent.getInsetsStateController().getControlsForDispatch(mAppWindow);
@@ -271,8 +269,7 @@
         final InsetsPolicy policy = spy(mDisplayContent.getInsetsPolicy());
         doNothing().when(policy).startAnimation(anyBoolean(), any(), any());
         policy.updateBarControlTarget(mAppWindow);
-        policy.showTransient(
-                IntArray.wrap(new int[]{ITYPE_STATUS_BAR, ITYPE_NAVIGATION_BAR}));
+        policy.showTransient(new int[]{ITYPE_STATUS_BAR, ITYPE_NAVIGATION_BAR});
         waitUntilWindowAnimatorIdle();
         final InsetsSourceControl[] controls =
                 mDisplayContent.getInsetsStateController().getControlsForDispatch(mAppWindow);
@@ -301,8 +298,7 @@
         final InsetsPolicy policy = spy(mDisplayContent.getInsetsPolicy());
         doNothing().when(policy).startAnimation(anyBoolean(), any(), any());
         policy.updateBarControlTarget(mAppWindow);
-        policy.showTransient(
-                IntArray.wrap(new int[]{ITYPE_STATUS_BAR, ITYPE_NAVIGATION_BAR}));
+        policy.showTransient(new int[]{ITYPE_STATUS_BAR, ITYPE_NAVIGATION_BAR});
         waitUntilWindowAnimatorIdle();
         InsetsSourceControl[] controls =
                 mDisplayContent.getInsetsStateController().getControlsForDispatch(mAppWindow);
@@ -340,8 +336,7 @@
         final InsetsPolicy policy = spy(mDisplayContent.getInsetsPolicy());
         doNothing().when(policy).startAnimation(anyBoolean(), any(), any());
         policy.updateBarControlTarget(app);
-        policy.showTransient(
-                IntArray.wrap(new int[]{ITYPE_STATUS_BAR, ITYPE_NAVIGATION_BAR}));
+        policy.showTransient(new int[]{ITYPE_STATUS_BAR, ITYPE_NAVIGATION_BAR});
         final InsetsSourceControl[] controls =
                 mDisplayContent.getInsetsStateController().getControlsForDispatch(app);
         policy.updateBarControlTarget(app2);
diff --git a/services/tests/wmtests/src/com/android/server/wm/InsetsStateControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/InsetsStateControllerTest.java
index 5e83e66..59f8cc8 100644
--- a/services/tests/wmtests/src/com/android/server/wm/InsetsStateControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/InsetsStateControllerTest.java
@@ -28,6 +28,9 @@
 import static android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
 import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION;
 
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
+
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotNull;
@@ -40,6 +43,7 @@
 
 import android.graphics.Rect;
 import android.platform.test.annotations.Presubmit;
+import android.util.IntArray;
 import android.view.InsetsSourceControl;
 import android.view.InsetsState;
 import android.view.test.InsetsModeSession;
@@ -328,6 +332,26 @@
         assertNull(getController().getControlsForDispatch(app));
     }
 
+    @Test
+    public void testTransientVisibilityOfFixedRotationState() {
+        final WindowState statusBar = createWindow(null, TYPE_APPLICATION, "statusBar");
+        final WindowState app = createWindow(null, TYPE_APPLICATION, "app");
+        final InsetsSourceProvider provider = getController().getSourceProvider(ITYPE_STATUS_BAR);
+        provider.setWindow(statusBar, null, null);
+
+        final InsetsState rotatedState = new InsetsState(app.getInsetsState(),
+                true /* copySources */);
+        spyOn(app.mToken);
+        doReturn(rotatedState).when(app.mToken).getFixedRotationTransformInsetsState();
+        assertTrue(rotatedState.getSource(ITYPE_STATUS_BAR).isVisible());
+
+        provider.getSource().setVisible(false);
+        mDisplayContent.getInsetsPolicy().showTransient(new int[] { ITYPE_STATUS_BAR });
+
+        assertTrue(mDisplayContent.getInsetsPolicy().isTransient(ITYPE_STATUS_BAR));
+        assertFalse(app.getInsetsState().getSource(ITYPE_STATUS_BAR).isVisible());
+    }
+
     private InsetsStateController getController() {
         return mDisplayContent.getInsetsStateController();
     }
diff --git a/services/tests/wmtests/src/com/android/server/wm/LaunchParamsControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/LaunchParamsControllerTests.java
index a7a8505..820eca4 100644
--- a/services/tests/wmtests/src/com/android/server/wm/LaunchParamsControllerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/LaunchParamsControllerTests.java
@@ -68,14 +68,14 @@
 @MediumTest
 @Presubmit
 @RunWith(WindowTestRunner.class)
-public class LaunchParamsControllerTests extends ActivityTestsBase {
+public class LaunchParamsControllerTests extends WindowTestsBase {
     private LaunchParamsController mController;
     private TestLaunchParamsPersister mPersister;
 
     @Before
     public void setUp() throws Exception {
         mPersister = new TestLaunchParamsPersister();
-        mController = new LaunchParamsController(mService, mPersister);
+        mController = new LaunchParamsController(mAtm, mPersister);
     }
 
     /**
@@ -87,8 +87,8 @@
                 positioner = mock(LaunchParamsModifier.class);
         mController.registerModifier(positioner);
 
-        final ActivityRecord record = new ActivityBuilder(mService).build();
-        final ActivityRecord source = new ActivityBuilder(mService).build();
+        final ActivityRecord record = new ActivityBuilder(mAtm).build();
+        final ActivityRecord source = new ActivityBuilder(mAtm).build();
         final WindowLayout layout = new WindowLayout(0, 0, 0, 0, 0, 0, 0);
         final ActivityOptions options = mock(ActivityOptions.class);
 
@@ -108,7 +108,7 @@
 
         final ComponentName name = new ComponentName("com.android.foo", ".BarActivity");
         final int userId = 0;
-        final ActivityRecord activity = new ActivityBuilder(mService).setComponent(name)
+        final ActivityRecord activity = new ActivityBuilder(mAtm).setComponent(name)
                 .setUid(userId).build();
         final LaunchParams expected = new LaunchParams();
         expected.mPreferredTaskDisplayArea = mock(TaskDisplayArea.class);
@@ -228,10 +228,10 @@
     @Test
     public void testVrPreferredDisplay() {
         final TestDisplayContent vrDisplay = createNewDisplayContent();
-        mService.mVr2dDisplayId = vrDisplay.mDisplayId;
+        mAtm.mVr2dDisplayId = vrDisplay.mDisplayId;
 
         final LaunchParams result = new LaunchParams();
-        final ActivityRecord vrActivity = new ActivityBuilder(mService).build();
+        final ActivityRecord vrActivity = new ActivityBuilder(mAtm).build();
         vrActivity.requestedVrComponent = vrActivity.mActivityComponent;
 
         // VR activities should always land on default display.
@@ -241,7 +241,7 @@
                 result.mPreferredTaskDisplayArea);
 
         // Otherwise, always lands on VR 2D display.
-        final ActivityRecord vr2dActivity = new ActivityBuilder(mService).build();
+        final ActivityRecord vr2dActivity = new ActivityBuilder(mAtm).build();
         mController.calculate(null /*task*/, null /*layout*/, vr2dActivity /*activity*/,
                 null /*source*/, null /*options*/, PHASE_BOUNDS, result);
         assertEquals(vrDisplay.getDefaultTaskDisplayArea(), result.mPreferredTaskDisplayArea);
@@ -249,7 +249,7 @@
                 null /*options*/, PHASE_BOUNDS, result);
         assertEquals(vrDisplay.getDefaultTaskDisplayArea(), result.mPreferredTaskDisplayArea);
 
-        mService.mVr2dDisplayId = INVALID_DISPLAY;
+        mAtm.mVr2dDisplayId = INVALID_DISPLAY;
     }
 
 
@@ -262,8 +262,8 @@
         final LaunchParamsModifier positioner = mock(LaunchParamsModifier.class);
         mController.registerModifier(positioner);
 
-        final ActivityRecord record = new ActivityBuilder(mService).build();
-        final ActivityRecord source = new ActivityBuilder(mService).build();
+        final ActivityRecord record = new ActivityBuilder(mAtm).build();
+        final ActivityRecord source = new ActivityBuilder(mAtm).build();
         final WindowLayout layout = new WindowLayout(0, 0, 0, 0, 0, 0, 0);
         final ActivityOptions options = mock(ActivityOptions.class);
 
@@ -284,7 +284,7 @@
         final TaskDisplayArea preferredTaskDisplayArea = display.getDefaultTaskDisplayArea();
         params.mPreferredTaskDisplayArea = preferredTaskDisplayArea;
         final InstrumentedPositioner positioner = new InstrumentedPositioner(RESULT_DONE, params);
-        final Task task = new TaskBuilder(mService.mStackSupervisor).build();
+        final Task task = new TaskBuilder(mAtm.mStackSupervisor).build();
 
         mController.registerModifier(positioner);
 
@@ -305,7 +305,7 @@
         final int windowingMode = WINDOWING_MODE_FREEFORM;
         params.mWindowingMode = windowingMode;
         final InstrumentedPositioner positioner = new InstrumentedPositioner(RESULT_DONE, params);
-        final Task task = new TaskBuilder(mService.mStackSupervisor).build();
+        final Task task = new TaskBuilder(mAtm.mStackSupervisor).build();
 
         mController.registerModifier(positioner);
 
@@ -330,7 +330,7 @@
         params.mWindowingMode = WINDOWING_MODE_FREEFORM;
         params.mBounds.set(expected);
         final InstrumentedPositioner positioner = new InstrumentedPositioner(RESULT_DONE, params);
-        final Task task = new TaskBuilder(mService.mStackSupervisor).build();
+        final Task task = new TaskBuilder(mAtm.mStackSupervisor).build();
 
         mController.registerModifier(positioner);
 
@@ -355,7 +355,7 @@
         params.mWindowingMode = WINDOWING_MODE_FULLSCREEN;
         params.mBounds.set(expected);
         final InstrumentedPositioner positioner = new InstrumentedPositioner(RESULT_DONE, params);
-        final Task task = new TaskBuilder(mService.mStackSupervisor).build();
+        final Task task = new TaskBuilder(mAtm.mStackSupervisor).build();
 
         mController.registerModifier(positioner);
 
diff --git a/services/tests/wmtests/src/com/android/server/wm/LaunchParamsPersisterTests.java b/services/tests/wmtests/src/com/android/server/wm/LaunchParamsPersisterTests.java
index e389a53..18a2d13 100644
--- a/services/tests/wmtests/src/com/android/server/wm/LaunchParamsPersisterTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/LaunchParamsPersisterTests.java
@@ -65,7 +65,7 @@
 @MediumTest
 @Presubmit
 @RunWith(WindowTestRunner.class)
-public class LaunchParamsPersisterTests extends ActivityTestsBase {
+public class LaunchParamsPersisterTests extends WindowTestsBase {
     private static final int TEST_USER_ID = 3;
     private static final int ALTERNATIVE_USER_ID = 0;
     private static final ComponentName TEST_COMPONENT =
@@ -109,7 +109,7 @@
         deleteRecursively(mFolder);
 
         mDisplayUniqueId = "test:" + sNextUniqueId++;
-        mTestDisplay = new TestDisplayContent.Builder(mService, 1000, 1500)
+        mTestDisplay = new TestDisplayContent.Builder(mAtm, 1000, 1500)
                 .setUniqueId(mDisplayUniqueId).build();
         when(mRootWindowContainer.getDisplayContent(eq(mDisplayUniqueId)))
                 .thenReturn(mTestDisplay);
@@ -172,7 +172,7 @@
     public void testFetchesSameResultWithActivity() {
         mTarget.saveTask(mTestTask);
 
-        final ActivityRecord activity = new ActivityBuilder(mService).setComponent(TEST_COMPONENT)
+        final ActivityRecord activity = new ActivityBuilder(mAtm).setComponent(TEST_COMPONENT)
                 .setUid(TEST_USER_ID * UserHandle.PER_USER_RANGE).build();
 
         mTarget.getLaunchParams(null, activity, mResult);
diff --git a/services/tests/wmtests/src/com/android/server/wm/LockTaskControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/LockTaskControllerTest.java
index a137cde..044f819 100644
--- a/services/tests/wmtests/src/com/android/server/wm/LockTaskControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/LockTaskControllerTest.java
@@ -168,8 +168,8 @@
 
     @Test
     public void testStartLockTaskMode_once() throws Exception {
-        // GIVEN a task record with whitelisted auth
-        Task tr = getTask(Task.LOCK_TASK_AUTH_WHITELISTED);
+        // GIVEN a task record with allowlisted auth
+        Task tr = getTask(Task.LOCK_TASK_AUTH_ALLOWLISTED);
 
         // WHEN calling setLockTaskMode for LOCKED mode without resuming
         mLockTaskController.startLockTaskMode(tr, false, TEST_UID);
@@ -185,9 +185,9 @@
 
     @Test
     public void testStartLockTaskMode_twice() throws Exception {
-        // GIVEN two task records with whitelisted auth
-        Task tr1 = getTask(Task.LOCK_TASK_AUTH_WHITELISTED);
-        Task tr2 = getTask(Task.LOCK_TASK_AUTH_WHITELISTED);
+        // GIVEN two task records with allowlisted auth
+        Task tr1 = getTask(Task.LOCK_TASK_AUTH_ALLOWLISTED);
+        Task tr2 = getTask(Task.LOCK_TASK_AUTH_ALLOWLISTED);
 
         // WHEN calling setLockTaskMode for LOCKED mode on both tasks
         mLockTaskController.startLockTaskMode(tr1, false, TEST_UID);
@@ -205,7 +205,7 @@
 
     @Test
     public void testStartLockTaskMode_pinningRequest() {
-        // GIVEN a task record that is not whitelisted, i.e. with pinned auth
+        // GIVEN a task record that is not allowlisted, i.e. with pinned auth
         Task tr = getTask(Task.LOCK_TASK_AUTH_PINNABLE);
 
         // WHEN calling startLockTaskMode
@@ -236,23 +236,23 @@
 
     @Test
     public void testLockTaskViolation() {
-        // GIVEN one task record with whitelisted auth that is in lock task mode
-        Task tr = getTask(Task.LOCK_TASK_AUTH_WHITELISTED);
+        // GIVEN one task record with allowlisted auth that is in lock task mode
+        Task tr = getTask(Task.LOCK_TASK_AUTH_ALLOWLISTED);
         mLockTaskController.startLockTaskMode(tr, false, TEST_UID);
 
         // THEN it's not a lock task violation to try and launch this task without clearing
         assertFalse(mLockTaskController.isLockTaskModeViolation(tr, false));
 
-        // THEN it's a lock task violation to launch another task that is not whitelisted
+        // THEN it's a lock task violation to launch another task that is not allowlisted
         assertTrue(mLockTaskController.isLockTaskModeViolation(getTask(
                 Task.LOCK_TASK_AUTH_PINNABLE)));
         // THEN it's a lock task violation to launch another task that is disallowed from lock task
         assertTrue(mLockTaskController.isLockTaskModeViolation(getTask(
                 Task.LOCK_TASK_AUTH_DONT_LOCK)));
 
-        // THEN it's no a lock task violation to launch another task that is whitelisted
+        // THEN it's no a lock task violation to launch another task that is allowlisted
         assertFalse(mLockTaskController.isLockTaskModeViolation(getTask(
-                Task.LOCK_TASK_AUTH_WHITELISTED)));
+                Task.LOCK_TASK_AUTH_ALLOWLISTED)));
         assertFalse(mLockTaskController.isLockTaskModeViolation(getTask(
                 Task.LOCK_TASK_AUTH_LAUNCHABLE)));
         // THEN it's not a lock task violation to launch another task that is priv launchable
@@ -262,8 +262,8 @@
 
     @Test
     public void testLockTaskViolation_emergencyCall() {
-        // GIVEN one task record with whitelisted auth that is in lock task mode
-        Task tr = getTask(Task.LOCK_TASK_AUTH_WHITELISTED);
+        // GIVEN one task record with allowlisted auth that is in lock task mode
+        Task tr = getTask(Task.LOCK_TASK_AUTH_ALLOWLISTED);
         mLockTaskController.startLockTaskMode(tr, false, TEST_UID);
 
         // GIVEN tasks necessary for emergency calling
@@ -294,8 +294,8 @@
 
     @Test
     public void testStopLockTaskMode() throws Exception {
-        // GIVEN one task record with whitelisted auth that is in lock task mode
-        Task tr = getTask(Task.LOCK_TASK_AUTH_WHITELISTED);
+        // GIVEN one task record with allowlisted auth that is in lock task mode
+        Task tr = getTask(Task.LOCK_TASK_AUTH_ALLOWLISTED);
         mLockTaskController.startLockTaskMode(tr, false, TEST_UID);
 
         // WHEN the same caller calls stopLockTaskMode
@@ -311,8 +311,8 @@
 
     @Test(expected = SecurityException.class)
     public void testStopLockTaskMode_differentCaller() {
-        // GIVEN one task record with whitelisted auth that is in lock task mode
-        Task tr = getTask(Task.LOCK_TASK_AUTH_WHITELISTED);
+        // GIVEN one task record with allowlisted auth that is in lock task mode
+        Task tr = getTask(Task.LOCK_TASK_AUTH_ALLOWLISTED);
         mLockTaskController.startLockTaskMode(tr, false, TEST_UID);
 
         // WHEN a different caller calls stopLockTaskMode
@@ -323,8 +323,8 @@
 
     @Test
     public void testStopLockTaskMode_systemCaller() {
-        // GIVEN one task record with whitelisted auth that is in lock task mode
-        Task tr = getTask(Task.LOCK_TASK_AUTH_WHITELISTED);
+        // GIVEN one task record with allowlisted auth that is in lock task mode
+        Task tr = getTask(Task.LOCK_TASK_AUTH_ALLOWLISTED);
         mLockTaskController.startLockTaskMode(tr, false, TEST_UID);
 
         // WHEN system calls stopLockTaskMode
@@ -336,9 +336,9 @@
 
     @Test
     public void testStopLockTaskMode_twoTasks() throws Exception {
-        // GIVEN two task records with whitelisted auth that is in lock task mode
-        Task tr1 = getTask(Task.LOCK_TASK_AUTH_WHITELISTED);
-        Task tr2 = getTask(Task.LOCK_TASK_AUTH_WHITELISTED);
+        // GIVEN two task records with allowlisted auth that is in lock task mode
+        Task tr1 = getTask(Task.LOCK_TASK_AUTH_ALLOWLISTED);
+        Task tr2 = getTask(Task.LOCK_TASK_AUTH_ALLOWLISTED);
         mLockTaskController.startLockTaskMode(tr1, false, TEST_UID);
         mLockTaskController.startLockTaskMode(tr2, false, TEST_UID);
 
@@ -357,9 +357,9 @@
 
     @Test
     public void testStopLockTaskMode_rootTask() throws Exception {
-        // GIVEN two task records with whitelisted auth that is in lock task mode
-        Task tr1 = getTask(Task.LOCK_TASK_AUTH_WHITELISTED);
-        Task tr2 = getTask(Task.LOCK_TASK_AUTH_WHITELISTED);
+        // GIVEN two task records with allowlisted auth that is in lock task mode
+        Task tr1 = getTask(Task.LOCK_TASK_AUTH_ALLOWLISTED);
+        Task tr2 = getTask(Task.LOCK_TASK_AUTH_ALLOWLISTED);
         mLockTaskController.startLockTaskMode(tr1, false, TEST_UID);
         mLockTaskController.startLockTaskMode(tr2, false, TEST_UID);
 
@@ -405,9 +405,9 @@
 
     @Test
     public void testClearLockedTasks() throws Exception {
-        // GIVEN two task records with whitelisted auth that is in lock task mode
-        Task tr1 = getTask(Task.LOCK_TASK_AUTH_WHITELISTED);
-        Task tr2 = getTask(Task.LOCK_TASK_AUTH_WHITELISTED);
+        // GIVEN two task records with allowlisted auth that is in lock task mode
+        Task tr1 = getTask(Task.LOCK_TASK_AUTH_ALLOWLISTED);
+        Task tr2 = getTask(Task.LOCK_TASK_AUTH_ALLOWLISTED);
         mLockTaskController.startLockTaskMode(tr1, false, TEST_UID);
         mLockTaskController.startLockTaskMode(tr2, false, TEST_UID);
 
@@ -434,7 +434,7 @@
                 .thenReturn(DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED);
 
         // AND there is a task record
-        Task tr1 = getTask(Task.LOCK_TASK_AUTH_WHITELISTED);
+        Task tr1 = getTask(Task.LOCK_TASK_AUTH_ALLOWLISTED);
         mLockTaskController.startLockTaskMode(tr1, true, TEST_UID);
 
         // WHEN calling clearLockedTasks on the root task
@@ -454,7 +454,7 @@
                 .thenReturn(true);
 
         // AND there is a task record
-        Task tr1 = getTask(Task.LOCK_TASK_AUTH_WHITELISTED);
+        Task tr1 = getTask(Task.LOCK_TASK_AUTH_ALLOWLISTED);
         mLockTaskController.startLockTaskMode(tr1, true, TEST_UID);
 
         // WHEN calling clearLockedTasks on the root task
@@ -471,7 +471,7 @@
                 Settings.Secure.LOCK_TO_APP_EXIT_LOCKED, 1, mContext.getUserId());
 
         // AND there is a task record
-        Task tr1 = getTask(Task.LOCK_TASK_AUTH_WHITELISTED);
+        Task tr1 = getTask(Task.LOCK_TASK_AUTH_ALLOWLISTED);
         mLockTaskController.startLockTaskMode(tr1, true, TEST_UID);
 
         // WHEN calling clearLockedTasks on the root task
@@ -488,7 +488,7 @@
                 Settings.Secure.LOCK_TO_APP_EXIT_LOCKED, 0, mContext.getUserId());
 
         // AND there is a task record
-        Task tr1 = getTask(Task.LOCK_TASK_AUTH_WHITELISTED);
+        Task tr1 = getTask(Task.LOCK_TASK_AUTH_ALLOWLISTED);
         mLockTaskController.startLockTaskMode(tr1, true, TEST_UID);
 
         // WHEN calling clearLockedTasks on the root task
@@ -500,45 +500,45 @@
 
     @Test
     public void testUpdateLockTaskPackages() {
-        String[] whitelist1 = {TEST_PACKAGE_NAME, TEST_PACKAGE_NAME_2};
-        String[] whitelist2 = {TEST_PACKAGE_NAME};
+        String[] allowlist1 = {TEST_PACKAGE_NAME, TEST_PACKAGE_NAME_2};
+        String[] allowlist2 = {TEST_PACKAGE_NAME};
 
-        // No package is whitelisted initially
-        for (String pkg : whitelist1) {
-            assertFalse("Package shouldn't be whitelisted: " + pkg,
-                    mLockTaskController.isPackageWhitelisted(TEST_USER_ID, pkg));
-            assertFalse("Package shouldn't be whitelisted for user 0: " + pkg,
-                    mLockTaskController.isPackageWhitelisted(0, pkg));
+        // No package is allowlisted initially
+        for (String pkg : allowlist1) {
+            assertFalse("Package shouldn't be allowlisted: " + pkg,
+                    mLockTaskController.isPackageAllowlisted(TEST_USER_ID, pkg));
+            assertFalse("Package shouldn't be allowlisted for user 0: " + pkg,
+                    mLockTaskController.isPackageAllowlisted(0, pkg));
         }
 
-        // Apply whitelist
-        mLockTaskController.updateLockTaskPackages(TEST_USER_ID, whitelist1);
+        // Apply allowlist
+        mLockTaskController.updateLockTaskPackages(TEST_USER_ID, allowlist1);
 
-        // Assert the whitelist is applied to the correct user
-        for (String pkg : whitelist1) {
-            assertTrue("Package should be whitelisted: " + pkg,
-                    mLockTaskController.isPackageWhitelisted(TEST_USER_ID, pkg));
-            assertFalse("Package shouldn't be whitelisted for user 0: " + pkg,
-                    mLockTaskController.isPackageWhitelisted(0, pkg));
+        // Assert the allowlist is applied to the correct user
+        for (String pkg : allowlist1) {
+            assertTrue("Package should be allowlisted: " + pkg,
+                    mLockTaskController.isPackageAllowlisted(TEST_USER_ID, pkg));
+            assertFalse("Package shouldn't be allowlisted for user 0: " + pkg,
+                    mLockTaskController.isPackageAllowlisted(0, pkg));
         }
 
-        // Update whitelist
-        mLockTaskController.updateLockTaskPackages(TEST_USER_ID, whitelist2);
+        // Update allowlist
+        mLockTaskController.updateLockTaskPackages(TEST_USER_ID, allowlist2);
 
-        // Assert the new whitelist is applied
-        assertTrue("Package should remain whitelisted: " + TEST_PACKAGE_NAME,
-                mLockTaskController.isPackageWhitelisted(TEST_USER_ID, TEST_PACKAGE_NAME));
-        assertFalse("Package should no longer be whitelisted: " + TEST_PACKAGE_NAME_2,
-                mLockTaskController.isPackageWhitelisted(TEST_USER_ID, TEST_PACKAGE_NAME_2));
+        // Assert the new allowlist is applied
+        assertTrue("Package should remain allowlisted: " + TEST_PACKAGE_NAME,
+                mLockTaskController.isPackageAllowlisted(TEST_USER_ID, TEST_PACKAGE_NAME));
+        assertFalse("Package should no longer be allowlisted: " + TEST_PACKAGE_NAME_2,
+                mLockTaskController.isPackageAllowlisted(TEST_USER_ID, TEST_PACKAGE_NAME_2));
     }
 
     @Test
     public void testUpdateLockTaskPackages_taskRemoved() throws Exception {
-        // GIVEN two tasks which are whitelisted initially
+        // GIVEN two tasks which are allowlisted initially
         Task tr1 = getTaskForUpdate(TEST_PACKAGE_NAME, true);
         Task tr2 = getTaskForUpdate(TEST_PACKAGE_NAME_2, false);
-        String[] whitelist = {TEST_PACKAGE_NAME, TEST_PACKAGE_NAME_2};
-        mLockTaskController.updateLockTaskPackages(TEST_USER_ID, whitelist);
+        String[] allowlist = {TEST_PACKAGE_NAME, TEST_PACKAGE_NAME_2};
+        mLockTaskController.updateLockTaskPackages(TEST_USER_ID, allowlist);
 
         // GIVEN the tasks are launched into LockTask mode
         mLockTaskController.startLockTaskMode(tr1, false, TEST_UID);
@@ -548,9 +548,9 @@
         assertTrue(mLockTaskController.isTaskLocked(tr2));
         verifyLockTaskStarted(STATUS_BAR_MASK_LOCKED, DISABLE2_MASK);
 
-        // WHEN removing one package from whitelist
-        whitelist = new String[] {TEST_PACKAGE_NAME};
-        mLockTaskController.updateLockTaskPackages(TEST_USER_ID, whitelist);
+        // WHEN removing one package from allowlist
+        allowlist = new String[] {TEST_PACKAGE_NAME};
+        mLockTaskController.updateLockTaskPackages(TEST_USER_ID, allowlist);
 
         // THEN the task running that package should be stopped
         verify(tr2).performClearTaskLocked();
@@ -560,9 +560,9 @@
         assertTrue(mLockTaskController.isTaskLocked(tr1));
         verifyLockTaskStarted(STATUS_BAR_MASK_LOCKED, DISABLE2_MASK);
 
-        // WHEN removing the last package from whitelist
-        whitelist = new String[] {};
-        mLockTaskController.updateLockTaskPackages(TEST_USER_ID, whitelist);
+        // WHEN removing the last package from allowlist
+        allowlist = new String[] {};
+        mLockTaskController.updateLockTaskPackages(TEST_USER_ID, allowlist);
 
         // THEN the last task should be cleared, and the system should quit LockTask mode
         verify(tr1).performClearTaskLocked();
@@ -574,7 +574,7 @@
     @Test
     public void testUpdateLockTaskFeatures() throws Exception {
         // GIVEN a locked task
-        Task tr = getTask(Task.LOCK_TASK_AUTH_WHITELISTED);
+        Task tr = getTask(Task.LOCK_TASK_AUTH_ALLOWLISTED);
         mLockTaskController.startLockTaskMode(tr, false, TEST_UID);
 
         // THEN lock task mode should be started with default status bar masks
@@ -616,7 +616,7 @@
     @Test
     public void testUpdateLockTaskFeatures_differentUser() throws Exception {
         // GIVEN a locked task
-        Task tr = getTask(Task.LOCK_TASK_AUTH_WHITELISTED);
+        Task tr = getTask(Task.LOCK_TASK_AUTH_ALLOWLISTED);
         mLockTaskController.startLockTaskMode(tr, false, TEST_UID);
 
         // THEN lock task mode should be started with default status bar masks
@@ -638,7 +638,7 @@
     @Test
     public void testUpdateLockTaskFeatures_keyguard() {
         // GIVEN a locked task
-        Task tr = getTask(Task.LOCK_TASK_AUTH_WHITELISTED);
+        Task tr = getTask(Task.LOCK_TASK_AUTH_ALLOWLISTED);
         mLockTaskController.startLockTaskMode(tr, false, TEST_UID);
 
         // THEN keyguard should be disabled
@@ -704,7 +704,7 @@
                 TEST_USER_ID, TEST_PACKAGE_NAME, LOCK_TASK_LAUNCH_MODE_DEFAULT));
 
         // Start lock task mode
-        Task tr = getTask(Task.LOCK_TASK_AUTH_WHITELISTED);
+        Task tr = getTask(Task.LOCK_TASK_AUTH_ALLOWLISTED);
         mLockTaskController.startLockTaskMode(tr, false, TEST_UID);
 
         // WHEN LOCK_TASK_FEATURE_BLOCK_ACTIVITY_START_IN_TASK is not enabled
@@ -719,15 +719,15 @@
         assertTrue(mLockTaskController.isActivityAllowed(
                 TEST_USER_ID, TEST_PACKAGE_NAME, LOCK_TASK_LAUNCH_MODE_ALWAYS));
 
-        // unwhitelisted package should not be allowed
+        // unallowlisted package should not be allowed
         assertFalse(mLockTaskController.isActivityAllowed(
                 TEST_USER_ID, TEST_PACKAGE_NAME, LOCK_TASK_LAUNCH_MODE_DEFAULT));
 
-        // update the whitelist
-        String[] whitelist = new String[] { TEST_PACKAGE_NAME };
-        mLockTaskController.updateLockTaskPackages(TEST_USER_ID, whitelist);
+        // update the allowlist
+        String[] allowlist = new String[] { TEST_PACKAGE_NAME };
+        mLockTaskController.updateLockTaskPackages(TEST_USER_ID, allowlist);
 
-        // whitelisted package should be allowed
+        // allowlisted package should be allowed
         assertTrue(mLockTaskController.isActivityAllowed(
                 TEST_USER_ID, TEST_PACKAGE_NAME, LOCK_TASK_LAUNCH_MODE_DEFAULT));
 
@@ -755,17 +755,17 @@
     }
 
     /**
-     * @param isAppAware {@code true} if the app has marked if_whitelisted in its manifest
+     * @param isAppAware {@code true} if the app has marked if allowlisted in its manifest
      */
     private Task getTaskForUpdate(String pkg, boolean isAppAware) {
-        final int authIfWhitelisted = isAppAware
+        final int authIfAllowlisted = isAppAware
                 ? Task.LOCK_TASK_AUTH_LAUNCHABLE
-                : Task.LOCK_TASK_AUTH_WHITELISTED;
-        Task tr = getTask(pkg, authIfWhitelisted);
+                : Task.LOCK_TASK_AUTH_ALLOWLISTED;
+        Task tr = getTask(pkg, authIfAllowlisted);
         doAnswer((invocation) -> {
-            boolean isWhitelisted =
-                    mLockTaskController.isPackageWhitelisted(TEST_USER_ID, pkg);
-            tr.mLockTaskAuth = isWhitelisted ? authIfWhitelisted : Task.LOCK_TASK_AUTH_PINNABLE;
+            boolean isAllowlisted =
+                    mLockTaskController.isPackageAllowlisted(TEST_USER_ID, pkg);
+            tr.mLockTaskAuth = isAllowlisted ? authIfAllowlisted : Task.LOCK_TASK_AUTH_PINNABLE;
             return null;
         }).when(tr).setLockTaskAuth();
         return tr;
diff --git a/services/tests/wmtests/src/com/android/server/wm/ProtoLogIntegrationTest.java b/services/tests/wmtests/src/com/android/server/wm/ProtoLogIntegrationTest.java
index d1510cf..578a43c 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ProtoLogIntegrationTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ProtoLogIntegrationTest.java
@@ -25,9 +25,12 @@
 
 import androidx.test.filters.SmallTest;
 
-import com.android.server.protolog.ProtoLogImpl;
+import com.android.internal.protolog.ProtoLogGroup;
+import com.android.internal.protolog.ProtoLogImpl;
+import com.android.internal.protolog.common.ProtoLog;
 
 import org.junit.After;
+import org.junit.Ignore;
 import org.junit.Test;
 
 /**
@@ -41,20 +44,25 @@
         ProtoLogImpl.setSingleInstance(null);
     }
 
+    @Ignore("b/163095037")
     @Test
     public void testProtoLogToolIntegration() {
         ProtoLogImpl mockedProtoLog = mock(ProtoLogImpl.class);
-        runWith(mockedProtoLog, () -> {
-            ProtoLogGroup.testProtoLog();
-        });
+        runWith(mockedProtoLog, this::testProtoLog);
         verify(mockedProtoLog).log(eq(ProtoLogImpl.LogLevel.ERROR), eq(ProtoLogGroup.TEST_GROUP),
                 anyInt(), eq(0b0010101001010111),
-                eq(ProtoLogGroup.TEST_GROUP.isLogToLogcat()
+                eq(com.android.internal.protolog.ProtoLogGroup.TEST_GROUP.isLogToLogcat()
                         ? "Test completed successfully: %b %d %o %x %e %g %f %% %s"
                         : null),
                 eq(new Object[]{true, 1L, 2L, 3L, 0.4, 0.5, 0.6, "ok"}));
     }
 
+    private void testProtoLog() {
+        ProtoLog.e(ProtoLogGroup.TEST_GROUP,
+                "Test completed successfully: %b %d %o %x %e %g %f %% %s.",
+                true, 1, 2, 3, 0.4, 0.5, 0.6, "ok");
+    }
+
     /**
      * Starts protolog for the duration of {@code runnable}, with a ProtoLogImpl instance installed.
      */
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 1724303..54c7f27 100644
--- a/services/tests/wmtests/src/com/android/server/wm/RecentTasksTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/RecentTasksTest.java
@@ -94,7 +94,7 @@
 @MediumTest
 @Presubmit
 @RunWith(WindowTestRunner.class)
-public class RecentTasksTest extends ActivityTestsBase {
+public class RecentTasksTest extends WindowTestsBase {
     private static final int TEST_USER_0_ID = 0;
     private static final int TEST_USER_1_ID = 10;
     private static final int TEST_QUIET_USER_ID = 20;
@@ -122,14 +122,14 @@
         mTaskContainer = mRootWindowContainer.getDefaultTaskDisplayArea();
 
         // Set the recent tasks we should use for testing in this class.
-        mRecentTasks = new TestRecentTasks(mService, mTaskPersister);
+        mRecentTasks = new TestRecentTasks(mAtm, mTaskPersister);
         spyOn(mRecentTasks);
-        mService.setRecentTasks(mRecentTasks);
+        mAtm.setRecentTasks(mRecentTasks);
         mRecentTasks.loadParametersFromResources(mContext.getResources());
 
         // Set the running tasks we should use for testing in this class.
         mRunningTasks = new TestRunningTasks();
-        mService.mStackSupervisor.setRunningTasks(mRunningTasks);
+        mAtm.mStackSupervisor.setRunningTasks(mRunningTasks);
 
         mStack = mTaskContainer.createStack(
                 WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, true /* onTop */);
@@ -455,7 +455,7 @@
         final Function<Boolean, Task> taskBuilder = visible -> {
             final Task task = createTaskBuilder(className).build();
             // Make the task non-empty.
-            final ActivityRecord r = new ActivityBuilder(mService).setTask(task).build();
+            final ActivityRecord r = new ActivityBuilder(mAtm).setTask(task).build();
             r.setVisibility(visible);
             return task;
         };
@@ -831,7 +831,7 @@
 
         Task stack = mTasks.get(2).getRootTask();
         stack.moveToFront("", mTasks.get(2));
-        doReturn(stack).when(mService.mRootWindowContainer).getTopDisplayFocusedStack();
+        doReturn(stack).when(mAtm.mRootWindowContainer).getTopDisplayFocusedStack();
 
         // Simulate the reset from the timeout
         mRecentTasks.resetFreezeTaskListReorderingOnTimeout();
@@ -994,16 +994,16 @@
         mStack.removeIfPossible();
 
         // The following APIs should not restore task from recents to the active list.
-        assertNotRestoreTask(() -> mService.setFocusedTask(taskId));
-        assertNotRestoreTask(() -> mService.startSystemLockTaskMode(taskId));
-        assertNotRestoreTask(() -> mService.cancelTaskWindowTransition(taskId));
+        assertNotRestoreTask(() -> mAtm.setFocusedTask(taskId));
+        assertNotRestoreTask(() -> mAtm.startSystemLockTaskMode(taskId));
+        assertNotRestoreTask(() -> mAtm.cancelTaskWindowTransition(taskId));
         assertNotRestoreTask(
-                () -> mService.resizeTask(taskId, null /* bounds */, 0 /* resizeMode */));
+                () -> mAtm.resizeTask(taskId, null /* bounds */, 0 /* resizeMode */));
         assertNotRestoreTask(
-                () -> mService.setTaskWindowingMode(taskId, WINDOWING_MODE_FULLSCREEN,
+                () -> mAtm.setTaskWindowingMode(taskId, WINDOWING_MODE_FULLSCREEN,
                         false/* toTop */));
         assertNotRestoreTask(
-                () -> mService.setTaskWindowingModeSplitScreenPrimary(taskId, false /* toTop */));
+                () -> mAtm.setTaskWindowingModeSplitScreenPrimary(taskId, false /* toTop */));
     }
 
     @Test
@@ -1014,7 +1014,7 @@
         mRecentTasks.remove(task);
 
         TaskChangeNotificationController controller =
-                mService.getTaskChangeNotificationController();
+                mAtm.getTaskChangeNotificationController();
         verify(controller, times(2)).notifyTaskListUpdated();
     }
 
@@ -1027,7 +1027,7 @@
 
         // 2 calls - Once for add and once for remove
         TaskChangeNotificationController controller =
-                mService.getTaskChangeNotificationController();
+                mAtm.getTaskChangeNotificationController();
         verify(controller, times(2)).notifyTaskListUpdated();
     }
 
@@ -1042,7 +1042,7 @@
 
         // 4 calls - Twice for add and twice for remove
         TaskChangeNotificationController controller =
-                mService.getTaskChangeNotificationController();
+                mAtm.getTaskChangeNotificationController();
         verify(controller, times(4)).notifyTaskListUpdated();
     }
 
@@ -1054,7 +1054,7 @@
         final Bundle data = new Bundle();
         data.putInt("key", 100);
         final Task task1 = createTaskBuilder(".Task").build();
-        final ActivityRecord r1 = new ActivityBuilder(mService)
+        final ActivityRecord r1 = new ActivityBuilder(mAtm)
                 .setTask(task1)
                 .setIntentExtras(data)
                 .build();
@@ -1106,7 +1106,7 @@
 
     @Test
     public void testNotRecentsComponent_denyApiAccess() throws Exception {
-        doReturn(PackageManager.PERMISSION_DENIED).when(mService)
+        doReturn(PackageManager.PERMISSION_DENIED).when(mAtm)
                 .checkGetTasksPermission(anyString(), anyInt(), anyInt());
         // Expect the following methods to fail due to recents component not being set
         mRecentTasks.setIsCallerRecentsOverride(TestRecentTasks.DENY_THROW_SECURITY_EXCEPTION);
@@ -1118,7 +1118,7 @@
 
     @Test
     public void testRecentsComponent_allowApiAccessWithoutPermissions() {
-        doReturn(PackageManager.PERMISSION_DENIED).when(mService)
+        doReturn(PackageManager.PERMISSION_DENIED).when(mAtm)
                 .checkGetTasksPermission(anyString(), anyInt(), anyInt());
         // Set the recents component and ensure that the following calls do not fail
         mRecentTasks.setIsCallerRecentsOverride(TestRecentTasks.GRANT);
@@ -1127,50 +1127,50 @@
     }
 
     private void doTestRecentTasksApis(boolean expectCallable) {
-        assertSecurityException(expectCallable, () -> mService.removeStack(INVALID_STACK_ID));
+        assertSecurityException(expectCallable, () -> mAtm.removeStack(INVALID_STACK_ID));
         assertSecurityException(expectCallable,
-                () -> mService.removeStacksInWindowingModes(
+                () -> mAtm.removeStacksInWindowingModes(
                         new int[]{WINDOWING_MODE_UNDEFINED}));
         assertSecurityException(expectCallable,
-                () -> mService.removeStacksWithActivityTypes(
+                () -> mAtm.removeStacksWithActivityTypes(
                         new int[]{ACTIVITY_TYPE_UNDEFINED}));
-        assertSecurityException(expectCallable, () -> mService.removeTask(0));
+        assertSecurityException(expectCallable, () -> mAtm.removeTask(0));
         assertSecurityException(expectCallable,
-                () -> mService.setTaskWindowingMode(0, WINDOWING_MODE_UNDEFINED, true));
+                () -> mAtm.setTaskWindowingMode(0, WINDOWING_MODE_UNDEFINED, true));
         assertSecurityException(expectCallable,
-                () -> mService.moveTaskToStack(0, INVALID_STACK_ID, true));
+                () -> mAtm.moveTaskToStack(0, INVALID_STACK_ID, true));
         assertSecurityException(expectCallable,
-                () -> mService.setTaskWindowingModeSplitScreenPrimary(0, true));
+                () -> mAtm.setTaskWindowingModeSplitScreenPrimary(0, true));
         assertSecurityException(expectCallable,
-                () -> mService.moveTopActivityToPinnedStack(INVALID_STACK_ID, new Rect()));
-        assertSecurityException(expectCallable, () -> mService.getAllStackInfos());
+                () -> mAtm.moveTopActivityToPinnedStack(INVALID_STACK_ID, new Rect()));
+        assertSecurityException(expectCallable, () -> mAtm.getAllStackInfos());
         assertSecurityException(expectCallable,
-                () -> mService.getStackInfo(WINDOWING_MODE_UNDEFINED, ACTIVITY_TYPE_UNDEFINED));
+                () -> mAtm.getStackInfo(WINDOWING_MODE_UNDEFINED, ACTIVITY_TYPE_UNDEFINED));
         assertSecurityException(expectCallable, () -> {
             try {
-                mService.getFocusedStackInfo();
+                mAtm.getFocusedStackInfo();
             } catch (RemoteException e) {
                 // Ignore
             }
         });
         assertSecurityException(expectCallable,
-                () -> mService.startActivityFromRecents(0, new Bundle()));
-        assertSecurityException(expectCallable, () -> mService.getTaskSnapshot(0, true));
-        assertSecurityException(expectCallable, () -> mService.registerTaskStackListener(null));
+                () -> mAtm.startActivityFromRecents(0, new Bundle()));
+        assertSecurityException(expectCallable, () -> mAtm.getTaskSnapshot(0, true));
+        assertSecurityException(expectCallable, () -> mAtm.registerTaskStackListener(null));
         assertSecurityException(expectCallable,
-                () -> mService.unregisterTaskStackListener(null));
-        assertSecurityException(expectCallable, () -> mService.getTaskDescription(0));
-        assertSecurityException(expectCallable, () -> mService.cancelTaskWindowTransition(0));
-        assertSecurityException(expectCallable, () -> mService.startRecentsActivity(null, null,
+                () -> mAtm.unregisterTaskStackListener(null));
+        assertSecurityException(expectCallable, () -> mAtm.getTaskDescription(0));
+        assertSecurityException(expectCallable, () -> mAtm.cancelTaskWindowTransition(0));
+        assertSecurityException(expectCallable, () -> mAtm.startRecentsActivity(null, null,
                 null));
-        assertSecurityException(expectCallable, () -> mService.cancelRecentsAnimation(true));
-        assertSecurityException(expectCallable, () -> mService.stopAppSwitches());
-        assertSecurityException(expectCallable, () -> mService.resumeAppSwitches());
+        assertSecurityException(expectCallable, () -> mAtm.cancelRecentsAnimation(true));
+        assertSecurityException(expectCallable, () -> mAtm.stopAppSwitches());
+        assertSecurityException(expectCallable, () -> mAtm.resumeAppSwitches());
     }
 
     private void testGetTasksApis(boolean expectCallable) {
-        mService.getRecentTasks(MAX_VALUE, 0, TEST_USER_0_ID);
-        mService.getTasks(MAX_VALUE);
+        mAtm.getRecentTasks(MAX_VALUE, 0, TEST_USER_0_ID);
+        mAtm.getTasks(MAX_VALUE);
         if (expectCallable) {
             assertTrue(mRecentTasks.mLastAllowed);
             assertTrue(mRunningTasks.mLastAllowed);
@@ -1185,7 +1185,7 @@
     }
 
     private TaskBuilder createTaskBuilder(String packageName, String className) {
-        return new TaskBuilder(mService.mStackSupervisor)
+        return new TaskBuilder(mAtm.mStackSupervisor)
                 .setComponent(new ComponentName(packageName, className))
                 .setStack(mStack)
                 .setUserId(TEST_USER_0_ID);
diff --git a/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationControllerTest.java
index 8bc8c0b..7fb7d40 100644
--- a/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationControllerTest.java
@@ -477,7 +477,7 @@
     }
 
     private ActivityRecord createHomeActivity() {
-        final ActivityRecord homeActivity = new ActivityTestsBase.ActivityBuilder(mWm.mAtmService)
+        final ActivityRecord homeActivity = new ActivityBuilder(mWm.mAtmService)
                 .setStack(mRootHomeTask)
                 .setCreateTask(true)
                 .build();
diff --git a/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationTest.java b/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationTest.java
index e5d1e46..d821d38 100644
--- a/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationTest.java
@@ -66,7 +66,7 @@
 @MediumTest
 @Presubmit
 @RunWith(WindowTestRunner.class)
-public class RecentsAnimationTest extends ActivityTestsBase {
+public class RecentsAnimationTest extends WindowTestsBase {
 
     private static final int TEST_USER_ID = 100;
 
@@ -77,11 +77,11 @@
     @Before
     public void setUp() throws Exception {
         mRecentsAnimationController = mock(RecentsAnimationController.class);
-        mService.mWindowManager.setRecentsAnimationController(mRecentsAnimationController);
-        doNothing().when(mService.mWindowManager).initializeRecentsAnimation(
+        mAtm.mWindowManager.setRecentsAnimationController(mRecentsAnimationController);
+        doNothing().when(mAtm.mWindowManager).initializeRecentsAnimation(
                 anyInt(), any(), any(), anyInt(), any(), any());
 
-        final RecentTasks recentTasks = mService.getRecentTasks();
+        final RecentTasks recentTasks = mAtm.getRecentTasks();
         spyOn(recentTasks);
         doReturn(mRecentsComponent).when(recentTasks).getRecentsComponent();
     }
@@ -91,12 +91,12 @@
         TaskDisplayArea taskDisplayArea = mRootWindowContainer.getDefaultTaskDisplayArea();
         Task recentsStack = taskDisplayArea.createStack(WINDOWING_MODE_FULLSCREEN,
                 ACTIVITY_TYPE_RECENTS, true /* onTop */);
-        ActivityRecord recentActivity = new ActivityBuilder(mService)
+        ActivityRecord recentActivity = new ActivityBuilder(mAtm)
                 .setComponent(mRecentsComponent)
                 .setCreateTask(true)
                 .setStack(recentsStack)
                 .build();
-        ActivityRecord topActivity = new ActivityBuilder(mService).setCreateTask(true).build();
+        ActivityRecord topActivity = new ActivityBuilder(mAtm).setCreateTask(true).build();
         topActivity.getRootTask().moveToFront("testRecentsActivityVisiblility");
 
         doCallRealMethod().when(mRootWindowContainer).ensureActivitiesVisible(
@@ -123,7 +123,7 @@
                 false /* includingParents */);
         ActivityRecord topRunningHomeActivity = homeStack.topRunningActivity();
         if (topRunningHomeActivity == null) {
-            topRunningHomeActivity = new ActivityBuilder(mService)
+            topRunningHomeActivity = new ActivityBuilder(mAtm)
                     .setStack(homeStack)
                     .setCreateTask(true)
                     .build();
@@ -139,15 +139,15 @@
                 anyInt() /* startFlags */, any() /* profilerInfo */);
 
         // Assume its process is alive because the caller should be the recents service.
-        WindowProcessController wpc = new WindowProcessController(mService, aInfo.applicationInfo,
+        WindowProcessController wpc = new WindowProcessController(mAtm, aInfo.applicationInfo,
                 aInfo.processName, aInfo.applicationInfo.uid, 0 /* userId */,
                 mock(Object.class) /* owner */, mock(WindowProcessListener.class));
         wpc.setThread(mock(IApplicationThread.class));
-        doReturn(wpc).when(mService).getProcessController(eq(wpc.mName), eq(wpc.mUid));
+        doReturn(wpc).when(mAtm).getProcessController(eq(wpc.mName), eq(wpc.mUid));
 
         Intent recentsIntent = new Intent().setComponent(mRecentsComponent);
         // Null animation indicates to preload.
-        mService.startRecentsActivity(recentsIntent, null /* assistDataReceiver */,
+        mAtm.startRecentsActivity(recentsIntent, null /* assistDataReceiver */,
                 null /* recentsAnimationRunner */);
 
         Task recentsStack = defaultTaskDisplayArea.getStack(WINDOWING_MODE_FULLSCREEN,
@@ -167,7 +167,7 @@
 
         spyOn(recentsActivity);
         // Start when the recents activity exists. It should ensure the configuration.
-        mService.startRecentsActivity(recentsIntent, null /* assistDataReceiver */,
+        mAtm.startRecentsActivity(recentsIntent, null /* assistDataReceiver */,
                 null /* recentsAnimationRunner */);
 
         verify(recentsActivity).ensureActivityConfiguration(anyInt() /* globalChanges */,
@@ -181,20 +181,20 @@
         TaskDisplayArea defaultTaskDisplayArea = mRootWindowContainer.getDefaultTaskDisplayArea();
         Task recentsStack = defaultTaskDisplayArea.createStack(WINDOWING_MODE_FULLSCREEN,
                 ACTIVITY_TYPE_RECENTS, true /* onTop */);
-        ActivityRecord recentActivity = new ActivityBuilder(mService).setComponent(
+        ActivityRecord recentActivity = new ActivityBuilder(mAtm).setComponent(
                 mRecentsComponent).setCreateTask(true).setStack(recentsStack).build();
         WindowProcessController app = recentActivity.app;
         recentActivity.app = null;
 
         // Start an activity on top.
-        new ActivityBuilder(mService).setCreateTask(true).build().getRootTask().moveToFront(
+        new ActivityBuilder(mAtm).setCreateTask(true).build().getRootTask().moveToFront(
                 "testRestartRecentsActivity");
 
         doCallRealMethod().when(mRootWindowContainer).ensureActivitiesVisible(
                 any() /* starting */, anyInt() /* configChanges */,
                 anyBoolean() /* preserveWindows */, anyBoolean() /* notifyClients */);
-        doReturn(app).when(mService).getProcessController(eq(recentActivity.processName), anyInt());
-        ClientLifecycleManager lifecycleManager = mService.getLifecycleManager();
+        doReturn(app).when(mAtm).getProcessController(eq(recentActivity.processName), anyInt());
+        ClientLifecycleManager lifecycleManager = mAtm.getLifecycleManager();
         doNothing().when(lifecycleManager).scheduleTransaction(any());
 
         startRecentsActivity();
@@ -212,20 +212,20 @@
         // Assume the home activity support recents.
         ActivityRecord targetActivity = homeStack.getTopNonFinishingActivity();
         if (targetActivity == null) {
-            targetActivity = new ActivityBuilder(mService)
+            targetActivity = new ActivityBuilder(mAtm)
                     .setCreateTask(true)
                     .setStack(homeStack)
                     .build();
         }
 
         // Put another home activity in home stack.
-        ActivityRecord anotherHomeActivity = new ActivityBuilder(mService)
+        ActivityRecord anotherHomeActivity = new ActivityBuilder(mAtm)
                 .setComponent(new ComponentName(mContext.getPackageName(), "Home2"))
                 .setCreateTask(true)
                 .setStack(homeStack)
                 .build();
         // Start an activity on top so the recents activity can be started.
-        new ActivityBuilder(mService)
+        new ActivityBuilder(mAtm)
                 .setCreateTask(true)
                 .build()
                 .getRootTask()
@@ -252,21 +252,21 @@
         TaskDisplayArea taskDisplayArea = mRootWindowContainer.getDefaultTaskDisplayArea();
         Task fullscreenStack = taskDisplayArea.createStack(WINDOWING_MODE_FULLSCREEN,
                 ACTIVITY_TYPE_STANDARD, true /* onTop */);
-        new ActivityBuilder(mService)
+        new ActivityBuilder(mAtm)
                 .setComponent(new ComponentName(mContext.getPackageName(), "App1"))
                 .setCreateTask(true)
                 .setStack(fullscreenStack)
                 .build();
         Task recentsStack = taskDisplayArea.createStack(WINDOWING_MODE_FULLSCREEN,
                 ACTIVITY_TYPE_RECENTS, true /* onTop */);
-        new ActivityBuilder(mService)
+        new ActivityBuilder(mAtm)
                 .setComponent(mRecentsComponent)
                 .setCreateTask(true)
                 .setStack(recentsStack)
                 .build();
         Task fullscreenStack2 = taskDisplayArea.createStack(WINDOWING_MODE_FULLSCREEN,
                 ACTIVITY_TYPE_STANDARD, true /* onTop */);
-        new ActivityBuilder(mService)
+        new ActivityBuilder(mAtm)
                 .setComponent(new ComponentName(mContext.getPackageName(), "App2"))
                 .setCreateTask(true)
                 .setStack(fullscreenStack2)
@@ -293,21 +293,21 @@
         TaskDisplayArea taskDisplayArea = mRootWindowContainer.getDefaultTaskDisplayArea();
         Task fullscreenStack = taskDisplayArea.createStack(WINDOWING_MODE_FULLSCREEN,
                 ACTIVITY_TYPE_STANDARD, true /* onTop */);
-        new ActivityBuilder(mService)
+        new ActivityBuilder(mAtm)
                 .setComponent(new ComponentName(mContext.getPackageName(), "App1"))
                 .setCreateTask(true)
                 .setStack(fullscreenStack)
                 .build();
         Task recentsStack = taskDisplayArea.createStack(WINDOWING_MODE_FULLSCREEN,
                 ACTIVITY_TYPE_RECENTS, true /* onTop */);
-        new ActivityBuilder(mService)
+        new ActivityBuilder(mAtm)
                 .setComponent(mRecentsComponent)
                 .setCreateTask(true)
                 .setStack(recentsStack)
                 .build();
         Task fullscreenStack2 = taskDisplayArea.createStack(WINDOWING_MODE_FULLSCREEN,
                 ACTIVITY_TYPE_STANDARD, true /* onTop */);
-        new ActivityBuilder(mService)
+        new ActivityBuilder(mAtm)
                 .setComponent(new ComponentName(mContext.getPackageName(), "App2"))
                 .setCreateTask(true)
                 .setStack(fullscreenStack2)
@@ -319,7 +319,7 @@
         fullscreenStack.removeIfPossible();
 
         // Ensure that the recents animation was NOT canceled
-        verify(mService.mWindowManager, times(0)).cancelRecentsAnimation(
+        verify(mAtm.mWindowManager, times(0)).cancelRecentsAnimation(
                 eq(REORDER_KEEP_IN_PLACE), any());
         verify(mRecentsAnimationController, times(0)).setCancelOnNextTransitionStart();
     }
@@ -330,7 +330,7 @@
                 .getDefaultTaskDisplayArea();
         Task homeStack = taskDisplayArea.getStack(WINDOWING_MODE_UNDEFINED,
                 ACTIVITY_TYPE_HOME);
-        ActivityRecord otherUserHomeActivity = new ActivityBuilder(mService)
+        ActivityRecord otherUserHomeActivity = new ActivityBuilder(mAtm)
                 .setStack(homeStack)
                 .setCreateTask(true)
                 .setComponent(new ComponentName(mContext.getPackageName(), "Home2"))
@@ -339,13 +339,13 @@
 
         Task fullscreenStack = taskDisplayArea.createStack(WINDOWING_MODE_FULLSCREEN,
                 ACTIVITY_TYPE_STANDARD, true /* onTop */);
-        new ActivityBuilder(mService)
+        new ActivityBuilder(mAtm)
                 .setComponent(new ComponentName(mContext.getPackageName(), "App1"))
                 .setCreateTask(true)
                 .setStack(fullscreenStack)
                 .build();
 
-        doReturn(TEST_USER_ID).when(mService).getCurrentUserId();
+        doReturn(TEST_USER_ID).when(mAtm).getCurrentUserId();
         doCallRealMethod().when(mRootWindowContainer).ensureActivitiesVisible(
                 any() /* starting */, anyInt() /* configChanges */,
                 anyBoolean() /* preserveWindows */, anyBoolean() /* notifyClients */);
@@ -373,7 +373,7 @@
                 // The callback is actually RecentsAnimation.
                 recentsAnimation[0] = invocation.getArgument(2);
                 return null;
-            }).when(mService.mWindowManager).initializeRecentsAnimation(
+            }).when(mAtm.mWindowManager).initializeRecentsAnimation(
                     anyInt() /* targetActivityType */, any() /* recentsAnimationRunner */,
                     any() /* callbacks */, anyInt() /* displayId */, any() /* recentTaskIds */,
                     any() /* targetActivity */);
@@ -381,7 +381,7 @@
 
         Intent recentsIntent = new Intent();
         recentsIntent.setComponent(recentsComponent);
-        mService.startRecentsActivity(recentsIntent, null /* assistDataReceiver */,
+        mAtm.startRecentsActivity(recentsIntent, null /* assistDataReceiver */,
                 mock(IRecentsAnimationRunner.class));
         return recentsAnimation[0];
     }
diff --git a/services/tests/wmtests/src/com/android/server/wm/RootActivityContainerTests.java b/services/tests/wmtests/src/com/android/server/wm/RootActivityContainerTests.java
index 74be2c9..b89d168 100644
--- a/services/tests/wmtests/src/com/android/server/wm/RootActivityContainerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/RootActivityContainerTests.java
@@ -89,14 +89,14 @@
 @MediumTest
 @Presubmit
 @RunWith(WindowTestRunner.class)
-public class RootActivityContainerTests extends ActivityTestsBase {
+public class RootActivityContainerTests extends WindowTestsBase {
     private Task mFullscreenStack;
 
     @Before
     public void setUp() throws Exception {
         mFullscreenStack = mRootWindowContainer.getDefaultTaskDisplayArea().createStack(
                 WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, true /* onTop */);
-        doNothing().when(mService).updateSleepIfNeededLocked();
+        doNothing().when(mAtm).updateSleepIfNeededLocked();
     }
 
     /**
@@ -117,11 +117,11 @@
      */
     @Test
     public void testReplacingTaskInPinnedStack() {
-        final ActivityRecord firstActivity = new ActivityBuilder(mService).setCreateTask(true)
+        final ActivityRecord firstActivity = new ActivityBuilder(mAtm).setCreateTask(true)
                 .setStack(mFullscreenStack).build();
         final Task task = firstActivity.getTask();
 
-        final ActivityRecord secondActivity = new ActivityBuilder(mService).setTask(task)
+        final ActivityRecord secondActivity = new ActivityBuilder(mAtm).setTask(task)
                 .setStack(mFullscreenStack).build();
 
         mFullscreenStack.moveToFront("testReplacingTaskInPinnedStack");
@@ -152,11 +152,11 @@
 
     @Test
     public void testMovingBottomMostStackActivityToPinnedStack() {
-        final ActivityRecord firstActivity = new ActivityBuilder(mService).setCreateTask(true)
+        final ActivityRecord firstActivity = new ActivityBuilder(mAtm).setCreateTask(true)
                 .setStack(mFullscreenStack).build();
         final Task task = firstActivity.getTask();
 
-        final ActivityRecord secondActivity = new ActivityBuilder(mService).setTask(task)
+        final ActivityRecord secondActivity = new ActivityBuilder(mAtm).setTask(task)
                 .setStack(mFullscreenStack).build();
 
         mFullscreenStack.moveTaskToBack(task);
@@ -252,7 +252,7 @@
     @Test
     public void testAwakeFromSleepingWithAppConfiguration() {
         final DisplayContent display = mRootWindowContainer.getDefaultDisplay();
-        final ActivityRecord activity = new ActivityBuilder(mService).setCreateTask(true).build();
+        final ActivityRecord activity = new ActivityBuilder(mAtm).setCreateTask(true).build();
         activity.moveFocusableActivityToTop("test");
         assertTrue(activity.getStack().isFocusedStackOnDisplay());
         ActivityRecordTests.setRotatedScreenOrientationSilently(activity);
@@ -264,13 +264,13 @@
         // Assume the activity was shown in different orientation. For example, the top activity is
         // landscape and the portrait lockscreen is shown.
         activity.setLastReportedConfiguration(
-                new MergedConfiguration(mService.getGlobalConfiguration(), rotatedConfig));
+                new MergedConfiguration(mAtm.getGlobalConfiguration(), rotatedConfig));
         activity.setState(ActivityState.STOPPED, "sleep");
 
         display.setIsSleeping(true);
         doReturn(false).when(display).shouldSleep();
         // Allow to resume when awaking.
-        setBooted(mService);
+        setBooted(mAtm);
         mRootWindowContainer.applySleepTokens(true);
 
         // The display orientation should be changed by the activity so there is no relaunch.
@@ -288,7 +288,7 @@
         final int originalStackCount = defaultTaskDisplayArea.getStackCount();
         final Task stack = defaultTaskDisplayArea.createStack(
                 WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, false /* onTop */);
-        final ActivityRecord firstActivity = new ActivityBuilder(mService).setCreateTask(true)
+        final ActivityRecord firstActivity = new ActivityBuilder(mAtm).setCreateTask(true)
                 .setStack(stack).build();
 
         assertEquals(originalStackCount + 1, defaultTaskDisplayArea.getStackCount());
@@ -312,16 +312,16 @@
         final int originalStackCount = defaultTaskDisplayArea.getStackCount();
         final Task stack = defaultTaskDisplayArea.createStack(
                 WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, false /* onTop */);
-        final ActivityRecord firstActivity = new ActivityBuilder(mService).setCreateTask(true)
+        final ActivityRecord firstActivity = new ActivityBuilder(mAtm).setCreateTask(true)
                 .setStack(stack).build();
         assertEquals(originalStackCount + 1, defaultTaskDisplayArea.getStackCount());
 
         final DisplayContent dc = defaultTaskDisplayArea.getDisplayContent();
-        final TaskDisplayArea secondTaskDisplayArea = WindowTestsBase.createTaskDisplayArea(dc,
-                mRootWindowContainer.mWmService, "TestTaskDisplayArea", FEATURE_VENDOR_FIRST);
+        final TaskDisplayArea secondTaskDisplayArea = WindowTestsBase.createTaskDisplayArea(
+                dc, mRootWindowContainer.mWmService, "TestTaskDisplayArea", FEATURE_VENDOR_FIRST);
         final Task secondStack = secondTaskDisplayArea.createStack(
                 WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, false /* onTop */);
-        new ActivityBuilder(mService).setCreateTask(true).setStack(secondStack)
+        new ActivityBuilder(mAtm).setCreateTask(true).setStack(secondStack)
                 .setUseProcess(firstActivity.app).build();
         assertEquals(1, secondTaskDisplayArea.getStackCount());
 
@@ -340,7 +340,7 @@
                 .getDefaultTaskDisplayArea();
         final Task stack = defaultTaskDisplayArea.createStack(
                 WINDOWING_MODE_SPLIT_SCREEN_PRIMARY, ACTIVITY_TYPE_STANDARD, true /* onTop */);
-        final ActivityRecord activity = new ActivityBuilder(mService).setCreateTask(true)
+        final ActivityRecord activity = new ActivityBuilder(mAtm).setCreateTask(true)
                 .setStack(stack).build();
 
         // Created stacks are focusable by default.
@@ -354,7 +354,7 @@
 
         final Task pinnedStack = defaultTaskDisplayArea.createStack(
                 WINDOWING_MODE_PINNED, ACTIVITY_TYPE_STANDARD, true /* onTop */);
-        final ActivityRecord pinnedActivity = new ActivityBuilder(mService).setCreateTask(true)
+        final ActivityRecord pinnedActivity = new ActivityBuilder(mAtm).setCreateTask(true)
                 .setStack(pinnedStack).build();
 
         // We should not be focusable when in pinned mode
@@ -385,7 +385,7 @@
                 .createStack(WINDOWING_MODE_SPLIT_SCREEN_PRIMARY, ACTIVITY_TYPE_STANDARD,
                         true /* onTop */);
         final Task task = new TaskBuilder(mSupervisor).setStack(primaryStack).build();
-        final ActivityRecord r = new ActivityBuilder(mService).setTask(task).build();
+        final ActivityRecord r = new ActivityBuilder(mAtm).setTask(task).build();
 
         // Find a launch stack for the top activity in split-screen primary, while requesting
         // split-screen secondary.
@@ -439,7 +439,7 @@
         final Task stack = secondDisplay.getDefaultTaskDisplayArea()
                 .createStack(WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_RECENTS, true /* onTop */);
         final Task task = new TaskBuilder(mSupervisor).setStack(stack).build();
-        new ActivityBuilder(mService).setTask(task).build();
+        new ActivityBuilder(mAtm).setTask(task).build();
 
         final String reason = "findTaskToMoveToFront";
         mSupervisor.findTaskToMoveToFront(targetTask, 0, ActivityOptions.makeBasic(), reason,
@@ -459,7 +459,7 @@
         final Task targetStack = spy(taskDisplayArea.createStack(WINDOWING_MODE_FULLSCREEN,
                 ACTIVITY_TYPE_STANDARD, false /* onTop */));
         final Task task = new TaskBuilder(mSupervisor).setStack(targetStack).build();
-        final ActivityRecord activity = new ActivityBuilder(mService).setTask(task).build();
+        final ActivityRecord activity = new ActivityBuilder(mAtm).setTask(task).build();
         taskDisplayArea.positionChildAt(POSITION_BOTTOM, targetStack, false /*includingParents*/);
 
         // Assume the stack is not at the topmost position (e.g. behind always-on-top stacks) but it
@@ -489,7 +489,7 @@
 
         doReturn(true).when(mRootWindowContainer).resumeHomeActivity(any(), any(), any());
 
-        mService.setBooted(true);
+        mAtm.setBooted(true);
 
         // Trigger resume on all displays
         mRootWindowContainer.resumeFocusedStacksTopActivities();
@@ -515,11 +515,11 @@
         final Task stack = secondDisplay.getDefaultTaskDisplayArea()
                 .createStack(WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, true /* onTop */);
         final Task task = new TaskBuilder(mSupervisor).setStack(stack).build();
-        new ActivityBuilder(mService).setTask(task).build();
+        new ActivityBuilder(mAtm).setTask(task).build();
 
         doReturn(true).when(mRootWindowContainer).resumeHomeActivity(any(), any(), any());
 
-        mService.setBooted(true);
+        mAtm.setBooted(true);
 
         // Trigger resume on all displays
         mRootWindowContainer.resumeFocusedStacksTopActivities();
@@ -539,7 +539,7 @@
         final Task targetStack = spy(taskDisplayArea.createStack(WINDOWING_MODE_FULLSCREEN,
                 ACTIVITY_TYPE_STANDARD, false /* onTop */));
         final Task task = new TaskBuilder(mSupervisor).setStack(targetStack).build();
-        final ActivityRecord activity = new ActivityBuilder(mService).setTask(task).build();
+        final ActivityRecord activity = new ActivityBuilder(mAtm).setTask(task).build();
         activity.setState(ActivityState.RESUMED, "test");
 
         // Assume the stack is at the topmost position
@@ -559,7 +559,7 @@
         final Task targetStack = spy(taskDisplayArea.createStack(WINDOWING_MODE_FULLSCREEN,
                 ACTIVITY_TYPE_STANDARD, false /* onTop */));
         final Task task = new TaskBuilder(mSupervisor).setStack(targetStack).build();
-        final ActivityRecord activity = new ActivityBuilder(mService).setTask(task).build();
+        final ActivityRecord activity = new ActivityBuilder(mAtm).setTask(task).build();
         activity.setState(ActivityState.RESUMED, "test");
         taskDisplayArea.positionChildAt(POSITION_BOTTOM, targetStack, false /*includingParents*/);
 
@@ -584,7 +584,7 @@
 
         // Create secondary displays.
         final TestDisplayContent secondDisplay =
-                new TestDisplayContent.Builder(mService, 1000, 1500)
+                new TestDisplayContent.Builder(mAtm, 1000, 1500)
                         .setSystemDecorations(true).build();
 
         doReturn(true).when(mRootWindowContainer)
@@ -605,16 +605,16 @@
     @Test
     public void testNotStartHomeBeforeBoot() {
         final int displayId = 1;
-        final boolean isBooting = mService.mAmInternal.isBooting();
-        final boolean isBooted = mService.mAmInternal.isBooted();
+        final boolean isBooting = mAtm.mAmInternal.isBooting();
+        final boolean isBooted = mAtm.mAmInternal.isBooted();
         try {
-            mService.mAmInternal.setBooting(false);
-            mService.mAmInternal.setBooted(false);
+            mAtm.mAmInternal.setBooting(false);
+            mAtm.mAmInternal.setBooted(false);
             mRootWindowContainer.onDisplayAdded(displayId);
             verify(mRootWindowContainer, never()).startHomeOnDisplay(anyInt(), any(), anyInt());
         } finally {
-            mService.mAmInternal.setBooting(isBooting);
-            mService.mAmInternal.setBooted(isBooted);
+            mAtm.mAmInternal.setBooting(isBooting);
+            mAtm.mAmInternal.setBooted(isBooted);
         }
     }
 
@@ -626,7 +626,7 @@
         final ActivityInfo info = new ActivityInfo();
         info.applicationInfo = new ApplicationInfo();
         final WindowProcessController app = mock(WindowProcessController.class);
-        doReturn(app).when(mService).getProcessController(any(), anyInt());
+        doReturn(app).when(mAtm).getProcessController(any(), anyInt());
 
         // Can not start home if we don't want to start home while home is being instrumented.
         doReturn(true).when(app).isInstrumenting();
@@ -653,7 +653,7 @@
     public void testStartSecondaryHomeOnDisplayWithUserKeyLocked() {
         // Create secondary displays.
         final TestDisplayContent secondDisplay =
-                new TestDisplayContent.Builder(mService, 1000, 1500)
+                new TestDisplayContent.Builder(mAtm, 1000, 1500)
                         .setSystemDecorations(true).build();
 
         // Use invalid user id to let StorageManager.isUserKeyUnlocked() return false.
@@ -678,7 +678,7 @@
     public void testStartSecondaryHomeOnDisplayWithoutSysDecorations() {
         // Create secondary displays.
         final TestDisplayContent secondDisplay =
-                new TestDisplayContent.Builder(mService, 1000, 1500)
+                new TestDisplayContent.Builder(mAtm, 1000, 1500)
                         .setSystemDecorations(false).build();
 
         mRootWindowContainer.startHomeOnDisplay(0 /* userId */, "testStartSecondaryHome",
@@ -712,7 +712,7 @@
     @Test
     public void testResolveSecondaryHomeActivityWhenPrimaryHomeNotSet() {
         // Setup: primary home not set.
-        final Intent primaryHomeIntent = mService.getHomeIntent();
+        final Intent primaryHomeIntent = mAtm.getHomeIntent();
         final ActivityInfo aInfoPrimary = new ActivityInfo();
         aInfoPrimary.name = ResolverActivity.class.getName();
         doReturn(aInfoPrimary).when(mRootWindowContainer).resolveHomeActivity(anyInt(),
@@ -740,7 +740,7 @@
         // SetUp: set secondary home and force it.
         mockResolveHomeActivity(false /* primaryHome */, true /* forceSystemProvided */);
         final Intent secondaryHomeIntent =
-                mService.getSecondaryHomeIntent(null /* preferredPackage */);
+                mAtm.getSecondaryHomeIntent(null /* preferredPackage */);
         final List<ResolveInfo> resolutions = new ArrayList<>();
         final ResolveInfo resolveInfo = new ResolveInfo();
         final ActivityInfo aInfoSecondary = getFakeHomeActivityInfo(false /* primaryHome*/);
@@ -855,11 +855,11 @@
     public void testGetLaunchStackWithRealCallerId() {
         // Create a non-system owned virtual display.
         final TestDisplayContent secondaryDisplay =
-                new TestDisplayContent.Builder(mService, 1000, 1500)
+                new TestDisplayContent.Builder(mAtm, 1000, 1500)
                         .setType(TYPE_VIRTUAL).setOwnerUid(100).build();
 
         // Create an activity with specify the original launch pid / uid.
-        final ActivityRecord r = new ActivityBuilder(mService).setLaunchedFromPid(200)
+        final ActivityRecord r = new ActivityBuilder(mAtm).setLaunchedFromPid(200)
                 .setLaunchedFromUid(200).build();
 
         // Simulate ActivityStarter to find a launch stack for requesting the activity to launch
@@ -882,17 +882,16 @@
     @Test
     public void testGetValidLaunchStackOnDisplayWithCandidateRootTask() {
         // Create a root task with an activity on secondary display.
-        final TestDisplayContent secondaryDisplay = new TestDisplayContent.Builder(mService, 300,
+        final TestDisplayContent secondaryDisplay = new TestDisplayContent.Builder(mAtm, 300,
                 600).build();
-        final Task task = new ActivityTestsBase.StackBuilder(mRootWindowContainer).setDisplay(
-                secondaryDisplay).build();
-        final ActivityRecord activity = new ActivityTestsBase.ActivityBuilder(mService)
-                .setTask(task).build();
+        final Task task = new StackBuilder(mRootWindowContainer)
+                .setDisplay(secondaryDisplay).build();
+        final ActivityRecord activity = new ActivityBuilder(mAtm).setTask(task).build();
 
         // Make sure the root task is valid and can be reused on default display.
         final Task stack = mRootWindowContainer.getValidLaunchStackInTaskDisplayArea(
-                mRootWindowContainer.getDefaultTaskDisplayArea(), activity, task, null,
-                null);
+                mRootWindowContainer.getDefaultTaskDisplayArea(), activity, task,
+                null /* options */, null /* launchParams */);
         assertEquals(task, stack);
     }
 
@@ -923,7 +922,7 @@
                 DisplayContent.POSITION_TOP);
         final Task stack = secondDisplay.getDefaultTaskDisplayArea()
                 .createStack(WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, true /* onTop */);
-        final ActivityRecord activity = new ActivityBuilder(mService).setStack(stack).build();
+        final ActivityRecord activity = new ActivityBuilder(mAtm).setStack(stack).build();
         spyOn(activity);
         spyOn(stack);
 
@@ -946,7 +945,7 @@
         ActivityInfo targetActivityInfo = getFakeHomeActivityInfo(primaryHome);
         Intent targetIntent;
         if (primaryHome) {
-            targetIntent = mService.getHomeIntent();
+            targetIntent = mAtm.getHomeIntent();
         } else {
             Resources resources = mContext.getResources();
             spyOn(resources);
@@ -954,7 +953,7 @@
                     com.android.internal.R.string.config_secondaryHomePackage);
             doReturn(forceSystemProvided).when(resources).getBoolean(
                     com.android.internal.R.bool.config_useSystemProvidedLauncherForSecondary);
-            targetIntent = mService.getSecondaryHomeIntent(null /* preferredPackage */);
+            targetIntent = mAtm.getSecondaryHomeIntent(null /* preferredPackage */);
         }
         doReturn(targetActivityInfo).when(mRootWindowContainer).resolveHomeActivity(anyInt(),
                 refEq(targetIntent));
@@ -965,7 +964,7 @@
      * activity info for test cases.
      */
     private void mockResolveSecondaryHomeActivity() {
-        final Intent secondaryHomeIntent = mService
+        final Intent secondaryHomeIntent = mAtm
                 .getSecondaryHomeIntent(null /* preferredPackage */);
         final ActivityInfo aInfoSecondary = getFakeHomeActivityInfo(false);
         doReturn(Pair.create(aInfoSecondary, secondaryHomeIntent)).when(mRootWindowContainer)
diff --git a/services/tests/wmtests/src/com/android/server/wm/RootWindowContainerTests.java b/services/tests/wmtests/src/com/android/server/wm/RootWindowContainerTests.java
index 2e4c9ea..72899e7 100644
--- a/services/tests/wmtests/src/com/android/server/wm/RootWindowContainerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/RootWindowContainerTests.java
@@ -133,10 +133,10 @@
     @Test
     public void testFindActivityByTargetComponent() {
         final ComponentName aliasComponent = ComponentName.createRelative(
-                ActivityTestsBase.DEFAULT_COMPONENT_PACKAGE_NAME, ".AliasActivity");
+                DEFAULT_COMPONENT_PACKAGE_NAME, ".AliasActivity");
         final ComponentName targetComponent = ComponentName.createRelative(
                 aliasComponent.getPackageName(), ".TargetActivity");
-        final ActivityRecord activity = new ActivityTestsBase.ActivityBuilder(mWm.mAtmService)
+        final ActivityRecord activity = new ActivityBuilder(mWm.mAtmService)
                 .setComponent(aliasComponent)
                 .setTargetActivity(targetComponent.getClassName())
                 .setLaunchMode(ActivityInfo.LAUNCH_SINGLE_INSTANCE)
@@ -174,24 +174,29 @@
 
     @Test
     public void testForceStopPackage() {
-        final Task task = new ActivityTestsBase.StackBuilder(mWm.mRoot).build();
-        final ActivityRecord activity1 = task.getTopMostActivity();
-        final ActivityRecord activity2 =
-                new ActivityTestsBase.ActivityBuilder(mWm.mAtmService).setStack(task).build();
-        final WindowProcessController wpc = activity1.app;
+        final Task task = new StackBuilder(mWm.mRoot).build();
+        final ActivityRecord activity = task.getTopMostActivity();
+        final WindowProcessController wpc = activity.app;
+        final ActivityRecord[] activities = {
+                activity,
+                new ActivityBuilder(mWm.mAtmService).setStack(task).setUseProcess(wpc).build(),
+                new ActivityBuilder(mWm.mAtmService).setStack(task).setUseProcess(wpc).build()
+        };
+        activities[0].detachFromProcess();
+        activities[1].finishing = true;
+        activities[1].destroyImmediately(true /* removeFromApp */, "test");
         spyOn(wpc);
-        activity1.app = null;
-        activity2.setProcess(wpc);
         doReturn(true).when(wpc).isRemoved();
 
         mWm.mAtmService.mInternal.onForceStopPackage(wpc.mInfo.packageName, true /* doit */,
                 false /* evenPersistent */, wpc.mUserId);
         // The activity without process should be removed.
-        assertEquals(1, task.getChildCount());
+        assertEquals(2, task.getChildCount());
 
-        mWm.mRoot.handleAppDied(wpc);
-        // The activity with process should be removed because WindowProcessController#isRemoved.
+        wpc.handleAppDied();
+        // The activities with process should be removed because WindowProcessController#isRemoved.
         assertFalse(task.hasChild());
+        assertFalse(wpc.hasActivities());
     }
 }
 
diff --git a/services/tests/wmtests/src/com/android/server/wm/RunningTasksTest.java b/services/tests/wmtests/src/com/android/server/wm/RunningTasksTest.java
index e51a133..3415093 100644
--- a/services/tests/wmtests/src/com/android/server/wm/RunningTasksTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/RunningTasksTest.java
@@ -42,7 +42,7 @@
 @MediumTest
 @Presubmit
 @RunWith(WindowTestRunner.class)
-public class RunningTasksTest extends ActivityTestsBase {
+public class RunningTasksTest extends WindowTestsBase {
 
     private static final ArraySet<Integer> PROFILE_IDS = new ArraySet<>();
 
@@ -57,7 +57,7 @@
     public void testCollectTasksByLastActiveTime() {
         // Create a number of stacks with tasks (of incrementing active time)
         final ArrayList<DisplayContent> displays = new ArrayList<>();
-        final DisplayContent display = new TestDisplayContent.Builder(mService, 1000, 2500).build();
+        final DisplayContent display = new TestDisplayContent.Builder(mAtm, 1000, 2500).build();
         displays.add(display);
 
         final int numStacks = 2;
@@ -101,7 +101,7 @@
 
     @Test
     public void testTaskInfo_expectNoExtras() {
-        final DisplayContent display = new TestDisplayContent.Builder(mService, 1000, 2500).build();
+        final DisplayContent display = new TestDisplayContent.Builder(mAtm, 1000, 2500).build();
         final int numTasks = 10;
         for (int i = 0; i < numTasks; i++) {
             final Task stack = new StackBuilder(mRootWindowContainer)
@@ -132,13 +132,13 @@
      */
     private Task createTask(Task stack, String className, int taskId,
             int lastActiveTime, Bundle extras) {
-        final Task task = new TaskBuilder(mService.mStackSupervisor)
+        final Task task = new TaskBuilder(mAtm.mStackSupervisor)
                 .setComponent(new ComponentName(mContext.getPackageName(), className))
                 .setTaskId(taskId)
                 .setStack(stack)
                 .build();
         task.lastActiveTime = lastActiveTime;
-        final ActivityRecord activity = new ActivityBuilder(mService)
+        final ActivityRecord activity = new ActivityBuilder(mAtm)
                 .setTask(task)
                 .setComponent(new ComponentName(mContext.getPackageName(), ".TaskActivity"))
                 .setIntentExtras(extras)
diff --git a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
index 250cf09..982e469 100644
--- a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
@@ -64,7 +64,7 @@
 @MediumTest
 @Presubmit
 @RunWith(WindowTestRunner.class)
-public class SizeCompatTests extends ActivityTestsBase {
+public class SizeCompatTests extends WindowTestsBase {
     private Task mStack;
     private Task mTask;
     private ActivityRecord mActivity;
@@ -76,7 +76,7 @@
     }
 
     private void setUpDisplaySizeWithApp(int dw, int dh) {
-        final TestDisplayContent.Builder builder = new TestDisplayContent.Builder(mService, dw, dh);
+        final TestDisplayContent.Builder builder = new TestDisplayContent.Builder(mAtm, dw, dh);
         setUpApp(builder.build());
     }
 
@@ -92,7 +92,7 @@
         final Rect originalOverrideBounds = new Rect(mActivity.getBounds());
         resizeDisplay(mStack.getDisplay(), 600, 1200);
         // The visible activity should recompute configuration according to the last parent bounds.
-        mService.restartActivityProcessIfVisible(mActivity.appToken);
+        mAtm.restartActivityProcessIfVisible(mActivity.appToken);
 
         assertEquals(Task.ActivityState.RESTARTING_PROCESS, mActivity.getState());
         assertNotEquals(originalOverrideBounds, mActivity.getBounds());
@@ -102,7 +102,7 @@
     public void testKeepBoundsWhenChangingFromFreeformToFullscreen() {
         removeGlobalMinSizeRestriction();
         // create freeform display and a freeform app
-        DisplayContent display = new TestDisplayContent.Builder(mService, 2000, 1000)
+        DisplayContent display = new TestDisplayContent.Builder(mAtm, 2000, 1000)
                 .setCanRotate(false)
                 .setWindowingMode(WindowConfiguration.WINDOWING_MODE_FREEFORM).build();
         setUpApp(display);
@@ -135,7 +135,7 @@
     @Test
     public void testFixedAspectRatioBoundsWithDecorInSquareDisplay() {
         final int notchHeight = 100;
-        setUpApp(new TestDisplayContent.Builder(mService, 600, 800).setNotch(notchHeight).build());
+        setUpApp(new TestDisplayContent.Builder(mAtm, 600, 800).setNotch(notchHeight).build());
         // Rotation is ignored so because the display size is close to square (700/600<1.333).
         assertTrue(mActivity.mDisplayContent.ignoreRotationForApps());
 
@@ -186,10 +186,10 @@
 
         // Make a new less-tall display with lower density
         final DisplayContent newDisplay =
-                new TestDisplayContent.Builder(mService, 1000, 2000)
+                new TestDisplayContent.Builder(mAtm, 1000, 2000)
                         .setDensityDpi(200).build();
 
-        mActivity = new ActivityBuilder(mService)
+        mActivity = new ActivityBuilder(mAtm)
                 .setTask(mTask)
                 .setResizeMode(RESIZE_MODE_UNRESIZEABLE)
                 .setMaxAspectRatio(1.5f)
@@ -262,7 +262,7 @@
 
     @Test
     public void testAspectRatioMatchParentBoundsAndImeAttachable() {
-        setUpApp(new TestDisplayContent.Builder(mService, 1000, 2000)
+        setUpApp(new TestDisplayContent.Builder(mAtm, 1000, 2000)
                 .setSystemDecorations(true).build());
         prepareUnresizable(2f /* maxAspect */, SCREEN_ORIENTATION_UNSPECIFIED);
         assertFitted();
@@ -293,7 +293,7 @@
         final int origHeight = configBounds.height();
 
         final int notchHeight = 100;
-        final DisplayContent newDisplay = new TestDisplayContent.Builder(mService, 2000, 1000)
+        final DisplayContent newDisplay = new TestDisplayContent.Builder(mAtm, 2000, 1000)
                 .setCanRotate(false).setNotch(notchHeight).build();
 
         // Move the non-resizable activity to the new display.
@@ -327,7 +327,7 @@
     public void testFixedOrientRotateCutoutDisplay() {
         // Create a display with a notch/cutout
         final int notchHeight = 60;
-        setUpApp(new TestDisplayContent.Builder(mService, 1000, 2500)
+        setUpApp(new TestDisplayContent.Builder(mAtm, 1000, 2500)
                 .setNotch(notchHeight).build());
         // Bounds=[0, 0 - 1000, 1460], AppBounds=[0, 60 - 1000, 1460].
         prepareUnresizable(1.4f /* maxAspect */, SCREEN_ORIENTATION_PORTRAIT);
@@ -430,14 +430,14 @@
         // Change display density
         display.mBaseDisplayDensity = (int) (0.7f * display.mBaseDisplayDensity);
         display.computeScreenConfiguration(rotatedConfig);
-        mService.mAmInternal = mock(ActivityManagerInternal.class);
+        mAtm.mAmInternal = mock(ActivityManagerInternal.class);
         display.onRequestedOverrideConfigurationChanged(rotatedConfig);
 
         // The override configuration should be reset and the activity's process will be killed.
         assertFitted();
         verify(mActivity).restartProcessIfVisible();
-        waitHandlerIdle(mService.mH);
-        verify(mService.mAmInternal).killProcess(
+        waitHandlerIdle(mAtm.mH);
+        verify(mAtm.mAmInternal).killProcess(
                 eq(mActivity.app.mName), eq(mActivity.app.mUid), anyString());
     }
 
@@ -454,7 +454,7 @@
         assertFitted();
 
         final ArrayList<IBinder> compatTokens = new ArrayList<>();
-        mService.getTaskChangeNotificationController().registerTaskStackListener(
+        mAtm.getTaskChangeNotificationController().registerTaskStackListener(
                 new TaskStackListener() {
                     @Override
                     public void onSizeCompatModeActivityChanged(int displayId,
@@ -492,7 +492,7 @@
         mActivity.info.resizeMode = ActivityInfo.RESIZE_MODE_RESIZEABLE;
 
         // Create a size compat activity on the same task.
-        final ActivityRecord activity = new ActivityBuilder(mService)
+        final ActivityRecord activity = new ActivityBuilder(mAtm)
                 .setTask(mTask)
                 .setResizeMode(ActivityInfo.RESIZE_MODE_UNRESIZEABLE)
                 .setScreenOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT)
@@ -516,7 +516,7 @@
         final int dw = 1000;
         final int dh = 2500;
         final int notchHeight = 200;
-        setUpApp(new TestDisplayContent.Builder(mService, dw, dh).setNotch(notchHeight).build());
+        setUpApp(new TestDisplayContent.Builder(mAtm, dw, dh).setNotch(notchHeight).build());
         addStatusBar(mActivity.mDisplayContent);
 
         mActivity.setVisible(false);
@@ -568,7 +568,9 @@
     private static WindowState addWindowToActivity(ActivityRecord activity) {
         final WindowManager.LayoutParams params = new WindowManager.LayoutParams();
         params.type = WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
-        final WindowTestUtils.TestWindowState w = new WindowTestUtils.TestWindowState(
+        params.setFitInsetsSides(0);
+        params.setFitInsetsTypes(0);
+        final TestWindowState w = new TestWindowState(
                 activity.mWmService, mock(Session.class), new TestIWindow(), params, activity);
         WindowTestsBase.makeWindowVisible(w);
         w.mWinAnimator.mDrawState = WindowStateAnimator.HAS_DRAWN;
@@ -581,7 +583,7 @@
         doReturn(true).when(displayPolicy).hasStatusBar();
         displayPolicy.onConfigurationChanged();
 
-        final WindowTestUtils.TestWindowToken token = WindowTestUtils.createTestWindowToken(
+        final TestWindowToken token = createTestWindowToken(
                 WindowManager.LayoutParams.TYPE_STATUS_BAR, displayContent);
         final WindowManager.LayoutParams attrs =
                 new WindowManager.LayoutParams(WindowManager.LayoutParams.TYPE_STATUS_BAR);
@@ -589,7 +591,7 @@
         attrs.layoutInDisplayCutoutMode =
                 WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
         attrs.setFitInsetsTypes(0 /* types */);
-        final WindowTestUtils.TestWindowState statusBar = new WindowTestUtils.TestWindowState(
+        final TestWindowState statusBar = new TestWindowState(
                 displayContent.mWmService, mock(Session.class), new TestIWindow(), attrs, token);
         token.addWindow(statusBar);
         statusBar.setRequestedSize(displayContent.mBaseDisplayWidth,
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskDisplayAreaTests.java b/services/tests/wmtests/src/com/android/server/wm/TaskDisplayAreaTests.java
index 27a8fc3..260f1e9 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskDisplayAreaTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskDisplayAreaTests.java
@@ -76,8 +76,7 @@
         // Stack should contain visible app window to be considered visible.
         final Task pinnedTask = createTaskInStack(mPinnedStack, 0 /* userId */);
         assertFalse(mPinnedStack.isVisible());
-        final ActivityRecord pinnedApp =
-                WindowTestUtils.createTestActivityRecord(mDisplayContent);
+        final ActivityRecord pinnedApp = createTestActivityRecord(mDisplayContent);
         pinnedTask.addChild(pinnedApp, 0 /* addPos */);
         assertTrue(mPinnedStack.isVisible());
     }
@@ -92,7 +91,7 @@
         final Task stack = createTaskStackOnDisplay(
                 WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, mDisplayContent);
         final Task task = createTaskInStack(stack, 0 /* userId */);
-        final ActivityRecord activity = WindowTestUtils.createTestActivityRecord(mDisplayContent);
+        final ActivityRecord activity = createTestActivityRecord(mDisplayContent);
         task.addChild(activity, 0 /* addPos */);
         final TaskDisplayArea taskDisplayArea = activity.getDisplayArea();
         activity.mNeedsAnimationBoundsLayer = true;
@@ -219,8 +218,7 @@
         final Task rootHomeTask = defaultTaskDisplayArea.getRootHomeTask();
         rootHomeTask.mResizeMode = RESIZE_MODE_UNRESIZEABLE;
 
-        final Task primarySplitTask =
-                new ActivityTestsBase.StackBuilder(rootWindowContainer)
+        final Task primarySplitTask = new StackBuilder(rootWindowContainer)
                 .setTaskDisplayArea(defaultTaskDisplayArea)
                 .setWindowingMode(WINDOWING_MODE_SPLIT_SCREEN_PRIMARY)
                 .setActivityType(ACTIVITY_TYPE_STANDARD)
@@ -234,7 +232,7 @@
 
         ActivityRecord homeActivity = rootHomeTask.getTopNonFinishingActivity();
         if (homeActivity == null) {
-            homeActivity = new ActivityTestsBase.ActivityBuilder(mWm.mAtmService)
+            homeActivity = new ActivityBuilder(mWm.mAtmService)
                     .setStack(rootHomeTask).setCreateTask(true).build();
         }
         homeActivity.setVisible(false);
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskLaunchParamsModifierTests.java b/services/tests/wmtests/src/com/android/server/wm/TaskLaunchParamsModifierTests.java
index a048526..42de5e6 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskLaunchParamsModifierTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskLaunchParamsModifierTests.java
@@ -67,7 +67,7 @@
 @SmallTest
 @Presubmit
 @RunWith(WindowTestRunner.class)
-public class TaskLaunchParamsModifierTests extends ActivityTestsBase {
+public class TaskLaunchParamsModifierTests extends WindowTestsBase {
     private static final Rect DISPLAY_BOUNDS = new Rect(/* left */ 0, /* top */ 0,
             /* right */ 1920, /* bottom */ 1080);
     private static final Rect DISPLAY_STABLE_BOUNDS = new Rect(/* left */ 100,
@@ -82,7 +82,7 @@
 
     @Before
     public void setUp() throws Exception {
-        mActivity = new ActivityBuilder(mService).build();
+        mActivity = new ActivityBuilder(mAtm).build();
         mActivity.info.applicationInfo.targetSdkVersion = Build.VERSION_CODES.N_MR1;
         mActivity.info.applicationInfo.flags |= ApplicationInfo.FLAG_SUPPORTS_SCREEN_DENSITIES;
 
@@ -449,7 +449,7 @@
 
     @Test
     public void testForceMaximizesUnresizeableApp() {
-        mService.mSizeCompatFreeform = false;
+        mAtm.mSizeCompatFreeform = false;
         final TestDisplayContent freeformDisplay = createNewDisplayContent(
                 WINDOWING_MODE_FREEFORM);
 
@@ -472,7 +472,7 @@
 
     @Test
     public void testLaunchesAppInWindowOnFreeformDisplay() {
-        mService.mSizeCompatFreeform = true;
+        mAtm.mSizeCompatFreeform = true;
         final TestDisplayContent freeformDisplay = createNewDisplayContent(
                 WINDOWING_MODE_FREEFORM);
 
@@ -1318,18 +1318,18 @@
 
     @Test
     public void testNoMultiDisplaySupports() {
-        final boolean orgValue = mService.mSupportsMultiDisplay;
+        final boolean orgValue = mAtm.mSupportsMultiDisplay;
         final TestDisplayContent display = createNewDisplayContent(WINDOWING_MODE_FULLSCREEN);
         mCurrent.mPreferredTaskDisplayArea = display.getDefaultTaskDisplayArea();
 
         try {
-            mService.mSupportsMultiDisplay = false;
+            mAtm.mSupportsMultiDisplay = false;
             assertEquals(RESULT_CONTINUE, mTarget.onCalculate(/* task */ null, /* layout */ null,
                     mActivity, /* source */ null, /* options */ null, mCurrent, mResult));
             assertEquals(mRootWindowContainer.getDefaultTaskDisplayArea(),
                     mResult.mPreferredTaskDisplayArea);
         } finally {
-            mService.mSupportsMultiDisplay = orgValue;
+            mAtm.mSupportsMultiDisplay = orgValue;
         }
     }
 
@@ -1351,7 +1351,7 @@
     private ActivityRecord createSourceActivity(TestDisplayContent display) {
         final Task stack = display.getDefaultTaskDisplayArea()
                 .createStack(display.getWindowingMode(), ACTIVITY_TYPE_STANDARD, true);
-        return new ActivityBuilder(mService).setStack(stack).setCreateTask(true).build();
+        return new ActivityBuilder(mAtm).setStack(stack).setCreateTask(true).build();
     }
 
     private void addFreeformTaskTo(TestDisplayContent display, Rect bounds) {
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskPositionerTests.java b/services/tests/wmtests/src/com/android/server/wm/TaskPositionerTests.java
index 0db3f94..27cae2f 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskPositionerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskPositionerTests.java
@@ -77,7 +77,7 @@
         removeGlobalMinSizeRestriction();
 
         final Task stack = createTaskStackOnDisplay(mDisplayContent);
-        final ActivityRecord activity = new ActivityTestsBase.ActivityBuilder(stack.mAtmService)
+        final ActivityRecord activity = new ActivityBuilder(stack.mAtmService)
                 .setStack(stack)
                 // In real case, there is no additional level for freeform mode.
                 .setCreateTask(false)
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskRecordTests.java b/services/tests/wmtests/src/com/android/server/wm/TaskRecordTests.java
index bf76c8e..08537a4 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskRecordTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskRecordTests.java
@@ -50,6 +50,7 @@
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertThat;
 import static org.junit.Assert.assertTrue;
@@ -95,7 +96,7 @@
 @MediumTest
 @Presubmit
 @RunWith(WindowTestRunner.class)
-public class TaskRecordTests extends ActivityTestsBase {
+public class TaskRecordTests extends WindowTestsBase {
 
     private static final String TASK_TAG = "task";
 
@@ -172,7 +173,7 @@
     @Test
     public void testFitWithinBounds() {
         final Rect parentBounds = new Rect(10, 10, 200, 200);
-        TaskDisplayArea taskDisplayArea = mService.mRootWindowContainer.getDefaultTaskDisplayArea();
+        TaskDisplayArea taskDisplayArea = mAtm.mRootWindowContainer.getDefaultTaskDisplayArea();
         Task stack = taskDisplayArea.createStack(WINDOWING_MODE_FREEFORM,
                 ACTIVITY_TYPE_STANDARD, true /* onTop */);
         Task task = new TaskBuilder(mSupervisor).setStack(stack).build();
@@ -210,7 +211,7 @@
     /** Tests that the task bounds adjust properly to changes between FULLSCREEN and FREEFORM */
     @Test
     public void testBoundsOnModeChangeFreeformToFullscreen() {
-        DisplayContent display = mService.mRootWindowContainer.getDefaultDisplay();
+        DisplayContent display = mAtm.mRootWindowContainer.getDefaultDisplay();
         Task stack = new StackBuilder(mRootWindowContainer).setDisplay(display)
                 .setWindowingMode(WINDOWING_MODE_FREEFORM).build();
         Task task = stack.getBottomMostTask();
@@ -243,19 +244,19 @@
     public void testFullscreenBoundsForcedOrientation() {
         final Rect fullScreenBounds = new Rect(0, 0, 1920, 1080);
         final Rect fullScreenBoundsPort = new Rect(0, 0, 1080, 1920);
-        final DisplayContent display = new TestDisplayContent.Builder(mService,
+        final DisplayContent display = new TestDisplayContent.Builder(mAtm,
                 fullScreenBounds.width(), fullScreenBounds.height()).setCanRotate(false).build();
-        assertTrue(mRootWindowContainer.getDisplayContent(display.mDisplayId) != null);
+        assertNotNull(mRootWindowContainer.getDisplayContent(display.mDisplayId));
         // Fix the display orientation to landscape which is the natural rotation (0) for the test
         // display.
         final DisplayRotation dr = display.mDisplayContent.getDisplayRotation();
         dr.setFixedToUserRotation(FIXED_TO_USER_ROTATION_ENABLED);
         dr.setUserRotation(USER_ROTATION_FREE, ROTATION_0);
 
-        Task stack = new StackBuilder(mRootWindowContainer)
+        final Task stack = new StackBuilder(mRootWindowContainer)
                 .setWindowingMode(WINDOWING_MODE_FULLSCREEN).setDisplay(display).build();
-        Task task = stack.getBottomMostTask();
-        ActivityRecord root = task.getTopNonFinishingActivity();
+        final Task task = stack.getBottomMostTask();
+        final ActivityRecord root = task.getTopNonFinishingActivity();
 
         assertEquals(fullScreenBounds, task.getBounds());
 
@@ -267,7 +268,7 @@
         assertEquals(fullScreenBounds.height(), task.getBounds().height());
 
         // Top activity gets used
-        ActivityRecord top = new ActivityBuilder(mService).setTask(task).setStack(stack).build();
+        final ActivityRecord top = new ActivityBuilder(mAtm).setTask(task).setStack(stack).build();
         assertEquals(top, task.getTopNonFinishingActivity());
         top.setRequestedOrientation(SCREEN_ORIENTATION_LANDSCAPE);
         assertThat(task.getBounds().width()).isGreaterThan(task.getBounds().height());
@@ -304,10 +305,37 @@
     }
 
     @Test
+    public void testReportsOrientationRequestInLetterboxForOrientation() {
+        final Rect fullScreenBounds = new Rect(0, 0, 1920, 1080);
+        final Rect fullScreenBoundsPort = new Rect(0, 0, 1080, 1920);
+        final DisplayContent display = new TestDisplayContent.Builder(mAtm,
+                fullScreenBounds.width(), fullScreenBounds.height()).setCanRotate(false).build();
+        assertNotNull(mRootWindowContainer.getDisplayContent(display.mDisplayId));
+        // Fix the display orientation to landscape which is the natural rotation (0) for the test
+        // display.
+        final DisplayRotation dr = display.mDisplayContent.getDisplayRotation();
+        dr.setFixedToUserRotation(FIXED_TO_USER_ROTATION_ENABLED);
+        dr.setUserRotation(USER_ROTATION_FREE, ROTATION_0);
+
+        final Task stack = new StackBuilder(mRootWindowContainer)
+                .setWindowingMode(WINDOWING_MODE_FULLSCREEN).setDisplay(display).build();
+        final Task task = stack.getBottomMostTask();
+        ActivityRecord root = task.getTopNonFinishingActivity();
+
+        assertEquals(fullScreenBounds, task.getBounds());
+
+        // Setting app to fixed portrait fits within parent
+        root.setRequestedOrientation(SCREEN_ORIENTATION_PORTRAIT);
+        assertThat(task.getBounds().width()).isLessThan(task.getBounds().height());
+
+        assertEquals(SCREEN_ORIENTATION_PORTRAIT, task.getOrientation());
+    }
+
+    @Test
     public void testIgnoresForcedOrientationWhenParentHandles() {
         final Rect fullScreenBounds = new Rect(0, 0, 1920, 1080);
         DisplayContent display = new TestDisplayContent.Builder(
-                mService, fullScreenBounds.width(), fullScreenBounds.height()).build();
+                mAtm, fullScreenBounds.width(), fullScreenBounds.height()).build();
 
         display.getRequestedOverrideConfiguration().orientation =
                 Configuration.ORIENTATION_LANDSCAPE;
@@ -339,7 +367,7 @@
     public void testComputeConfigResourceOverrides() {
         final Rect fullScreenBounds = new Rect(0, 0, 1080, 1920);
         TestDisplayContent display = new TestDisplayContent.Builder(
-                mService, fullScreenBounds.width(), fullScreenBounds.height()).build();
+                mAtm, fullScreenBounds.width(), fullScreenBounds.height()).build();
         final Task task = new TaskBuilder(mSupervisor).setDisplay(display).build();
         final Configuration inOutConfig = new Configuration();
         final Configuration parentConfig = new Configuration();
@@ -404,6 +432,31 @@
     }
 
     @Test
+    public void testComputeConfigResourceLayoutOverrides() {
+        final Rect fullScreenBounds = new Rect(0, 0, 1000, 2500);
+        TestDisplayContent display = new TestDisplayContent.Builder(
+                mAtm, fullScreenBounds.width(), fullScreenBounds.height()).build();
+        final Task task = new TaskBuilder(mSupervisor).setDisplay(display).build();
+        final Configuration inOutConfig = new Configuration();
+        final Configuration parentConfig = new Configuration();
+        final Rect nonLongBounds = new Rect(0, 0, 1000, 1250);
+        parentConfig.windowConfiguration.setBounds(fullScreenBounds);
+        parentConfig.windowConfiguration.setAppBounds(fullScreenBounds);
+        parentConfig.densityDpi = 400;
+        parentConfig.screenHeightDp = (fullScreenBounds.bottom * 160) / parentConfig.densityDpi;
+        parentConfig.screenWidthDp = (fullScreenBounds.right * 160) / parentConfig.densityDpi;
+        parentConfig.windowConfiguration.setRotation(ROTATION_0);
+
+        // Set BOTH screenW/H to an override value
+        inOutConfig.screenWidthDp = nonLongBounds.width() * 160 / parentConfig.densityDpi;
+        inOutConfig.screenHeightDp = nonLongBounds.height() * 160 / parentConfig.densityDpi;
+        task.computeConfigResourceOverrides(inOutConfig, parentConfig);
+
+        // screenLayout should honor override when both screenW/H are set.
+        assertTrue((inOutConfig.screenLayout & Configuration.SCREENLAYOUT_LONG_NO) != 0);
+    }
+
+    @Test
     public void testComputeNestedConfigResourceOverrides() {
         final Task task = new TaskBuilder(mSupervisor).build();
         assertTrue(task.getResolvedOverrideBounds().isEmpty());
@@ -438,11 +491,11 @@
 
     @Test
     public void testInsetDisregardedWhenFreeformOverlapsNavBar() {
-        TaskDisplayArea taskDisplayArea = mService.mRootWindowContainer.getDefaultTaskDisplayArea();
+        TaskDisplayArea taskDisplayArea = mAtm.mRootWindowContainer.getDefaultTaskDisplayArea();
         Task stack = taskDisplayArea.createStack(WINDOWING_MODE_FULLSCREEN,
                 ACTIVITY_TYPE_STANDARD, true /* onTop */);
         DisplayInfo displayInfo = new DisplayInfo();
-        mService.mContext.getDisplay().getDisplayInfo(displayInfo);
+        mAtm.mContext.getDisplay().getDisplayInfo(displayInfo);
         final int displayHeight = displayInfo.logicalHeight;
         final Task task = new TaskBuilder(mSupervisor).setStack(stack).build();
         final Configuration inOutConfig = new Configuration();
@@ -514,23 +567,23 @@
         info.packageName = DEFAULT_COMPONENT_PACKAGE_NAME;
         info.targetActivity = targetClassName;
 
-        final Task task = new Task(mService, 1 /* taskId */, info, intent,
+        final Task task = new Task(mAtm, 1 /* taskId */, info, intent,
                 null /* voiceSession */, null /* voiceInteractor */, null /* taskDescriptor */,
                 null /*stack*/);
         assertEquals("The alias activity component should be saved in task intent.", aliasClassName,
                 task.intent.getComponent().getClassName());
 
-        ActivityRecord aliasActivity = new ActivityBuilder(mService).setComponent(
+        ActivityRecord aliasActivity = new ActivityBuilder(mAtm).setComponent(
                 aliasComponent).setTargetActivity(targetClassName).build();
         assertEquals("Should be the same intent filter.", true,
                 task.isSameIntentFilter(aliasActivity));
 
-        ActivityRecord targetActivity = new ActivityBuilder(mService).setComponent(
+        ActivityRecord targetActivity = new ActivityBuilder(mAtm).setComponent(
                 targetComponent).build();
         assertEquals("Should be the same intent filter.", true,
                 task.isSameIntentFilter(targetActivity));
 
-        ActivityRecord defaultActivity = new ActivityBuilder(mService).build();
+        ActivityRecord defaultActivity = new ActivityBuilder(mAtm).build();
         assertEquals("Should not be the same intent filter.", false,
                 task.isSameIntentFilter(defaultActivity));
     }
@@ -540,7 +593,7 @@
     public void testFindRootIndex() {
         final Task task = getTestTask();
         // Add an extra activity on top of the root one
-        new ActivityBuilder(mService).setTask(task).build();
+        new ActivityBuilder(mAtm).setTask(task).build();
 
         assertEquals("The root activity in the task must be reported.", task.getChildAt(0),
                 task.getRootActivity(
@@ -557,9 +610,9 @@
         // Add extra two activities and mark the two on the bottom as finishing.
         final ActivityRecord activity0 = task.getBottomMostActivity();
         activity0.finishing = true;
-        final ActivityRecord activity1 = new ActivityBuilder(mService).setTask(task).build();
+        final ActivityRecord activity1 = new ActivityBuilder(mAtm).setTask(task).build();
         activity1.finishing = true;
-        new ActivityBuilder(mService).setTask(task).build();
+        new ActivityBuilder(mAtm).setTask(task).build();
 
         assertEquals("The first non-finishing activity in the task must be reported.",
                 task.getChildAt(2), task.getRootActivity(
@@ -574,7 +627,7 @@
     public void testFindRootIndex_effectiveRoot() {
         final Task task = getTestTask();
         // Add an extra activity on top of the root one
-        new ActivityBuilder(mService).setTask(task).build();
+        new ActivityBuilder(mAtm).setTask(task).build();
 
         assertEquals("The root activity in the task must be reported.",
                 task.getChildAt(0), task.getRootActivity(
@@ -592,9 +645,9 @@
         // one above as finishing.
         final ActivityRecord activity0 = task.getBottomMostActivity();
         activity0.info.flags |= FLAG_RELINQUISH_TASK_IDENTITY;
-        final ActivityRecord activity1 = new ActivityBuilder(mService).setTask(task).build();
+        final ActivityRecord activity1 = new ActivityBuilder(mAtm).setTask(task).build();
         activity1.finishing = true;
-        new ActivityBuilder(mService).setTask(task).build();
+        new ActivityBuilder(mAtm).setTask(task).build();
 
         assertEquals("The first non-finishing activity and non-relinquishing task identity "
                 + "must be reported.", task.getChildAt(2), task.getRootActivity(
@@ -626,7 +679,7 @@
         // Set relinquishTaskIdentity for all activities in the task
         final ActivityRecord activity0 = task.getBottomMostActivity();
         activity0.info.flags |= FLAG_RELINQUISH_TASK_IDENTITY;
-        final ActivityRecord activity1 = new ActivityBuilder(mService).setTask(task).build();
+        final ActivityRecord activity1 = new ActivityBuilder(mAtm).setTask(task).build();
         activity1.info.flags |= FLAG_RELINQUISH_TASK_IDENTITY;
 
         assertEquals("The topmost activity in the task must be reported.",
@@ -639,7 +692,7 @@
     public void testGetRootActivity() {
         final Task task = getTestTask();
         // Add an extra activity on top of the root one
-        new ActivityBuilder(mService).setTask(task).build();
+        new ActivityBuilder(mAtm).setTask(task).build();
 
         assertEquals("The root activity in the task must be reported.",
                 task.getBottomMostActivity(), task.getRootActivity());
@@ -652,7 +705,7 @@
     public void testGetRootActivity_finishing() {
         final Task task = getTestTask();
         // Add an extra activity on top of the root one
-        new ActivityBuilder(mService).setTask(task).build();
+        new ActivityBuilder(mAtm).setTask(task).build();
         // Mark the root as finishing
         task.getBottomMostActivity().finishing = true;
 
@@ -670,7 +723,7 @@
         final ActivityRecord activity0 = task.getBottomMostActivity();
         activity0.info.flags |= FLAG_RELINQUISH_TASK_IDENTITY;
         // Add an extra activity on top of the root one.
-        new ActivityBuilder(mService).setTask(task).build();
+        new ActivityBuilder(mAtm).setTask(task).build();
 
         assertEquals("The root activity in the task must be reported.",
                 task.getBottomMostActivity(), task.getRootActivity());
@@ -687,7 +740,7 @@
         final ActivityRecord activity0 = task.getBottomMostActivity();
         activity0.finishing = true;
         // Add an extra activity on top of the root one and mark it as finishing
-        final ActivityRecord activity1 = new ActivityBuilder(mService).setTask(task).build();
+        final ActivityRecord activity1 = new ActivityBuilder(mAtm).setTask(task).build();
         activity1.finishing = true;
 
         assertNull("No activity must be reported if all are finishing", task.getRootActivity());
@@ -703,7 +756,7 @@
         final ActivityRecord activity0 = task.getBottomMostActivity();
         activity0.finishing = true;
         // Add an extra activity on top of the root one.
-        final ActivityRecord activity1 = new ActivityBuilder(mService).setTask(task).build();
+        final ActivityRecord activity1 = new ActivityBuilder(mAtm).setTask(task).build();
 
         assertFalse("Finishing activity must not be the root of task", activity0.isRootOfTask());
         assertTrue("Non-finishing activity must be the root of task", activity1.isRootOfTask());
@@ -720,7 +773,7 @@
         final ActivityRecord activity0 = task.getBottomMostActivity();
         activity0.finishing = true;
         // Add an extra activity on top of the root one and mark it as finishing
-        final ActivityRecord activity1 = new ActivityBuilder(mService).setTask(task).build();
+        final ActivityRecord activity1 = new ActivityBuilder(mAtm).setTask(task).build();
         activity1.finishing = true;
 
         assertTrue("Bottom activity must be the root of task", activity0.isRootOfTask());
@@ -756,9 +809,9 @@
         final ActivityRecord activity0 = task.getBottomMostActivity();
         activity0.finishing = true;
         // Add an extra activity on top - this will be the new root
-        final ActivityRecord activity1 = new ActivityBuilder(mService).setTask(task).build();
+        final ActivityRecord activity1 = new ActivityBuilder(mAtm).setTask(task).build();
         // Add one more on top
-        final ActivityRecord activity2 = new ActivityBuilder(mService).setTask(task).build();
+        final ActivityRecord activity2 = new ActivityBuilder(mAtm).setTask(task).build();
 
         assertEquals(task.mTaskId,
                 ActivityRecord.getTaskForActivityLocked(activity0.appToken, true /* onlyRoot */));
@@ -779,9 +832,9 @@
         final ActivityRecord activity0 = task.getBottomMostActivity();
         activity0.info.flags |= FLAG_RELINQUISH_TASK_IDENTITY;
         // Add an extra activity on top - this will be the new root
-        final ActivityRecord activity1 = new ActivityBuilder(mService).setTask(task).build();
+        final ActivityRecord activity1 = new ActivityBuilder(mAtm).setTask(task).build();
         // Add one more on top
-        final ActivityRecord activity2 = new ActivityBuilder(mService).setTask(task).build();
+        final ActivityRecord activity2 = new ActivityBuilder(mAtm).setTask(task).build();
 
         assertEquals(task.mTaskId,
                 ActivityRecord.getTaskForActivityLocked(activity0.appToken, true /* onlyRoot */));
@@ -803,11 +856,11 @@
         activity0.finishing = true;
 
         // Add an extra activity on top of the root one and make it relinquish task identity
-        final ActivityRecord activity1 = new ActivityBuilder(mService).setTask(task).build();
+        final ActivityRecord activity1 = new ActivityBuilder(mAtm).setTask(task).build();
         activity1.info.flags |= FLAG_RELINQUISH_TASK_IDENTITY;
 
         // Add one more activity on top
-        final ActivityRecord activity2 = new ActivityBuilder(mService).setTask(task).build();
+        final ActivityRecord activity2 = new ActivityBuilder(mAtm).setTask(task).build();
 
         assertEquals(task.mTaskId,
                 ActivityRecord.getTaskForActivityLocked(activity0.appToken, false /* onlyRoot */));
@@ -843,7 +896,7 @@
         // Mark the bottom-most activity as finishing.
         activity0.finishing = true;
         // Add an extra activity on top of the root one
-        final ActivityRecord activity1 = new ActivityBuilder(mService).setTask(task).build();
+        final ActivityRecord activity1 = new ActivityBuilder(mAtm).setTask(task).build();
 
         spyOn(task);
         task.updateEffectiveIntent();
@@ -863,7 +916,7 @@
         // Mark the bottom-most activity as finishing.
         activity0.finishing = true;
         // Add an extra activity on top of the root one and make it relinquish task identity
-        final ActivityRecord activity1 = new ActivityBuilder(mService).setTask(task).build();
+        final ActivityRecord activity1 = new ActivityBuilder(mAtm).setTask(task).build();
         activity1.finishing = true;
 
         // Task must still update the intent using the root activity (preserving legacy behavior).
@@ -874,7 +927,7 @@
 
     @Test
     public void testSaveLaunchingStateWhenConfigurationChanged() {
-        LaunchParamsPersister persister = mService.mStackSupervisor.mLaunchParamsPersister;
+        LaunchParamsPersister persister = mAtm.mStackSupervisor.mLaunchParamsPersister;
         spyOn(persister);
 
         final Task task = getTestTask();
@@ -890,7 +943,7 @@
 
     @Test
     public void testSaveLaunchingStateWhenClearingParent() {
-        LaunchParamsPersister persister = mService.mStackSupervisor.mLaunchParamsPersister;
+        LaunchParamsPersister persister = mAtm.mStackSupervisor.mLaunchParamsPersister;
         spyOn(persister);
 
         final Task task = getTestTask();
@@ -915,7 +968,7 @@
 
     @Test
     public void testNotSaveLaunchingStateNonFreeformDisplay() {
-        LaunchParamsPersister persister = mService.mStackSupervisor.mLaunchParamsPersister;
+        LaunchParamsPersister persister = mAtm.mStackSupervisor.mLaunchParamsPersister;
         spyOn(persister);
 
         final Task task = getTestTask();
@@ -930,7 +983,7 @@
 
     @Test
     public void testNotSaveLaunchingStateWhenNotFullscreenOrFreeformWindow() {
-        LaunchParamsPersister persister = mService.mStackSupervisor.mLaunchParamsPersister;
+        LaunchParamsPersister persister = mAtm.mStackSupervisor.mLaunchParamsPersister;
         spyOn(persister);
 
         final Task task = getTestTask();
@@ -983,7 +1036,7 @@
     private void testStackBoundsConfiguration(int windowingMode, Rect parentBounds, Rect bounds,
             Rect expectedConfigBounds) {
 
-        TaskDisplayArea taskDisplayArea = mService.mRootWindowContainer.getDefaultTaskDisplayArea();
+        TaskDisplayArea taskDisplayArea = mAtm.mRootWindowContainer.getDefaultTaskDisplayArea();
         Task stack = taskDisplayArea.createStack(windowingMode, ACTIVITY_TYPE_STANDARD,
                 true /* onTop */);
         Task task = new TaskBuilder(mSupervisor).setStack(stack).build();
@@ -1019,12 +1072,12 @@
             parser.setInput(reader);
             assertEquals(XmlPullParser.START_TAG, parser.next());
             assertEquals(TASK_TAG, parser.getName());
-            return Task.restoreFromXml(parser, mService.mStackSupervisor);
+            return Task.restoreFromXml(parser, mAtm.mStackSupervisor);
         }
     }
 
     private Task createTask(int taskId) {
-        return new Task(mService, taskId, new Intent(), null, null, null,
+        return new Task(mAtm, taskId, new Intent(), null, null, null,
                 ActivityBuilder.getDefaultComponent(), null, false, false, false, 0, 10050, null,
                 0, false, null, 0, 0, 0, 0, null, null, 0, false, false, false, 0,
                 0, null /*ActivityInfo*/, null /*_voiceSession*/, null /*_voiceInteractor*/,
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotSurfaceTest.java b/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotSurfaceTest.java
index d950344..b4a1337 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotSurfaceTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotSurfaceTest.java
@@ -16,6 +16,7 @@
 
 package com.android.server.wm;
 
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
 import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
 import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
 import static android.view.WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS;
@@ -87,7 +88,7 @@
                 0 /* systemUiVisibility */, false /* isTranslucent */);
         mSurface = new TaskSnapshotSurface(mWm, new Window(), new SurfaceControl(), snapshot, "Test",
                 createTaskDescription(Color.WHITE, Color.RED, Color.BLUE), sysuiVis, windowFlags, 0,
-                taskBounds, ORIENTATION_PORTRAIT, new InsetsState());
+                taskBounds, ORIENTATION_PORTRAIT, ACTIVITY_TYPE_STANDARD, new InsetsState());
     }
 
     private static TaskDescription createTaskDescription(int background, int statusBar,
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskStackTests.java b/services/tests/wmtests/src/com/android/server/wm/TaskStackTests.java
index 205b842..7cf30c0 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskStackTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskStackTests.java
@@ -85,14 +85,12 @@
     public void testClosingAppDifferentStackOrientation() {
         final Task stack = createTaskStackOnDisplay(mDisplayContent);
         final Task task1 = createTaskInStack(stack, 0 /* userId */);
-        ActivityRecord activity1 =
-                WindowTestUtils.createTestActivityRecord(mDisplayContent);
+        ActivityRecord activity1 = createTestActivityRecord(mDisplayContent);
         task1.addChild(activity1, 0);
         activity1.setOrientation(SCREEN_ORIENTATION_LANDSCAPE);
 
         final Task task2 = createTaskInStack(stack, 1 /* userId */);
-        ActivityRecord activity2=
-                WindowTestUtils.createTestActivityRecord(mDisplayContent);
+        ActivityRecord activity2 = createTestActivityRecord(mDisplayContent);
         task2.addChild(activity2, 0);
         activity2.setOrientation(SCREEN_ORIENTATION_PORTRAIT);
 
@@ -105,14 +103,12 @@
     public void testMoveTaskToBackDifferentStackOrientation() {
         final Task stack = createTaskStackOnDisplay(mDisplayContent);
         final Task task1 = createTaskInStack(stack, 0 /* userId */);
-        ActivityRecord activity1 =
-                WindowTestUtils.createTestActivityRecord(mDisplayContent);
+        ActivityRecord activity1 = createTestActivityRecord(mDisplayContent);
         task1.addChild(activity1, 0);
         activity1.setOrientation(SCREEN_ORIENTATION_LANDSCAPE);
 
         final Task task2 = createTaskInStack(stack, 1 /* userId */);
-        ActivityRecord activity2 =
-                WindowTestUtils.createTestActivityRecord(mDisplayContent);
+        ActivityRecord activity2 = createTestActivityRecord(mDisplayContent);
         task2.addChild(activity2, 0);
         activity2.setOrientation(SCREEN_ORIENTATION_PORTRAIT);
         assertEquals(SCREEN_ORIENTATION_PORTRAIT, stack.getOrientation());
@@ -221,7 +217,7 @@
     public void testActivityAndTaskGetsProperType() {
         final Task stack = createTaskStackOnDisplay(mDisplayContent);
         final Task task1 = createTaskInStack(stack, 0 /* userId */);
-        ActivityRecord activity1 = WindowTestUtils.createTestActivityRecord(mDisplayContent);
+        ActivityRecord activity1 = createTestActivityRecord(mDisplayContent);
 
         // First activity should become standard
         task1.addChild(activity1, 0);
@@ -229,7 +225,7 @@
         assertEquals(WindowConfiguration.ACTIVITY_TYPE_STANDARD, task1.getActivityType());
 
         // Second activity should also become standard
-        ActivityRecord activity2 = WindowTestUtils.createTestActivityRecord(mDisplayContent);
+        ActivityRecord activity2 = createTestActivityRecord(mDisplayContent);
         task1.addChild(activity2, WindowContainer.POSITION_TOP);
         assertEquals(WindowConfiguration.ACTIVITY_TYPE_STANDARD, activity2.getActivityType());
         assertEquals(WindowConfiguration.ACTIVITY_TYPE_STANDARD, task1.getActivityType());
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskTests.java b/services/tests/wmtests/src/com/android/server/wm/TaskTests.java
index 92b6e6e..ace0400 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskTests.java
@@ -56,8 +56,7 @@
     public void testRemoveContainer() {
         final Task stackController1 = createTaskStackOnDisplay(mDisplayContent);
         final Task task = createTaskInStack(stackController1, 0 /* userId */);
-        final ActivityRecord activity =
-                WindowTestUtils.createActivityRecordInTask(mDisplayContent, task);
+        final ActivityRecord activity = createActivityRecordInTask(mDisplayContent, task);
 
         task.removeIfPossible();
         // Assert that the container was removed.
@@ -70,8 +69,7 @@
     public void testRemoveContainer_deferRemoval() {
         final Task stackController1 = createTaskStackOnDisplay(mDisplayContent);
         final Task task = createTaskInStack(stackController1, 0 /* userId */);
-        final ActivityRecord activity =
-                WindowTestUtils.createActivityRecordInTask(mDisplayContent, task);
+        final ActivityRecord activity = createActivityRecordInTask(mDisplayContent, task);
 
         doReturn(true).when(task).shouldDeferRemoval();
 
@@ -153,10 +151,8 @@
     public void testIsInStack() {
         final Task task1 = createTaskStackOnDisplay(mDisplayContent);
         final Task task2 = createTaskStackOnDisplay(mDisplayContent);
-        final ActivityRecord activity1 =
-                WindowTestUtils.createActivityRecordInTask(mDisplayContent, task1);
-        final ActivityRecord activity2 =
-                WindowTestUtils.createActivityRecordInTask(mDisplayContent, task2);
+        final ActivityRecord activity1 = createActivityRecordInTask(mDisplayContent, task1);
+        final ActivityRecord activity2 = createActivityRecordInTask(mDisplayContent, task2);
         assertEquals(activity1, task1.isInTask(activity1));
         assertNull(task1.isInTask(activity2));
     }
@@ -165,12 +161,9 @@
     public void testRemoveChildForOverlayTask() {
         final Task task = createTaskStackOnDisplay(mDisplayContent);
         final int taskId = task.mTaskId;
-        final ActivityRecord activity1 =
-                WindowTestUtils.createActivityRecordInTask(mDisplayContent, task);
-        final ActivityRecord activity2 =
-                WindowTestUtils.createActivityRecordInTask(mDisplayContent, task);
-        final ActivityRecord activity3 =
-                WindowTestUtils.createActivityRecordInTask(mDisplayContent, task);
+        final ActivityRecord activity1 = createActivityRecordInTask(mDisplayContent, task);
+        final ActivityRecord activity2 = createActivityRecordInTask(mDisplayContent, task);
+        final ActivityRecord activity3 = createActivityRecordInTask(mDisplayContent, task);
         activity1.setTaskOverlay(true);
         activity2.setTaskOverlay(true);
         activity3.setTaskOverlay(true);
diff --git a/services/tests/wmtests/src/com/android/server/wm/UnknownAppVisibilityControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/UnknownAppVisibilityControllerTest.java
index e8a4e90..78dfd40 100644
--- a/services/tests/wmtests/src/com/android/server/wm/UnknownAppVisibilityControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/UnknownAppVisibilityControllerTest.java
@@ -44,7 +44,7 @@
 
     @Test
     public void testFlow() {
-        final ActivityRecord activity = WindowTestUtils.createTestActivityRecord(mDisplayContent);
+        final ActivityRecord activity = createTestActivityRecord(mDisplayContent);
         mDisplayContent.mUnknownAppVisibilityController.notifyLaunched(activity);
         mDisplayContent.mUnknownAppVisibilityController.notifyAppResumedFinished(activity);
         mDisplayContent.mUnknownAppVisibilityController.notifyRelayouted(activity);
@@ -55,9 +55,21 @@
     }
 
     @Test
+    public void testSkipResume() {
+        final ActivityRecord activity = createTestActivityRecord(mDisplayContent);
+        activity.mLaunchTaskBehind = true;
+        mDisplayContent.mUnknownAppVisibilityController.notifyLaunched(activity);
+        mDisplayContent.mUnknownAppVisibilityController.notifyRelayouted(activity);
+
+        // Make sure our handler processed the message.
+        waitHandlerIdle(mWm.mH);
+        assertTrue(mDisplayContent.mUnknownAppVisibilityController.allResolved());
+    }
+
+    @Test
     public void testMultiple() {
-        final ActivityRecord activity1 = WindowTestUtils.createTestActivityRecord(mDisplayContent);
-        final ActivityRecord activity2 = WindowTestUtils.createTestActivityRecord(mDisplayContent);
+        final ActivityRecord activity1 = createTestActivityRecord(mDisplayContent);
+        final ActivityRecord activity2 = createTestActivityRecord(mDisplayContent);
         mDisplayContent.mUnknownAppVisibilityController.notifyLaunched(activity1);
         mDisplayContent.mUnknownAppVisibilityController.notifyAppResumedFinished(activity1);
         mDisplayContent.mUnknownAppVisibilityController.notifyLaunched(activity2);
@@ -72,15 +84,25 @@
 
     @Test
     public void testClear() {
-        final ActivityRecord activity = WindowTestUtils.createTestActivityRecord(mDisplayContent);
+        final ActivityRecord activity = createTestActivityRecord(mDisplayContent);
         mDisplayContent.mUnknownAppVisibilityController.notifyLaunched(activity);
         mDisplayContent.mUnknownAppVisibilityController.clear();
         assertTrue(mDisplayContent.mUnknownAppVisibilityController.allResolved());
     }
 
     @Test
+    public void testRemoveFinishingInvisibleActivityFromUnknown() {
+        final ActivityRecord activity = createTestActivityRecord(mDisplayContent);
+        mDisplayContent.mUnknownAppVisibilityController.notifyLaunched(activity);
+        activity.finishing = true;
+        activity.mVisibleRequested = true;
+        activity.setVisibility(false, false);
+        assertTrue(mDisplayContent.mUnknownAppVisibilityController.allResolved());
+    }
+
+    @Test
     public void testAppRemoved() {
-        final ActivityRecord activity = WindowTestUtils.createTestActivityRecord(mDisplayContent);
+        final ActivityRecord activity = createTestActivityRecord(mDisplayContent);
         mDisplayContent.mUnknownAppVisibilityController.notifyLaunched(activity);
         mDisplayContent.mUnknownAppVisibilityController.appRemovedOrHidden(activity);
         assertTrue(mDisplayContent.mUnknownAppVisibilityController.allResolved());
diff --git a/services/tests/wmtests/src/com/android/server/wm/WallpaperControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/WallpaperControllerTests.java
index 573e37a..ed9e270 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WallpaperControllerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WallpaperControllerTests.java
@@ -295,7 +295,7 @@
     }
 
     private WindowState createWallpaperTargetWindow(DisplayContent dc) {
-        final ActivityRecord homeActivity = new ActivityTestsBase.ActivityBuilder(mWm.mAtmService)
+        final ActivityRecord homeActivity = new ActivityBuilder(mWm.mAtmService)
                 .setStack(dc.getDefaultTaskDisplayArea().getRootHomeTask())
                 .setCreateTask(true)
                 .build();
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowContainerTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowContainerTests.java
index 3ebc288..4163a9a 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowContainerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowContainerTests.java
@@ -810,8 +810,7 @@
     public void testOnDisplayChanged() {
         final Task stack = createTaskStackOnDisplay(mDisplayContent);
         final Task task = createTaskInStack(stack, 0 /* userId */);
-        final ActivityRecord activity =
-                WindowTestUtils.createActivityRecordInTask(mDisplayContent, task);
+        final ActivityRecord activity = createActivityRecordInTask(mDisplayContent, task);
 
         final DisplayContent newDc = createNewDisplay();
         stack.getDisplayArea().removeStack(stack);
@@ -830,7 +829,7 @@
         final DisplayContent displayContent = createNewDisplay();
         // Do not reparent activity to default display when removing the display.
         doReturn(true).when(displayContent).shouldDestroyContentOnRemove();
-        final ActivityRecord r = new ActivityTestsBase.StackBuilder(mWm.mRoot)
+        final ActivityRecord r = new StackBuilder(mWm.mRoot)
                 .setDisplay(displayContent).build().getTopMostActivity();
         // Add a window and make the activity animating so the removal of activity is deferred.
         createWindow(null, TYPE_BASE_APPLICATION, r, "win");
@@ -854,19 +853,17 @@
     public void testTaskCanApplyAnimation() {
         final Task stack = createTaskStackOnDisplay(mDisplayContent);
         final Task task = createTaskInStack(stack, 0 /* userId */);
-        final ActivityRecord activity2 =
-                WindowTestUtils.createActivityRecordInTask(mDisplayContent, task);
-        final ActivityRecord activity1 =
-                WindowTestUtils.createActivityRecordInTask(mDisplayContent, task);
+        final ActivityRecord activity2 = createActivityRecordInTask(mDisplayContent, task);
+        final ActivityRecord activity1 = createActivityRecordInTask(mDisplayContent, task);
         verifyWindowContainerApplyAnimation(task, activity1, activity2);
     }
 
     @Test
     public void testStackCanApplyAnimation() {
         final Task stack = createTaskStackOnDisplay(mDisplayContent);
-        final ActivityRecord activity2 = WindowTestUtils.createActivityRecordInTask(mDisplayContent,
+        final ActivityRecord activity2 = createActivityRecordInTask(mDisplayContent,
                 createTaskInStack(stack, 0 /* userId */));
-        final ActivityRecord activity1 = WindowTestUtils.createActivityRecordInTask(mDisplayContent,
+        final ActivityRecord activity1 = createActivityRecordInTask(mDisplayContent,
                 createTaskInStack(stack, 0 /* userId */));
         verifyWindowContainerApplyAnimation(stack, activity1, activity2);
     }
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java
index 800a5d4..289d54e 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java
@@ -88,20 +88,14 @@
 @Presubmit
 @RunWith(WindowTestRunner.class)
 public class WindowOrganizerTests extends WindowTestsBase {
-    private ITaskOrganizer registerMockOrganizer(int windowingMode) {
+    private ITaskOrganizer registerMockOrganizer() {
         final ITaskOrganizer organizer = mock(ITaskOrganizer.class);
         when(organizer.asBinder()).thenReturn(new Binder());
 
-        mWm.mAtmService.mTaskOrganizerController.registerTaskOrganizer(
-                organizer, windowingMode);
-
+        mWm.mAtmService.mTaskOrganizerController.registerTaskOrganizer(organizer);
         return organizer;
     }
 
-    private ITaskOrganizer registerMockOrganizer() {
-        return registerMockOrganizer(WINDOWING_MODE_MULTI_WINDOW);
-    }
-
     Task createTask(Task stack, boolean fakeDraw) {
         final Task task = createTaskInStack(stack, 0);
 
@@ -133,11 +127,12 @@
         final Task task = createTask(stack);
         final ITaskOrganizer organizer = registerMockOrganizer();
 
-        task.setTaskOrganizer(organizer);
+        stack.setWindowingMode(WINDOWING_MODE_MULTI_WINDOW);
+        stack.setTaskOrganizer(organizer);
         verify(organizer).onTaskAppeared(any(RunningTaskInfo.class), any(SurfaceControl.class));
 
 
-        task.removeImmediately();
+        stack.removeImmediately();
         verify(organizer).onTaskVanished(any());
     }
 
@@ -147,16 +142,17 @@
         final Task task = createTask(stack, false);
         final ITaskOrganizer organizer = registerMockOrganizer();
 
-        task.setTaskOrganizer(organizer);
+        stack.setWindowingMode(WINDOWING_MODE_MULTI_WINDOW);
+        stack.setTaskOrganizer(organizer);
 
         verify(organizer, never())
                 .onTaskAppeared(any(RunningTaskInfo.class), any(SurfaceControl.class));
-        task.setHasBeenVisible(true);
+        stack.setHasBeenVisible(true);
         assertTrue(stack.getHasBeenVisible());
 
         verify(organizer).onTaskAppeared(any(RunningTaskInfo.class), any(SurfaceControl.class));
 
-        task.removeImmediately();
+        stack.removeImmediately();
         verify(organizer).onTaskVanished(any());
     }
 
@@ -169,42 +165,14 @@
         // In this test we skip making the Task visible, and verify
         // that even though a TaskOrganizer is set remove doesn't emit
         // a vanish callback, because we never emitted appear.
-        task.setTaskOrganizer(organizer);
+        stack.setTaskOrganizer(organizer);
         verify(organizer, never())
                 .onTaskAppeared(any(RunningTaskInfo.class), any(SurfaceControl.class));
-        task.removeImmediately();
+        stack.removeImmediately();
         verify(organizer, never()).onTaskVanished(any());
     }
 
     @Test
-    public void testSwapOrganizer() throws RemoteException {
-        final Task stack = createStack();
-        final Task task = createTask(stack);
-        final ITaskOrganizer organizer = registerMockOrganizer(WINDOWING_MODE_MULTI_WINDOW);
-        final ITaskOrganizer organizer2 = registerMockOrganizer(WINDOWING_MODE_PINNED);
-
-        task.setTaskOrganizer(organizer);
-        verify(organizer).onTaskAppeared(any(RunningTaskInfo.class), any(SurfaceControl.class));
-        task.setTaskOrganizer(organizer2);
-        verify(organizer).onTaskVanished(any());
-        verify(organizer2).onTaskAppeared(any(RunningTaskInfo.class), any(SurfaceControl.class));
-    }
-
-    @Test
-    public void testSwapWindowingModes() throws RemoteException {
-        final Task stack = createStack();
-        final Task task = createTask(stack);
-        final ITaskOrganizer organizer = registerMockOrganizer(WINDOWING_MODE_MULTI_WINDOW);
-        final ITaskOrganizer organizer2 = registerMockOrganizer(WINDOWING_MODE_PINNED);
-
-        stack.setWindowingMode(WINDOWING_MODE_MULTI_WINDOW);
-        verify(organizer).onTaskAppeared(any(RunningTaskInfo.class), any(SurfaceControl.class));
-        stack.setWindowingMode(WINDOWING_MODE_PINNED);
-        verify(organizer).onTaskVanished(any());
-        verify(organizer2).onTaskAppeared(any(RunningTaskInfo.class), any(SurfaceControl.class));
-    }
-
-    @Test
     public void testTaskNoDraw() throws RemoteException {
         final Task stack = createStack();
         final Task task = createTask(stack, false /* fakeDraw */);
@@ -226,6 +194,7 @@
         final Task task = createTask(stack);
         final ITaskOrganizer organizer = registerMockOrganizer();
 
+        stack.setWindowingMode(WINDOWING_MODE_MULTI_WINDOW);
         stack.setTaskOrganizer(organizer);
         verify(organizer).onTaskAppeared(any(RunningTaskInfo.class), any(SurfaceControl.class));
         assertTrue(stack.isOrganized());
@@ -258,7 +227,7 @@
         final Task task2 = createTask(stack2);
         final Task stack3 = createStack();
         final Task task3 = createTask(stack3);
-        final ITaskOrganizer organizer = registerMockOrganizer(WINDOWING_MODE_MULTI_WINDOW);
+        final ITaskOrganizer organizer = registerMockOrganizer();
 
         // First organizer is registered, verify a task appears when changing windowing mode
         stack.setWindowingMode(WINDOWING_MODE_MULTI_WINDOW);
@@ -268,7 +237,7 @@
 
         // Now we replace the registration and1 verify the new organizer receives tasks
         // newly entering the windowing mode.
-        final ITaskOrganizer organizer2 = registerMockOrganizer(WINDOWING_MODE_MULTI_WINDOW);
+        final ITaskOrganizer organizer2 = registerMockOrganizer();
         stack2.setWindowingMode(WINDOWING_MODE_MULTI_WINDOW);
         // One each for task and task2
         verify(organizer2, times(2))
@@ -294,7 +263,7 @@
 
     @Test
     public void testRegisterTaskOrganizerStackWindowingModeChanges() throws RemoteException {
-        final ITaskOrganizer organizer = registerMockOrganizer(WINDOWING_MODE_PINNED);
+        final ITaskOrganizer organizer = registerMockOrganizer();
 
         final Task stack = createStack();
         final Task task = createTask(stack);
@@ -313,7 +282,7 @@
         final Task task = createTask(stack);
         stack.setWindowingMode(WINDOWING_MODE_PINNED);
 
-        final ITaskOrganizer organizer = registerMockOrganizer(WINDOWING_MODE_PINNED);
+        final ITaskOrganizer organizer = registerMockOrganizer();
         verify(organizer, times(1))
                 .onTaskAppeared(any(RunningTaskInfo.class), any(SurfaceControl.class));
     }
@@ -321,7 +290,7 @@
     @Test
     public void testTaskTransaction() {
         removeGlobalMinSizeRestriction();
-        final Task stack = new ActivityTestsBase.StackBuilder(mWm.mRoot)
+        final Task stack = new StackBuilder(mWm.mRoot)
                 .setWindowingMode(WINDOWING_MODE_FREEFORM).build();
         final Task task = stack.getTopMostTask();
         testTransaction(task);
@@ -330,7 +299,7 @@
     @Test
     public void testStackTransaction() {
         removeGlobalMinSizeRestriction();
-        final Task stack = new ActivityTestsBase.StackBuilder(mWm.mRoot)
+        final Task stack = new StackBuilder(mWm.mRoot)
                 .setWindowingMode(WINDOWING_MODE_FREEFORM).build();
         StackInfo info =
                 mWm.mAtmService.getStackInfo(WINDOWING_MODE_FREEFORM, ACTIVITY_TYPE_STANDARD);
@@ -355,7 +324,7 @@
 
     @Test
     public void testSetWindowingMode() {
-        final Task stack = new ActivityTestsBase.StackBuilder(mWm.mRoot)
+        final Task stack = new StackBuilder(mWm.mRoot)
                 .setWindowingMode(WINDOWING_MODE_FREEFORM).build();
         testSetWindowingMode(stack);
 
@@ -389,7 +358,7 @@
     @Test
     public void testContainerFocusableChanges() {
         removeGlobalMinSizeRestriction();
-        final Task stack = new ActivityTestsBase.StackBuilder(mWm.mRoot)
+        final Task stack = new StackBuilder(mWm.mRoot)
                 .setWindowingMode(WINDOWING_MODE_FREEFORM).build();
         final Task task = stack.getTopMostTask();
         WindowContainerTransaction t = new WindowContainerTransaction();
@@ -405,7 +374,7 @@
     @Test
     public void testContainerHiddenChanges() {
         removeGlobalMinSizeRestriction();
-        final Task stack = new ActivityTestsBase.StackBuilder(mWm.mRoot)
+        final Task stack = new StackBuilder(mWm.mRoot)
                 .setWindowingMode(WINDOWING_MODE_FREEFORM).build();
         WindowContainerTransaction t = new WindowContainerTransaction();
         assertTrue(stack.shouldBeVisible(null));
@@ -420,7 +389,7 @@
     @Test
     public void testOverrideConfigSize() {
         removeGlobalMinSizeRestriction();
-        final Task stack = new ActivityTestsBase.StackBuilder(mWm.mRoot)
+        final Task stack = new StackBuilder(mWm.mRoot)
                 .setWindowingMode(WINDOWING_MODE_FREEFORM).build();
         final Task task = stack.getTopMostTask();
         WindowContainerTransaction t = new WindowContainerTransaction();
@@ -483,8 +452,7 @@
             public void onBackPressedOnTaskRoot(RunningTaskInfo taskInfo) {
             }
         };
-        mWm.mAtmService.mTaskOrganizerController.registerTaskOrganizer(listener,
-                WINDOWING_MODE_SPLIT_SCREEN_SECONDARY);
+        mWm.mAtmService.mTaskOrganizerController.registerTaskOrganizer(listener);
         RunningTaskInfo info1 = mWm.mAtmService.mTaskOrganizerController.createRootTask(
                 mDisplayContent.mDisplayId, WINDOWING_MODE_SPLIT_SCREEN_SECONDARY);
 
@@ -542,8 +510,7 @@
             public void onBackPressedOnTaskRoot(RunningTaskInfo taskInfo) {
             }
         };
-        mWm.mAtmService.mTaskOrganizerController.registerTaskOrganizer(listener,
-                WINDOWING_MODE_SPLIT_SCREEN_SECONDARY);
+        mWm.mAtmService.mTaskOrganizerController.registerTaskOrganizer(listener);
         RunningTaskInfo info1 = mWm.mAtmService.mTaskOrganizerController.createRootTask(
                 mDisplayContent.mDisplayId, WINDOWING_MODE_SPLIT_SCREEN_SECONDARY);
         lastReportedTiles.clear();
@@ -604,10 +571,7 @@
             public void onBackPressedOnTaskRoot(RunningTaskInfo taskInfo) {
             }
         };
-        mWm.mAtmService.mTaskOrganizerController.registerTaskOrganizer(
-                listener, WINDOWING_MODE_SPLIT_SCREEN_PRIMARY);
-        mWm.mAtmService.mTaskOrganizerController.registerTaskOrganizer(
-                listener, WINDOWING_MODE_SPLIT_SCREEN_SECONDARY);
+        mWm.mAtmService.mTaskOrganizerController.registerTaskOrganizer(listener);
         RunningTaskInfo info1 = mWm.mAtmService.mTaskOrganizerController.createRootTask(
                 mDisplayContent.mDisplayId, WINDOWING_MODE_SPLIT_SCREEN_PRIMARY);
         RunningTaskInfo info2 = mWm.mAtmService.mTaskOrganizerController.createRootTask(
@@ -874,7 +838,7 @@
     @Test
     public void testEnterPipParams() {
         final StubOrganizer o = new StubOrganizer();
-        mWm.mAtmService.mTaskOrganizerController.registerTaskOrganizer(o, WINDOWING_MODE_PINNED);
+        mWm.mAtmService.mTaskOrganizerController.registerTaskOrganizer(o);
         final ActivityRecord record = makePipableActivity();
 
         final PictureInPictureParams p = new PictureInPictureParams.Builder()
@@ -895,7 +859,7 @@
             }
         }
         ChangeSavingOrganizer o = new ChangeSavingOrganizer();
-        mWm.mAtmService.mTaskOrganizerController.registerTaskOrganizer(o, WINDOWING_MODE_PINNED);
+        mWm.mAtmService.mTaskOrganizerController.registerTaskOrganizer(o);
 
         final ActivityRecord record = makePipableActivity();
         final PictureInPictureParams p = new PictureInPictureParams.Builder()
@@ -926,13 +890,11 @@
             }
         }
         ChangeSavingOrganizer o = new ChangeSavingOrganizer();
-        mWm.mAtmService.mTaskOrganizerController.registerTaskOrganizer(o,
-                WINDOWING_MODE_MULTI_WINDOW);
+        mWm.mAtmService.mTaskOrganizerController.registerTaskOrganizer(o);
 
         final Task stack = createStack();
         final Task task = createTask(stack);
-        final ActivityRecord record = WindowTestUtils.createActivityRecordInTask(
-                stack.mDisplayContent, task);
+        final ActivityRecord record = createActivityRecordInTask(stack.mDisplayContent, task);
 
         stack.setWindowingMode(WINDOWING_MODE_MULTI_WINDOW);
         record.setTaskDescription(new ActivityManager.TaskDescription("TestDescription"));
@@ -943,22 +905,23 @@
     @Test
     public void testPreventDuplicateAppear() throws RemoteException {
         final Task stack = createStack();
-        final Task task = createTask(stack);
+        final Task task = createTask(stack, false /* fakeDraw */);
         final ITaskOrganizer organizer = registerMockOrganizer();
 
-        task.setTaskOrganizer(organizer);
+        stack.setWindowingMode(WINDOWING_MODE_MULTI_WINDOW);
+        stack.setTaskOrganizer(organizer);
         // setHasBeenVisible was already called once by the set-up code.
-        task.setHasBeenVisible(true);
+        stack.setHasBeenVisible(true);
         verify(organizer, times(1))
                 .onTaskAppeared(any(RunningTaskInfo.class), any(SurfaceControl.class));
 
-        task.setTaskOrganizer(null);
+        stack.setTaskOrganizer(null);
         verify(organizer, times(1)).onTaskVanished(any());
-        task.setTaskOrganizer(organizer);
+        stack.setTaskOrganizer(organizer);
         verify(organizer, times(2))
                 .onTaskAppeared(any(RunningTaskInfo.class), any(SurfaceControl.class));
 
-        task.removeImmediately();
+        stack.removeImmediately();
         verify(organizer, times(2)).onTaskVanished(any());
     }
 
@@ -966,9 +929,8 @@
     public void testInterceptBackPressedOnTaskRoot() throws RemoteException {
         final Task stack = createStack();
         final Task task = createTask(stack);
-        final ActivityRecord activity = WindowTestUtils.createActivityRecordInTask(
-                stack.mDisplayContent, task);
-        final ITaskOrganizer organizer = registerMockOrganizer(WINDOWING_MODE_MULTI_WINDOW);
+        final ActivityRecord activity = createActivityRecordInTask(stack.mDisplayContent, task);
+        final ITaskOrganizer organizer = registerMockOrganizer();
 
         // Setup the task to be controlled by the MW mode organizer
         stack.setWindowingMode(WINDOWING_MODE_MULTI_WINDOW);
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowProcessControllerMapTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowProcessControllerMapTests.java
index cb7bff3..c2ee079 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowProcessControllerMapTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowProcessControllerMapTests.java
@@ -31,6 +31,7 @@
 
 import org.junit.Before;
 import org.junit.Test;
+import org.junit.runner.RunWith;
 
 /**
  * Tests for the {@link WindowProcessControllerMap} class.
@@ -40,7 +41,8 @@
  */
 @SmallTest
 @Presubmit
-public class WindowProcessControllerMapTests extends ActivityTestsBase {
+@RunWith(WindowTestRunner.class)
+public class WindowProcessControllerMapTests extends WindowTestsBase {
 
     private static final int FAKE_UID1 = 666;
     private static final int FAKE_UID2 = 667;
@@ -60,23 +62,23 @@
     public void setUp() throws Exception {
         mProcessMap = new WindowProcessControllerMap();
         pid1uid1 = new WindowProcessController(
-                mService, mService.mContext.getApplicationInfo(), "fakepid1fakeuid1", FAKE_UID1,
+                mAtm, mAtm.mContext.getApplicationInfo(), "fakepid1fakeuid1", FAKE_UID1,
                 UserHandle.getUserId(12345), mock(Object.class), mock(WindowProcessListener.class));
         pid1uid1.setPid(FAKE_PID1);
         pid1uid2 = new WindowProcessController(
-                mService, mService.mContext.getApplicationInfo(), "fakepid1fakeuid2", FAKE_UID2,
+                mAtm, mAtm.mContext.getApplicationInfo(), "fakepid1fakeuid2", FAKE_UID2,
                 UserHandle.getUserId(12345), mock(Object.class), mock(WindowProcessListener.class));
         pid1uid2.setPid(FAKE_PID1);
         pid2uid1 = new WindowProcessController(
-                mService, mService.mContext.getApplicationInfo(), "fakepid2fakeuid1", FAKE_UID1,
+                mAtm, mAtm.mContext.getApplicationInfo(), "fakepid2fakeuid1", FAKE_UID1,
                 UserHandle.getUserId(12345), mock(Object.class), mock(WindowProcessListener.class));
         pid2uid1.setPid(FAKE_PID2);
         pid3uid1 = new WindowProcessController(
-                mService, mService.mContext.getApplicationInfo(), "fakepid3fakeuid1", FAKE_UID1,
+                mAtm, mAtm.mContext.getApplicationInfo(), "fakepid3fakeuid1", FAKE_UID1,
                 UserHandle.getUserId(12345), mock(Object.class), mock(WindowProcessListener.class));
         pid3uid1.setPid(FAKE_PID3);
         pid4uid2 = new WindowProcessController(
-                mService, mService.mContext.getApplicationInfo(), "fakepid4fakeuid2", FAKE_UID2,
+                mAtm, mAtm.mContext.getApplicationInfo(), "fakepid4fakeuid2", FAKE_UID2,
                 UserHandle.getUserId(12345), mock(Object.class), mock(WindowProcessListener.class));
         pid4uid2.setPid(FAKE_PID4);
     }
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowProcessControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowProcessControllerTests.java
index cdf8eb4..189e540 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowProcessControllerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowProcessControllerTests.java
@@ -16,6 +16,8 @@
 
 package com.android.server.wm;
 
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
 import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
 import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
 import static android.view.Display.INVALID_DISPLAY;
@@ -50,7 +52,7 @@
  */
 @Presubmit
 @RunWith(WindowTestRunner.class)
-public class WindowProcessControllerTests extends ActivityTestsBase {
+public class WindowProcessControllerTests extends WindowTestsBase {
 
     WindowProcessController mWpc;
     WindowProcessListener mMockListener;
@@ -62,7 +64,7 @@
         ApplicationInfo info = mock(ApplicationInfo.class);
         info.packageName = "test.package.name";
         mWpc = new WindowProcessController(
-                mService, info, null, 0, -1, null, mMockListener);
+                mAtm, info, null, 0, -1, null, mMockListener);
         mWpc.setThread(mock(IApplicationThread.class));
     }
 
@@ -108,7 +110,7 @@
     public void testSetRunningRecentsAnimation() {
         mWpc.setRunningRecentsAnimation(true);
         mWpc.setRunningRecentsAnimation(false);
-        waitHandlerIdle(mService.mH);
+        waitHandlerIdle(mAtm.mH);
 
         InOrder orderVerifier = Mockito.inOrder(mMockListener);
         orderVerifier.verify(mMockListener).setRunningRemoteAnimation(eq(true));
@@ -119,7 +121,7 @@
     public void testSetRunningRemoteAnimation() {
         mWpc.setRunningRemoteAnimation(true);
         mWpc.setRunningRemoteAnimation(false);
-        waitHandlerIdle(mService.mH);
+        waitHandlerIdle(mAtm.mH);
 
         InOrder orderVerifier = Mockito.inOrder(mMockListener);
         orderVerifier.verify(mMockListener).setRunningRemoteAnimation(eq(true));
@@ -133,7 +135,7 @@
 
         mWpc.setRunningRecentsAnimation(false);
         mWpc.setRunningRemoteAnimation(false);
-        waitHandlerIdle(mService.mH);
+        waitHandlerIdle(mAtm.mH);
 
         InOrder orderVerifier = Mockito.inOrder(mMockListener);
         orderVerifier.verify(mMockListener, times(3)).setRunningRemoteAnimation(eq(true));
@@ -147,12 +149,12 @@
         assertEquals(INVALID_DISPLAY, mWpc.getDisplayId());
 
         // Register to a new display as a listener.
-        final DisplayContent display = new TestDisplayContent.Builder(mService, 2000, 1000)
+        final DisplayContent display = new TestDisplayContent.Builder(mAtm, 2000, 1000)
                 .setDensityDpi(300).setPosition(DisplayContent.POSITION_TOP).build();
         mWpc.registerDisplayConfigurationListener(display);
 
         assertEquals(display.mDisplayId, mWpc.getDisplayId());
-        final Configuration expectedConfig = mService.mRootWindowContainer.getConfiguration();
+        final Configuration expectedConfig = mAtm.mRootWindowContainer.getConfiguration();
         expectedConfig.updateFrom(display.getConfiguration());
         assertEquals(expectedConfig, mWpc.getConfiguration());
     }
@@ -184,15 +186,15 @@
 
     @Test
     public void testActivityNotOverridingSystemUiProcessConfig() {
-        final ComponentName systemUiServiceComponent = mService.getSysUiServiceComponentLocked();
+        final ComponentName systemUiServiceComponent = mAtm.getSysUiServiceComponentLocked();
         ApplicationInfo applicationInfo = mock(ApplicationInfo.class);
         applicationInfo.packageName = systemUiServiceComponent.getPackageName();
 
         WindowProcessController wpc = new WindowProcessController(
-                mService, applicationInfo, null, 0, -1, null, mMockListener);
+                mAtm, applicationInfo, null, 0, -1, null, mMockListener);
         wpc.setThread(mock(IApplicationThread.class));
 
-        final ActivityRecord activity = new ActivityBuilder(mService)
+        final ActivityRecord activity = new ActivityBuilder(mAtm)
                 .setCreateTask(true)
                 .setUseProcess(wpc)
                 .build();
@@ -209,7 +211,7 @@
         // Notify WPC that this process has started an IME service.
         mWpc.onServiceStarted(serviceInfo);
 
-        final ActivityRecord activity = new ActivityBuilder(mService)
+        final ActivityRecord activity = new ActivityBuilder(mAtm)
                 .setCreateTask(true)
                 .setUseProcess(mWpc)
                 .build();
@@ -226,7 +228,7 @@
         // Notify WPC that this process has started an ally service.
         mWpc.onServiceStarted(serviceInfo);
 
-        final ActivityRecord activity = new ActivityBuilder(mService)
+        final ActivityRecord activity = new ActivityBuilder(mAtm)
                 .setCreateTask(true)
                 .setUseProcess(mWpc)
                 .build();
@@ -243,7 +245,7 @@
         // Notify WPC that this process has started an voice interaction service.
         mWpc.onServiceStarted(serviceInfo);
 
-        final ActivityRecord activity = new ActivityBuilder(mService)
+        final ActivityRecord activity = new ActivityBuilder(mAtm)
                 .setCreateTask(true)
                 .setUseProcess(mWpc)
                 .build();
@@ -253,8 +255,21 @@
         assertFalse(mWpc.registeredForActivityConfigChanges());
     }
 
+    @Test
+    public void testProcessLevelConfiguration() {
+        Configuration config = new Configuration();
+        config.windowConfiguration.setActivityType(ACTIVITY_TYPE_HOME);
+        mWpc.onRequestedOverrideConfigurationChanged(config);
+        assertEquals(ACTIVITY_TYPE_HOME, config.windowConfiguration.getActivityType());
+        assertEquals(ACTIVITY_TYPE_UNDEFINED, mWpc.getActivityType());
+
+        mWpc.onMergedOverrideConfigurationChanged(config);
+        assertEquals(ACTIVITY_TYPE_HOME, config.windowConfiguration.getActivityType());
+        assertEquals(ACTIVITY_TYPE_UNDEFINED, mWpc.getActivityType());
+    }
+
     private TestDisplayContent createTestDisplayContentInContainer() {
-        return new TestDisplayContent.Builder(mService, 1000, 1500).build();
+        return new TestDisplayContent.Builder(mAtm, 1000, 1500).build();
     }
 
     private static void invertOrientation(Configuration config) {
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
index 3894a2e..f095fd4 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
@@ -357,8 +357,7 @@
 
         // Call prepareWindowToDisplayDuringRelayout for a windows that are not children of an
         // activity. Both windows have the FLAG_TURNS_SCREEN_ON so both should call wakeup
-        final WindowToken windowToken = WindowTestUtils.createTestWindowToken(FIRST_SUB_WINDOW,
-                mDisplayContent);
+        final WindowToken windowToken = createTestWindowToken(FIRST_SUB_WINDOW, mDisplayContent);
         final WindowState firstWindow = createWindow(null, TYPE_APPLICATION, windowToken,
                 "firstWindow");
         final WindowState secondWindow = createWindow(null, TYPE_APPLICATION, windowToken,
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowTestUtils.java b/services/tests/wmtests/src/com/android/server/wm/WindowTestUtils.java
deleted file mode 100644
index 2502932..0000000
--- a/services/tests/wmtests/src/com/android/server/wm/WindowTestUtils.java
+++ /dev/null
@@ -1,134 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.wm;
-
-import static android.app.AppOpsManager.OP_NONE;
-
-import static com.android.dx.mockito.inline.extended.ExtendedMockito.mock;
-import static com.android.server.wm.WindowContainer.POSITION_TOP;
-
-import android.os.IBinder;
-import android.view.IWindow;
-import android.view.WindowManager;
-
-import com.android.server.wm.ActivityTestsBase.ActivityBuilder;
-
-/**
- * A collection of static functions that provide access to WindowManager related test functionality.
- */
-class WindowTestUtils {
-
-    /** Creates a {@link Task} and adds it to the specified {@link Task}. */
-    static Task createTaskInStack(WindowManagerService service, Task stack, int userId) {
-        final Task task = new ActivityTestsBase.TaskBuilder(stack.mStackSupervisor)
-                .setUserId(userId)
-                .setStack(stack)
-                .build();
-        return task;
-    }
-
-    /** Creates an {@link ActivityRecord} and adds it to the specified {@link Task}. */
-    static ActivityRecord createActivityRecordInTask(DisplayContent dc, Task task) {
-        final ActivityRecord activity = createTestActivityRecord(dc);
-        task.addChild(activity, POSITION_TOP);
-        return activity;
-    }
-
-    static ActivityRecord createTestActivityRecord(Task stack) {
-        final ActivityRecord activity = new ActivityTestsBase.ActivityBuilder(stack.mAtmService)
-                .setStack(stack)
-                .setCreateTask(true)
-                .build();
-        postCreateActivitySetup(activity, stack.getDisplayContent());
-        return activity;
-    }
-
-    static ActivityRecord createTestActivityRecord(DisplayContent dc) {
-        final ActivityRecord activity = new ActivityBuilder(dc.mWmService.mAtmService).build();
-        postCreateActivitySetup(activity, dc);
-        return activity;
-    }
-
-    private static void postCreateActivitySetup(ActivityRecord activity, DisplayContent dc) {
-        activity.onDisplayChanged(dc);
-        activity.setOccludesParent(true);
-        activity.setVisible(true);
-        activity.mVisibleRequested = true;
-    }
-
-    static TestWindowToken createTestWindowToken(int type, DisplayContent dc) {
-        return createTestWindowToken(type, dc, false /* persistOnEmpty */);
-    }
-
-    static TestWindowToken createTestWindowToken(int type, DisplayContent dc,
-            boolean persistOnEmpty) {
-        SystemServicesTestRule.checkHoldsLock(dc.mWmService.mGlobalLock);
-
-        return new TestWindowToken(type, dc, persistOnEmpty);
-    }
-
-    /* Used so we can gain access to some protected members of the {@link WindowToken} class */
-    static class TestWindowToken extends WindowToken {
-
-        private TestWindowToken(int type, DisplayContent dc, boolean persistOnEmpty) {
-            super(dc.mWmService, mock(IBinder.class), type, persistOnEmpty, dc,
-                    false /* ownerCanManageAppTokens */);
-        }
-
-        int getWindowsCount() {
-            return mChildren.size();
-        }
-
-        boolean hasWindow(WindowState w) {
-            return mChildren.contains(w);
-        }
-    }
-
-    /** Used to track resize reports. */
-    static class TestWindowState extends WindowState {
-        boolean mResizeReported;
-
-        TestWindowState(WindowManagerService service, Session session, IWindow window,
-                WindowManager.LayoutParams attrs, WindowToken token) {
-            super(service, session, window, token, null, OP_NONE, 0, attrs, 0, 0, 0,
-                    false /* ownerCanAddInternalSystemWindow */);
-        }
-
-        @Override
-        void reportResized() {
-            super.reportResized();
-            mResizeReported = true;
-        }
-
-        @Override
-        public boolean isGoneForLayoutLw() {
-            return false;
-        }
-
-        @Override
-        void updateResizingWindowIfNeeded() {
-            // Used in AppWindowTokenTests#testLandscapeSeascapeRotationRelayout to deceive
-            // the system that it can actually update the window.
-            boolean hadSurface = mHasSurface;
-            mHasSurface = true;
-
-            super.updateResizingWindowIfNeeded();
-
-            mHasSurface = hadSurface;
-        }
-    }
-}
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
index dc38883..f86d8f1 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
@@ -18,7 +18,13 @@
 
 import static android.app.AppOpsManager.OP_NONE;
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
 import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
+import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY;
+import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_SECONDARY;
+import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
+import static android.content.pm.ActivityInfo.RESIZE_MODE_RESIZEABLE;
+import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
 import static android.os.Process.SYSTEM_UID;
 import static android.view.View.VISIBLE;
 import static android.view.WindowManager.LayoutParams.FIRST_APPLICATION_WINDOW;
@@ -37,22 +43,46 @@
 
 import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
 
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.doNothing;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
+import static com.android.server.wm.WindowContainer.POSITION_BOTTOM;
+import static com.android.server.wm.WindowContainer.POSITION_TOP;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
 import static org.mockito.Mockito.mock;
 
 import android.annotation.IntDef;
+import android.app.ActivityManager;
+import android.app.ActivityOptions;
+import android.app.IApplicationThread;
+import android.app.WindowConfiguration;
+import android.content.ComponentName;
+import android.content.Context;
 import android.content.Intent;
+import android.content.pm.ActivityInfo;
+import android.content.pm.ApplicationInfo;
+import android.content.res.Configuration;
 import android.hardware.display.DisplayManager;
+import android.os.Build;
+import android.os.Bundle;
+import android.os.IBinder;
 import android.os.RemoteException;
 import android.os.UserHandle;
+import android.service.voice.IVoiceInteractionSession;
 import android.view.Display;
 import android.view.DisplayInfo;
 import android.view.IDisplayWindowInsetsController;
 import android.view.IWindow;
 import android.view.InsetsSourceControl;
 import android.view.InsetsState;
+import android.view.SurfaceControl;
 import android.view.SurfaceControl.Transaction;
 import android.view.View;
 import android.view.WindowManager;
+import android.window.ITaskOrganizer;
+import android.window.WindowContainerToken;
 
 import com.android.internal.util.ArrayUtils;
 import com.android.server.AttributeCache;
@@ -68,11 +98,20 @@
 
 /** Common base class for window manager unit test classes. */
 class WindowTestsBase extends SystemServiceTestsBase {
+    final Context mContext = getInstrumentation().getTargetContext();
 
+    // Default package name
+    static final String DEFAULT_COMPONENT_PACKAGE_NAME = "com.foo";
+
+    // Default base activity name
+    private static final String DEFAULT_COMPONENT_CLASS_NAME = ".BarActivity";
+
+    ActivityTaskManagerService mAtm;
+    RootWindowContainer mRootWindowContainer;
+    ActivityStackSupervisor mSupervisor;
     WindowManagerService mWm;
     private final IWindow mIWindow = new TestIWindow();
     private Session mMockSession;
-    static int sNextStackId = 1000;
 
     DisplayInfo mDisplayInfo = new DisplayInfo();
     DisplayContent mDefaultDisplay;
@@ -107,6 +146,9 @@
 
     @Before
     public void setUpBase() {
+        mAtm = mSystemServicesTestRule.getActivityTaskManagerService();
+        mSupervisor = mAtm.mStackSupervisor;
+        mRootWindowContainer = mAtm.mRootWindowContainer;
         mWm = mSystemServicesTestRule.getWindowManagerService();
         SystemServicesTestRule.checkHoldsLock(mWm.mGlobalLock);
 
@@ -114,7 +156,7 @@
         mTransaction = mSystemServicesTestRule.mTransaction;
         mMockSession = mock(Session.class);
 
-        getInstrumentation().getTargetContext().getSystemService(DisplayManager.class)
+        mContext.getSystemService(DisplayManager.class)
                 .getDisplay(Display.DEFAULT_DISPLAY).getDisplayInfo(mDisplayInfo);
 
         // Only create an additional test display for annotated test class/method because it may
@@ -199,7 +241,7 @@
     private WindowToken createWindowToken(
             DisplayContent dc, int windowingMode, int activityType, int type) {
         if (type < FIRST_APPLICATION_WINDOW || type > LAST_APPLICATION_WINDOW) {
-            return WindowTestUtils.createTestWindowToken(type, dc);
+            return createTestWindowToken(type, dc);
         }
 
         return createActivityRecord(dc, windowingMode, activityType);
@@ -209,10 +251,39 @@
         return createTestActivityRecord(dc, windowingMode, activityType);
     }
 
-    ActivityRecord createTestActivityRecord(DisplayContent dc, int
-            windowingMode, int activityType) {
+    ActivityRecord createTestActivityRecord(DisplayContent dc, int windowingMode,
+            int activityType) {
         final Task stack = createTaskStackOnDisplay(windowingMode, activityType, dc);
-        return WindowTestUtils.createTestActivityRecord(stack);
+        return createTestActivityRecord(stack);
+    }
+
+    /** Creates an {@link ActivityRecord} and adds it to the specified {@link Task}. */
+    static ActivityRecord createActivityRecordInTask(DisplayContent dc, Task task) {
+        final ActivityRecord activity = createTestActivityRecord(dc);
+        task.addChild(activity, POSITION_TOP);
+        return activity;
+    }
+
+    static ActivityRecord createTestActivityRecord(DisplayContent dc) {
+        final ActivityRecord activity = new ActivityBuilder(dc.mWmService.mAtmService).build();
+        postCreateActivitySetup(activity, dc);
+        return activity;
+    }
+
+    static ActivityRecord createTestActivityRecord(Task stack) {
+        final ActivityRecord activity = new ActivityBuilder(stack.mAtmService)
+                .setStack(stack)
+                .setCreateTask(true)
+                .build();
+        postCreateActivitySetup(activity, stack.getDisplayContent());
+        return activity;
+    }
+
+    private static void postCreateActivitySetup(ActivityRecord activity, DisplayContent dc) {
+        activity.onDisplayChanged(dc);
+        activity.setOccludesParent(true);
+        activity.setVisible(true);
+        activity.mVisibleRequested = true;
     }
 
     WindowState createWindow(WindowState parent, int type, String name) {
@@ -234,8 +305,7 @@
     }
 
     WindowState createAppWindow(Task task, int type, String name) {
-        final ActivityRecord activity =
-                WindowTestUtils.createTestActivityRecord(task.getDisplayContent());
+        final ActivityRecord activity = createTestActivityRecord(task.getDisplayContent());
         task.addChild(activity, 0);
         return createWindow(null, type, activity, name);
     }
@@ -328,7 +398,7 @@
     }
 
     Task createTaskStackOnDisplay(int windowingMode, int activityType, DisplayContent dc) {
-        return new ActivityTestsBase.StackBuilder(dc.mWmService.mRoot)
+        return new StackBuilder(dc.mWmService.mRoot)
                 .setDisplay(dc)
                 .setWindowingMode(windowingMode)
                 .setActivityType(activityType)
@@ -339,7 +409,7 @@
 
     Task createTaskStackOnTaskDisplayArea(int windowingMode, int activityType,
             TaskDisplayArea tda) {
-        return new ActivityTestsBase.StackBuilder(tda.mWmService.mRoot)
+        return new StackBuilder(tda.mWmService.mRoot)
                 .setTaskDisplayArea(tda)
                 .setWindowingMode(windowingMode)
                 .setActivityType(activityType)
@@ -350,7 +420,11 @@
 
     /** Creates a {@link Task} and adds it to the specified {@link Task}. */
     Task createTaskInStack(Task stack, int userId) {
-        return WindowTestUtils.createTaskInStack(mWm, stack, userId);
+        final Task task = new TaskBuilder(stack.mStackSupervisor)
+                .setUserId(userId)
+                .setStack(stack)
+                .build();
+        return task;
     }
 
     /** Creates a {@link DisplayContent} that supports IME and adds it to the system. */
@@ -371,7 +445,7 @@
     /** Creates a {@link DisplayContent} and adds it to the system. */
     private DisplayContent createNewDisplay(DisplayInfo info, boolean supportIme) {
         final DisplayContent display =
-                new TestDisplayContent.Builder(mWm.mAtmService, info).build();
+                new TestDisplayContent.Builder(mAtm, info).build();
         final DisplayContent dc = display.mDisplayContent;
         // this display can show IME.
         dc.mWmService.mDisplayWindowSettings.setShouldShowImeLocked(dc, supportIme);
@@ -392,12 +466,11 @@
         return createNewDisplay(displayInfo, true /* supportIme */);
     }
 
-    /** Creates a {@link com.android.server.wm.WindowTestUtils.TestWindowState} */
-    WindowTestUtils.TestWindowState createWindowState(WindowManager.LayoutParams attrs,
-            WindowToken token) {
+    /** Creates a {@link TestWindowState} */
+    TestWindowState createWindowState(WindowManager.LayoutParams attrs, WindowToken token) {
         SystemServicesTestRule.checkHoldsLock(mWm.mGlobalLock);
 
-        return new WindowTestUtils.TestWindowState(mWm, mMockSession, mIWindow, attrs, token);
+        return new TestWindowState(mWm, mMockSession, mIWindow, attrs, token);
     }
 
     /** Creates a {@link DisplayContent} as parts of simulate display info for test. */
@@ -435,11 +508,6 @@
         };
     }
 
-    /** Sets the default minimum task size to 1 so that tests can use small task sizes */
-    void removeGlobalMinSizeRestriction() {
-        mWm.mAtmService.mRootWindowContainer.mDefaultMinSizeOfResizeableTaskDp = 1;
-    }
-
     /**
      * Avoids rotating screen disturbed by some conditions. It is usually used for the default
      * display that is not the instance of {@link TestDisplayContent} (it bypasses the conditions).
@@ -501,4 +569,582 @@
         boolean addAllCommonWindows() default false;
         @CommonTypes int[] addWindows() default {};
     }
+
+    /** Creates and adds a {@link TestDisplayContent} to supervisor at the given position. */
+    TestDisplayContent addNewDisplayContentAt(int position) {
+        return new TestDisplayContent.Builder(mAtm, 1000, 1500).setPosition(position).build();
+    }
+
+    /** Sets the default minimum task size to 1 so that tests can use small task sizes */
+    public void removeGlobalMinSizeRestriction() {
+        mAtm.mRootWindowContainer.mDefaultMinSizeOfResizeableTaskDp = 1;
+    }
+
+    /**
+     * Builder for creating new activities.
+     */
+    protected static class ActivityBuilder {
+        // An id appended to the end of the component name to make it unique
+        private static int sCurrentActivityId = 0;
+
+        private final ActivityTaskManagerService mService;
+
+        private ComponentName mComponent;
+        private String mTargetActivity;
+        private Task mTask;
+        private String mProcessName = "name";
+        private String mAffinity;
+        private int mUid = 12345;
+        private boolean mCreateTask;
+        private Task mStack;
+        private int mActivityFlags;
+        private int mLaunchMode;
+        private int mResizeMode = RESIZE_MODE_RESIZEABLE;
+        private float mMaxAspectRatio;
+        private int mScreenOrientation = SCREEN_ORIENTATION_UNSPECIFIED;
+        private boolean mLaunchTaskBehind;
+        private int mConfigChanges;
+        private int mLaunchedFromPid;
+        private int mLaunchedFromUid;
+        private WindowProcessController mWpc;
+        private Bundle mIntentExtras;
+
+        ActivityBuilder(ActivityTaskManagerService service) {
+            mService = service;
+        }
+
+        ActivityBuilder setComponent(ComponentName component) {
+            mComponent = component;
+            return this;
+        }
+
+        ActivityBuilder setTargetActivity(String targetActivity) {
+            mTargetActivity = targetActivity;
+            return this;
+        }
+
+        ActivityBuilder setIntentExtras(Bundle extras) {
+            mIntentExtras = extras;
+            return this;
+        }
+
+        static ComponentName getDefaultComponent() {
+            return ComponentName.createRelative(DEFAULT_COMPONENT_PACKAGE_NAME,
+                    DEFAULT_COMPONENT_PACKAGE_NAME);
+        }
+
+        ActivityBuilder setTask(Task task) {
+            mTask = task;
+            return this;
+        }
+
+        ActivityBuilder setActivityFlags(int flags) {
+            mActivityFlags = flags;
+            return this;
+        }
+
+        ActivityBuilder setLaunchMode(int launchMode) {
+            mLaunchMode = launchMode;
+            return this;
+        }
+
+        ActivityBuilder setStack(Task stack) {
+            mStack = stack;
+            return this;
+        }
+
+        ActivityBuilder setCreateTask(boolean createTask) {
+            mCreateTask = createTask;
+            return this;
+        }
+
+        ActivityBuilder setProcessName(String name) {
+            mProcessName = name;
+            return this;
+        }
+
+        ActivityBuilder setUid(int uid) {
+            mUid = uid;
+            return this;
+        }
+
+        ActivityBuilder setResizeMode(int resizeMode) {
+            mResizeMode = resizeMode;
+            return this;
+        }
+
+        ActivityBuilder setMaxAspectRatio(float maxAspectRatio) {
+            mMaxAspectRatio = maxAspectRatio;
+            return this;
+        }
+
+        ActivityBuilder setScreenOrientation(int screenOrientation) {
+            mScreenOrientation = screenOrientation;
+            return this;
+        }
+
+        ActivityBuilder setLaunchTaskBehind(boolean launchTaskBehind) {
+            mLaunchTaskBehind = launchTaskBehind;
+            return this;
+        }
+
+        ActivityBuilder setConfigChanges(int configChanges) {
+            mConfigChanges = configChanges;
+            return this;
+        }
+
+        ActivityBuilder setLaunchedFromPid(int pid) {
+            mLaunchedFromPid = pid;
+            return this;
+        }
+
+        ActivityBuilder setLaunchedFromUid(int uid) {
+            mLaunchedFromUid = uid;
+            return this;
+        }
+
+        ActivityBuilder setUseProcess(WindowProcessController wpc) {
+            mWpc = wpc;
+            return this;
+        }
+
+        ActivityBuilder setAffinity(String affinity) {
+            mAffinity = affinity;
+            return this;
+        }
+
+        ActivityRecord build() {
+            SystemServicesTestRule.checkHoldsLock(mService.mGlobalLock);
+            try {
+                mService.deferWindowLayout();
+                return buildInner();
+            } finally {
+                mService.continueWindowLayout();
+            }
+        }
+
+        ActivityRecord buildInner() {
+            if (mComponent == null) {
+                final int id = sCurrentActivityId++;
+                mComponent = ComponentName.createRelative(DEFAULT_COMPONENT_PACKAGE_NAME,
+                        DEFAULT_COMPONENT_CLASS_NAME + id);
+            }
+
+            if (mCreateTask) {
+                mTask = new TaskBuilder(mService.mStackSupervisor)
+                        .setComponent(mComponent)
+                        .setStack(mStack).build();
+            } else if (mTask == null && mStack != null && DisplayContent.alwaysCreateStack(
+                    mStack.getWindowingMode(), mStack.getActivityType())) {
+                // The stack can be the task root.
+                mTask = mStack;
+            }
+
+            Intent intent = new Intent();
+            intent.setComponent(mComponent);
+            if (mIntentExtras != null) {
+                intent.putExtras(mIntentExtras);
+            }
+            final ActivityInfo aInfo = new ActivityInfo();
+            aInfo.applicationInfo = new ApplicationInfo();
+            aInfo.applicationInfo.targetSdkVersion = Build.VERSION_CODES.CUR_DEVELOPMENT;
+            aInfo.applicationInfo.packageName = mComponent.getPackageName();
+            aInfo.applicationInfo.uid = mUid;
+            aInfo.processName = mProcessName;
+            aInfo.packageName = mComponent.getPackageName();
+            aInfo.name = mComponent.getClassName();
+            if (mTargetActivity != null) {
+                aInfo.targetActivity = mTargetActivity;
+            }
+            aInfo.flags |= mActivityFlags;
+            aInfo.launchMode = mLaunchMode;
+            aInfo.resizeMode = mResizeMode;
+            aInfo.maxAspectRatio = mMaxAspectRatio;
+            aInfo.screenOrientation = mScreenOrientation;
+            aInfo.configChanges |= mConfigChanges;
+            aInfo.taskAffinity = mAffinity;
+
+            ActivityOptions options = null;
+            if (mLaunchTaskBehind) {
+                options = ActivityOptions.makeTaskLaunchBehind();
+            }
+
+            final ActivityRecord activity = new ActivityRecord(mService, null /* caller */,
+                    mLaunchedFromPid /* launchedFromPid */, mLaunchedFromUid /* launchedFromUid */,
+                    null, null, intent, null, aInfo /*aInfo*/, new Configuration(),
+                    null /* resultTo */, null /* resultWho */, 0 /* reqCode */,
+                    false /*componentSpecified*/, false /* rootVoiceInteraction */,
+                    mService.mStackSupervisor, options, null /* sourceRecord */);
+            spyOn(activity);
+            if (mTask != null) {
+                // fullscreen value is normally read from resources in ctor, so for testing we need
+                // to set it somewhere else since we can't mock resources.
+                doReturn(true).when(activity).occludesParent();
+                doReturn(true).when(activity).fillsParent();
+                mTask.addChild(activity);
+                // Make visible by default...
+                activity.setVisible(true);
+            }
+
+            final WindowProcessController wpc;
+            if (mWpc != null) {
+                wpc = mWpc;
+            } else {
+                wpc = new WindowProcessController(mService,
+                        aInfo.applicationInfo, mProcessName, mUid,
+                        UserHandle.getUserId(12345), mock(Object.class),
+                        mock(WindowProcessListener.class));
+                wpc.setThread(mock(IApplicationThread.class));
+            }
+            wpc.setThread(mock(IApplicationThread.class));
+            activity.setProcess(wpc);
+            doReturn(wpc).when(mService).getProcessController(
+                    activity.processName, activity.info.applicationInfo.uid);
+
+            // Resume top activities to make sure all other signals in the system are connected.
+            mService.mRootWindowContainer.resumeFocusedStacksTopActivities();
+            return activity;
+        }
+    }
+
+    /**
+     * Builder for creating new tasks.
+     */
+    protected static class TaskBuilder {
+        private final ActivityStackSupervisor mSupervisor;
+
+        private ComponentName mComponent;
+        private String mPackage;
+        private int mFlags = 0;
+        // Task id 0 is reserved in ARC for the home app.
+        private int mTaskId = SystemServicesTestRule.sNextTaskId++;
+        private int mUserId = 0;
+        private IVoiceInteractionSession mVoiceSession;
+        private boolean mCreateStack = true;
+
+        private Task mStack;
+        private TaskDisplayArea mTaskDisplayArea;
+
+        TaskBuilder(ActivityStackSupervisor supervisor) {
+            mSupervisor = supervisor;
+        }
+
+        TaskBuilder setComponent(ComponentName component) {
+            mComponent = component;
+            return this;
+        }
+
+        TaskBuilder setPackage(String packageName) {
+            mPackage = packageName;
+            return this;
+        }
+
+        /**
+         * Set to {@code true} by default, set to {@code false} to prevent the task from
+         * automatically creating a parent stack.
+         */
+        TaskBuilder setCreateStack(boolean createStack) {
+            mCreateStack = createStack;
+            return this;
+        }
+
+        TaskBuilder setVoiceSession(IVoiceInteractionSession session) {
+            mVoiceSession = session;
+            return this;
+        }
+
+        TaskBuilder setFlags(int flags) {
+            mFlags = flags;
+            return this;
+        }
+
+        TaskBuilder setTaskId(int taskId) {
+            mTaskId = taskId;
+            return this;
+        }
+
+        TaskBuilder setUserId(int userId) {
+            mUserId = userId;
+            return this;
+        }
+
+        TaskBuilder setStack(Task stack) {
+            mStack = stack;
+            return this;
+        }
+
+        TaskBuilder setDisplay(DisplayContent display) {
+            mTaskDisplayArea = display.getDefaultTaskDisplayArea();
+            return this;
+        }
+
+        Task build() {
+            SystemServicesTestRule.checkHoldsLock(mSupervisor.mService.mGlobalLock);
+
+            if (mStack == null && mCreateStack) {
+                TaskDisplayArea displayArea = mTaskDisplayArea != null ? mTaskDisplayArea
+                        : mSupervisor.mRootWindowContainer.getDefaultTaskDisplayArea();
+                mStack = displayArea.createStack(
+                        WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, true /* onTop */);
+                spyOn(mStack);
+            }
+
+            final ActivityInfo aInfo = new ActivityInfo();
+            aInfo.applicationInfo = new ApplicationInfo();
+            aInfo.applicationInfo.packageName = mPackage;
+
+            Intent intent = new Intent();
+            if (mComponent == null) {
+                mComponent = ComponentName.createRelative(DEFAULT_COMPONENT_PACKAGE_NAME,
+                        DEFAULT_COMPONENT_CLASS_NAME);
+            }
+
+            intent.setComponent(mComponent);
+            intent.setFlags(mFlags);
+
+            final Task task = new Task(mSupervisor.mService, mTaskId, aInfo,
+                    intent /*intent*/, mVoiceSession, null /*_voiceInteractor*/,
+                    null /*taskDescription*/, mStack);
+            spyOn(task);
+            task.mUserId = mUserId;
+
+            if (mStack != null) {
+                mStack.moveToFront("test");
+                mStack.addChild(task, true, true);
+            }
+
+            return task;
+        }
+    }
+
+    static class StackBuilder {
+        private final RootWindowContainer mRootWindowContainer;
+        private DisplayContent mDisplay;
+        private TaskDisplayArea mTaskDisplayArea;
+        private int mStackId = -1;
+        private int mWindowingMode = WINDOWING_MODE_UNDEFINED;
+        private int mActivityType = ACTIVITY_TYPE_STANDARD;
+        private boolean mOnTop = true;
+        private boolean mCreateActivity = true;
+        private ActivityInfo mInfo;
+        private Intent mIntent;
+
+        StackBuilder(RootWindowContainer root) {
+            mRootWindowContainer = root;
+            mDisplay = mRootWindowContainer.getDefaultDisplay();
+            mTaskDisplayArea = mDisplay.getDefaultTaskDisplayArea();
+        }
+
+        StackBuilder setWindowingMode(int windowingMode) {
+            mWindowingMode = windowingMode;
+            return this;
+        }
+
+        StackBuilder setActivityType(int activityType) {
+            mActivityType = activityType;
+            return this;
+        }
+
+        StackBuilder setStackId(int stackId) {
+            mStackId = stackId;
+            return this;
+        }
+
+        /**
+         * Set the parent {@link DisplayContent} and use the default task display area. Overrides
+         * the task display area, if was set before.
+         */
+        StackBuilder setDisplay(DisplayContent display) {
+            mDisplay = display;
+            mTaskDisplayArea = mDisplay.getDefaultTaskDisplayArea();
+            return this;
+        }
+
+        /** Set the parent {@link TaskDisplayArea}. Overrides the display, if was set before. */
+        StackBuilder setTaskDisplayArea(TaskDisplayArea taskDisplayArea) {
+            mTaskDisplayArea = taskDisplayArea;
+            mDisplay = mTaskDisplayArea.mDisplayContent;
+            return this;
+        }
+
+        StackBuilder setOnTop(boolean onTop) {
+            mOnTop = onTop;
+            return this;
+        }
+
+        StackBuilder setCreateActivity(boolean createActivity) {
+            mCreateActivity = createActivity;
+            return this;
+        }
+
+        StackBuilder setActivityInfo(ActivityInfo info) {
+            mInfo = info;
+            return this;
+        }
+
+        StackBuilder setIntent(Intent intent) {
+            mIntent = intent;
+            return this;
+        }
+
+        Task build() {
+            SystemServicesTestRule.checkHoldsLock(mRootWindowContainer.mWmService.mGlobalLock);
+
+            final int stackId = mStackId >= 0 ? mStackId : mTaskDisplayArea.getNextStackId();
+            final Task stack = mTaskDisplayArea.createStackUnchecked(
+                    mWindowingMode, mActivityType, stackId, mOnTop, mInfo, mIntent,
+                    false /* createdByOrganizer */);
+            final ActivityStackSupervisor supervisor = mRootWindowContainer.mStackSupervisor;
+
+            if (mCreateActivity) {
+                new ActivityBuilder(supervisor.mService)
+                        .setCreateTask(true)
+                        .setStack(stack)
+                        .build();
+                if (mOnTop) {
+                    // We move the task to front again in order to regain focus after activity
+                    // added to the stack. Or {@link DisplayContent#mPreferredTopFocusableStack}
+                    // could be other stacks (e.g. home stack).
+                    stack.moveToFront("createActivityStack");
+                } else {
+                    stack.moveToBack("createActivityStack", null);
+                }
+            }
+            spyOn(stack);
+
+            doNothing().when(stack).startActivityLocked(
+                    any(), any(), anyBoolean(), anyBoolean(), any());
+
+            return stack;
+        }
+
+    }
+
+    static class TestSplitOrganizer extends ITaskOrganizer.Stub {
+        final ActivityTaskManagerService mService;
+        Task mPrimary;
+        Task mSecondary;
+        boolean mInSplit = false;
+        // moves everything to secondary. Most tests expect this since sysui usually does it.
+        boolean mMoveToSecondaryOnEnter = true;
+        int mDisplayId;
+        TestSplitOrganizer(ActivityTaskManagerService service, int displayId) {
+            mService = service;
+            mDisplayId = displayId;
+            mService.mTaskOrganizerController.registerTaskOrganizer(this);
+            WindowContainerToken primary = mService.mTaskOrganizerController.createRootTask(
+                    displayId, WINDOWING_MODE_SPLIT_SCREEN_PRIMARY).token;
+            mPrimary = WindowContainer.fromBinder(primary.asBinder()).asTask();
+            WindowContainerToken secondary = mService.mTaskOrganizerController.createRootTask(
+                    displayId, WINDOWING_MODE_SPLIT_SCREEN_SECONDARY).token;
+            mSecondary = WindowContainer.fromBinder(secondary.asBinder()).asTask();
+        }
+        TestSplitOrganizer(ActivityTaskManagerService service) {
+            this(service,
+                    service.mStackSupervisor.mRootWindowContainer.getDefaultDisplay().mDisplayId);
+        }
+        public void setMoveToSecondaryOnEnter(boolean move) {
+            mMoveToSecondaryOnEnter = move;
+        }
+        @Override
+        public void onTaskAppeared(ActivityManager.RunningTaskInfo info, SurfaceControl leash) {
+        }
+        @Override
+        public void onTaskVanished(ActivityManager.RunningTaskInfo info) {
+        }
+        @Override
+        public void onTaskInfoChanged(ActivityManager.RunningTaskInfo info) {
+            if (mInSplit) {
+                return;
+            }
+            if (info.topActivityType == ACTIVITY_TYPE_UNDEFINED) {
+                // Not populated
+                return;
+            }
+            if (info.configuration.windowConfiguration.getWindowingMode()
+                    != WINDOWING_MODE_SPLIT_SCREEN_PRIMARY) {
+                return;
+            }
+            mInSplit = true;
+            if (!mMoveToSecondaryOnEnter) {
+                return;
+            }
+            mService.mTaskOrganizerController.setLaunchRoot(mDisplayId,
+                    mSecondary.mRemoteToken.toWindowContainerToken());
+            DisplayContent dc = mService.mRootWindowContainer.getDisplayContent(mDisplayId);
+            dc.forAllTaskDisplayAreas(taskDisplayArea -> {
+                for (int sNdx = taskDisplayArea.getStackCount() - 1; sNdx >= 0; --sNdx) {
+                    final Task stack = taskDisplayArea.getStackAt(sNdx);
+                    if (!WindowConfiguration.isSplitScreenWindowingMode(stack.getWindowingMode())) {
+                        stack.reparent(mSecondary, POSITION_BOTTOM);
+                    }
+                }
+            });
+        }
+        @Override
+        public void onBackPressedOnTaskRoot(ActivityManager.RunningTaskInfo taskInfo) {
+        }
+    }
+
+    static TestWindowToken createTestWindowToken(int type, DisplayContent dc) {
+        return createTestWindowToken(type, dc, false /* persistOnEmpty */);
+    }
+
+    static TestWindowToken createTestWindowToken(int type, DisplayContent dc,
+            boolean persistOnEmpty) {
+        SystemServicesTestRule.checkHoldsLock(dc.mWmService.mGlobalLock);
+
+        return new TestWindowToken(type, dc, persistOnEmpty);
+    }
+
+    /** Used so we can gain access to some protected members of the {@link WindowToken} class */
+    static class TestWindowToken extends WindowToken {
+
+        private TestWindowToken(int type, DisplayContent dc, boolean persistOnEmpty) {
+            super(dc.mWmService, mock(IBinder.class), type, persistOnEmpty, dc,
+                    false /* ownerCanManageAppTokens */);
+        }
+
+        int getWindowsCount() {
+            return mChildren.size();
+        }
+
+        boolean hasWindow(WindowState w) {
+            return mChildren.contains(w);
+        }
+    }
+
+    /** Used to track resize reports. */
+    static class TestWindowState extends WindowState {
+        boolean mResizeReported;
+
+        TestWindowState(WindowManagerService service, Session session, IWindow window,
+                WindowManager.LayoutParams attrs, WindowToken token) {
+            super(service, session, window, token, null, OP_NONE, 0, attrs, 0, 0, 0,
+                    false /* ownerCanAddInternalSystemWindow */);
+        }
+
+        @Override
+        void reportResized() {
+            super.reportResized();
+            mResizeReported = true;
+        }
+
+        @Override
+        public boolean isGoneForLayoutLw() {
+            return false;
+        }
+
+        @Override
+        void updateResizingWindowIfNeeded() {
+            // Used in AppWindowTokenTests#testLandscapeSeascapeRotationRelayout to deceive
+            // the system that it can actually update the window.
+            boolean hadSurface = mHasSurface;
+            mHasSurface = true;
+
+            super.updateResizingWindowIfNeeded();
+
+            mHasSurface = hadSurface;
+        }
+    }
 }
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowTokenTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowTokenTests.java
index f185da3..e0785c1 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowTokenTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowTokenTests.java
@@ -27,6 +27,7 @@
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotEquals;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
@@ -58,8 +59,7 @@
 
     @Test
     public void testAddWindow() {
-        final WindowTestUtils.TestWindowToken token =
-                WindowTestUtils.createTestWindowToken(0, mDisplayContent);
+        final TestWindowToken token = createTestWindowToken(0, mDisplayContent);
 
         assertEquals(0, token.getWindowsCount());
 
@@ -93,7 +93,7 @@
     @Test
     public void testChildRemoval() {
         final DisplayContent dc = mDisplayContent;
-        final WindowTestUtils.TestWindowToken token = WindowTestUtils.createTestWindowToken(0, dc);
+        final TestWindowToken token = createTestWindowToken(0, dc);
 
         assertEquals(token, dc.getWindowToken(token.token));
 
@@ -116,7 +116,7 @@
      */
     @Test
     public void testTokenRemovalProcess() {
-        final WindowTestUtils.TestWindowToken token = WindowTestUtils.createTestWindowToken(
+        final TestWindowToken token = createTestWindowToken(
                 TYPE_TOAST, mDisplayContent, true /* persistOnEmpty */);
 
         // Verify that the token is on the display
@@ -146,29 +146,40 @@
         assertEquals(0, token.getWindowsCount());
     }
 
-    @UseTestDisplay(addWindows = { W_ACTIVITY, W_WALLPAPER })
     @Test
     public void testFinishFixedRotationTransform() {
-        final WindowToken appToken = mAppWindow.mToken;
-        final WindowToken wallpaperToken = mWallpaperWindow.mToken;
+        final WindowToken[] tokens = new WindowToken[3];
+        for (int i = 0; i < tokens.length; i++) {
+            tokens[i] = createTestWindowToken(TYPE_APPLICATION_OVERLAY, mDisplayContent);
+        }
+
         final Configuration config = new Configuration(mDisplayContent.getConfiguration());
         final int originalRotation = config.windowConfiguration.getRotation();
         final int targetRotation = (originalRotation + 1) % 4;
 
         config.windowConfiguration.setRotation(targetRotation);
-        appToken.applyFixedRotationTransform(mDisplayInfo, mDisplayContent.mDisplayFrames, config);
-        wallpaperToken.linkFixedRotationTransform(appToken);
+        tokens[0].applyFixedRotationTransform(mDisplayInfo, mDisplayContent.mDisplayFrames, config);
+        tokens[1].linkFixedRotationTransform(tokens[0]);
 
         // The window tokens should apply the rotation by the transformation.
-        assertEquals(targetRotation, appToken.getWindowConfiguration().getRotation());
-        assertEquals(targetRotation, wallpaperToken.getWindowConfiguration().getRotation());
+        assertEquals(targetRotation, tokens[0].getWindowConfiguration().getRotation());
+        assertEquals(targetRotation, tokens[1].getWindowConfiguration().getRotation());
 
-        // The display doesn't rotate, the transformation will be canceled.
-        mAppWindow.mToken.finishFixedRotationTransform();
+        tokens[2].applyFixedRotationTransform(mDisplayInfo, mDisplayContent.mDisplayFrames, config);
+        // The tokens[1] was linked to tokens[0], this should make tokens[1] link to tokens[2].
+        tokens[1].linkFixedRotationTransform(tokens[2]);
 
-        // The window tokens should restore to the original rotation.
-        assertEquals(originalRotation, appToken.getWindowConfiguration().getRotation());
-        assertEquals(originalRotation, wallpaperToken.getWindowConfiguration().getRotation());
+        // Assume the display doesn't rotate, the transformation will be canceled.
+        tokens[0].finishFixedRotationTransform();
+
+        // The tokens[0] should restore to the original rotation.
+        assertEquals(originalRotation, tokens[0].getWindowConfiguration().getRotation());
+        // The tokens[1] is linked to tokens[2], it should keep the target rotation.
+        assertNotEquals(originalRotation, tokens[1].getWindowConfiguration().getRotation());
+
+        tokens[2].finishFixedRotationTransform();
+        // The rotation of tokens[1] should be restored because its linked state is finished.
+        assertEquals(originalRotation, tokens[1].getWindowConfiguration().getRotation());
     }
 
     /**
diff --git a/services/usage/java/com/android/server/usage/UsageStatsService.java b/services/usage/java/com/android/server/usage/UsageStatsService.java
index 9b18ec6..81aad97 100644
--- a/services/usage/java/com/android/server/usage/UsageStatsService.java
+++ b/services/usage/java/com/android/server/usage/UsageStatsService.java
@@ -61,7 +61,6 @@
 import android.content.pm.PackageManagerInternal;
 import android.content.pm.ParceledListSlice;
 import android.content.pm.ShortcutServiceInternal;
-import android.content.pm.UserInfo;
 import android.content.res.Configuration;
 import android.os.Binder;
 import android.os.Environment;
@@ -92,6 +91,7 @@
 import com.android.internal.util.IndentingPrintWriter;
 import com.android.server.LocalServices;
 import com.android.server.SystemService;
+import com.android.server.SystemService.TargetUser;
 import com.android.server.usage.AppStandbyInternal.AppIdleStateChangeListener;
 
 import java.io.BufferedReader;
@@ -291,42 +291,40 @@
     }
 
     @Override
-    public void onStartUser(UserInfo userInfo) {
+    public void onUserStarting(@NonNull TargetUser user) {
         // Create an entry in the user state map to indicate that the user has been started but
         // not necessarily unlocked. This will ensure that reported events are flushed to disk
         // event if the user is never unlocked (following the logic in #flushToDiskLocked)
-        mUserState.put(userInfo.id, null);
-        super.onStartUser(userInfo);
+        mUserState.put(user.getUserIdentifier(), null);
     }
 
     @Override
-    public void onUnlockUser(@NonNull UserInfo userInfo) {
-        mHandler.obtainMessage(MSG_UNLOCKED_USER, userInfo.id, 0).sendToTarget();
-        super.onUnlockUser(userInfo);
+    public void onUserUnlocking(@NonNull TargetUser user) {
+        mHandler.obtainMessage(MSG_UNLOCKED_USER, user.getUserIdentifier(), 0).sendToTarget();
     }
 
     @Override
-    public void onStopUser(@NonNull UserInfo userInfo) {
+    public void onUserStopping(@NonNull TargetUser user) {
+        final int userId = user.getUserIdentifier();
+
         synchronized (mLock) {
             // User was started but never unlocked so no need to report a user stopped event
-            if (!mUserUnlockedStates.get(userInfo.id)) {
-                persistPendingEventsLocked(userInfo.id);
-                super.onStopUser(userInfo);
+            if (!mUserUnlockedStates.get(userId)) {
+                persistPendingEventsLocked(userId);
                 return;
             }
 
             // Report a user stopped event before persisting all stats to disk via the user service
             final Event event = new Event(USER_STOPPED, SystemClock.elapsedRealtime());
             event.mPackage = Event.DEVICE_EVENT_PACKAGE_NAME;
-            reportEvent(event, userInfo.id);
-            final UserUsageStatsService userService = mUserState.get(userInfo.id);
+            reportEvent(event, userId);
+            final UserUsageStatsService userService = mUserState.get(userId);
             if (userService != null) {
                 userService.userStopped();
             }
-            mUserUnlockedStates.put(userInfo.id, false);
-            mUserState.put(userInfo.id, null); // release the service (mainly for GC)
+            mUserUnlockedStates.put(userId, false);
+            mUserState.put(userId, null); // release the service (mainly for GC)
         }
-        super.onStopUser(userInfo);
     }
 
     private void onUserUnlocked(int userId) {
@@ -764,11 +762,12 @@
                 return;
             }
 
-            final LinkedList<Event> events = mReportedEvents.get(userId, new LinkedList<>());
-            events.add(event);
-            if (mReportedEvents.get(userId) == null) {
+            LinkedList<Event> events = mReportedEvents.get(userId);
+            if (events == null) {
+                events = new LinkedList<>();
                 mReportedEvents.put(userId, events);
             }
+            events.add(event);
             if (events.size() == 1) {
                 // Every time a file is persisted to disk, mReportedEvents is cleared for this user
                 // so trigger a flush to disk every time the first event has been added.
@@ -1654,8 +1653,8 @@
 
             // If the calling app is asking about itself, continue, else check for permission.
             if (packageName.equals(callingPackage)) {
-                final int actualCallingUid = mPackageManagerInternal.getPackageUidInternal(
-                        callingPackage, 0, userId);
+                final int actualCallingUid = mPackageManagerInternal.getPackageUid(
+                        callingPackage, /* flags= */ 0, userId);
                 if (actualCallingUid != callingUid) {
                     return false;
                 }
diff --git a/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerService.java b/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerService.java
index 534fe03..26d46db 100644
--- a/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerService.java
+++ b/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerService.java
@@ -221,14 +221,6 @@
         }
     }
 
-    @Override
-    public void onStartUser(int userHandle) {
-    }
-
-    @Override
-    public void onSwitchUser(int userHandle) {
-    }
-
     private synchronized void initSoundTriggerHelper() {
         if (mSoundTriggerHelper == null) {
             mSoundTriggerHelper = new SoundTriggerHelper(mContext);
@@ -1284,32 +1276,25 @@
          * @return The initialized AudioRecord
          */
         private @NonNull AudioRecord createAudioRecordForEvent(
-                @NonNull SoundTrigger.GenericRecognitionEvent event) {
+                @NonNull SoundTrigger.GenericRecognitionEvent event)
+                throws IllegalArgumentException, UnsupportedOperationException {
             AudioAttributes.Builder attributesBuilder = new AudioAttributes.Builder();
             attributesBuilder.setInternalCapturePreset(MediaRecorder.AudioSource.HOTWORD);
             AudioAttributes attributes = attributesBuilder.build();
 
-            // Use same AudioFormat processing as in RecognitionEvent.fromParcel
             AudioFormat originalFormat = event.getCaptureFormat();
-            AudioFormat captureFormat = (new AudioFormat.Builder())
-                    .setChannelMask(originalFormat.getChannelMask())
-                    .setEncoding(originalFormat.getEncoding())
-                    .setSampleRate(originalFormat.getSampleRate())
-                    .build();
-
-            int bufferSize = AudioRecord.getMinBufferSize(
-                    captureFormat.getSampleRate() == AudioFormat.SAMPLE_RATE_UNSPECIFIED
-                            ? AudioFormat.SAMPLE_RATE_HZ_MAX
-                            : captureFormat.getSampleRate(),
-                    captureFormat.getChannelCount() == 2
-                            ? AudioFormat.CHANNEL_IN_STEREO
-                            : AudioFormat.CHANNEL_IN_MONO,
-                    captureFormat.getEncoding());
 
             sEventLogger.log(new SoundTriggerLogger.StringEvent("createAudioRecordForEvent"));
 
-            return new AudioRecord(attributes, captureFormat, bufferSize,
-                    event.getCaptureSession());
+            return (new AudioRecord.Builder())
+                        .setAudioAttributes(attributes)
+                        .setAudioFormat((new AudioFormat.Builder())
+                            .setChannelMask(originalFormat.getChannelMask())
+                            .setEncoding(originalFormat.getEncoding())
+                            .setSampleRate(originalFormat.getSampleRate())
+                            .build())
+                        .setSessionId(event.getCaptureSession())
+                        .build();
         }
 
         @Override
@@ -1335,12 +1320,14 @@
                     // execute if throttled:
                     () -> {
                         if (event.isCaptureAvailable()) {
-                            AudioRecord capturedData = createAudioRecordForEvent(event);
-
-                            // Currently we need to start and release the audio record to reset
-                            // the DSP even if we don't want to process the event
-                            capturedData.startRecording();
-                            capturedData.release();
+                            try {
+                                AudioRecord capturedData = createAudioRecordForEvent(event);
+                                capturedData.startRecording();
+                                capturedData.release();
+                            } catch (IllegalArgumentException | UnsupportedOperationException e) {
+                                Slog.w(TAG, mPuuid + ": createAudioRecordForEvent(" + event
+                                        + "), failed to create AudioRecord");
+                            }
                         }
                     }));
         }
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
index 9621f68..a2215ce 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
@@ -164,13 +164,13 @@
         }
     }
 
-    private boolean isSupported(UserInfo user) {
+    @Override
+    public boolean isUserSupported(@NonNull TargetUser user) {
         return user.isFull();
     }
 
-    @Override
-    public boolean isUserSupported(TargetUser user) {
-        return isSupported(user.getUserInfo());
+    private boolean isUserSupported(@NonNull UserInfo user) {
+        return user.isFull();
     }
 
     @Override
@@ -189,10 +189,10 @@
     }
 
     @Override
-    public void onSwitchUser(@NonNull UserInfo from, @NonNull UserInfo to) {
+    public void onUserSwitching(@Nullable TargetUser from, @NonNull TargetUser to) {
         if (DEBUG_USER) Slog.d(TAG, "onSwitchUser(" + from + " > " + to + ")");
 
-        mServiceStub.switchUser(to.id);
+        mServiceStub.switchUser(to.getUserIdentifier());
     }
 
     class LocalService extends VoiceInteractionManagerInternal {
@@ -461,7 +461,7 @@
         private void setCurrentUserLocked(@UserIdInt int userHandle) {
             mCurUser = userHandle;
             final UserInfo userInfo = mUserManagerInternal.getUserInfo(mCurUser);
-            mCurUserSupported = isSupported(userInfo);
+            mCurUserSupported = isUserSupported(userInfo);
         }
 
         public void switchUser(@UserIdInt int userHandle) {
@@ -967,10 +967,10 @@
                 throw new IllegalArgumentException("Illegal argument(s) in getKeyphraseSoundModel");
             }
 
-            final int callingUid = UserHandle.getCallingUserId();
+            final int callingUserId = UserHandle.getCallingUserId();
             final long caller = Binder.clearCallingIdentity();
             try {
-                return mDbHelper.getKeyphraseSoundModel(keyphraseId, callingUid, bcp47Locale);
+                return mDbHelper.getKeyphraseSoundModel(keyphraseId, callingUserId, bcp47Locale);
             } finally {
                 Binder.restoreCallingIdentity(caller);
             }
@@ -1010,7 +1010,7 @@
                         "Illegal argument(s) in deleteKeyphraseSoundModel");
             }
 
-            final int callingUid = UserHandle.getCallingUserId();
+            final int callingUserId = UserHandle.getCallingUserId();
             final long caller = Binder.clearCallingIdentity();
             boolean deleted = false;
             try {
@@ -1018,7 +1018,8 @@
                 if (unloadStatus != SoundTriggerInternal.STATUS_OK) {
                     Slog.w(TAG, "Unable to unload keyphrase sound model:" + unloadStatus);
                 }
-                deleted = mDbHelper.deleteKeyphraseSoundModel(keyphraseId, callingUid, bcp47Locale);
+                deleted = mDbHelper.deleteKeyphraseSoundModel(
+                        keyphraseId, callingUserId, bcp47Locale);
                 return deleted ? SoundTriggerInternal.STATUS_OK : SoundTriggerInternal.STATUS_ERROR;
             } finally {
                 if (deleted) {
@@ -1045,11 +1046,11 @@
                 throw new IllegalArgumentException("Illegal argument(s) in isEnrolledForKeyphrase");
             }
 
-            final int callingUid = UserHandle.getCallingUserId();
+            final int callingUserId = UserHandle.getCallingUserId();
             final long caller = Binder.clearCallingIdentity();
             try {
                 KeyphraseSoundModel model =
-                        mDbHelper.getKeyphraseSoundModel(keyphraseId, callingUid, bcp47Locale);
+                        mDbHelper.getKeyphraseSoundModel(keyphraseId, callingUserId, bcp47Locale);
                 return model != null;
             } finally {
                 Binder.restoreCallingIdentity(caller);
@@ -1067,11 +1068,11 @@
                 throw new IllegalArgumentException("Illegal argument(s) in isEnrolledForKeyphrase");
             }
 
-            final int callingUid = UserHandle.getCallingUserId();
+            final int callingUserId = UserHandle.getCallingUserId();
             final long caller = Binder.clearCallingIdentity();
             try {
                 KeyphraseSoundModel model =
-                        mDbHelper.getKeyphraseSoundModel(keyphrase, callingUid, bcp47Locale);
+                        mDbHelper.getKeyphraseSoundModel(keyphrase, callingUserId, bcp47Locale);
                 if (model == null) {
                     return null;
                 }
@@ -1118,11 +1119,11 @@
                 }
             }
 
-            int callingUid = UserHandle.getCallingUserId();
+            final int callingUserId = UserHandle.getCallingUserId();
             final long caller = Binder.clearCallingIdentity();
             try {
                 KeyphraseSoundModel soundModel =
-                        mDbHelper.getKeyphraseSoundModel(keyphraseId, callingUid, bcp47Locale);
+                        mDbHelper.getKeyphraseSoundModel(keyphraseId, callingUserId, bcp47Locale);
                 if (soundModel == null
                         || soundModel.getUuid() == null
                         || soundModel.getKeyphrases() == null) {
diff --git a/startop/OWNERS b/startop/OWNERS
index 3394be9..2d1eb38 100644
--- a/startop/OWNERS
+++ b/startop/OWNERS
@@ -1,7 +1,7 @@
 # mailing list: startop-eng@google.com
+calin@google.com
 chriswailes@google.com
 eholk@google.com
 iam@google.com
 mathieuc@google.com
-sehr@google.com
 yawanng@google.com
diff --git a/startop/iorap/src/com/google/android/startop/iorap/IorapForwardingService.java b/startop/iorap/src/com/google/android/startop/iorap/IorapForwardingService.java
index 8f1d0ad..3104c7e 100644
--- a/startop/iorap/src/com/google/android/startop/iorap/IorapForwardingService.java
+++ b/startop/iorap/src/com/google/android/startop/iorap/IorapForwardingService.java
@@ -123,7 +123,7 @@
         try {
             iorap = IIorap.Stub.asInterface(ServiceManager.getServiceOrThrow("iorapd"));
         } catch (ServiceManager.ServiceNotFoundException e) {
-            handleRemoteError(e);
+            Log.w(TAG, e.getMessage());
             return null;
         }
 
diff --git a/telecomm/java/android/telecom/ConnectionService.java b/telecomm/java/android/telecom/ConnectionService.java
index 3646647..6288bc1 100755
--- a/telecomm/java/android/telecom/ConnectionService.java
+++ b/telecomm/java/android/telecom/ConnectionService.java
@@ -2488,6 +2488,42 @@
     }
 
     /**
+     * Ask some other {@code ConnectionService} to create a {@code RemoteConference} given an
+     * incoming request. This is used by {@code ConnectionService}s that are registered with
+     * {@link PhoneAccount#CAPABILITY_ADHOC_CONFERENCE_CALLING}.
+     *
+     * @param connectionManagerPhoneAccount See description at
+     *          {@link #onCreateOutgoingConnection(PhoneAccountHandle, ConnectionRequest)}.
+     * @param request Details about the incoming conference call.
+     * @return The {@code RemoteConference} object to satisfy this call, or {@code null} to not
+     *         handle the call.
+     */
+    public final @Nullable RemoteConference createRemoteIncomingConference(
+            @Nullable PhoneAccountHandle connectionManagerPhoneAccount,
+            @Nullable ConnectionRequest request) {
+        return mRemoteConnectionManager.createRemoteConference(connectionManagerPhoneAccount,
+                request, true);
+    }
+
+    /**
+     * Ask some other {@code ConnectionService} to create a {@code RemoteConference} given an
+     * outgoing request. This is used by {@code ConnectionService}s that are registered with
+     * {@link PhoneAccount#CAPABILITY_ADHOC_CONFERENCE_CALLING}.
+     *
+     * @param connectionManagerPhoneAccount See description at
+     *          {@link #onCreateOutgoingConnection(PhoneAccountHandle, ConnectionRequest)}.
+     * @param request Details about the outgoing conference call.
+     * @return The {@code RemoteConference} object to satisfy this call, or {@code null} to not
+     *         handle the call.
+     */
+    public final @Nullable RemoteConference createRemoteOutgoingConference(
+            @Nullable PhoneAccountHandle connectionManagerPhoneAccount,
+            @Nullable ConnectionRequest request) {
+        return mRemoteConnectionManager.createRemoteConference(connectionManagerPhoneAccount,
+                request, false);
+    }
+
+    /**
      * Indicates to the relevant {@code RemoteConnectionService} that the specified
      * {@link RemoteConnection}s should be merged into a conference call.
      * <p>
diff --git a/telecomm/java/android/telecom/RemoteConference.java b/telecomm/java/android/telecom/RemoteConference.java
index 502b7c0..e024e61 100644
--- a/telecomm/java/android/telecom/RemoteConference.java
+++ b/telecomm/java/android/telecom/RemoteConference.java
@@ -16,14 +16,14 @@
 
 package android.telecom;
 
-import com.android.internal.telecom.IConnectionService;
-
 import android.annotation.Nullable;
 import android.annotation.SystemApi;
 import android.os.Bundle;
 import android.os.Handler;
 import android.os.RemoteException;
 
+import com.android.internal.telecom.IConnectionService;
+
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.List;
@@ -155,6 +155,14 @@
     }
 
     /** @hide */
+    RemoteConference(DisconnectCause disconnectCause) {
+        mId = "NULL";
+        mConnectionService = null;
+        mState = Connection.STATE_DISCONNECTED;
+        mDisconnectCause = disconnectCause;
+    }
+
+    /** @hide */
     String getId() {
         return mId;
     }
@@ -583,4 +591,16 @@
             }
         }
     }
+
+    /**
+     * Create a {@link RemoteConference} represents a failure, and which will
+     * be in {@link Connection#STATE_DISCONNECTED}.
+     *
+     * @param disconnectCause The disconnect cause.
+     * @return a failed {@link RemoteConference}
+     * @hide
+     */
+    public static RemoteConference failure(DisconnectCause disconnectCause) {
+        return new RemoteConference(disconnectCause);
+    }
 }
diff --git a/telecomm/java/android/telecom/RemoteConnection.java b/telecomm/java/android/telecom/RemoteConnection.java
index df336257..52210a5 100644
--- a/telecomm/java/android/telecom/RemoteConnection.java
+++ b/telecomm/java/android/telecom/RemoteConnection.java
@@ -16,10 +16,6 @@
 
 package android.telecom;
 
-import com.android.internal.telecom.IConnectionService;
-import com.android.internal.telecom.IVideoCallback;
-import com.android.internal.telecom.IVideoProvider;
-
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.SystemApi;
@@ -33,6 +29,10 @@
 import android.telecom.Logging.Session;
 import android.view.Surface;
 
+import com.android.internal.telecom.IConnectionService;
+import com.android.internal.telecom.IVideoCallback;
+import com.android.internal.telecom.IVideoProvider;
+
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collections;
@@ -1114,6 +1114,23 @@
     }
 
     /**
+     * Instructs this {@link RemoteConnection} to initiate a conference with a list of
+     * participants.
+     * <p>
+     *
+     * @param participants with which conference call will be formed.
+     */
+    public void addConferenceParticipants(@NonNull List<Uri> participants) {
+        try {
+            if (mConnected) {
+                mConnectionService.addConferenceParticipants(mConnectionId, participants,
+                        null /*Session.Info*/);
+            }
+        } catch (RemoteException ignored) {
+        }
+    }
+
+    /**
      * Set the audio state of this {@code RemoteConnection}.
      *
      * @param state The audio state of this {@code RemoteConnection}.
diff --git a/telecomm/java/android/telecom/RemoteConnectionManager.java b/telecomm/java/android/telecom/RemoteConnectionManager.java
index 0322218..f3c7bd8 100644
--- a/telecomm/java/android/telecom/RemoteConnectionManager.java
+++ b/telecomm/java/android/telecom/RemoteConnectionManager.java
@@ -73,6 +73,37 @@
         return null;
     }
 
+    /**
+     * Ask a {@code RemoteConnectionService} to create a {@code RemoteConference}.
+     * @param connectionManagerPhoneAccount See description at
+     * {@link ConnectionService#onCreateOutgoingConnection(PhoneAccountHandle, ConnectionRequest)}.
+     * @param request Details about the incoming conference call.
+     * @param isIncoming {@code true} if it's an incoming conference.
+     * @return
+     */
+    public RemoteConference createRemoteConference(
+            PhoneAccountHandle connectionManagerPhoneAccount,
+            ConnectionRequest request,
+            boolean isIncoming) {
+        PhoneAccountHandle accountHandle = request.getAccountHandle();
+        if (accountHandle == null) {
+            throw new IllegalArgumentException("accountHandle must be specified.");
+        }
+
+        ComponentName componentName = request.getAccountHandle().getComponentName();
+        if (!mRemoteConnectionServices.containsKey(componentName)) {
+            throw new UnsupportedOperationException("accountHandle not supported: "
+                    + componentName);
+        }
+
+        RemoteConnectionService remoteService = mRemoteConnectionServices.get(componentName);
+        if (remoteService != null) {
+            return remoteService.createRemoteConference(
+                    connectionManagerPhoneAccount, request, isIncoming);
+        }
+        return null;
+    }
+
     public void conferenceRemoteConnections(RemoteConnection a, RemoteConnection b) {
         if (a.getConnectionService() == b.getConnectionService()) {
             try {
diff --git a/telecomm/java/android/telecom/RemoteConnectionService.java b/telecomm/java/android/telecom/RemoteConnectionService.java
index a083301..bf6a6ef7 100644
--- a/telecomm/java/android/telecom/RemoteConnectionService.java
+++ b/telecomm/java/android/telecom/RemoteConnectionService.java
@@ -31,9 +31,9 @@
 import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.HashSet;
+import java.util.List;
 import java.util.Map;
 import java.util.Set;
-import java.util.List;
 import java.util.UUID;
 
 /**
@@ -591,6 +591,38 @@
         }
     }
 
+    RemoteConference createRemoteConference(
+            PhoneAccountHandle connectionManagerPhoneAccount,
+            ConnectionRequest request,
+            boolean isIncoming) {
+        final String id = UUID.randomUUID().toString();
+        try {
+            if (mConferenceById.isEmpty()) {
+                mOutgoingConnectionServiceRpc.addConnectionServiceAdapter(mServant.getStub(),
+                        null /*Session.Info*/);
+            }
+            RemoteConference conference = new RemoteConference(id, mOutgoingConnectionServiceRpc);
+            mOutgoingConnectionServiceRpc.createConference(connectionManagerPhoneAccount,
+                    id,
+                    request,
+                    isIncoming,
+                    false /* isUnknownCall */,
+                    null /*Session.info*/);
+            conference.registerCallback(new RemoteConference.Callback() {
+                @Override
+                public void onDestroyed(RemoteConference conference) {
+                    mConferenceById.remove(id);
+                    maybeDisconnectAdapter();
+                }
+            });
+            conference.putExtras(request.getExtras());
+            return conference;
+        } catch (RemoteException e) {
+            return RemoteConference.failure(
+                    new DisconnectCause(DisconnectCause.ERROR, e.toString()));
+        }
+    }
+
     private boolean hasConnection(String callId) {
         return mConnectionById.containsKey(callId);
     }
diff --git a/telecomm/java/android/telecom/TelecomManager.java b/telecomm/java/android/telecom/TelecomManager.java
index bcb1736..464f318 100644
--- a/telecomm/java/android/telecom/TelecomManager.java
+++ b/telecomm/java/android/telecom/TelecomManager.java
@@ -1209,11 +1209,14 @@
     /**
      *  Returns a list of all {@link PhoneAccount}s registered for the calling package.
      *
+     * @deprecated Use {@link #getSelfManagedPhoneAccounts()} instead to get only self-managed
+     * {@link PhoneAccountHandle} for the calling package.
      * @return A list of {@code PhoneAccountHandle} objects.
      * @hide
      */
     @SystemApi
     @SuppressLint("Doclava125")
+    @Deprecated
     public List<PhoneAccountHandle> getPhoneAccountsForPackage() {
         try {
             if (isServiceConnected()) {
diff --git a/telephony/api/system-current.txt b/telephony/api/system-current.txt
index ef94c76..944edd5 100644
--- a/telephony/api/system-current.txt
+++ b/telephony/api/system-current.txt
@@ -316,6 +316,7 @@
     method @Deprecated public int getDataConnectionApnTypeBitMask();
     method @Deprecated public int getDataConnectionFailCause();
     method @Deprecated public int getDataConnectionState();
+    method public int getId();
   }
 
   public final class PreciseDisconnectCause {
@@ -686,10 +687,8 @@
     method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public boolean isAnyRadioPoweredOn();
     method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public boolean isApnMetered(int);
     method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public boolean isApplicationOnUicc(int);
-    method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public boolean isDataConnectionAllowed();
     method public boolean isDataConnectivityPossible();
     method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public boolean isDataEnabledForApn(int);
-    method @RequiresPermission(anyOf={android.Manifest.permission.ACCESS_NETWORK_STATE, android.Manifest.permission.READ_PHONE_STATE}) public boolean isDataEnabledWithReason(int);
     method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public boolean isEmergencyAssistanceEnabled();
     method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) @WorkerThread public boolean isIccLockEnabled();
     method @Deprecated @RequiresPermission(anyOf={android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE, android.Manifest.permission.READ_PHONE_STATE}) public boolean isIdle();
@@ -721,7 +720,6 @@
     method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public int setCarrierRestrictionRules(@NonNull android.telephony.CarrierRestrictionRules);
     method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setDataActivationState(int);
     method @Deprecated @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setDataEnabled(int, boolean);
-    method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setDataEnabledWithReason(int, boolean);
     method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setDataRoamingEnabled(boolean);
     method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setMultiSimCarrierRestriction(boolean);
     method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public boolean setOpportunisticNetworkState(boolean);
@@ -759,10 +757,6 @@
     field public static final int CARRIER_PRIVILEGE_STATUS_HAS_ACCESS = 1; // 0x1
     field public static final int CARRIER_PRIVILEGE_STATUS_NO_ACCESS = 0; // 0x0
     field public static final int CARRIER_PRIVILEGE_STATUS_RULES_NOT_LOADED = -1; // 0xffffffff
-    field public static final int DATA_ENABLED_REASON_CARRIER = 2; // 0x2
-    field public static final int DATA_ENABLED_REASON_POLICY = 1; // 0x1
-    field public static final int DATA_ENABLED_REASON_THERMAL = 3; // 0x3
-    field public static final int DATA_ENABLED_REASON_USER = 0; // 0x0
     field public static final String EXTRA_ANOMALY_DESCRIPTION = "android.telephony.extra.ANOMALY_DESCRIPTION";
     field public static final String EXTRA_ANOMALY_ID = "android.telephony.extra.ANOMALY_ID";
     field public static final String EXTRA_PHONE_IN_ECM_STATE = "android.telephony.extra.PHONE_IN_ECM_STATE";
@@ -1624,6 +1618,7 @@
     method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) @WorkerThread public int setProvisioningStringValue(int, @NonNull String);
     method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) @WorkerThread public void setRcsProvisioningStatusForCapability(int, boolean);
     method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public void unregisterProvisioningChangedCallback(@NonNull android.telephony.ims.ProvisioningManager.Callback);
+    field public static final int KEY_VOICE_OVER_WIFI_ENTITLEMENT_ID = 67; // 0x43
     field public static final int KEY_VOICE_OVER_WIFI_MODE_OVERRIDE = 27; // 0x1b
     field public static final int KEY_VOICE_OVER_WIFI_ROAMING_ENABLED_OVERRIDE = 26; // 0x1a
     field public static final int PROVISIONING_VALUE_DISABLED = 0; // 0x0
diff --git a/telephony/common/com/android/internal/telephony/CarrierAppUtils.java b/telephony/common/com/android/internal/telephony/CarrierAppUtils.java
index e57b030..d4308c4 100644
--- a/telephony/common/com/android/internal/telephony/CarrierAppUtils.java
+++ b/telephony/common/com/android/internal/telephony/CarrierAppUtils.java
@@ -17,6 +17,7 @@
 package com.android.internal.telephony;
 
 import android.annotation.Nullable;
+import android.annotation.UserIdInt;
 import android.content.ContentResolver;
 import android.content.Context;
 import android.content.pm.ApplicationInfo;
@@ -74,7 +75,7 @@
      * privileged apps may have changed.
      */
     public static synchronized void disableCarrierAppsUntilPrivileged(String callingPackage,
-            TelephonyManager telephonyManager, int userId, Context context) {
+            TelephonyManager telephonyManager, @UserIdInt int userId, Context context) {
         if (DEBUG) {
             Log.d(TAG, "disableCarrierAppsUntilPrivileged");
         }
@@ -101,7 +102,7 @@
      * Manager can kill it, and this can lead to crashes as the app is in an unexpected state.
      */
     public static synchronized void disableCarrierAppsUntilPrivileged(String callingPackage,
-            int userId, Context context) {
+            @UserIdInt int userId, Context context) {
         if (DEBUG) {
             Log.d(TAG, "disableCarrierAppsUntilPrivileged");
         }
@@ -117,9 +118,9 @@
                 systemCarrierAssociatedAppsDisabledUntilUsed, context);
     }
 
-    private static ContentResolver getContentResolverForUser(Context context, int userId) {
-        Context userContext = context.createContextAsUser(UserHandle.getUserHandleForUid(userId),
-                0);
+    private static ContentResolver getContentResolverForUser(Context context,
+            @UserIdInt int userId) {
+        Context userContext = context.createContextAsUser(UserHandle.of(userId), 0);
         return userContext.getContentResolver();
     }
 
diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java
index fa229fb..a229efb 100755
--- a/telephony/java/android/telephony/CarrierConfigManager.java
+++ b/telephony/java/android/telephony/CarrierConfigManager.java
@@ -4406,7 +4406,7 @@
                 });
         sDefaults.putBoolean(KEY_SUPPORT_WPS_OVER_IMS_BOOL, true);
         sDefaults.putAll(Ims.getDefaults());
-        sDefaults.putStringArray(KEY_CARRIER_CERTIFICATE_STRING_ARRAY, null);
+        sDefaults.putStringArray(KEY_CARRIER_CERTIFICATE_STRING_ARRAY, new String[0]);
          sDefaults.putBoolean(KEY_FORMAT_INCOMING_NUMBER_TO_NATIONAL_FOR_JP_BOOL, false);
         sDefaults.putIntArray(KEY_DISCONNECT_CAUSE_PLAY_BUSYTONE_INT_ARRAY,
                 new int[] {4 /* BUSY */});
diff --git a/telephony/java/android/telephony/CellIdentityNr.java b/telephony/java/android/telephony/CellIdentityNr.java
index e34bbfc..905f908 100644
--- a/telephony/java/android/telephony/CellIdentityNr.java
+++ b/telephony/java/android/telephony/CellIdentityNr.java
@@ -37,7 +37,7 @@
     private static final String TAG = "CellIdentityNr";
 
     private static final int MAX_PCI = 1007;
-    private static final int MAX_TAC = 65535;
+    private static final int MAX_TAC = 16777215; // 0xffffff
     private static final int MAX_NRARFCN = 3279165;
     private static final long MAX_NCI = 68719476735L;
 
@@ -50,10 +50,22 @@
     // a list of additional PLMN-IDs reported for this cell
     private final ArraySet<String> mAdditionalPlmns;
 
+    /** @hide */
+    public CellIdentityNr() {
+        super(TAG, CellInfo.TYPE_NR, null, null, null, null);
+        mNrArfcn = CellInfo.UNAVAILABLE;
+        mPci = CellInfo.UNAVAILABLE;
+        mTac = CellInfo.UNAVAILABLE;
+        mNci = CellInfo.UNAVAILABLE;
+        mBands = new int[] {};
+        mAdditionalPlmns = new ArraySet();
+        mGlobalCellId = null;
+    }
+
     /**
      *
      * @param pci Physical Cell Id in range [0, 1007].
-     * @param tac 16-bit Tracking Area Code.
+     * @param tac 24-bit Tracking Area Code.
      * @param nrArfcn NR Absolute Radio Frequency Channel Number, in range [0, 3279165].
      * @param bands Bands used by the cell. Band number defined in 3GPP TS 38.101-1 and TS 38.101-2.
      * @param mccStr 3-digit Mobile Country Code in string format.
@@ -199,9 +211,9 @@
 
     /**
      * Get the tracking area code.
-     * @return a 16 bit integer or {@link CellInfo#UNAVAILABLE} if unknown.
+     * @return a 24 bit integer or {@link CellInfo#UNAVAILABLE} if unknown.
      */
-    @IntRange(from = 0, to = 65535)
+    @IntRange(from = 0, to = 16777215)
     public int getTac() {
         return mTac;
     }
diff --git a/telephony/java/android/telephony/CellInfoNr.java b/telephony/java/android/telephony/CellInfoNr.java
index a7e79f9..e01e8f0 100644
--- a/telephony/java/android/telephony/CellInfoNr.java
+++ b/telephony/java/android/telephony/CellInfoNr.java
@@ -29,9 +29,16 @@
 public final class CellInfoNr extends CellInfo {
     private static final String TAG = "CellInfoNr";
 
-    private final CellIdentityNr mCellIdentity;
+    private CellIdentityNr mCellIdentity;
     private final CellSignalStrengthNr mCellSignalStrength;
 
+    /** @hide */
+    public CellInfoNr() {
+        super();
+        mCellIdentity = new CellIdentityNr();
+        mCellSignalStrength = new CellSignalStrengthNr();
+    }
+
     private CellInfoNr(Parcel in) {
         super(in);
         mCellIdentity = CellIdentityNr.CREATOR.createFromParcel(in);
@@ -71,6 +78,11 @@
         return mCellIdentity;
     }
 
+    /** @hide */
+    public void setCellIdentity(CellIdentityNr cid) {
+        mCellIdentity = cid;
+    }
+
     /**
      * @return a {@link CellSignalStrengthNr} instance.
      */
diff --git a/telephony/java/android/telephony/PreciseDataConnectionState.java b/telephony/java/android/telephony/PreciseDataConnectionState.java
index aee8617..fd9f460 100644
--- a/telephony/java/android/telephony/PreciseDataConnectionState.java
+++ b/telephony/java/android/telephony/PreciseDataConnectionState.java
@@ -28,11 +28,15 @@
 import android.os.Build;
 import android.os.Parcel;
 import android.os.Parcelable;
+import android.telephony.AccessNetworkConstants.TransportType;
 import android.telephony.Annotation.ApnType;
 import android.telephony.Annotation.DataFailureCause;
 import android.telephony.Annotation.DataState;
 import android.telephony.Annotation.NetworkType;
 import android.telephony.data.ApnSetting;
+import android.telephony.data.DataCallResponse;
+
+import com.android.internal.telephony.util.TelephonyUtils;
 
 import java.util.Objects;
 
@@ -53,6 +57,8 @@
  *
  */
 public final class PreciseDataConnectionState implements Parcelable {
+    private final @TransportType int mTransportType;
+    private final int mId;
     private final @DataState int mState;
     private final @NetworkType int mNetworkType;
     private final @DataFailureCause int mFailCause;
@@ -74,16 +80,20 @@
                                       @ApnType int apnTypes, @NonNull String apn,
                                       @Nullable LinkProperties linkProperties,
                                       @DataFailureCause int failCause) {
-        this(state, networkType, linkProperties, failCause, new ApnSetting.Builder()
-                .setApnTypeBitmask(apnTypes)
-                .setApnName(apn)
-                .build());
+        this(AccessNetworkConstants.TRANSPORT_TYPE_INVALID, -1, state, networkType,
+                linkProperties, failCause, new ApnSetting.Builder()
+                        .setApnTypeBitmask(apnTypes)
+                        .setApnName(apn)
+                        .setEntryName(apn)
+                        .build());
     }
 
 
     /**
      * Constructor of PreciseDataConnectionState
      *
+     * @param transportType The transport of the data connection
+     * @param id The id of the data connection
      * @param state The state of the data connection
      * @param networkType The access network that is/would carry this data connection
      * @param linkProperties If the data connection is connected, the properties of the connection
@@ -92,11 +102,12 @@
      * @param apnSetting If there is a valid APN for this Data Connection, then the APN Settings;
      *        if there is no valid APN setting for the specific type, then this will be null
      */
-    private PreciseDataConnectionState(@DataState int state,
-                                      @NetworkType int networkType,
-                                      @Nullable LinkProperties linkProperties,
-                                      @DataFailureCause int failCause,
-                                      @Nullable ApnSetting apnSetting) {
+    private PreciseDataConnectionState(@TransportType int transportType, int id,
+            @DataState int state, @NetworkType int networkType,
+            @Nullable LinkProperties linkProperties, @DataFailureCause int failCause,
+            @Nullable ApnSetting apnSetting) {
+        mTransportType = transportType;
+        mId = id;
         mState = state;
         mNetworkType = networkType;
         mLinkProperties = linkProperties;
@@ -110,6 +121,8 @@
      * @hide
      */
     private PreciseDataConnectionState(Parcel in) {
+        mTransportType = in.readInt();
+        mId = in.readInt();
         mState = in.readInt();
         mNetworkType = in.readInt();
         mLinkProperties = in.readParcelable(LinkProperties.class.getClassLoader());
@@ -144,7 +157,29 @@
     }
 
     /**
-     * Returns the high-level state of this data connection.
+     * @return The transport type of this data connection.
+     */
+    public @TransportType int getTransportType() {
+        return mTransportType;
+    }
+
+    /**
+     * @return The unique id of the data connection
+     *
+     * Note this is the id assigned in {@link DataCallResponse}.
+     * The id remains the same for data connection handover between
+     * {@link AccessNetworkConstants#TRANSPORT_TYPE_WLAN} and
+     * {@link AccessNetworkConstants#TRANSPORT_TYPE_WWAN}
+     *
+     * @hide
+     */
+    @SystemApi
+    public int getId() {
+        return mId;
+    }
+
+    /**
+     * @return The high-level state of this data connection.
      */
     public @DataState int getState() {
         return mState;
@@ -222,7 +257,9 @@
     /**
      * Return the APN Settings for this data connection.
      *
-     * @return the ApnSetting that was used to configure this data connection.
+     * @return the ApnSetting that was used to configure this data connection. Note that a data
+     * connection cannot be established without a valid {@link ApnSetting}. The return value would
+     * never be {@code null} even though it has {@link Nullable} annotation.
      */
     public @Nullable ApnSetting getApnSetting() {
         return mApnSetting;
@@ -235,6 +272,8 @@
 
     @Override
     public void writeToParcel(@NonNull Parcel out, int flags) {
+        out.writeInt(mTransportType);
+        out.writeInt(mId);
         out.writeInt(mState);
         out.writeInt(mNetworkType);
         out.writeParcelable(mLinkProperties, flags);
@@ -256,7 +295,8 @@
 
     @Override
     public int hashCode() {
-        return Objects.hash(mState, mNetworkType, mFailCause, mLinkProperties, mApnSetting);
+        return Objects.hash(mTransportType, mId, mState, mNetworkType, mFailCause,
+                mLinkProperties, mApnSetting);
     }
 
 
@@ -265,7 +305,9 @@
         if (this == o) return true;
         if (o == null || getClass() != o.getClass()) return false;
         PreciseDataConnectionState that = (PreciseDataConnectionState) o;
-        return mState == that.mState
+        return mTransportType == that.mTransportType
+                && mId == that.mId
+                && mState == that.mState
                 && mNetworkType == that.mNetworkType
                 && mFailCause == that.mFailCause
                 && Objects.equals(mLinkProperties, that.mLinkProperties)
@@ -277,14 +319,14 @@
     public String toString() {
         StringBuilder sb = new StringBuilder();
 
-        sb.append("Data Connection state: " + mState);
-        sb.append(", Network type: " + mNetworkType);
-        sb.append(", APN types: " + ApnSetting.getApnTypesStringFromBitmask(
-                getDataConnectionApnTypeBitMask()));
-        sb.append(", APN: " + getDataConnectionApn());
-        sb.append(", Link properties: " + mLinkProperties);
-        sb.append(", Fail cause: " + DataFailCause.toString(mFailCause));
-        sb.append(", Apn Setting: " + mApnSetting);
+        sb.append(" state: " + TelephonyUtils.dataStateToString(mState));
+        sb.append(", transport: "
+                + AccessNetworkConstants.transportTypeToString(mTransportType));
+        sb.append(", id: " + mId);
+        sb.append(", network type: " + TelephonyManager.getNetworkTypeName(mNetworkType));
+        sb.append(", APN Setting: " + mApnSetting);
+        sb.append(", link properties: " + mLinkProperties);
+        sb.append(", fail cause: " + DataFailCause.toString(mFailCause));
 
         return sb.toString();
     }
@@ -295,6 +337,15 @@
      * @hide
      */
     public static final class Builder {
+        /** The transport type of the data connection */
+        private @TransportType int mTransportType = AccessNetworkConstants.TRANSPORT_TYPE_INVALID;
+
+        /**
+         * The unique ID of the data connection. This is the id assigned in
+         * {@link DataCallResponse)}.
+         */
+        private int mId = -1;
+
         /** The state of the data connection */
         private @DataState int mState = TelephonyManager.DATA_UNKNOWN;
 
@@ -314,12 +365,34 @@
         private @Nullable ApnSetting mApnSetting = null;
 
         /**
+         * Set the transport type of the data connection.
+         *
+         * @param transportType The transport type of the data connection
+         * @return The builder
+         */
+        public @NonNull Builder setTransportType(@TransportType int transportType) {
+            mTransportType = transportType;
+            return this;
+        }
+
+        /**
+         * Set the id of the data connection.
+         *
+         * @param id The id of the data connection
+         * @return The builder
+         */
+        public @NonNull Builder setId(int id) {
+            mId = id;
+            return this;
+        }
+
+        /**
          * Set the state of the data connection.
          *
          * @param state The state of the data connection
          * @return The builder
          */
-        public Builder setState(@DataState int state) {
+        public @NonNull Builder setState(@DataState int state) {
             mState = state;
             return this;
         }
@@ -330,7 +403,7 @@
          * @param networkType The network type
          * @return The builder
          */
-        public Builder setNetworkType(@NetworkType int networkType) {
+        public @NonNull Builder setNetworkType(@NetworkType int networkType) {
             mNetworkType = networkType;
             return this;
         }
@@ -341,7 +414,7 @@
          * @param linkProperties Link properties
          * @return The builder
          */
-        public Builder setLinkProperties(@NonNull LinkProperties linkProperties) {
+        public @NonNull Builder setLinkProperties(LinkProperties linkProperties) {
             mLinkProperties = linkProperties;
             return this;
         }
@@ -353,7 +426,7 @@
          * error code indicating the cause of the failure.
          * @return The builder
          */
-        public Builder setFailCause(@DataFailureCause int failCause) {
+        public @NonNull Builder setFailCause(@DataFailureCause int failCause) {
             mFailCause = failCause;
             return this;
         }
@@ -364,7 +437,7 @@
          * @param apnSetting APN setting
          * @return This builder
          */
-        public Builder setApnSetting(@NonNull ApnSetting apnSetting) {
+        public @NonNull Builder setApnSetting(@NonNull ApnSetting apnSetting) {
             mApnSetting = apnSetting;
             return this;
         }
@@ -375,8 +448,8 @@
          * @return The {@link PreciseDataConnectionState} instance
          */
         public PreciseDataConnectionState build() {
-            return new PreciseDataConnectionState(mState, mNetworkType, mLinkProperties, mFailCause,
-                    mApnSetting);
+            return new PreciseDataConnectionState(mTransportType, mId, mState, mNetworkType,
+                    mLinkProperties, mFailCause, mApnSetting);
         }
     }
 }
diff --git a/telephony/java/android/telephony/SmsManager.java b/telephony/java/android/telephony/SmsManager.java
index 2b26087..78dc377 100644
--- a/telephony/java/android/telephony/SmsManager.java
+++ b/telephony/java/android/telephony/SmsManager.java
@@ -46,6 +46,7 @@
 import com.android.internal.telephony.ISms;
 import com.android.internal.telephony.ITelephony;
 import com.android.internal.telephony.SmsRawData;
+import com.android.telephony.Rlog;
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
@@ -1953,7 +1954,6 @@
     public boolean enableCellBroadcastRange(int startMessageId, int endMessageId,
             @android.telephony.SmsCbMessage.MessageFormat int ranType) {
         boolean success = false;
-
         if (endMessageId < startMessageId) {
             throw new IllegalArgumentException("endMessageId < startMessageId");
         }
@@ -1962,10 +1962,14 @@
             if (iSms != null) {
                 // If getSubscriptionId() returns INVALID or an inactive subscription, we will use
                 // the default phone internally.
-                success = iSms.enableCellBroadcastRangeForSubscriber(getSubscriptionId(),
+                int subId = getSubscriptionId();
+                success = iSms.enableCellBroadcastRangeForSubscriber(subId,
                         startMessageId, endMessageId, ranType);
+                Rlog.d(TAG, "enableCellBroadcastRange: " + (success ? "succeeded" : "failed")
+                        + " at calling enableCellBroadcastRangeForSubscriber. subId = " + subId);
             }
         } catch (RemoteException ex) {
+            Rlog.d(TAG, "enableCellBroadcastRange: " + ex.getStackTrace());
             // ignore it
         }
 
@@ -2019,10 +2023,14 @@
             if (iSms != null) {
                 // If getSubscriptionId() returns INVALID or an inactive subscription, we will use
                 // the default phone internally.
-                success = iSms.disableCellBroadcastRangeForSubscriber(getSubscriptionId(),
+                int subId = getSubscriptionId();
+                success = iSms.disableCellBroadcastRangeForSubscriber(subId,
                         startMessageId, endMessageId, ranType);
+                Rlog.d(TAG, "disableCellBroadcastRange: " + (success ? "succeeded" : "failed")
+                        + " at calling disableCellBroadcastRangeForSubscriber. subId = " + subId);
             }
         } catch (RemoteException ex) {
+            Rlog.d(TAG, "disableCellBroadcastRange: " + ex.getStackTrace());
             // ignore it
         }
 
diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java
index f9148d0..7a77922 100644
--- a/telephony/java/android/telephony/TelephonyManager.java
+++ b/telephony/java/android/telephony/TelephonyManager.java
@@ -9231,7 +9231,7 @@
      * app has carrier privileges (see {@link #hasCarrierPrivileges}).
      *
      * @param enable Whether to enable mobile data.
-     * @deprecated use setDataEnabledWithReason with reason DATA_ENABLED_REASON_USER instead.
+     * @deprecated use setDataEnabledForReason with reason DATA_ENABLED_REASON_USER instead.
      *
      */
     @Deprecated
@@ -9243,16 +9243,16 @@
 
     /**
      * @hide
-     * @deprecated use {@link #setDataEnabledWithReason(int, boolean)} instead.
+     * @deprecated use {@link #setDataEnabledForReason(int, boolean)} instead.
     */
     @SystemApi
     @Deprecated
     @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE)
     public void setDataEnabled(int subId, boolean enable) {
         try {
-            setDataEnabledWithReason(subId, DATA_ENABLED_REASON_USER, enable);
+            setDataEnabledForReason(subId, DATA_ENABLED_REASON_USER, enable);
         } catch (RuntimeException e) {
-            Log.e(TAG, "Error calling setDataEnabledWithReason e:" + e);
+            Log.e(TAG, "Error calling setDataEnabledForReason e:" + e);
         }
     }
 
@@ -9461,9 +9461,9 @@
     @SystemApi
     public boolean getDataEnabled(int subId) {
         try {
-            return isDataEnabledWithReason(DATA_ENABLED_REASON_USER);
+            return isDataEnabledForReason(DATA_ENABLED_REASON_USER);
         } catch (RuntimeException e) {
-            Log.e(TAG, "Error calling isDataEnabledWithReason e:" + e);
+            Log.e(TAG, "Error calling isDataEnabledForReason e:" + e);
         }
         return false;
     }
@@ -11016,7 +11016,7 @@
      *
      * @param enabled control enable or disable carrier data.
      * @see #resetAllCarrierActions()
-     * @deprecated use {@link #setDataEnabledWithReason(int, boolean) with
+     * @deprecated use {@link #setDataEnabledForReason(int, boolean) with
      * reason {@link #DATA_ENABLED_REASON_CARRIER}} instead.
      * @hide
      */
@@ -11025,9 +11025,9 @@
     @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE)
     public void setCarrierDataEnabled(boolean enabled) {
         try {
-            setDataEnabledWithReason(DATA_ENABLED_REASON_CARRIER, enabled);
+            setDataEnabledForReason(DATA_ENABLED_REASON_CARRIER, enabled);
         } catch (RuntimeException e) {
-            Log.e(TAG, "Error calling setDataEnabledWithReason e:" + e);
+            Log.e(TAG, "Error calling setDataEnabledForReason e:" + e);
         }
     }
 
@@ -11113,7 +11113,7 @@
     /**
      * Policy control of data connection. Usually used when data limit is passed.
      * @param enabled True if enabling the data, otherwise disabling.
-     * @deprecated use {@link #setDataEnabledWithReason(int, boolean) with
+     * @deprecated use {@link #setDataEnabledForReason(int, boolean) with
      * reason {@link #DATA_ENABLED_REASON_POLICY}} instead.
      * @hide
      */
@@ -11121,9 +11121,9 @@
     @RequiresPermission(Manifest.permission.MODIFY_PHONE_STATE)
     public void setPolicyDataEnabled(boolean enabled) {
         try {
-            setDataEnabledWithReason(DATA_ENABLED_REASON_POLICY, enabled);
+            setDataEnabledForReason(DATA_ENABLED_REASON_POLICY, enabled);
         } catch (RuntimeException e) {
-            Log.e(TAG, "Error calling setDataEnabledWithReason e:" + e);
+            Log.e(TAG, "Error calling setDataEnabledForReason e:" + e);
         }
     }
 
@@ -11139,36 +11139,28 @@
 
     /**
      * To indicate that user enabled or disabled data.
-     * @hide
      */
-    @SystemApi
     public static final int DATA_ENABLED_REASON_USER = 0;
 
     /**
      * To indicate that data control due to policy. Usually used when data limit is passed.
      * Policy data on/off won't affect user settings but will bypass the
      * settings and turns off data internally if set to {@code false}.
-     * @hide
      */
-    @SystemApi
     public static final int DATA_ENABLED_REASON_POLICY = 1;
 
     /**
      * To indicate enable or disable carrier data by the system based on carrier signalling or
      * carrier privileged apps. Carrier data on/off won't affect user settings but will bypass the
      * settings and turns off data internally if set to {@code false}.
-     * @hide
      */
-    @SystemApi
     public static final int DATA_ENABLED_REASON_CARRIER = 2;
 
     /**
      * To indicate enable or disable data by thermal service.
      * Thermal data on/off won't affect user settings but will bypass the
      * settings and turns off data internally if set to {@code false}.
-     * @hide
      */
-    @SystemApi
     public static final int DATA_ENABLED_REASON_THERMAL = 3;
 
     /**
@@ -11197,25 +11189,23 @@
      * has {@link android.Manifest.permission#MODIFY_PHONE_STATE MODIFY_PHONE_STATE} irrespective of
      * the reason.
      * @throws IllegalStateException if the Telephony process is not currently available.
-     * @hide
      */
     @RequiresPermission(Manifest.permission.MODIFY_PHONE_STATE)
-    @SystemApi
-    public void setDataEnabledWithReason(@DataEnabledReason int reason, boolean enabled) {
-        setDataEnabledWithReason(getSubId(), reason, enabled);
+    public void setDataEnabledForReason(@DataEnabledReason int reason, boolean enabled) {
+        setDataEnabledForReason(getSubId(), reason, enabled);
     }
 
-    private void setDataEnabledWithReason(int subId, @DataEnabledReason int reason,
+    private void setDataEnabledForReason(int subId, @DataEnabledReason int reason,
             boolean enabled) {
         try {
             ITelephony service = getITelephony();
             if (service != null) {
-                service.setDataEnabledWithReason(subId, reason, enabled);
+                service.setDataEnabledForReason(subId, reason, enabled);
             } else {
                 throw new IllegalStateException("telephony service is null.");
             }
         } catch (RemoteException ex) {
-            Log.e(TAG, "Telephony#setDataEnabledWithReason RemoteException", ex);
+            Log.e(TAG, "Telephony#setDataEnabledForReason RemoteException", ex);
             ex.rethrowFromSystemServer();
         }
     }
@@ -11223,9 +11213,11 @@
     /**
      * Return whether data is enabled for certain reason .
      *
-     * If {@link #isDataEnabledWithReason} returns false, it means in data enablement for a
+     * If {@link #isDataEnabledForReason} returns false, it means in data enablement for a
      * specific reason is turned off. If any of the reason is off, then it will result in
-     * bypassing user preference and result in data to be turned off.
+     * bypassing user preference and result in data to be turned off. Call
+     * {@link #isDataConnectionAllowed} in order to know whether
+     * data connection is allowed on the device.
      *
      * <p>If this object has been created with {@link #createForSubscriptionId}, applies
      *      to the given subId. Otherwise, applies to
@@ -11234,27 +11226,26 @@
      * @param reason the reason the data enable change is taking place
      * @return whether data is enabled for a reason.
      * <p>Requires Permission:
-     * {@link android.Manifest.permission#READ_PHONE_STATE READ_PHONE_STATE}
+     * {@link android.Manifest.permission#READ_PHONE_STATE READ_PHONE_STATE} or
+     * {@link android.Manifest.permission#ACCESS_NETWORK_STATE}
      * @throws IllegalStateException if the Telephony process is not currently available.
-     * @hide
      */
     @RequiresPermission(anyOf = {android.Manifest.permission.ACCESS_NETWORK_STATE,
             android.Manifest.permission.READ_PHONE_STATE})
-    @SystemApi
-    public boolean isDataEnabledWithReason(@DataEnabledReason int reason) {
-        return isDataEnabledWithReason(getSubId(), reason);
+    public boolean isDataEnabledForReason(@DataEnabledReason int reason) {
+        return isDataEnabledForReason(getSubId(), reason);
     }
 
-    private boolean isDataEnabledWithReason(int subId, @DataEnabledReason int reason) {
+    private boolean isDataEnabledForReason(int subId, @DataEnabledReason int reason) {
         try {
             ITelephony service = getITelephony();
             if (service != null) {
-                return service.isDataEnabledWithReason(subId, reason);
+                return service.isDataEnabledForReason(subId, reason);
             } else {
                 throw new IllegalStateException("telephony service is null.");
             }
         } catch (RemoteException ex) {
-            Log.e(TAG, "Telephony#isDataEnabledWithReason RemoteException", ex);
+            Log.e(TAG, "Telephony#isDataEnabledForReason RemoteException", ex);
             ex.rethrowFromSystemServer();
         }
         return false;
@@ -11395,10 +11386,14 @@
      *   <LI>And possibly others.</LI>
      * </UL>
      * @return {@code true} if the overall data connection is allowed; {@code false} if not.
-     * @hide
+     * <p>Requires Permission:
+     * {@link android.Manifest.permission#READ_PHONE_STATE READ_PHONE_STATE} or
+     * {@link android.Manifest.permission#ACCESS_NETWORK_STATE} or
+     * android.Manifest.permission#READ_PRIVILEGED_PHONE_STATE
      */
-    @SystemApi
-    @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
+    @RequiresPermission(anyOf = {android.Manifest.permission.ACCESS_NETWORK_STATE,
+            android.Manifest.permission.READ_PHONE_STATE,
+            android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE})
     public boolean isDataConnectionAllowed() {
         boolean retVal = false;
         try {
diff --git a/telephony/java/android/telephony/data/ApnSetting.java b/telephony/java/android/telephony/data/ApnSetting.java
index e60ae89..ff9329e 100644
--- a/telephony/java/android/telephony/data/ApnSetting.java
+++ b/telephony/java/android/telephony/data/ApnSetting.java
@@ -1214,12 +1214,16 @@
         return false;
     }
 
-    // TODO - if we have this function we should also have hashCode.
-    // Also should handle changes in type order and perhaps case-insensitivity.
+    @Override
+    public int hashCode() {
+        return Objects.hash(mApnName, mProxyAddress, mProxyPort, mMmsc, mMmsProxyAddress,
+                mMmsProxyPort, mUser, mPassword, mAuthType, mApnTypeBitmask, mId, mOperatorNumeric,
+                mProtocol, mRoamingProtocol, mMtu, mCarrierEnabled, mNetworkTypeBitmask, mProfileId,
+                mPersistent, mMaxConns, mWaitTime, mMaxConnsTime, mMvnoType, mMvnoMatchData,
+                mApnSetId, mCarrierId, mSkip464Xlat);
+    }
 
-    /**
-     * @hide
-     */
+    @Override
     public boolean equals(Object o) {
         if (o instanceof ApnSetting == false) {
             return false;
diff --git a/telephony/java/android/telephony/ims/ImsConferenceState.java b/telephony/java/android/telephony/ims/ImsConferenceState.java
index 21bef00..9bf2f44 100644
--- a/telephony/java/android/telephony/ims/ImsConferenceState.java
+++ b/telephony/java/android/telephony/ims/ImsConferenceState.java
@@ -203,10 +203,10 @@
                     for (String key : participantData.keySet()) {
                         sb.append(key);
                         sb.append("=");
-                        if (ENDPOINT.equals(key) || USER.equals(key)) {
-                            sb.append(Rlog.pii(TAG, participantData.get(key)));
-                        } else {
+                        if (STATUS.equals(key)) {
                             sb.append(participantData.get(key));
+                        } else {
+                            sb.append(Rlog.pii(TAG, participantData.get(key)));
                         }
                         sb.append(", ");
                     }
diff --git a/telephony/java/android/telephony/ims/ProvisioningManager.java b/telephony/java/android/telephony/ims/ProvisioningManager.java
index 1a606b7..2a073a1 100644
--- a/telephony/java/android/telephony/ims/ProvisioningManager.java
+++ b/telephony/java/android/telephony/ims/ProvisioningManager.java
@@ -851,6 +851,19 @@
     public static final int KEY_RTT_ENABLED = 66;
 
     /**
+     * An obfuscated string defined by the carrier to indicate VoWiFi entitlement status.
+     *
+     * <p>Implementation note: how to generate the value and how it affects VoWiFi service
+     * should follow carrier requirements. For example, set an empty string could result in
+     * VoWiFi being disabled by IMS service, and set to a specific string could enable.
+     *
+     * <p>Value is in String format.
+     * @see #setProvisioningStringValue(int, String)
+     * @see #getProvisioningStringValue(int)
+     */
+    public static final int KEY_VOICE_OVER_WIFI_ENTITLEMENT_ID = 67;
+
+    /**
      * Callback for IMS provisioning changes.
      */
     public static class Callback {
diff --git a/telephony/java/com/android/ims/ImsConfig.java b/telephony/java/com/android/ims/ImsConfig.java
index d0cec52d..4877860 100644
--- a/telephony/java/com/android/ims/ImsConfig.java
+++ b/telephony/java/com/android/ims/ImsConfig.java
@@ -729,7 +729,8 @@
 
         // Expand the operator config items as needed here, need to change
         // PROVISIONED_CONFIG_END after that.
-        public static final int PROVISIONED_CONFIG_END = RTT_SETTING_ENABLED;
+        public static final int PROVISIONED_CONFIG_END =
+                ProvisioningManager.KEY_VOICE_OVER_WIFI_ENTITLEMENT_ID;
 
         // Expand the operator config items as needed here.
     }
diff --git a/telephony/java/com/android/internal/telephony/ITelephony.aidl b/telephony/java/com/android/internal/telephony/ITelephony.aidl
index e2de5c8..4021d0a 100644
--- a/telephony/java/com/android/internal/telephony/ITelephony.aidl
+++ b/telephony/java/com/android/internal/telephony/ITelephony.aidl
@@ -1015,7 +1015,7 @@
      * @param reason the reason the data enable change is taking place
      * @param enable true to turn on, else false
      */
-     void setDataEnabledWithReason(int subId, int reason, boolean enable);
+     void setDataEnabledForReason(int subId, int reason, boolean enable);
 
     /**
      * Return whether data is enabled for certain reason
@@ -1023,7 +1023,7 @@
      * @param reason the reason the data enable change is taking place
      * @return true on enabled
     */
-    boolean isDataEnabledWithReason(int subId, int reason);
+    boolean isDataEnabledForReason(int subId, int reason);
 
      /**
      * Checks if manual network selection is allowed.
diff --git a/test-runner/src/android/test/AndroidTestRunner.java b/test-runner/src/android/test/AndroidTestRunner.java
index f898516..b2fdc50 100644
--- a/test-runner/src/android/test/AndroidTestRunner.java
+++ b/test-runner/src/android/test/AndroidTestRunner.java
@@ -125,7 +125,7 @@
         } catch (IllegalArgumentException e) {
             runFailed("Illegal argument passed to constructor. Class: " + testClass.getName());
         } catch (InvocationTargetException e) {
-            runFailed("Constructor thew an exception. Class: " + testClass.getName());
+            runFailed("Constructor threw an exception. Class: " + testClass.getName());
         }
         return null;
     }
diff --git a/tests/AutoVerify/app1/Android.bp b/tests/AutoVerify/app1/Android.bp
deleted file mode 100644
index 548519f..0000000
--- a/tests/AutoVerify/app1/Android.bp
+++ /dev/null
@@ -1,11 +0,0 @@
-android_app {
-    name: "AutoVerifyTest",
-    srcs: ["src/**/*.java"],
-    resource_dirs: ["res"],
-    platform_apis: true,
-    min_sdk_version: "26",
-    target_sdk_version: "26",
-    optimize: {
-        enabled: false,
-    },
-}
diff --git a/tests/AutoVerify/app1/AndroidManifest.xml b/tests/AutoVerify/app1/AndroidManifest.xml
deleted file mode 100644
index d9caad4..0000000
--- a/tests/AutoVerify/app1/AndroidManifest.xml
+++ /dev/null
@@ -1,43 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-     Copyright (C) 2020 The Android Open Source Project
-
-     Licensed under the Apache License, Version 2.0 (the "License");
-     you may not use this file except in compliance with the License.
-     You may obtain a copy of the License at
-
-          http://www.apache.org/licenses/LICENSE-2.0
-
-     Unless required by applicable law or agreed to in writing, software
-     distributed under the License is distributed on an "AS IS" BASIS,
-     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-     See the License for the specific language governing permissions and
-     limitations under the License.
--->
-
-<manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="com.android.test.autoverify" >
-
-    <uses-sdk android:targetSdkVersion="26" />
-
-    <application
-        android:label="@string/app_name" >
-        <activity
-            android:name=".MainActivity"
-            android:label="@string/app_name" >
-            <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.LAUNCHER" />
-            </intent-filter>
-
-            <intent-filter android:autoVerify="true">
-                <action android:name="android.intent.action.VIEW" />
-                <category android:name="android.intent.category.DEFAULT" />
-                <category android:name="android.intent.category.BROWSABLE" />
-                <data android:scheme="http" />
-                <data android:scheme="https" />
-                <data android:host="explicit.example.com" />
-            </intent-filter>
-        </activity>
-    </application>
-</manifest>
diff --git a/tests/AutoVerify/app1/res/values/strings.xml b/tests/AutoVerify/app1/res/values/strings.xml
deleted file mode 100644
index e234355..0000000
--- a/tests/AutoVerify/app1/res/values/strings.xml
+++ /dev/null
@@ -1,21 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-Copyright 2020 The Android Open Source Project
-
-Licensed under the Apache License, Version 2.0 (the "License");
-you may not use this file except in compliance with the License.
-You may obtain a copy of the License at
-
-     http://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing, software
-distributed under the License is distributed on an "AS IS" BASIS,
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-See the License for the specific language governing permissions and
-limitations under the License.
--->
-
-<resources>
-    <!-- app icon label, do not translate -->
-    <string name="app_name" translatable="false">AutoVerify Test</string>
-</resources>
diff --git a/tests/AutoVerify/app2/Android.bp b/tests/AutoVerify/app2/Android.bp
deleted file mode 100644
index 1c6c97b..0000000
--- a/tests/AutoVerify/app2/Android.bp
+++ /dev/null
@@ -1,11 +0,0 @@
-android_app {
-    name: "AutoVerifyTest2",
-    srcs: ["src/**/*.java"],
-    resource_dirs: ["res"],
-    platform_apis: true,
-    min_sdk_version: "26",
-    target_sdk_version: "26",
-    optimize: {
-        enabled: false,
-    },
-}
diff --git a/tests/AutoVerify/app2/AndroidManifest.xml b/tests/AutoVerify/app2/AndroidManifest.xml
deleted file mode 100644
index a008078..0000000
--- a/tests/AutoVerify/app2/AndroidManifest.xml
+++ /dev/null
@@ -1,44 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-     Copyright (C) 2020 The Android Open Source Project
-
-     Licensed under the Apache License, Version 2.0 (the "License");
-     you may not use this file except in compliance with the License.
-     You may obtain a copy of the License at
-
-          http://www.apache.org/licenses/LICENSE-2.0
-
-     Unless required by applicable law or agreed to in writing, software
-     distributed under the License is distributed on an "AS IS" BASIS,
-     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-     See the License for the specific language governing permissions and
-     limitations under the License.
--->
-
-<manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="com.android.test.autoverify" >
-
-    <uses-sdk android:targetSdkVersion="26" />
-
-    <application
-        android:label="@string/app_name" >
-        <activity
-            android:name=".MainActivity"
-            android:label="@string/app_name" >
-            <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.LAUNCHER" />
-            </intent-filter>
-
-            <intent-filter android:autoVerify="true">
-                <action android:name="android.intent.action.VIEW" />
-                <category android:name="android.intent.category.DEFAULT" />
-                <category android:name="android.intent.category.BROWSABLE" />
-                <data android:scheme="http" />
-                <data android:scheme="https" />
-                <data android:host="explicit.example.com" />
-                <data android:host="*.wildcard.tld" />
-            </intent-filter>
-        </activity>
-    </application>
-</manifest>
diff --git a/tests/AutoVerify/app2/res/values/strings.xml b/tests/AutoVerify/app2/res/values/strings.xml
deleted file mode 100644
index e234355..0000000
--- a/tests/AutoVerify/app2/res/values/strings.xml
+++ /dev/null
@@ -1,21 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-Copyright 2020 The Android Open Source Project
-
-Licensed under the Apache License, Version 2.0 (the "License");
-you may not use this file except in compliance with the License.
-You may obtain a copy of the License at
-
-     http://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing, software
-distributed under the License is distributed on an "AS IS" BASIS,
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-See the License for the specific language governing permissions and
-limitations under the License.
--->
-
-<resources>
-    <!-- app icon label, do not translate -->
-    <string name="app_name" translatable="false">AutoVerify Test</string>
-</resources>
diff --git a/tests/AutoVerify/app2/src/com/android/test/autoverify/MainActivity.java b/tests/AutoVerify/app2/src/com/android/test/autoverify/MainActivity.java
deleted file mode 100644
index 09ef472..0000000
--- a/tests/AutoVerify/app2/src/com/android/test/autoverify/MainActivity.java
+++ /dev/null
@@ -1,15 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
diff --git a/tests/AutoVerify/app3/Android.bp b/tests/AutoVerify/app3/Android.bp
deleted file mode 100644
index 70a2b77..0000000
--- a/tests/AutoVerify/app3/Android.bp
+++ /dev/null
@@ -1,11 +0,0 @@
-android_app {
-    name: "AutoVerifyTest3",
-    srcs: ["src/**/*.java"],
-    resource_dirs: ["res"],
-    platform_apis: true,
-    min_sdk_version: "26",
-    target_sdk_version: "26",
-    optimize: {
-        enabled: false,
-    },
-}
diff --git a/tests/AutoVerify/app3/AndroidManifest.xml b/tests/AutoVerify/app3/AndroidManifest.xml
deleted file mode 100644
index efaabc9..0000000
--- a/tests/AutoVerify/app3/AndroidManifest.xml
+++ /dev/null
@@ -1,44 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-     Copyright (C) 2020 The Android Open Source Project
-
-     Licensed under the Apache License, Version 2.0 (the "License");
-     you may not use this file except in compliance with the License.
-     You may obtain a copy of the License at
-
-          http://www.apache.org/licenses/LICENSE-2.0
-
-     Unless required by applicable law or agreed to in writing, software
-     distributed under the License is distributed on an "AS IS" BASIS,
-     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-     See the License for the specific language governing permissions and
-     limitations under the License.
--->
-
-<manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="com.android.test.autoverify" >
-
-    <uses-sdk android:targetSdkVersion="26" />
-
-    <application
-        android:label="@string/app_name" >
-        <activity
-            android:name=".MainActivity"
-            android:label="@string/app_name" >
-            <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.LAUNCHER" />
-            </intent-filter>
-
-            <!-- does not request autoVerify -->
-            <intent-filter>
-                <action android:name="android.intent.action.VIEW" />
-                <category android:name="android.intent.category.DEFAULT" />
-                <category android:name="android.intent.category.BROWSABLE" />
-                <data android:scheme="http" />
-                <data android:scheme="https" />
-                <data android:host="explicit.example.com" />
-            </intent-filter>
-        </activity>
-    </application>
-</manifest>
diff --git a/tests/AutoVerify/app3/res/values/strings.xml b/tests/AutoVerify/app3/res/values/strings.xml
deleted file mode 100644
index e234355..0000000
--- a/tests/AutoVerify/app3/res/values/strings.xml
+++ /dev/null
@@ -1,21 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-Copyright 2020 The Android Open Source Project
-
-Licensed under the Apache License, Version 2.0 (the "License");
-you may not use this file except in compliance with the License.
-You may obtain a copy of the License at
-
-     http://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing, software
-distributed under the License is distributed on an "AS IS" BASIS,
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-See the License for the specific language governing permissions and
-limitations under the License.
--->
-
-<resources>
-    <!-- app icon label, do not translate -->
-    <string name="app_name" translatable="false">AutoVerify Test</string>
-</resources>
diff --git a/tests/AutoVerify/app3/src/com/android/test/autoverify/MainActivity.java b/tests/AutoVerify/app3/src/com/android/test/autoverify/MainActivity.java
deleted file mode 100644
index 09ef472..0000000
--- a/tests/AutoVerify/app3/src/com/android/test/autoverify/MainActivity.java
+++ /dev/null
@@ -1,15 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
diff --git a/tests/AutoVerify/app4/Android.bp b/tests/AutoVerify/app4/Android.bp
deleted file mode 100644
index fbdae11..0000000
--- a/tests/AutoVerify/app4/Android.bp
+++ /dev/null
@@ -1,11 +0,0 @@
-android_app {
-    name: "AutoVerifyTest4",
-    srcs: ["src/**/*.java"],
-    resource_dirs: ["res"],
-    platform_apis: true,
-    min_sdk_version: "26",
-    target_sdk_version: "26",
-    optimize: {
-        enabled: false,
-    },
-}
diff --git a/tests/AutoVerify/app4/AndroidManifest.xml b/tests/AutoVerify/app4/AndroidManifest.xml
deleted file mode 100644
index 1c975f8..0000000
--- a/tests/AutoVerify/app4/AndroidManifest.xml
+++ /dev/null
@@ -1,45 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-     Copyright (C) 2020 The Android Open Source Project
-
-     Licensed under the Apache License, Version 2.0 (the "License");
-     you may not use this file except in compliance with the License.
-     You may obtain a copy of the License at
-
-          http://www.apache.org/licenses/LICENSE-2.0
-
-     Unless required by applicable law or agreed to in writing, software
-     distributed under the License is distributed on an "AS IS" BASIS,
-     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-     See the License for the specific language governing permissions and
-     limitations under the License.
--->
-
-<manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="com.android.test.autoverify" >
-
-    <uses-sdk android:targetSdkVersion="26" />
-
-    <application
-        android:label="@string/app_name" >
-        <activity
-            android:name=".MainActivity"
-            android:label="@string/app_name" >
-            <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.LAUNCHER" />
-            </intent-filter>
-
-            <!-- intentionally does not autoVerify -->
-            <intent-filter>
-                <action android:name="android.intent.action.VIEW" />
-                <category android:name="android.intent.category.DEFAULT" />
-                <category android:name="android.intent.category.BROWSABLE" />
-                <data android:scheme="http" />
-                <data android:scheme="https" />
-                <data android:host="explicit.example.com" />
-                <data android:host="*.wildcard.tld" />
-            </intent-filter>
-        </activity>
-    </application>
-</manifest>
diff --git a/tests/AutoVerify/app4/res/values/strings.xml b/tests/AutoVerify/app4/res/values/strings.xml
deleted file mode 100644
index e234355..0000000
--- a/tests/AutoVerify/app4/res/values/strings.xml
+++ /dev/null
@@ -1,21 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-Copyright 2020 The Android Open Source Project
-
-Licensed under the Apache License, Version 2.0 (the "License");
-you may not use this file except in compliance with the License.
-You may obtain a copy of the License at
-
-     http://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing, software
-distributed under the License is distributed on an "AS IS" BASIS,
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-See the License for the specific language governing permissions and
-limitations under the License.
--->
-
-<resources>
-    <!-- app icon label, do not translate -->
-    <string name="app_name" translatable="false">AutoVerify Test</string>
-</resources>
diff --git a/tests/AutoVerify/app4/src/com/android/test/autoverify/MainActivity.java b/tests/AutoVerify/app4/src/com/android/test/autoverify/MainActivity.java
deleted file mode 100644
index 09ef472..0000000
--- a/tests/AutoVerify/app4/src/com/android/test/autoverify/MainActivity.java
+++ /dev/null
@@ -1,15 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
diff --git a/tests/BootImageProfileTest/OWNERS b/tests/BootImageProfileTest/OWNERS
index 657b3f2..7ee0d9a 100644
--- a/tests/BootImageProfileTest/OWNERS
+++ b/tests/BootImageProfileTest/OWNERS
@@ -1,4 +1,4 @@
-mathieuc@google.com
 calin@google.com
+mathieuc@google.com
+ngeoffray@google.com
 yawanng@google.com
-sehr@google.com
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeAutoOpenWindowToHomeTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeAutoOpenWindowToHomeTest.kt
index 2e4d390..c0658fe 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeAutoOpenWindowToHomeTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeAutoOpenWindowToHomeTest.kt
@@ -84,7 +84,7 @@
                     navBarLayerIsAlwaysVisible(bugId = 140855415)
                     statusBarLayerIsAlwaysVisible(bugId = 140855415)
                     noUncoveredRegions(rotation, Surface.ROTATION_0, allStates = false)
-                    navBarLayerRotatesAndScales(rotation, Surface.ROTATION_0)
+                    navBarLayerRotatesAndScales(rotation, Surface.ROTATION_0, bugId = 140855415)
                     statusBarLayerRotatesScales(rotation, Surface.ROTATION_0)
                     imeLayerBecomesInvisible(bugId = 141458352)
                     imeAppLayerBecomesInvisible(testApp, bugId = 153739621)
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeWindowToHomeTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeWindowToHomeTest.kt
index 1c0da4f..dcf3085 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeWindowToHomeTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeWindowToHomeTest.kt
@@ -92,7 +92,7 @@
                     navBarLayerIsAlwaysVisible(bugId = 140855415)
                     statusBarLayerIsAlwaysVisible(bugId = 140855415)
                     noUncoveredRegions(rotation, Surface.ROTATION_0, allStates = false)
-                    navBarLayerRotatesAndScales(rotation, Surface.ROTATION_0)
+                    navBarLayerRotatesAndScales(rotation, Surface.ROTATION_0, bugId = 140855415)
                     statusBarLayerRotatesScales(rotation, Surface.ROTATION_0)
                     imeLayerBecomesInvisible(bugId = 153739621)
                     imeAppLayerBecomesInvisible(testApp, bugId = 153739621)
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppWarmTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppWarmTest.kt
index 9a8e37b..57d6127 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppWarmTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppWarmTest.kt
@@ -88,7 +88,7 @@
                     noUncoveredRegions(Surface.ROTATION_0, rotation, bugId = 141361128)
                     navBarLayerRotatesAndScales(Surface.ROTATION_0, rotation)
                     statusBarLayerRotatesScales(Surface.ROTATION_0, rotation)
-                    navBarLayerIsAlwaysVisible()
+                    navBarLayerIsAlwaysVisible(bugId = 140855415)
                     statusBarLayerIsAlwaysVisible(enabled = false)
                     wallpaperLayerBecomesInvisible()
                 }
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/splitscreen/OpenAppToSplitScreenTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/splitscreen/OpenAppToSplitScreenTest.kt
index e078f26..279092d 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/splitscreen/OpenAppToSplitScreenTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/splitscreen/OpenAppToSplitScreenTest.kt
@@ -87,10 +87,10 @@
                 }
 
                 layersTrace {
-                    navBarLayerIsAlwaysVisible()
+                    navBarLayerIsAlwaysVisible(bugId = 140855415)
                     statusBarLayerIsAlwaysVisible()
-                    noUncoveredRegions(rotation)
-                    navBarLayerRotatesAndScales(rotation)
+                    noUncoveredRegions(rotation, enabled = false)
+                    navBarLayerRotatesAndScales(rotation, bugId = 140855415)
                     statusBarLayerRotatesScales(rotation)
 
                     all("dividerLayerBecomesVisible") {
@@ -102,7 +102,8 @@
 
                 eventLog {
                     focusChanges(testApp.`package`,
-                            "recents_animation_input_consumer", "NexusLauncherActivity")
+                            "recents_animation_input_consumer", "NexusLauncherActivity",
+                            bugId = 151179149)
                 }
             }
         }
diff --git a/tests/HwAccelerationTest/AndroidManifest.xml b/tests/HwAccelerationTest/AndroidManifest.xml
index 7790043..05a59ef 100644
--- a/tests/HwAccelerationTest/AndroidManifest.xml
+++ b/tests/HwAccelerationTest/AndroidManifest.xml
@@ -744,6 +744,15 @@
             </intent-filter>
         </activity>
 
+        <activity android:name="BlurActivity"
+                  android:label="Shaders/Blur"
+                  android:exported="true">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="com.android.test.hwui.TEST"/>
+            </intent-filter>
+        </activity>
+
         <activity android:name="TextActivity"
              android:label="Text/Simple Text"
              android:theme="@android:style/Theme.NoTitleBar"
diff --git a/tests/HwAccelerationTest/src/com/android/test/hwui/BlurActivity.java b/tests/HwAccelerationTest/src/com/android/test/hwui/BlurActivity.java
new file mode 100644
index 0000000..033fb0e
--- /dev/null
+++ b/tests/HwAccelerationTest/src/com/android/test/hwui/BlurActivity.java
@@ -0,0 +1,110 @@
+/*
+ * 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.test.hwui;
+
+import android.app.Activity;
+import android.content.Context;
+import android.graphics.BlurShader;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.LinearGradient;
+import android.graphics.Paint;
+import android.graphics.Shader;
+import android.os.Bundle;
+import android.view.Gravity;
+import android.view.View;
+import android.widget.LinearLayout;
+
+@SuppressWarnings({"UnusedDeclaration"})
+public class BlurActivity extends Activity {
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        LinearLayout layout = new LinearLayout(this);
+        layout.setClipChildren(false);
+        layout.setGravity(Gravity.CENTER);
+        layout.setOrientation(LinearLayout.VERTICAL);
+
+
+        LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(500, 500);
+        params.bottomMargin = 100;
+
+        layout.addView(new BlurGradientView(this), params);
+        layout.addView(new BlurView(this), params);
+
+        setContentView(layout);
+    }
+
+    public static class BlurGradientView extends View {
+        private BlurShader mBlurShader = null;
+        private Paint mPaint;
+
+        public BlurGradientView(Context c) {
+            super(c);
+        }
+
+        @Override
+        protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
+            if (changed || mBlurShader == null) {
+                LinearGradient gradient = new LinearGradient(
+                        0f,
+                        0f,
+                        right - left,
+                        bottom - top,
+                        Color.CYAN,
+                        Color.YELLOW,
+                        Shader.TileMode.CLAMP
+                );
+                mBlurShader = new BlurShader(30f, 40f, gradient);
+                mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
+                mPaint.setShader(mBlurShader);
+            }
+        }
+
+        @Override
+        protected void onDraw(Canvas canvas) {
+            super.onDraw(canvas);
+            canvas.drawRect(0, 0, getWidth(), getHeight(), mPaint);
+        }
+    }
+
+    public static class BlurView extends View {
+
+        private final BlurShader mBlurShader;
+        private final Paint mPaint;
+
+        public BlurView(Context c) {
+            super(c);
+
+            mBlurShader = new BlurShader(20f, 20f, null);
+            mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
+            mPaint.setShader(mBlurShader);
+        }
+
+        @Override
+        protected void onDraw(Canvas canvas) {
+            super.onDraw(canvas);
+
+            mPaint.setColor(Color.BLUE);
+            canvas.drawRect(0, 0, getWidth(), getHeight(), mPaint);
+
+            mPaint.setColor(Color.RED);
+            canvas.drawCircle(getWidth() / 2f, getHeight() / 2f, 50f, mPaint);
+        }
+    }
+}
diff --git a/tests/HwAccelerationTest/src/com/android/test/hwui/ColorFiltersMutateActivity.java b/tests/HwAccelerationTest/src/com/android/test/hwui/ColorFiltersMutateActivity.java
index 51bae3a..08144c8 100644
--- a/tests/HwAccelerationTest/src/com/android/test/hwui/ColorFiltersMutateActivity.java
+++ b/tests/HwAccelerationTest/src/com/android/test/hwui/ColorFiltersMutateActivity.java
@@ -60,7 +60,7 @@
 
         static final String sSkSL =
                 "uniform float param1;\n"
-                + "void main(float x, float y, inout half4 color) {\n"
+                + "void main(float2 xy, inout half4 color) {\n"
                 + "color = half4(color.r, half(param1), color.b, 1.0);\n"
                 + "}\n";
 
diff --git a/tests/Input/Android.bp b/tests/Input/Android.bp
new file mode 100644
index 0000000..9d35cbc
--- /dev/null
+++ b/tests/Input/Android.bp
@@ -0,0 +1,12 @@
+android_test {
+    name: "InputTests",
+    srcs: ["src/**/*.kt"],
+    platform_apis: true,
+    certificate: "platform",
+    static_libs: [
+            "androidx.test.ext.junit",
+            "androidx.test.rules",
+            "android-support-test",
+            "ub-uiautomator",
+        ],
+}
diff --git a/tests/Input/AndroidManifest.xml b/tests/Input/AndroidManifest.xml
new file mode 100644
index 0000000..4195df7
--- /dev/null
+++ b/tests/Input/AndroidManifest.xml
@@ -0,0 +1,35 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2020 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+     package="com.android.test.input">
+    <uses-permission android:name="android.permission.MONITOR_INPUT"/>
+    <uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS"/>
+    <uses-permission android:name="android.permission.INJECT_EVENTS"/>
+
+    <application android:label="InputTest">
+
+        <activity android:name=".UnresponsiveGestureMonitorActivity"
+             android:label="Unresponsive gesture monitor"
+             android:process=":externalProcess">
+        </activity>
+
+
+    </application>
+    <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+                     android:targetPackage="com.android.test.input"
+                     android:label="Input Tests"/>
+</manifest>
diff --git a/tests/Input/AndroidTest.xml b/tests/Input/AndroidTest.xml
new file mode 100644
index 0000000..c62db1ea
--- /dev/null
+++ b/tests/Input/AndroidTest.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ * Copyright 2020 Google Inc. All Rights Reserved.
+ -->
+<configuration description="Runs Input Tests">
+    <option name="test-tag" value="InputTests" />
+    <target_preparer class="com.android.tradefed.targetprep.DeviceSetup">
+        <!-- keeps the screen on during tests -->
+        <option name="screen-always-on" value="on" />
+        <!-- prevents the phone from restarting -->
+        <option name="force-skip-system-props" value="true" />
+    </target_preparer>
+    <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
+        <option name="cleanup-apks" value="true"/>
+        <option name="test-file-name" value="InputTests.apk"/>
+
+    </target_preparer>
+    <test class="com.android.tradefed.testtype.AndroidJUnitTest">
+        <option name="package" value="com.android.test.input"/>
+        <option name="exclude-annotation" value="androidx.test.filters.FlakyTest" />
+        <option name="shell-timeout" value="660s" />
+        <option name="test-timeout" value="600s" />
+        <option name="runner" value="androidx.test.runner.AndroidJUnitRunner" />
+    </test>
+</configuration>
diff --git a/tests/Input/src/com/android/test/input/AnrTest.kt b/tests/Input/src/com/android/test/input/AnrTest.kt
new file mode 100644
index 0000000..4da3eca
--- /dev/null
+++ b/tests/Input/src/com/android/test/input/AnrTest.kt
@@ -0,0 +1,115 @@
+/*
+ * 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.test.input
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.platform.app.InstrumentationRegistry
+import androidx.test.filters.MediumTest
+
+import android.graphics.Rect
+import android.os.SystemClock
+import android.provider.Settings
+import android.provider.Settings.Global.HIDE_ERROR_DIALOGS
+import android.support.test.uiautomator.By
+import android.support.test.uiautomator.UiDevice
+import android.support.test.uiautomator.UiObject2
+import android.support.test.uiautomator.Until
+import android.view.InputDevice
+import android.view.MotionEvent
+
+import org.junit.After
+import org.junit.Assert.fail
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+/**
+ * This test makes sure that an unresponsive gesture monitor gets an ANR.
+ *
+ * The gesture monitor must be registered from a different process than the instrumented process.
+ * Otherwise, when the test runs, you will get:
+ * Test failed to run to completion.
+ * Reason: 'Instrumentation run failed due to 'keyDispatchingTimedOut''.
+ * Check device logcat for details
+ * RUNNER ERROR: Instrumentation run failed due to 'keyDispatchingTimedOut'
+ */
+@MediumTest
+@RunWith(AndroidJUnit4::class)
+class AnrTest {
+    companion object {
+        private const val TAG = "AnrTest"
+    }
+
+    val mInstrumentation = InstrumentationRegistry.getInstrumentation()
+    var mHideErrorDialogs = 0
+
+    @Before
+    fun setUp() {
+        val contentResolver = mInstrumentation.targetContext.contentResolver
+        mHideErrorDialogs = Settings.Global.getInt(contentResolver, HIDE_ERROR_DIALOGS, 0)
+        Settings.Global.putInt(contentResolver, HIDE_ERROR_DIALOGS, 0)
+    }
+
+    @After
+    fun tearDown() {
+        val contentResolver = mInstrumentation.targetContext.contentResolver
+        Settings.Global.putInt(contentResolver, HIDE_ERROR_DIALOGS, mHideErrorDialogs)
+    }
+
+    @Test
+    fun testGestureMonitorAnr() {
+        startUnresponsiveActivity()
+        val uiDevice: UiDevice = UiDevice.getInstance(mInstrumentation)
+        val obj: UiObject2? = uiDevice.wait(Until.findObject(
+                By.text("Unresponsive gesture monitor")), 10000)
+
+        if (obj == null) {
+            fail("Could not find unresponsive activity")
+            return
+        }
+
+        val rect: Rect = obj.visibleBounds
+        val downTime = SystemClock.uptimeMillis()
+        val downEvent = MotionEvent.obtain(downTime, downTime,
+                MotionEvent.ACTION_DOWN, rect.left.toFloat(), rect.top.toFloat(), 0 /* metaState */)
+        downEvent.source = InputDevice.SOURCE_TOUCHSCREEN
+
+        mInstrumentation.uiAutomation.injectInputEvent(downEvent, false /* sync*/)
+
+        // Todo: replace using timeout from android.hardware.input.IInputManager
+        SystemClock.sleep(5000) // default ANR timeout for gesture monitors
+
+        clickCloseAppOnAnrDialog()
+    }
+
+    private fun clickCloseAppOnAnrDialog() {
+        // Find anr dialog and kill app
+        val uiDevice: UiDevice = UiDevice.getInstance(mInstrumentation)
+        val closeAppButton: UiObject2? =
+                uiDevice.wait(Until.findObject(By.res("android:id/aerr_close")), 20000)
+        if (closeAppButton == null) {
+            fail("Could not find anr dialog")
+            return
+        }
+        closeAppButton.click()
+    }
+
+    private fun startUnresponsiveActivity() {
+        val flags = " -W -n "
+        val startCmd = "am start $flags com.android.test.input/.UnresponsiveGestureMonitorActivity"
+        mInstrumentation.uiAutomation.executeShellCommand(startCmd)
+    }
+}
\ No newline at end of file
diff --git a/tests/Input/src/com/android/test/input/UnresponsiveGestureMonitorActivity.kt b/tests/Input/src/com/android/test/input/UnresponsiveGestureMonitorActivity.kt
new file mode 100644
index 0000000..d83a457
--- /dev/null
+++ b/tests/Input/src/com/android/test/input/UnresponsiveGestureMonitorActivity.kt
@@ -0,0 +1,52 @@
+/**
+ * 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.test.input
+
+import android.app.Activity
+import android.hardware.input.InputManager
+import android.os.Bundle
+import android.os.Looper
+import android.util.Log
+import android.view.InputChannel
+import android.view.InputEvent
+import android.view.InputEventReceiver
+import android.view.InputMonitor
+
+class UnresponsiveReceiver(channel: InputChannel, looper: Looper) :
+        InputEventReceiver(channel, looper) {
+    companion object {
+        const val TAG = "UnresponsiveReceiver"
+    }
+    override fun onInputEvent(event: InputEvent) {
+        Log.i(TAG, "Received $event")
+        // Not calling 'finishInputEvent' in order to trigger the ANR
+    }
+}
+
+class UnresponsiveGestureMonitorActivity : Activity() {
+    companion object {
+        const val MONITOR_NAME = "unresponsive gesture monitor"
+    }
+    private lateinit var mInputEventReceiver: InputEventReceiver
+    private lateinit var mInputMonitor: InputMonitor
+    override fun onCreate(savedInstanceState: Bundle?) {
+        super.onCreate(savedInstanceState)
+        mInputMonitor = InputManager.getInstance().monitorGestureInput(MONITOR_NAME, displayId)
+        mInputEventReceiver = UnresponsiveReceiver(
+                mInputMonitor.getInputChannel(), Looper.myLooper())
+    }
+}
diff --git a/tests/Internal/Android.bp b/tests/Internal/Android.bp
index e233fed..9da17db 100644
--- a/tests/Internal/Android.bp
+++ b/tests/Internal/Android.bp
@@ -11,6 +11,7 @@
         "androidx.test.rules",
         "mockito-target-minus-junit4",
         "truth-prebuilt",
+        "platform-test-annotations",
     ],
     java_resource_dirs: ["res"],
     certificate: "platform",
diff --git a/services/tests/servicestests/src/com/android/server/protolog/ProtoLogImplTest.java b/tests/Internal/src/com/android/internal/protolog/ProtoLogImplTest.java
similarity index 98%
rename from services/tests/servicestests/src/com/android/server/protolog/ProtoLogImplTest.java
rename to tests/Internal/src/com/android/internal/protolog/ProtoLogImplTest.java
index 3e9f625..3db0116 100644
--- a/services/tests/servicestests/src/com/android/server/protolog/ProtoLogImplTest.java
+++ b/tests/Internal/src/com/android/internal/protolog/ProtoLogImplTest.java
@@ -14,11 +14,11 @@
  * limitations under the License.
  */
 
-package com.android.server.protolog;
+package com.android.internal.protolog;
 
 import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
 
-import static com.android.server.protolog.ProtoLogImpl.PROTOLOG_VERSION;
+import static com.android.internal.protolog.ProtoLogImpl.PROTOLOG_VERSION;
 
 import static org.junit.Assert.assertArrayEquals;
 import static org.junit.Assert.assertEquals;
@@ -42,7 +42,7 @@
 
 import androidx.test.filters.SmallTest;
 
-import com.android.server.protolog.common.IProtoLogGroup;
+import com.android.internal.protolog.common.IProtoLogGroup;
 
 import org.junit.After;
 import org.junit.Before;
diff --git a/services/tests/servicestests/src/com/android/server/protolog/ProtoLogViewerConfigReaderTest.java b/tests/Internal/src/com/android/internal/protolog/ProtoLogViewerConfigReaderTest.java
similarity index 98%
rename from services/tests/servicestests/src/com/android/server/protolog/ProtoLogViewerConfigReaderTest.java
rename to tests/Internal/src/com/android/internal/protolog/ProtoLogViewerConfigReaderTest.java
index 0254055..ae50216 100644
--- a/services/tests/servicestests/src/com/android/server/protolog/ProtoLogViewerConfigReaderTest.java
+++ b/tests/Internal/src/com/android/internal/protolog/ProtoLogViewerConfigReaderTest.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.server.protolog;
+package com.android.internal.protolog;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNull;
diff --git a/services/tests/servicestests/src/com/android/server/protolog/common/LogDataTypeTest.java b/tests/Internal/src/com/android/internal/protolog/common/LogDataTypeTest.java
similarity index 97%
rename from services/tests/servicestests/src/com/android/server/protolog/common/LogDataTypeTest.java
rename to tests/Internal/src/com/android/internal/protolog/common/LogDataTypeTest.java
index 4c7f5fd..e20ca3d 100644
--- a/services/tests/servicestests/src/com/android/server/protolog/common/LogDataTypeTest.java
+++ b/tests/Internal/src/com/android/internal/protolog/common/LogDataTypeTest.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.server.protolog.common;
+package com.android.internal.protolog.common;
 
 import static org.junit.Assert.assertEquals;
 
diff --git a/tests/RollbackTest/README.txt b/tests/RollbackTest/README.txt
index c0b718a..bc3b3bc 100644
--- a/tests/RollbackTest/README.txt
+++ b/tests/RollbackTest/README.txt
@@ -9,10 +9,10 @@
   - device driven test for staged rollbacks.
 
 TestApp
-  - source for dummy apks used in testing.
+  - source for fake apks used in testing.
 
 TestApex
-  - source for dummy apex modules used in testing.
+  - source for fake apex modules used in testing.
 
 Running the tests
 =================
diff --git a/tests/SilkFX/Android.bp b/tests/SilkFX/Android.bp
new file mode 100644
index 0000000..ca0a091
--- /dev/null
+++ b/tests/SilkFX/Android.bp
@@ -0,0 +1,22 @@
+//
+// Copyright (C) 2010 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+android_test {
+    name: "SilkFX",
+    srcs: ["**/*.java", "**/*.kt"],
+    platform_apis: true,
+    certificate: "platform",
+}
diff --git a/tests/SilkFX/AndroidManifest.xml b/tests/SilkFX/AndroidManifest.xml
new file mode 100644
index 0000000..ca9550a
--- /dev/null
+++ b/tests/SilkFX/AndroidManifest.xml
@@ -0,0 +1,43 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2010 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+  
+          http://www.apache.org/licenses/LICENSE-2.0
+  
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT 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.test.silkfx">
+
+    <uses-sdk android:minSdkVersion="30"/>
+
+    <uses-permission android:name="android.permission.CONTROL_DISPLAY_BRIGHTNESS" />
+
+    <application android:label="SilkFX"
+         android:theme="@android:style/Theme.Material">
+
+        <activity android:name=".Main"
+             android:label="SilkFX Demos"
+             android:exported="true">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.DEFAULT"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
+            </intent-filter>
+        </activity>
+
+        <activity android:name=".app.CommonDemoActivity" />
+
+        <activity android:name=".hdr.GlowActivity"
+            android:label="Glow Examples"/>
+
+    </application>
+</manifest>
diff --git a/tests/SilkFX/res/drawable-nodpi/dark_notification.png b/tests/SilkFX/res/drawable-nodpi/dark_notification.png
new file mode 100644
index 0000000..6de6c2a
--- /dev/null
+++ b/tests/SilkFX/res/drawable-nodpi/dark_notification.png
Binary files differ
diff --git a/tests/SilkFX/res/drawable-nodpi/light_notification.png b/tests/SilkFX/res/drawable-nodpi/light_notification.png
new file mode 100644
index 0000000..81a67cd
--- /dev/null
+++ b/tests/SilkFX/res/drawable-nodpi/light_notification.png
Binary files differ
diff --git a/tests/SilkFX/res/layout/bling_notifications.xml b/tests/SilkFX/res/layout/bling_notifications.xml
new file mode 100644
index 0000000..6d266b7
--- /dev/null
+++ b/tests/SilkFX/res/layout/bling_notifications.xml
@@ -0,0 +1,35 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2020 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:orientation="vertical">
+
+    <com.android.test.silkfx.hdr.BlingyNotification
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_margin="8dp"
+        android:src="@drawable/dark_notification" />
+
+    <com.android.test.silkfx.hdr.BlingyNotification
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_margin="8dp"
+        android:src="@drawable/light_notification" />
+
+</LinearLayout>
\ No newline at end of file
diff --git a/tests/SilkFX/res/layout/color_mode_controls.xml b/tests/SilkFX/res/layout/color_mode_controls.xml
new file mode 100644
index 0000000..c0c0bab
--- /dev/null
+++ b/tests/SilkFX/res/layout/color_mode_controls.xml
@@ -0,0 +1,64 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2020 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<com.android.test.silkfx.common.ColorModeControls
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:orientation="vertical">
+
+    <TextView
+        android:id="@+id/current_mode"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content" />
+
+    <LinearLayout
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:orientation="horizontal">
+
+        <Button
+            android:id="@+id/mode_default"
+            android:layout_width="0dp"
+            android:layout_height="wrap_content"
+            android:layout_weight="1"
+            android:text="Default (sRGB)" />
+
+        <Button
+            android:id="@+id/mode_wide"
+            android:layout_width="0dp"
+            android:layout_height="wrap_content"
+            android:layout_weight="1"
+            android:text="Wide Gamut (P3)" />
+
+        <Button
+            android:id="@+id/mode_hdr"
+            android:layout_width="0dp"
+            android:layout_height="wrap_content"
+            android:layout_weight="1"
+            android:text="HDR" />
+
+        <Button
+            android:id="@+id/mode_hdr10"
+            android:layout_width="0dp"
+            android:layout_height="wrap_content"
+            android:layout_weight="1"
+            android:text="HDR10" />
+
+    </LinearLayout>
+
+</com.android.test.silkfx.common.ColorModeControls>
\ No newline at end of file
diff --git a/tests/SilkFX/res/layout/common_base.xml b/tests/SilkFX/res/layout/common_base.xml
new file mode 100644
index 0000000..944c684
--- /dev/null
+++ b/tests/SilkFX/res/layout/common_base.xml
@@ -0,0 +1,39 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2020 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:orientation="vertical">
+
+    <include layout="@layout/color_mode_controls" />
+
+    <FrameLayout android:id="@+id/demo_container"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content" />
+
+    <View
+        android:layout_width="match_parent"
+        android:layout_height="0dp"
+        android:layout_weight="1" />
+
+    <com.android.test.silkfx.common.HDRIndicator
+        android:layout_width="match_parent"
+        android:layout_height="50dp"
+        android:layout_margin="8dp" />
+
+</LinearLayout>
\ No newline at end of file
diff --git a/tests/SilkFX/res/layout/hdr_glows.xml b/tests/SilkFX/res/layout/hdr_glows.xml
new file mode 100644
index 0000000..b6050645
--- /dev/null
+++ b/tests/SilkFX/res/layout/hdr_glows.xml
@@ -0,0 +1,51 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2020 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:orientation="vertical">
+
+    <include layout="@layout/color_mode_controls" />
+
+    <com.android.test.silkfx.hdr.GlowingCard
+        android:layout_width="match_parent"
+        android:layout_height="100dp"
+        android:layout_margin="8dp" />
+
+    <com.android.test.silkfx.hdr.GlowingCard
+        android:id="@+id/card2"
+        android:layout_width="match_parent"
+        android:layout_height="100dp"
+        android:layout_margin="8dp"/>
+
+    <com.android.test.silkfx.hdr.RadialGlow
+        android:layout_width="match_parent"
+        android:layout_height="200dp"
+        android:layout_margin="8dp" />
+
+    <View
+        android:layout_width="match_parent"
+        android:layout_height="0dp"
+        android:layout_weight="1" />
+
+    <com.android.test.silkfx.common.HDRIndicator
+        android:layout_width="match_parent"
+        android:layout_height="50dp"
+        android:layout_margin="8dp" />
+
+</LinearLayout>
\ No newline at end of file
diff --git a/tests/SilkFX/src/com/android/test/silkfx/Main.kt b/tests/SilkFX/src/com/android/test/silkfx/Main.kt
new file mode 100644
index 0000000..76e62a6
--- /dev/null
+++ b/tests/SilkFX/src/com/android/test/silkfx/Main.kt
@@ -0,0 +1,125 @@
+/*
+ * 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.test.silkfx
+
+import android.app.Activity
+import android.content.Context
+import android.content.Intent
+import android.os.Bundle
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import android.widget.BaseExpandableListAdapter
+import android.widget.ExpandableListView
+import android.widget.TextView
+import com.android.test.silkfx.app.CommonDemoActivity
+import com.android.test.silkfx.app.EXTRA_LAYOUT
+import com.android.test.silkfx.app.EXTRA_TITLE
+import com.android.test.silkfx.hdr.GlowActivity
+import kotlin.reflect.KClass
+
+class Demo(val name: String, val makeIntent: (Context) -> Intent) {
+    constructor(name: String, activity: KClass<out Activity>) : this(name, { context ->
+        Intent(context, activity.java)
+    })
+    constructor(name: String, layout: Int) : this(name, { context ->
+        Intent(context, CommonDemoActivity::class.java).apply {
+            putExtra(EXTRA_LAYOUT, layout)
+            putExtra(EXTRA_TITLE, name)
+        }
+    })
+}
+data class DemoGroup(val groupName: String, val demos: List<Demo>)
+
+private val AllDemos = listOf(
+        DemoGroup("HDR", listOf(
+                Demo("Glow", GlowActivity::class),
+                Demo("Blingy Notifications", R.layout.bling_notifications)
+        ))
+)
+
+class Main : Activity() {
+
+    public override fun onCreate(savedInstanceState: Bundle?) {
+        super.onCreate(savedInstanceState)
+
+        val list = ExpandableListView(this)
+
+        setContentView(list)
+
+        val inflater = LayoutInflater.from(this)
+        list.setAdapter(object : BaseExpandableListAdapter() {
+            override fun getGroup(groupPosition: Int): DemoGroup {
+                return AllDemos[groupPosition]
+            }
+
+            override fun isChildSelectable(groupPosition: Int, childPosition: Int): Boolean = true
+
+            override fun hasStableIds(): Boolean = true
+
+            override fun getGroupView(
+                groupPosition: Int,
+                isExpanded: Boolean,
+                convertView: View?,
+                parent: ViewGroup?
+            ): View {
+                val view = (convertView ?: inflater.inflate(
+                        android.R.layout.simple_expandable_list_item_1, parent, false)) as TextView
+                view.text = AllDemos[groupPosition].groupName
+                return view
+            }
+
+            override fun getChildrenCount(groupPosition: Int): Int {
+                return AllDemos[groupPosition].demos.size
+            }
+
+            override fun getChild(groupPosition: Int, childPosition: Int): Demo {
+                return AllDemos[groupPosition].demos[childPosition]
+            }
+
+            override fun getGroupId(groupPosition: Int): Long = groupPosition.toLong()
+
+            override fun getChildView(
+                groupPosition: Int,
+                childPosition: Int,
+                isLastChild: Boolean,
+                convertView: View?,
+                parent: ViewGroup?
+            ): View {
+                val view = (convertView ?: inflater.inflate(
+                        android.R.layout.simple_expandable_list_item_1, parent, false)) as TextView
+                view.text = AllDemos[groupPosition].demos[childPosition].name
+                return view
+            }
+
+            override fun getChildId(groupPosition: Int, childPosition: Int): Long {
+                return (groupPosition.toLong() shl 32) or childPosition.toLong()
+            }
+
+            override fun getGroupCount(): Int {
+                return AllDemos.size
+            }
+        })
+
+        list.setOnChildClickListener { _, _, groupPosition, childPosition, _ ->
+            val demo = AllDemos[groupPosition].demos[childPosition]
+            startActivity(demo.makeIntent(this))
+            return@setOnChildClickListener true
+        }
+
+        AllDemos.forEachIndexed { index, _ -> list.expandGroup(index) }
+    }
+}
\ No newline at end of file
diff --git a/tests/SilkFX/src/com/android/test/silkfx/app/BaseDemoActivity.kt b/tests/SilkFX/src/com/android/test/silkfx/app/BaseDemoActivity.kt
new file mode 100644
index 0000000..89011b5
--- /dev/null
+++ b/tests/SilkFX/src/com/android/test/silkfx/app/BaseDemoActivity.kt
@@ -0,0 +1,77 @@
+/*
+ * 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.test.silkfx.app
+
+import android.app.Activity
+import android.content.Context
+import android.os.Bundle
+import android.util.AttributeSet
+import android.view.LayoutInflater
+import android.view.MenuItem
+import android.view.View
+
+open class BaseDemoActivity : Activity() {
+    override fun onCreate(savedInstanceState: Bundle?) {
+        super.onCreate(savedInstanceState)
+
+        val inflater = LayoutInflater.from(this)
+        inflater.factory2 = object : LayoutInflater.Factory2 {
+            private val sClassPrefixList = arrayOf(
+                    "android.widget.",
+                    "android.webkit.",
+                    "android.app.",
+                    null
+            )
+            override fun onCreateView(
+                parent: View?,
+                name: String,
+                context: Context,
+                attrs: AttributeSet
+            ): View? {
+                return onCreateView(name, context, attrs)
+            }
+
+            override fun onCreateView(name: String, context: Context, attrs: AttributeSet): View? {
+                for (prefix in sClassPrefixList) {
+                    try {
+                        val view = inflater.createView(name, prefix, attrs)
+                        if (view != null) {
+                            if (view is WindowObserver) {
+                                view.setWindow(window)
+                            }
+                            return view
+                        }
+                    } catch (e: ClassNotFoundException) { }
+                }
+                return null
+            }
+        }
+    }
+
+    override fun onStart() {
+        super.onStart()
+        actionBar?.setDisplayHomeAsUpEnabled(true)
+    }
+
+    override fun onOptionsItemSelected(item: MenuItem): Boolean {
+        if (item.itemId == android.R.id.home) {
+            onBackPressed()
+            return true
+        }
+        return super.onOptionsItemSelected(item)
+    }
+}
\ No newline at end of file
diff --git a/tests/SilkFX/src/com/android/test/silkfx/app/CommonDemoActivity.kt b/tests/SilkFX/src/com/android/test/silkfx/app/CommonDemoActivity.kt
new file mode 100644
index 0000000..e0a0a20
--- /dev/null
+++ b/tests/SilkFX/src/com/android/test/silkfx/app/CommonDemoActivity.kt
@@ -0,0 +1,45 @@
+/*
+ * 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.test.silkfx.app
+
+import com.android.test.silkfx.R
+import android.os.Bundle
+import android.view.LayoutInflater
+
+const val EXTRA_LAYOUT = "layout"
+const val EXTRA_TITLE = "title"
+
+class CommonDemoActivity : BaseDemoActivity() {
+
+    override fun onCreate(savedInstanceState: Bundle?) {
+        super.onCreate(savedInstanceState)
+
+        val extras = intent.extras ?: return finish()
+
+        val layout = extras.getInt(EXTRA_LAYOUT, -1)
+        if (layout == -1) {
+            finish()
+            return
+        }
+        val title = extras.getString(EXTRA_TITLE, "SilkFX")
+        window.setTitle(title)
+
+        setContentView(R.layout.common_base)
+        actionBar?.title = title
+        LayoutInflater.from(this).inflate(layout, findViewById(R.id.demo_container), true)
+    }
+}
\ No newline at end of file
diff --git a/tests/AutoVerify/app1/src/com/android/test/autoverify/MainActivity.java b/tests/SilkFX/src/com/android/test/silkfx/app/WindowObserver.kt
similarity index 82%
copy from tests/AutoVerify/app1/src/com/android/test/autoverify/MainActivity.java
copy to tests/SilkFX/src/com/android/test/silkfx/app/WindowObserver.kt
index 09ef472..3d989a5 100644
--- a/tests/AutoVerify/app1/src/com/android/test/autoverify/MainActivity.java
+++ b/tests/SilkFX/src/com/android/test/silkfx/app/WindowObserver.kt
@@ -13,3 +13,11 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
+
+package com.android.test.silkfx.app
+
+import android.view.Window
+
+interface WindowObserver {
+    fun setWindow(window: Window)
+}
\ No newline at end of file
diff --git a/tests/SilkFX/src/com/android/test/silkfx/common/BaseDrawingView.kt b/tests/SilkFX/src/com/android/test/silkfx/common/BaseDrawingView.kt
new file mode 100644
index 0000000..4b85953
--- /dev/null
+++ b/tests/SilkFX/src/com/android/test/silkfx/common/BaseDrawingView.kt
@@ -0,0 +1,43 @@
+/*
+ * 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.test.silkfx.common
+
+import android.content.Context
+import android.graphics.Color
+import android.graphics.ColorSpace
+import android.util.AttributeSet
+import android.view.View
+
+open class BaseDrawingView : View {
+    val scRGB = ColorSpace.get(ColorSpace.Named.EXTENDED_SRGB)
+    val bt2020 = ColorSpace.get(ColorSpace.Named.BT2020)
+    val lab = ColorSpace.get(ColorSpace.Named.CIE_LAB)
+
+    val density: Float
+    val dp: Int.() -> Float
+
+    fun color(red: Float, green: Float, blue: Float, alpha: Float = 1f): Long {
+        return Color.pack(red, green, blue, alpha, scRGB)
+    }
+
+    constructor(context: Context, attrs: AttributeSet?) : super(context, attrs) {
+        setWillNotDraw(false)
+        isClickable = true
+        density = resources.displayMetrics.density
+        dp = { this * density }
+    }
+}
\ No newline at end of file
diff --git a/tests/SilkFX/src/com/android/test/silkfx/common/ColorModeControls.kt b/tests/SilkFX/src/com/android/test/silkfx/common/ColorModeControls.kt
new file mode 100644
index 0000000..9b15b04
--- /dev/null
+++ b/tests/SilkFX/src/com/android/test/silkfx/common/ColorModeControls.kt
@@ -0,0 +1,122 @@
+/*
+ * 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.test.silkfx.common
+
+import android.content.Context
+import android.content.pm.ActivityInfo
+import android.hardware.display.DisplayManager
+import android.util.AttributeSet
+import android.view.Window
+import android.widget.Button
+import android.widget.LinearLayout
+import android.widget.TextView
+import com.android.test.silkfx.R
+import com.android.test.silkfx.app.WindowObserver
+
+class ColorModeControls : LinearLayout, WindowObserver {
+    private val COLOR_MODE_HDR10 = 3
+    private val SDR_WHITE_POINTS = floatArrayOf(200f, 250f, 300f, 350f, 400f, 100f, 150f)
+
+    constructor(context: Context) : this(context, null)
+    constructor(context: Context, attrs: AttributeSet?) : super(context, attrs) {
+        displayManager = context.getSystemService(DisplayManager::class.java)!!
+    }
+
+    private var window: Window? = null
+    private var currentModeDisplay: TextView? = null
+    private val displayManager: DisplayManager
+    private var targetSdrWhitePointIndex = 0
+
+    private val whitePoint get() = SDR_WHITE_POINTS[targetSdrWhitePointIndex]
+
+    override fun onFinishInflate() {
+        super.onFinishInflate()
+        val window = window ?: throw IllegalStateException("Failed to attach window")
+
+        currentModeDisplay = findViewById(R.id.current_mode)!!
+        setColorMode(window.colorMode)
+
+        findViewById<Button>(R.id.mode_default)!!.setOnClickListener {
+            setColorMode(ActivityInfo.COLOR_MODE_DEFAULT)
+        }
+        findViewById<Button>(R.id.mode_wide)!!.setOnClickListener {
+            setColorMode(ActivityInfo.COLOR_MODE_WIDE_COLOR_GAMUT)
+        }
+        findViewById<Button>(R.id.mode_hdr)!!.setOnClickListener {
+            setColorMode(ActivityInfo.COLOR_MODE_HDR)
+        }
+        findViewById<Button>(R.id.mode_hdr10)!!.setOnClickListener {
+            setColorMode(COLOR_MODE_HDR10)
+        }
+    }
+
+    private fun setColorMode(newMode: Int) {
+        val window = window!!
+        var sdrWhitepointChanged = false
+        // Need to do this before setting the colorMode, as setting the colorMode will
+        // trigger the attribute change listener
+        if (newMode == ActivityInfo.COLOR_MODE_HDR ||
+                newMode == COLOR_MODE_HDR10) {
+            if (window.colorMode == newMode) {
+                targetSdrWhitePointIndex = (targetSdrWhitePointIndex + 1) % SDR_WHITE_POINTS.size
+                sdrWhitepointChanged = true
+            }
+            setBrightness(1.0f)
+        } else {
+            setBrightness(.4f)
+        }
+        window.colorMode = newMode
+        if (sdrWhitepointChanged) {
+            threadedRenderer?.setColorMode(newMode, whitePoint)
+        }
+        val whitePoint = whitePoint.toInt()
+        currentModeDisplay?.run {
+            text = "Current Mode: " + when (newMode) {
+                ActivityInfo.COLOR_MODE_DEFAULT -> "Default/SRGB"
+                ActivityInfo.COLOR_MODE_WIDE_COLOR_GAMUT -> "Wide Gamut"
+                ActivityInfo.COLOR_MODE_HDR -> "HDR (sdr white point $whitePoint)"
+                COLOR_MODE_HDR10 -> "HDR10 (sdr white point $whitePoint)"
+                else -> "Unknown"
+            }
+        }
+    }
+
+    override fun setWindow(window: Window) {
+        this.window = window
+    }
+
+    private fun setBrightness(level: Float) {
+        // To keep window state in sync
+        window?.attributes?.screenBrightness = level
+        invalidate()
+        // To force an 'immediate' snap to what we want
+        // Imperfect, but close enough, synchronization by waiting for frame commit to set the value
+        viewTreeObserver.registerFrameCommitCallback {
+            try {
+                displayManager.setTemporaryBrightness(level)
+            } catch (ex: Exception) {
+                // Ignore a permission denied rejection - it doesn't meaningfully change much
+            }
+        }
+    }
+
+    override fun onAttachedToWindow() {
+        super.onAttachedToWindow()
+
+        threadedRenderer?.setColorMode(window!!.colorMode, whitePoint)
+    }
+}
\ No newline at end of file
diff --git a/tests/SilkFX/src/com/android/test/silkfx/common/HDRIndicator.kt b/tests/SilkFX/src/com/android/test/silkfx/common/HDRIndicator.kt
new file mode 100644
index 0000000..f42161f
--- /dev/null
+++ b/tests/SilkFX/src/com/android/test/silkfx/common/HDRIndicator.kt
@@ -0,0 +1,50 @@
+/*
+ * 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.test.silkfx.common
+
+import android.content.Context
+import android.graphics.Canvas
+import android.graphics.Color
+import android.graphics.ColorSpace
+import android.graphics.Paint
+import android.graphics.RectF
+import android.util.AttributeSet
+import android.view.View
+
+class HDRIndicator(context: Context) : View(context) {
+    constructor(context: Context, attrs: AttributeSet?) : this(context)
+
+    val scRGB = ColorSpace.get(ColorSpace.Named.EXTENDED_SRGB)
+
+    override fun onDraw(canvas: Canvas) {
+        super.onDraw(canvas)
+
+        val paint = Paint()
+        paint.isAntiAlias = true
+        val rect = RectF(0f, 0f, width.toFloat(), height.toFloat())
+        paint.textSize = height.toFloat()
+
+        canvas.drawColor(Color.pack(1f, 1f, 1f, 1f, scRGB))
+
+        paint.setColor(Color.pack(1.1f, 1.1f, 1.1f, 1f, scRGB))
+        canvas.drawText("H", rect.left, rect.bottom, paint)
+        paint.setColor(Color.pack(1.2f, 1.2f, 1.2f, 1f, scRGB))
+        canvas.drawText("D", rect.left + height.toFloat(), rect.bottom, paint)
+        paint.setColor(Color.pack(1.3f, 1.3f, 1.3f, 1f, scRGB))
+        canvas.drawText("R", rect.left + height.toFloat() * 2, rect.bottom, paint)
+    }
+}
\ No newline at end of file
diff --git a/tests/SilkFX/src/com/android/test/silkfx/hdr/BlingyNotification.kt b/tests/SilkFX/src/com/android/test/silkfx/hdr/BlingyNotification.kt
new file mode 100644
index 0000000..4ad21fa
--- /dev/null
+++ b/tests/SilkFX/src/com/android/test/silkfx/hdr/BlingyNotification.kt
@@ -0,0 +1,99 @@
+/*
+ * 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.test.silkfx.hdr
+
+import android.content.Context
+import android.graphics.Bitmap
+import android.graphics.BlendMode
+import android.graphics.Canvas
+import android.graphics.LinearGradient
+import android.graphics.Paint
+import android.graphics.Rect
+import android.graphics.Shader
+import android.graphics.drawable.BitmapDrawable
+import android.util.AttributeSet
+import com.android.test.silkfx.common.BaseDrawingView
+
+class BlingyNotification : BaseDrawingView {
+
+    private val image: Bitmap?
+    private val bounds = Rect()
+    private val paint = Paint()
+
+    constructor(context: Context, attrs: AttributeSet?) : super(context, attrs) {
+        val typed = context.obtainStyledAttributes(attrs, intArrayOf(android.R.attr.src))
+        val drawable = typed.getDrawable(0)
+        image = if (drawable is BitmapDrawable) {
+            drawable.bitmap
+        } else {
+            null
+        }
+        typed.recycle()
+    }
+
+    override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
+        val image = image ?: return super.onMeasure(widthMeasureSpec, heightMeasureSpec)
+
+        val widthMode = MeasureSpec.getMode(widthMeasureSpec)
+        val heightMode = MeasureSpec.getMode(heightMeasureSpec)
+
+        // Currently only used in this mode, so that's all we'll bother to support
+        if (widthMode == MeasureSpec.EXACTLY && heightMode != MeasureSpec.EXACTLY) {
+            val width = MeasureSpec.getSize(widthMeasureSpec)
+
+            var height = image.height * width / image.width
+            if (heightMode == MeasureSpec.AT_MOST) {
+                height = minOf(MeasureSpec.getSize(heightMeasureSpec), height)
+            }
+            setMeasuredDimension(width, height)
+        } else {
+            super.onMeasure(widthMeasureSpec, heightMeasureSpec)
+        }
+    }
+
+    override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {
+        super.onSizeChanged(w, h, oldw, oldh)
+        bounds.set(0, 0, w, h)
+        paint.shader = LinearGradient(0f, 0f, w.toFloat(), 0f,
+                longArrayOf(
+                        color(1f, 1f, 1f, 0f),
+                        color(1f, 1f, 1f, .1f),
+                        color(2f, 2f, 2f, .3f),
+                        color(1f, 1f, 1f, .2f),
+                        color(1f, 1f, 1f, 0f)
+                        ),
+                floatArrayOf(.2f, .4f, .5f, .6f, .8f),
+                Shader.TileMode.CLAMP)
+        paint.blendMode = BlendMode.PLUS
+    }
+
+    override fun onDraw(canvas: Canvas) {
+        super.onDraw(canvas)
+
+        val image = image ?: return
+
+        canvas.drawBitmap(image, null, bounds, null)
+
+        canvas.save()
+        val frac = ((drawingTime % 2000) / 300f) - 1f
+        canvas.translate(width * frac, 0f)
+        canvas.rotate(-45f)
+        canvas.drawPaint(paint)
+        canvas.restore()
+        invalidate()
+    }
+}
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/tests/src/com/android/wm/shell/WindowManagerShellTest.java b/tests/SilkFX/src/com/android/test/silkfx/hdr/GlowActivity.kt
similarity index 60%
copy from libs/WindowManager/Shell/tests/src/com/android/wm/shell/WindowManagerShellTest.java
copy to tests/SilkFX/src/com/android/test/silkfx/hdr/GlowActivity.kt
index f1ead3c..64dbb22 100644
--- a/libs/WindowManager/Shell/tests/src/com/android/wm/shell/WindowManagerShellTest.java
+++ b/tests/SilkFX/src/com/android/test/silkfx/hdr/GlowActivity.kt
@@ -14,25 +14,16 @@
  * limitations under the License.
  */
 
-package com.android.wm.shell;
+package com.android.test.silkfx.hdr
 
-import androidx.test.ext.junit.runners.AndroidJUnit4;
-import androidx.test.filters.SmallTest;
+import android.os.Bundle
+import com.android.test.silkfx.R
+import com.android.test.silkfx.app.BaseDemoActivity
 
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-/**
- * Tests for the shell.
- */
-@SmallTest
-@RunWith(AndroidJUnit4.class)
-public class WindowManagerShellTest {
-
-    WindowManagerShell mShell;
-
-    @Test
-    public void testNothing() {
-        // Do nothing
+class GlowActivity : BaseDemoActivity() {
+    override fun onCreate(savedInstanceState: Bundle?) {
+        super.onCreate(savedInstanceState)
+        setContentView(R.layout.hdr_glows)
+        findViewById<GlowingCard>(R.id.card2)!!.setGlowIntensity(4f)
     }
 }
diff --git a/tests/SilkFX/src/com/android/test/silkfx/hdr/GlowingCard.kt b/tests/SilkFX/src/com/android/test/silkfx/hdr/GlowingCard.kt
new file mode 100644
index 0000000..b388bb6
--- /dev/null
+++ b/tests/SilkFX/src/com/android/test/silkfx/hdr/GlowingCard.kt
@@ -0,0 +1,85 @@
+/*
+ * 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.test.silkfx.hdr
+
+import android.content.Context
+import android.graphics.Canvas
+import android.graphics.Color
+import android.graphics.LinearGradient
+import android.graphics.Paint
+import android.graphics.RectF
+import android.graphics.Shader
+import android.util.AttributeSet
+import com.android.test.silkfx.common.BaseDrawingView
+
+class GlowingCard : BaseDrawingView {
+
+    constructor(context: Context, attrs: AttributeSet?) : super(context, attrs)
+
+    val radius: Float
+    var COLOR_MAXIMIZER = 1f
+
+    init {
+        radius = 10.dp()
+    }
+
+    fun setGlowIntensity(multiplier: Float) {
+        COLOR_MAXIMIZER = multiplier
+        invalidate()
+    }
+
+    override fun setPressed(pressed: Boolean) {
+        super.setPressed(pressed)
+        invalidate()
+    }
+
+    override fun onDraw(canvas: Canvas) {
+        super.onDraw(canvas)
+        val paint = Paint()
+        paint.isAntiAlias = true
+        val rect = RectF(0f, 0f, width.toFloat(), height.toFloat())
+        val glowColor = Color.pack(.5f * COLOR_MAXIMIZER, .4f * COLOR_MAXIMIZER,
+                .75f * COLOR_MAXIMIZER, 1f, scRGB)
+
+        if (isPressed) {
+            paint.setColor(Color.pack(2f, 2f, 2f, 1f, scRGB))
+            paint.strokeWidth = 4.dp()
+            paint.style = Paint.Style.FILL
+            paint.shader = LinearGradient(rect.left, rect.bottom, rect.right, rect.top,
+                glowColor,
+                Color.pack(0f, 0f, 0f, 0f, scRGB),
+                Shader.TileMode.CLAMP)
+            canvas.drawRoundRect(rect, radius, radius, paint)
+        }
+
+        rect.inset(3.dp(), 3.dp())
+
+        paint.setColor(Color.pack(.14f, .14f, .14f, .8f, scRGB))
+        paint.style = Paint.Style.FILL
+        paint.shader = null
+        canvas.drawRoundRect(rect, radius, radius, paint)
+
+        rect.inset(5.dp(), 5.dp())
+        paint.textSize = 14.dp()
+        paint.isFakeBoldText = true
+
+        paint.color = Color.WHITE
+        canvas.drawText("glow = scRGB{${Color.red(glowColor)}, ${Color.green(glowColor)}, " +
+                "${Color.blue(glowColor)}}", rect.left, rect.centerY(), paint)
+        canvas.drawText("(press to activate)", rect.left, rect.bottom, paint)
+    }
+}
\ No newline at end of file
diff --git a/tests/SilkFX/src/com/android/test/silkfx/hdr/RadialGlow.kt b/tests/SilkFX/src/com/android/test/silkfx/hdr/RadialGlow.kt
new file mode 100644
index 0000000..599585e
--- /dev/null
+++ b/tests/SilkFX/src/com/android/test/silkfx/hdr/RadialGlow.kt
@@ -0,0 +1,91 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.test.silkfx.hdr
+
+import android.content.Context
+import android.graphics.Canvas
+import android.graphics.Paint
+import android.graphics.RadialGradient
+import android.graphics.RectF
+import android.graphics.Shader
+import android.util.AttributeSet
+import com.android.test.silkfx.common.BaseDrawingView
+import kotlin.math.min
+
+class RadialGlow : BaseDrawingView {
+
+    constructor(context: Context, attrs: AttributeSet?) : super(context, attrs)
+
+    var glowToggle = false
+
+    val glowColor = color(4f, 3.3f, 2.8f)
+    val bgColor = color(.15f, .15f, .15f)
+    val fgColor = color(.51f, .52f, .50f, .4f)
+    var glow: RadialGradient
+
+    init {
+        glow = RadialGradient(0f, 0f, 100.dp(), glowColor, bgColor, Shader.TileMode.CLAMP)
+        isClickable = true
+        setOnClickListener {
+            glowToggle = !glowToggle
+            invalidate()
+        }
+    }
+
+    override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {
+        super.onSizeChanged(w, h, oldw, oldh)
+        glow = RadialGradient(0f, 0f,
+            min(w, h).toFloat(), glowColor, bgColor, Shader.TileMode.CLAMP)
+    }
+
+    override fun onDraw(canvas: Canvas) {
+        super.onDraw(canvas)
+        val radius = 10.dp()
+
+        val paint = Paint()
+        paint.isDither = true
+        paint.isAntiAlias = true
+        paint.textSize = 18.dp()
+        paint.textAlign = Paint.Align.CENTER
+
+        val rect = RectF(0f, 0f, width.toFloat(), height.toFloat())
+
+        paint.setColor(bgColor)
+        canvas.drawRoundRect(rect, radius, radius, paint)
+
+        if (glowToggle) {
+            paint.shader = glow
+            canvas.save()
+            val frac = (drawingTime % 5000) / 5000f
+            canvas.translate(rect.width() * frac, rect.height() - (rect.height() * frac))
+            canvas.drawPaint(paint)
+            canvas.restore()
+            paint.shader = null
+            invalidate()
+        }
+
+        paint.setColor(fgColor)
+        val innerRect = RectF(rect)
+        innerRect.inset(rect.width() / 4, rect.height() / 4)
+        canvas.drawRoundRect(innerRect, radius, radius, paint)
+
+        paint.setColor(color(1f, 1f, 1f))
+        canvas.drawText("Tap to toggle animation", rect.centerX(), innerRect.top - 4.dp(), paint)
+        canvas.drawText("Outside text", rect.centerX(), rect.bottom - 4.dp(), paint)
+        canvas.drawText("Inside text", innerRect.centerX(), innerRect.bottom - 4.dp(), paint)
+    }
+}
\ No newline at end of file
diff --git a/tests/StagedInstallTest/Android.bp b/tests/StagedInstallTest/Android.bp
index da6018e..76f8df0 100644
--- a/tests/StagedInstallTest/Android.bp
+++ b/tests/StagedInstallTest/Android.bp
@@ -18,6 +18,9 @@
     srcs: ["app/src/**/*.java"],
     static_libs: ["androidx.test.rules", "cts-install-lib"],
     test_suites: ["general-tests"],
+    java_resources: [
+        ":com.android.apex.apkrollback.test_v2",
+    ],
 }
 
 java_test_host {
@@ -29,6 +32,7 @@
         "compatibility-tradefed",
         "frameworks-base-hostutils",
         "module_test_util",
+        "cts-install-lib-host",
     ],
     data: [
         ":com.android.apex.cts.shim.v2_prebuilt",
diff --git a/tests/StagedInstallTest/app/src/com/android/tests/stagedinstallinternal/StagedInstallInternalTest.java b/tests/StagedInstallTest/app/src/com/android/tests/stagedinstallinternal/StagedInstallInternalTest.java
index 02597d5..7817239 100644
--- a/tests/StagedInstallTest/app/src/com/android/tests/stagedinstallinternal/StagedInstallInternalTest.java
+++ b/tests/StagedInstallTest/app/src/com/android/tests/stagedinstallinternal/StagedInstallInternalTest.java
@@ -49,6 +49,11 @@
 public class StagedInstallInternalTest {
 
     private static final String TAG = StagedInstallInternalTest.class.getSimpleName();
+    private static final String APK_IN_APEX_TESTAPEX_NAME = "com.android.apex.apkrollback.test";
+    private static final TestApp TEST_APEX_WITH_APK_V1 = new TestApp("TestApexWithApkV1",
+            APK_IN_APEX_TESTAPEX_NAME, 1, /*isApex*/true, APK_IN_APEX_TESTAPEX_NAME + "_v1.apex");
+    private static final TestApp TEST_APEX_WITH_APK_V2 = new TestApp("TestApexWithApkV2",
+            APK_IN_APEX_TESTAPEX_NAME, 2, /*isApex*/true, APK_IN_APEX_TESTAPEX_NAME + "_v2.apex");
 
     private File mTestStateFile = new File(
             InstrumentationRegistry.getInstrumentation().getContext().getFilesDir(),
@@ -82,6 +87,24 @@
     }
 
     @Test
+    public void testDuplicateApkInApexShouldFail_Commit() throws Exception {
+        assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(1);
+        // Duplicate packages(TestApp.A) in TEST_APEX_WITH_APK_V2(apk-in-apex) and TestApp.A2(apk)
+        // should fail to install.
+        int sessionId = Install.multi(TEST_APEX_WITH_APK_V2, TestApp.A2).setStaged().commit();
+        storeSessionId(sessionId);
+    }
+
+    @Test
+    public void testDuplicateApkInApexShouldFail_Verify() throws Exception {
+        assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(1);
+        int sessionId = retrieveLastSessionId();
+        PackageInstaller.SessionInfo info =
+                InstallUtils.getPackageInstaller().getSessionInfo(sessionId);
+        assertThat(info.isStagedSessionFailed()).isTrue();
+    }
+
+    @Test
     public void testSystemServerRestartDoesNotAffectStagedSessions_Commit() throws Exception {
         int sessionId = Install.single(TestApp.A1).setStaged().commit();
         assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(-1);
@@ -96,6 +119,18 @@
         assertSessionReady(sessionId);
     }
 
+    @Test
+    public void testAbandonStagedSessionShouldCleanUp() throws Exception {
+        int id1 = Install.single(TestApp.A1).setStaged().createSession();
+        InstallUtils.getPackageInstaller().abandonSession(id1);
+        int id2 = Install.multi(TestApp.A1).setStaged().createSession();
+        InstallUtils.getPackageInstaller().abandonSession(id2);
+        int id3 = Install.single(TestApp.A1).setStaged().commit();
+        InstallUtils.getPackageInstaller().abandonSession(id3);
+        int id4 = Install.multi(TestApp.A1).setStaged().commit();
+        InstallUtils.getPackageInstaller().abandonSession(id4);
+    }
+
     private static void assertSessionReady(int sessionId) {
         assertSessionState(sessionId,
                 (session) -> assertThat(session.isStagedSessionReady()).isTrue());
diff --git a/tests/StagedInstallTest/src/com/android/tests/stagedinstallinternal/host/StagedInstallInternalTest.java b/tests/StagedInstallTest/src/com/android/tests/stagedinstallinternal/host/StagedInstallInternalTest.java
index 7cfbdc2..d7b0796 100644
--- a/tests/StagedInstallTest/src/com/android/tests/stagedinstallinternal/host/StagedInstallInternalTest.java
+++ b/tests/StagedInstallTest/src/com/android/tests/stagedinstallinternal/host/StagedInstallInternalTest.java
@@ -22,11 +22,16 @@
 import static org.junit.Assert.fail;
 import static org.junit.Assume.assumeTrue;
 
+import android.cts.install.lib.host.InstallUtilsHost;
+
+import com.android.compatibility.common.tradefed.build.CompatibilityBuildHelper;
 import com.android.ddmlib.Log;
 import com.android.tests.rollback.host.AbandonSessionsRule;
 import com.android.tests.util.ModuleTestUtils;
 import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
 import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test;
+import com.android.tradefed.util.CommandResult;
+import com.android.tradefed.util.CommandStatus;
 import com.android.tradefed.util.ProcessInfo;
 
 import org.junit.After;
@@ -36,6 +41,9 @@
 import org.junit.runner.RunWith;
 
 import java.io.File;
+import java.util.Collections;
+import java.util.List;
+import java.util.stream.Collectors;
 
 @RunWith(DeviceJUnit4ClassRunner.class)
 public class StagedInstallInternalTest extends BaseHostJUnit4Test {
@@ -47,8 +55,10 @@
     public AbandonSessionsRule mHostTestRule = new AbandonSessionsRule(this);
     private static final String SHIM_V2 = "com.android.apex.cts.shim.v2.apex";
     private static final String APK_A = "TestAppAv1.apk";
+    private static final String APK_IN_APEX_TESTAPEX_NAME = "com.android.apex.apkrollback.test";
 
     private final ModuleTestUtils mTestUtils = new ModuleTestUtils(this);
+    private final InstallUtilsHost mHostUtils = new InstallUtilsHost(this);
 
     /**
      * Runs the given phase of a test by calling into the device.
@@ -71,6 +81,8 @@
         } catch (AssertionError e) {
             Log.e(TAG, e);
         }
+        deleteFiles("/system/apex/" + APK_IN_APEX_TESTAPEX_NAME + "*.apex",
+                "/data/apex/active/" + APK_IN_APEX_TESTAPEX_NAME + "*.apex");
     }
 
     @Before
@@ -83,6 +95,55 @@
         cleanUp();
     }
 
+    /**
+     * Deletes files and reboots the device if necessary.
+     * @param files the paths of files which might contain wildcards
+     */
+    private void deleteFiles(String... files) throws Exception {
+        boolean found = false;
+        for (String file : files) {
+            CommandResult result = getDevice().executeShellV2Command("ls " + file);
+            if (result.getStatus() == CommandStatus.SUCCESS) {
+                found = true;
+                break;
+            }
+        }
+
+        if (found) {
+            if (!getDevice().isAdbRoot()) {
+                getDevice().enableAdbRoot();
+            }
+            getDevice().remountSystemWritable();
+            for (String file : files) {
+                getDevice().executeShellCommand("rm -rf " + file);
+            }
+            getDevice().reboot();
+        }
+    }
+
+    private void pushTestApex() throws Exception {
+        CompatibilityBuildHelper buildHelper = new CompatibilityBuildHelper(getBuild());
+        final String fileName = APK_IN_APEX_TESTAPEX_NAME + "_v1.apex";
+        final File apex = buildHelper.getTestFile(fileName);
+        if (!getDevice().isAdbRoot()) {
+            getDevice().enableAdbRoot();
+        }
+        getDevice().remountSystemWritable();
+        assertTrue(getDevice().pushFile(apex, "/system/apex/" + fileName));
+        getDevice().reboot();
+    }
+
+    /**
+     * Tests that duplicate packages in apk-in-apex and apk should fail to install.
+     */
+    @Test
+    public void testDuplicateApkInApexShouldFail() throws Exception {
+        pushTestApex();
+        runPhase("testDuplicateApkInApexShouldFail_Commit");
+        getDevice().reboot();
+        runPhase("testDuplicateApkInApexShouldFail_Verify");
+    }
+
     @Test
     public void testSystemServerRestartDoesNotAffectStagedSessions() throws Exception {
         runPhase("testSystemServerRestartDoesNotAffectStagedSessions_Commit");
@@ -93,7 +154,7 @@
     @Test
     public void testAdbStagedInstallWaitForReadyFlagWorks() throws Exception {
         assumeTrue("Device does not support updating APEX",
-                mTestUtils.isApexUpdateSupported());
+                mHostUtils.isApexUpdateSupported());
 
         File apexFile = mTestUtils.getTestFile(SHIM_V2);
         String output = getDevice().executeAdbCommand("install", "--staged",
@@ -107,7 +168,7 @@
     @Test
     public void testAdbStagedInstallNoWaitFlagWorks() throws Exception {
         assumeTrue("Device does not support updating APEX",
-                mTestUtils.isApexUpdateSupported());
+                mHostUtils.isApexUpdateSupported());
 
         File apexFile = mTestUtils.getTestFile(SHIM_V2);
         String output = getDevice().executeAdbCommand("install", "--staged",
@@ -122,7 +183,7 @@
     @Test
     public void testAdbInstallMultiPackageCommandWorks() throws Exception {
         assumeTrue("Device does not support updating APEX",
-                mTestUtils.isApexUpdateSupported());
+                mHostUtils.isApexUpdateSupported());
 
         File apexFile = mTestUtils.getTestFile(SHIM_V2);
         File apkFile = mTestUtils.getTestFile(APK_A);
@@ -142,6 +203,28 @@
         assertThat(sessionIds.length).isEqualTo(3);
     }
 
+    @Test
+    public void testAbandonStagedSessionShouldCleanUp() throws Exception {
+        List<String> before = getStagingDirectories();
+        runPhase("testAbandonStagedSessionShouldCleanUp");
+        List<String> after = getStagingDirectories();
+        // The staging directories generated during the test should be deleted
+        assertThat(after).isEqualTo(before);
+    }
+
+    private List<String> getStagingDirectories() {
+        String baseDir = "/data/app-staging";
+        try {
+            return getDevice().getFileEntry(baseDir).getChildren(false)
+                    .stream().filter(entry -> entry.getName().matches("session_\\d+"))
+                    .map(entry -> entry.getName())
+                    .collect(Collectors.toList());
+        } catch (Exception e) {
+            // Return an empty list if any error
+            return Collections.EMPTY_LIST;
+        }
+    }
+
     private void restartSystemServer() throws Exception {
         // Restart the system server
         ProcessInfo oldPs = getDevice().getProcessByName("system_server");
diff --git a/tests/TaskOrganizerTest/src/com/android/test/taskembed/TaskOrganizerMultiWindowTest.java b/tests/TaskOrganizerTest/src/com/android/test/taskembed/TaskOrganizerMultiWindowTest.java
index 073ae30..ca723b8 100644
--- a/tests/TaskOrganizerTest/src/com/android/test/taskembed/TaskOrganizerMultiWindowTest.java
+++ b/tests/TaskOrganizerTest/src/com/android/test/taskembed/TaskOrganizerMultiWindowTest.java
@@ -157,7 +157,7 @@
     protected void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
 
-        mOrganizer.registerOrganizer(WINDOWING_MODE_MULTI_WINDOW);
+        mOrganizer.registerOrganizer();
 
         mTaskView1 = new ResizingTaskView(this, makeSettingsIntent());
         mTaskView2 = new ResizingTaskView(this, makeContactsIntent());
diff --git a/tests/TaskOrganizerTest/src/com/android/test/taskembed/TaskOrganizerPipTest.java b/tests/TaskOrganizerTest/src/com/android/test/taskembed/TaskOrganizerPipTest.java
index 8fc5c5d..5ec9493 100644
--- a/tests/TaskOrganizerTest/src/com/android/test/taskembed/TaskOrganizerPipTest.java
+++ b/tests/TaskOrganizerTest/src/com/android/test/taskembed/TaskOrganizerPipTest.java
@@ -57,7 +57,7 @@
     public void onCreate() {
         super.onCreate();
 
-        mOrganizer.registerOrganizer(WINDOWING_MODE_PINNED);
+        mOrganizer.registerOrganizer();
 
         final WindowManager.LayoutParams wlp = new WindowManager.LayoutParams();
         wlp.setTitle("TaskOrganizerPipTest");
diff --git a/tests/net/common/java/android/net/DhcpInfoTest.java b/tests/net/common/java/android/net/DhcpInfoTest.java
index 4d45ad7..ab4726b 100644
--- a/tests/net/common/java/android/net/DhcpInfoTest.java
+++ b/tests/net/common/java/android/net/DhcpInfoTest.java
@@ -17,8 +17,8 @@
 package android.net;
 
 import static com.android.net.module.util.Inet4AddressUtils.inet4AddressToIntHTL;
-import static com.android.testutils.MiscAssertsKt.assertFieldCountEquals;
-import static com.android.testutils.ParcelUtilsKt.parcelingRoundTrip;
+import static com.android.testutils.MiscAsserts.assertFieldCountEquals;
+import static com.android.testutils.ParcelUtils.parcelingRoundTrip;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
diff --git a/tests/net/common/java/android/net/IpPrefixTest.java b/tests/net/common/java/android/net/IpPrefixTest.java
index 985e10d..9c0fc7c 100644
--- a/tests/net/common/java/android/net/IpPrefixTest.java
+++ b/tests/net/common/java/android/net/IpPrefixTest.java
@@ -16,10 +16,10 @@
 
 package android.net;
 
-import static com.android.testutils.MiscAssertsKt.assertEqualBothWays;
-import static com.android.testutils.MiscAssertsKt.assertFieldCountEquals;
-import static com.android.testutils.MiscAssertsKt.assertNotEqualEitherWay;
-import static com.android.testutils.ParcelUtilsKt.assertParcelingIsLossless;
+import static com.android.testutils.MiscAsserts.assertEqualBothWays;
+import static com.android.testutils.MiscAsserts.assertFieldCountEquals;
+import static com.android.testutils.MiscAsserts.assertNotEqualEitherWay;
+import static com.android.testutils.ParcelUtils.assertParcelingIsLossless;
 
 import static org.junit.Assert.assertArrayEquals;
 import static org.junit.Assert.assertEquals;
diff --git a/tests/net/common/java/android/net/LinkAddressTest.java b/tests/net/common/java/android/net/LinkAddressTest.java
index c74c112..60308e3 100644
--- a/tests/net/common/java/android/net/LinkAddressTest.java
+++ b/tests/net/common/java/android/net/LinkAddressTest.java
@@ -27,10 +27,10 @@
 import static android.system.OsConstants.RT_SCOPE_SITE;
 import static android.system.OsConstants.RT_SCOPE_UNIVERSE;
 
-import static com.android.testutils.MiscAssertsKt.assertEqualBothWays;
-import static com.android.testutils.MiscAssertsKt.assertFieldCountEquals;
-import static com.android.testutils.MiscAssertsKt.assertNotEqualEitherWay;
-import static com.android.testutils.ParcelUtilsKt.assertParcelingIsLossless;
+import static com.android.testutils.MiscAsserts.assertEqualBothWays;
+import static com.android.testutils.MiscAsserts.assertFieldCountEquals;
+import static com.android.testutils.MiscAsserts.assertNotEqualEitherWay;
+import static com.android.testutils.ParcelUtils.assertParcelingIsLossless;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
diff --git a/tests/net/common/java/android/net/LinkPropertiesTest.java b/tests/net/common/java/android/net/LinkPropertiesTest.java
index 6eba62e..3c3076f 100644
--- a/tests/net/common/java/android/net/LinkPropertiesTest.java
+++ b/tests/net/common/java/android/net/LinkPropertiesTest.java
@@ -20,9 +20,9 @@
 import static android.net.RouteInfo.RTN_UNICAST;
 import static android.net.RouteInfo.RTN_UNREACHABLE;
 
-import static com.android.testutils.ParcelUtilsKt.assertParcelSane;
-import static com.android.testutils.ParcelUtilsKt.assertParcelingIsLossless;
-import static com.android.testutils.ParcelUtilsKt.parcelingRoundTrip;
+import static com.android.testutils.ParcelUtils.assertParcelSane;
+import static com.android.testutils.ParcelUtils.assertParcelingIsLossless;
+import static com.android.testutils.ParcelUtils.parcelingRoundTrip;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
diff --git a/tests/net/common/java/android/net/NetworkCapabilitiesTest.java b/tests/net/common/java/android/net/NetworkCapabilitiesTest.java
index 3f8261d..e169312 100644
--- a/tests/net/common/java/android/net/NetworkCapabilitiesTest.java
+++ b/tests/net/common/java/android/net/NetworkCapabilitiesTest.java
@@ -42,8 +42,8 @@
 import static android.net.NetworkCapabilities.TRANSPORT_WIFI_AWARE;
 import static android.net.NetworkCapabilities.UNRESTRICTED_CAPABILITIES;
 
-import static com.android.testutils.ParcelUtilsKt.assertParcelSane;
-import static com.android.testutils.ParcelUtilsKt.assertParcelingIsLossless;
+import static com.android.testutils.ParcelUtils.assertParcelSane;
+import static com.android.testutils.ParcelUtils.assertParcelingIsLossless;
 
 import static org.junit.Assert.assertArrayEquals;
 import static org.junit.Assert.assertEquals;
diff --git a/tests/net/common/java/android/net/RouteInfoTest.java b/tests/net/common/java/android/net/RouteInfoTest.java
index 60cac0b..71689f9 100644
--- a/tests/net/common/java/android/net/RouteInfoTest.java
+++ b/tests/net/common/java/android/net/RouteInfoTest.java
@@ -18,10 +18,10 @@
 
 import static android.net.RouteInfo.RTN_UNREACHABLE;
 
-import static com.android.testutils.MiscAssertsKt.assertEqualBothWays;
-import static com.android.testutils.MiscAssertsKt.assertFieldCountEquals;
-import static com.android.testutils.MiscAssertsKt.assertNotEqualEitherWay;
-import static com.android.testutils.ParcelUtilsKt.assertParcelingIsLossless;
+import static com.android.testutils.MiscAsserts.assertEqualBothWays;
+import static com.android.testutils.MiscAsserts.assertFieldCountEquals;
+import static com.android.testutils.MiscAsserts.assertNotEqualEitherWay;
+import static com.android.testutils.ParcelUtils.assertParcelingIsLossless;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
diff --git a/tests/net/common/java/android/net/apf/ApfCapabilitiesTest.java b/tests/net/common/java/android/net/apf/ApfCapabilitiesTest.java
index 8480544..d50406f 100644
--- a/tests/net/common/java/android/net/apf/ApfCapabilitiesTest.java
+++ b/tests/net/common/java/android/net/apf/ApfCapabilitiesTest.java
@@ -16,7 +16,7 @@
 
 package android.net.apf;
 
-import static com.android.testutils.ParcelUtilsKt.assertParcelSane;
+import static com.android.testutils.ParcelUtils.assertParcelSane;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
diff --git a/tests/net/integration/util/com/android/server/ConnectivityServiceTestUtils.kt b/tests/net/integration/util/com/android/server/ConnectivityServiceTestUtils.kt
index fa2b99c..165fd37 100644
--- a/tests/net/integration/util/com/android/server/ConnectivityServiceTestUtils.kt
+++ b/tests/net/integration/util/com/android/server/ConnectivityServiceTestUtils.kt
@@ -14,6 +14,8 @@
  * limitations under the License
  */
 
+@file:JvmName("ConnectivityServiceTestUtils")
+
 package com.android.server
 
 import android.net.ConnectivityManager.TYPE_BLUETOOTH
diff --git a/tests/net/integration/util/com/android/server/NetworkAgentWrapper.java b/tests/net/integration/util/com/android/server/NetworkAgentWrapper.java
index 0ffafd4..9f0b41f 100644
--- a/tests/net/integration/util/com/android/server/NetworkAgentWrapper.java
+++ b/tests/net/integration/util/com/android/server/NetworkAgentWrapper.java
@@ -24,7 +24,7 @@
 import static android.net.NetworkCapabilities.TRANSPORT_WIFI;
 import static android.net.NetworkCapabilities.TRANSPORT_WIFI_AWARE;
 
-import static com.android.server.ConnectivityServiceTestUtilsKt.transportToLegacyType;
+import static com.android.server.ConnectivityServiceTestUtils.transportToLegacyType;
 
 import static junit.framework.Assert.assertTrue;
 
@@ -49,7 +49,7 @@
 import android.util.Log;
 
 import com.android.server.connectivity.ConnectivityConstants;
-import com.android.testutils.HandlerUtilsKt;
+import com.android.testutils.HandlerUtils;
 import com.android.testutils.TestableNetworkCallback;
 
 import java.util.Set;
@@ -265,6 +265,6 @@
     }
 
     public void waitForIdle(long timeoutMs) {
-        HandlerUtilsKt.waitForIdle(mHandlerThread, timeoutMs);
+        HandlerUtils.waitForIdle(mHandlerThread, timeoutMs);
     }
 }
diff --git a/tests/net/java/android/net/ConnectivityDiagnosticsManagerTest.java b/tests/net/java/android/net/ConnectivityDiagnosticsManagerTest.java
index 1d6c107..06e9405 100644
--- a/tests/net/java/android/net/ConnectivityDiagnosticsManagerTest.java
+++ b/tests/net/java/android/net/ConnectivityDiagnosticsManagerTest.java
@@ -21,7 +21,7 @@
 import static android.net.ConnectivityDiagnosticsManager.ConnectivityReport;
 import static android.net.ConnectivityDiagnosticsManager.DataStallReport;
 
-import static com.android.testutils.ParcelUtilsKt.assertParcelSane;
+import static com.android.testutils.ParcelUtils.assertParcelSane;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
diff --git a/tests/net/java/android/net/IpSecConfigTest.java b/tests/net/java/android/net/IpSecConfigTest.java
index c9888b2..25e225e 100644
--- a/tests/net/java/android/net/IpSecConfigTest.java
+++ b/tests/net/java/android/net/IpSecConfigTest.java
@@ -16,8 +16,8 @@
 
 package android.net;
 
-import static com.android.testutils.ParcelUtilsKt.assertParcelSane;
-import static com.android.testutils.ParcelUtilsKt.assertParcelingIsLossless;
+import static com.android.testutils.ParcelUtils.assertParcelSane;
+import static com.android.testutils.ParcelUtils.assertParcelingIsLossless;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotSame;
diff --git a/tests/net/java/android/net/TcpKeepalivePacketDataTest.java b/tests/net/java/android/net/TcpKeepalivePacketDataTest.java
index cea8c57..835a83e 100644
--- a/tests/net/java/android/net/TcpKeepalivePacketDataTest.java
+++ b/tests/net/java/android/net/TcpKeepalivePacketDataTest.java
@@ -16,7 +16,7 @@
 
 package android.net;
 
-import static com.android.testutils.ParcelUtilsKt.assertParcelingIsLossless;
+import static com.android.testutils.ParcelUtils.assertParcelingIsLossless;
 
 import static org.junit.Assert.assertArrayEquals;
 import static org.junit.Assert.assertEquals;
diff --git a/tests/net/java/android/net/TelephonyNetworkSpecifierTest.java b/tests/net/java/android/net/TelephonyNetworkSpecifierTest.java
index efb9203..6714bb1 100644
--- a/tests/net/java/android/net/TelephonyNetworkSpecifierTest.java
+++ b/tests/net/java/android/net/TelephonyNetworkSpecifierTest.java
@@ -16,7 +16,7 @@
 
 package android.net;
 
-import static com.android.testutils.ParcelUtilsKt.assertParcelSane;
+import static com.android.testutils.ParcelUtils.assertParcelSane;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
diff --git a/tests/net/java/android/net/nsd/NsdManagerTest.java b/tests/net/java/android/net/nsd/NsdManagerTest.java
index cf7587a..b0a9b8a 100644
--- a/tests/net/java/android/net/nsd/NsdManagerTest.java
+++ b/tests/net/java/android/net/nsd/NsdManagerTest.java
@@ -38,7 +38,7 @@
 import androidx.test.runner.AndroidJUnit4;
 
 import com.android.internal.util.AsyncChannel;
-import com.android.testutils.HandlerUtilsKt;
+import com.android.testutils.HandlerUtils;
 
 import org.junit.After;
 import org.junit.Before;
@@ -73,7 +73,7 @@
 
     @After
     public void tearDown() throws Exception {
-        HandlerUtilsKt.waitForIdle(mServiceHandler, mTimeoutMs);
+        HandlerUtils.waitForIdle(mServiceHandler, mTimeoutMs);
         mServiceHandler.chan.disconnect();
         mServiceHandler.stop();
         if (mManager != null) {
@@ -333,7 +333,7 @@
     }
 
     int verifyRequest(int expectedMessageType) {
-        HandlerUtilsKt.waitForIdle(mServiceHandler, mTimeoutMs);
+        HandlerUtils.waitForIdle(mServiceHandler, mTimeoutMs);
         verify(mServiceHandler, timeout(mTimeoutMs)).handleMessage(any());
         reset(mServiceHandler);
         Message received = mServiceHandler.getLastMessage();
diff --git a/tests/net/java/com/android/internal/net/VpnProfileTest.java b/tests/net/java/com/android/internal/net/VpnProfileTest.java
index e5daa71..46597d1 100644
--- a/tests/net/java/com/android/internal/net/VpnProfileTest.java
+++ b/tests/net/java/com/android/internal/net/VpnProfileTest.java
@@ -16,7 +16,7 @@
 
 package com.android.internal.net;
 
-import static com.android.testutils.ParcelUtilsKt.assertParcelSane;
+import static com.android.testutils.ParcelUtils.assertParcelSane;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
diff --git a/tests/net/java/com/android/server/ConnectivityServiceTest.java b/tests/net/java/com/android/server/ConnectivityServiceTest.java
index 0f24b0c..a3673df 100644
--- a/tests/net/java/com/android/server/ConnectivityServiceTest.java
+++ b/tests/net/java/com/android/server/ConnectivityServiceTest.java
@@ -78,16 +78,16 @@
 import static android.os.Process.INVALID_UID;
 import static android.system.OsConstants.IPPROTO_TCP;
 
-import static com.android.server.ConnectivityServiceTestUtilsKt.transportToLegacyType;
-import static com.android.testutils.ConcurrentUtilsKt.await;
-import static com.android.testutils.ConcurrentUtilsKt.durationOf;
+import static com.android.server.ConnectivityServiceTestUtils.transportToLegacyType;
+import static com.android.testutils.ConcurrentUtils.await;
+import static com.android.testutils.ConcurrentUtils.durationOf;
 import static com.android.testutils.ExceptionUtils.ignoreExceptions;
-import static com.android.testutils.HandlerUtilsKt.waitForIdleSerialExecutor;
-import static com.android.testutils.MiscAssertsKt.assertContainsExactly;
-import static com.android.testutils.MiscAssertsKt.assertEmpty;
-import static com.android.testutils.MiscAssertsKt.assertLength;
-import static com.android.testutils.MiscAssertsKt.assertRunsInAtMost;
-import static com.android.testutils.MiscAssertsKt.assertThrows;
+import static com.android.testutils.HandlerUtils.waitForIdleSerialExecutor;
+import static com.android.testutils.MiscAsserts.assertContainsExactly;
+import static com.android.testutils.MiscAsserts.assertEmpty;
+import static com.android.testutils.MiscAsserts.assertLength;
+import static com.android.testutils.MiscAsserts.assertRunsInAtMost;
+import static com.android.testutils.MiscAsserts.assertThrows;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
@@ -242,7 +242,7 @@
 import com.android.server.net.NetworkPinner;
 import com.android.server.net.NetworkPolicyManagerInternal;
 import com.android.testutils.ExceptionUtils;
-import com.android.testutils.HandlerUtilsKt;
+import com.android.testutils.HandlerUtils;
 import com.android.testutils.RecorderCallback.CallbackEntry;
 import com.android.testutils.TestableNetworkCallback;
 
@@ -518,12 +518,12 @@
     }
 
     private void waitForIdle() {
-        HandlerUtilsKt.waitForIdle(mCsHandlerThread, TIMEOUT_MS);
+        HandlerUtils.waitForIdle(mCsHandlerThread, TIMEOUT_MS);
         waitForIdle(mCellNetworkAgent, TIMEOUT_MS);
         waitForIdle(mWiFiNetworkAgent, TIMEOUT_MS);
         waitForIdle(mEthernetNetworkAgent, TIMEOUT_MS);
-        HandlerUtilsKt.waitForIdle(mCsHandlerThread, TIMEOUT_MS);
-        HandlerUtilsKt.waitForIdle(ConnectivityThread.get(), TIMEOUT_MS);
+        HandlerUtils.waitForIdle(mCsHandlerThread, TIMEOUT_MS);
+        HandlerUtils.waitForIdle(ConnectivityThread.get(), TIMEOUT_MS);
     }
 
     private void waitForIdle(TestNetworkAgentWrapper agent, long timeoutMs) {
@@ -614,8 +614,8 @@
             // Waits for the NetworkAgent to be registered, which includes the creation of the
             // NetworkMonitor.
             waitForIdle(TIMEOUT_MS);
-            HandlerUtilsKt.waitForIdle(mCsHandlerThread, TIMEOUT_MS);
-            HandlerUtilsKt.waitForIdle(ConnectivityThread.get(), TIMEOUT_MS);
+            HandlerUtils.waitForIdle(mCsHandlerThread, TIMEOUT_MS);
+            HandlerUtils.waitForIdle(ConnectivityThread.get(), TIMEOUT_MS);
         }
 
         @Override
@@ -7099,7 +7099,7 @@
                 mConnectivityDiagnosticsCallback, wifiRequest, mContext.getPackageName());
 
         // Block until all other events are done processing.
-        HandlerUtilsKt.waitForIdle(mCsHandlerThread, TIMEOUT_MS);
+        HandlerUtils.waitForIdle(mCsHandlerThread, TIMEOUT_MS);
 
         verify(mIBinder).linkToDeath(any(ConnectivityDiagnosticsCallbackInfo.class), anyInt());
         verify(mConnectivityDiagnosticsCallback).asBinder();
@@ -7122,7 +7122,7 @@
                 mConnectivityDiagnosticsCallback, wifiRequest, mContext.getPackageName());
 
         // Block until all other events are done processing.
-        HandlerUtilsKt.waitForIdle(mCsHandlerThread, TIMEOUT_MS);
+        HandlerUtils.waitForIdle(mCsHandlerThread, TIMEOUT_MS);
 
         verify(mIBinder).linkToDeath(any(ConnectivityDiagnosticsCallbackInfo.class), anyInt());
         verify(mConnectivityDiagnosticsCallback).asBinder();
@@ -7133,7 +7133,7 @@
                 mConnectivityDiagnosticsCallback, wifiRequest, mContext.getPackageName());
 
         // Block until all other events are done processing.
-        HandlerUtilsKt.waitForIdle(mCsHandlerThread, TIMEOUT_MS);
+        HandlerUtils.waitForIdle(mCsHandlerThread, TIMEOUT_MS);
 
         assertTrue(mService.mConnectivityDiagnosticsCallbacks.containsKey(mIBinder));
     }
@@ -7285,7 +7285,7 @@
                 mConnectivityDiagnosticsCallback, request, mContext.getPackageName());
 
         // Block until all other events are done processing.
-        HandlerUtilsKt.waitForIdle(mCsHandlerThread, TIMEOUT_MS);
+        HandlerUtils.waitForIdle(mCsHandlerThread, TIMEOUT_MS);
 
         verify(mConnectivityDiagnosticsCallback)
                 .onConnectivityReportAvailable(argThat(report -> {
@@ -7305,7 +7305,7 @@
                 mConnectivityDiagnosticsCallback, request, mContext.getPackageName());
 
         // Block until all other events are done processing.
-        HandlerUtilsKt.waitForIdle(mCsHandlerThread, TIMEOUT_MS);
+        HandlerUtils.waitForIdle(mCsHandlerThread, TIMEOUT_MS);
 
         // Connect the cell agent verify that it notifies TestNetworkCallback that it is available
         final TestNetworkCallback callback = new TestNetworkCallback();
@@ -7322,7 +7322,7 @@
         setUpConnectivityDiagnosticsCallback();
 
         // Block until all other events are done processing.
-        HandlerUtilsKt.waitForIdle(mCsHandlerThread, TIMEOUT_MS);
+        HandlerUtils.waitForIdle(mCsHandlerThread, TIMEOUT_MS);
 
         // Verify onConnectivityReport fired
         verify(mConnectivityDiagnosticsCallback).onConnectivityReportAvailable(
@@ -7343,7 +7343,7 @@
         mCellNetworkAgent.notifyDataStallSuspected();
 
         // Block until all other events are done processing.
-        HandlerUtilsKt.waitForIdle(mCsHandlerThread, TIMEOUT_MS);
+        HandlerUtils.waitForIdle(mCsHandlerThread, TIMEOUT_MS);
 
         // Verify onDataStallSuspected fired
         verify(mConnectivityDiagnosticsCallback).onDataStallSuspected(
@@ -7364,7 +7364,7 @@
         mService.reportNetworkConnectivity(n, hasConnectivity);
 
         // Block until all other events are done processing.
-        HandlerUtilsKt.waitForIdle(mCsHandlerThread, TIMEOUT_MS);
+        HandlerUtils.waitForIdle(mCsHandlerThread, TIMEOUT_MS);
 
         // Verify onNetworkConnectivityReported fired
         verify(mConnectivityDiagnosticsCallback)
@@ -7374,7 +7374,7 @@
         mService.reportNetworkConnectivity(n, noConnectivity);
 
         // Block until all other events are done processing.
-        HandlerUtilsKt.waitForIdle(mCsHandlerThread, TIMEOUT_MS);
+        HandlerUtils.waitForIdle(mCsHandlerThread, TIMEOUT_MS);
 
         // Wait for onNetworkConnectivityReported to fire
         verify(mConnectivityDiagnosticsCallback)
diff --git a/tests/net/java/com/android/server/connectivity/DnsManagerTest.java b/tests/net/java/com/android/server/connectivity/DnsManagerTest.java
index 508b5cd9..753dbf8 100644
--- a/tests/net/java/com/android/server/connectivity/DnsManagerTest.java
+++ b/tests/net/java/com/android/server/connectivity/DnsManagerTest.java
@@ -26,9 +26,9 @@
 import static android.provider.Settings.Global.PRIVATE_DNS_MODE;
 import static android.provider.Settings.Global.PRIVATE_DNS_SPECIFIER;
 
-import static com.android.testutils.MiscAssertsKt.assertContainsExactly;
-import static com.android.testutils.MiscAssertsKt.assertContainsStringsExactly;
-import static com.android.testutils.MiscAssertsKt.assertFieldCountEquals;
+import static com.android.testutils.MiscAsserts.assertContainsExactly;
+import static com.android.testutils.MiscAsserts.assertContainsStringsExactly;
+import static com.android.testutils.MiscAsserts.assertFieldCountEquals;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
diff --git a/tests/net/java/com/android/server/connectivity/NetdEventListenerServiceTest.java b/tests/net/java/com/android/server/connectivity/NetdEventListenerServiceTest.java
index aef9386..8ccea1a 100644
--- a/tests/net/java/com/android/server/connectivity/NetdEventListenerServiceTest.java
+++ b/tests/net/java/com/android/server/connectivity/NetdEventListenerServiceTest.java
@@ -19,7 +19,7 @@
 import static android.net.metrics.INetdEventListener.EVENT_GETADDRINFO;
 import static android.net.metrics.INetdEventListener.EVENT_GETHOSTBYNAME;
 
-import static com.android.testutils.MiscAssertsKt.assertStringContains;
+import static com.android.testutils.MiscAsserts.assertStringContains;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.fail;
diff --git a/tests/net/java/com/android/server/connectivity/PermissionMonitorTest.java b/tests/net/java/com/android/server/connectivity/PermissionMonitorTest.java
index eb0a867..5a29c2c 100644
--- a/tests/net/java/com/android/server/connectivity/PermissionMonitorTest.java
+++ b/tests/net/java/com/android/server/connectivity/PermissionMonitorTest.java
@@ -26,6 +26,8 @@
 import static android.content.pm.ApplicationInfo.PRIVATE_FLAG_OEM;
 import static android.content.pm.ApplicationInfo.PRIVATE_FLAG_PRODUCT;
 import static android.content.pm.ApplicationInfo.PRIVATE_FLAG_VENDOR;
+import static android.content.pm.PackageInfo.REQUESTED_PERMISSION_GRANTED;
+import static android.content.pm.PackageInfo.REQUESTED_PERMISSION_REQUIRED;
 import static android.content.pm.PackageManager.GET_PERMISSIONS;
 import static android.content.pm.PackageManager.MATCH_ANY_USER;
 import static android.os.Process.SYSTEM_UID;
@@ -95,6 +97,7 @@
     private static final int SYSTEM_UID1 = 1000;
     private static final int SYSTEM_UID2 = 1008;
     private static final int VPN_UID = 10002;
+    private static final String REAL_SYSTEM_PACKAGE_NAME = "android";
     private static final String MOCK_PACKAGE1 = "appName1";
     private static final String MOCK_PACKAGE2 = "appName2";
     private static final String SYSTEM_PACKAGE1 = "sysName1";
@@ -125,7 +128,6 @@
                         new UserInfo(MOCK_USER1, "", 0),
                         new UserInfo(MOCK_USER2, "", 0),
                 }));
-        doReturn(PackageManager.PERMISSION_DENIED).when(mDeps).uidPermission(anyString(), anyInt());
 
         mPermissionMonitor = spy(new PermissionMonitor(mContext, mNetdService, mDeps));
 
@@ -138,22 +140,35 @@
         verify(mMockPmi).getPackageList(mPermissionMonitor);
     }
 
-    /**
-     * Remove all permissions from the uid then build new package info and setup permissions to uid
-     * for checking restricted network permission.
-     */
     private boolean hasRestrictedNetworkPermission(String partition, int targetSdkVersion, int uid,
             String... permissions) {
-        final PackageInfo packageInfo = buildPackageInfo(partition, uid, MOCK_USER1);
+        final PackageInfo packageInfo =
+                packageInfoWithPermissions(REQUESTED_PERMISSION_GRANTED, permissions, partition);
         packageInfo.applicationInfo.targetSdkVersion = targetSdkVersion;
-        removeAllPermissions(uid);
-        addPermissions(uid, permissions);
-        return mPermissionMonitor.hasRestrictedNetworkPermission(packageInfo.applicationInfo);
+        packageInfo.applicationInfo.uid = uid;
+        return mPermissionMonitor.hasRestrictedNetworkPermission(packageInfo);
     }
 
-    private static PackageInfo packageInfoWithPartition(String partition) {
+    private static PackageInfo systemPackageInfoWithPermissions(String... permissions) {
+        return packageInfoWithPermissions(
+                REQUESTED_PERMISSION_GRANTED, permissions, PARTITION_SYSTEM);
+    }
+
+    private static PackageInfo vendorPackageInfoWithPermissions(String... permissions) {
+        return packageInfoWithPermissions(
+                REQUESTED_PERMISSION_GRANTED, permissions, PARTITION_VENDOR);
+    }
+
+    private static PackageInfo packageInfoWithPermissions(int permissionsFlags,
+            String[] permissions, String partition) {
+        int[] requestedPermissionsFlags = new int[permissions.length];
+        for (int i = 0; i < permissions.length; i++) {
+            requestedPermissionsFlags[i] = permissionsFlags;
+        }
         final PackageInfo packageInfo = new PackageInfo();
+        packageInfo.requestedPermissions = permissions;
         packageInfo.applicationInfo = new ApplicationInfo();
+        packageInfo.requestedPermissionsFlags = requestedPermissionsFlags;
         int privateFlags = 0;
         switch (partition) {
             case PARTITION_OEM:
@@ -170,65 +185,85 @@
         return packageInfo;
     }
 
-    private static PackageInfo buildPackageInfo(String partition, int uid, int userId) {
-        final PackageInfo pkgInfo = packageInfoWithPartition(partition);
+    private static PackageInfo buildPackageInfo(boolean hasSystemPermission, int uid, int userId) {
+        final PackageInfo pkgInfo;
+        if (hasSystemPermission) {
+            pkgInfo = systemPackageInfoWithPermissions(
+                    CHANGE_NETWORK_STATE, NETWORK_STACK, CONNECTIVITY_USE_RESTRICTED_NETWORKS);
+        } else {
+            pkgInfo = packageInfoWithPermissions(REQUESTED_PERMISSION_GRANTED, new String[] {}, "");
+        }
         pkgInfo.applicationInfo.uid = UserHandle.getUid(userId, UserHandle.getAppId(uid));
         return pkgInfo;
     }
 
-    /** This will REMOVE all previously set permissions from given uid. */
-    private void removeAllPermissions(int uid) {
-        doReturn(PackageManager.PERMISSION_DENIED).when(mDeps).uidPermission(anyString(), eq(uid));
-    }
-
-    /** Set up mocks so that given UID has the requested permissions. */
-    private void addPermissions(int uid, String... permissions) {
-        for (String permission : permissions) {
-            doReturn(PackageManager.PERMISSION_GRANTED)
-                    .when(mDeps).uidPermission(eq(permission), eq(uid));
-        }
-    }
-
     @Test
     public void testHasPermission() {
-        addPermissions(MOCK_UID1);
-        assertFalse(mPermissionMonitor.hasPermission(CHANGE_NETWORK_STATE, MOCK_UID1));
-        assertFalse(mPermissionMonitor.hasPermission(NETWORK_STACK, MOCK_UID1));
-        assertFalse(mPermissionMonitor.hasPermission(
-                CONNECTIVITY_USE_RESTRICTED_NETWORKS, MOCK_UID1));
-        assertFalse(mPermissionMonitor.hasPermission(CONNECTIVITY_INTERNAL, MOCK_UID1));
+        PackageInfo app = systemPackageInfoWithPermissions();
+        assertFalse(mPermissionMonitor.hasPermission(app, CHANGE_NETWORK_STATE));
+        assertFalse(mPermissionMonitor.hasPermission(app, NETWORK_STACK));
+        assertFalse(mPermissionMonitor.hasPermission(app, CONNECTIVITY_USE_RESTRICTED_NETWORKS));
+        assertFalse(mPermissionMonitor.hasPermission(app, CONNECTIVITY_INTERNAL));
 
-        addPermissions(MOCK_UID1, CHANGE_NETWORK_STATE, NETWORK_STACK);
-        assertTrue(mPermissionMonitor.hasPermission(CHANGE_NETWORK_STATE, MOCK_UID1));
-        assertTrue(mPermissionMonitor.hasPermission(NETWORK_STACK, MOCK_UID1));
-        assertFalse(mPermissionMonitor.hasPermission(
-                CONNECTIVITY_USE_RESTRICTED_NETWORKS, MOCK_UID1));
-        assertFalse(mPermissionMonitor.hasPermission(CONNECTIVITY_INTERNAL, MOCK_UID1));
-        assertFalse(mPermissionMonitor.hasPermission(CHANGE_NETWORK_STATE, MOCK_UID2));
-        assertFalse(mPermissionMonitor.hasPermission(NETWORK_STACK, MOCK_UID2));
+        app = systemPackageInfoWithPermissions(CHANGE_NETWORK_STATE, NETWORK_STACK);
+        assertTrue(mPermissionMonitor.hasPermission(app, CHANGE_NETWORK_STATE));
+        assertTrue(mPermissionMonitor.hasPermission(app, NETWORK_STACK));
+        assertFalse(mPermissionMonitor.hasPermission(app, CONNECTIVITY_USE_RESTRICTED_NETWORKS));
+        assertFalse(mPermissionMonitor.hasPermission(app, CONNECTIVITY_INTERNAL));
 
-        addPermissions(MOCK_UID2, CONNECTIVITY_USE_RESTRICTED_NETWORKS, CONNECTIVITY_INTERNAL);
-        assertFalse(mPermissionMonitor.hasPermission(
-                CONNECTIVITY_USE_RESTRICTED_NETWORKS, MOCK_UID1));
-        assertFalse(mPermissionMonitor.hasPermission(CONNECTIVITY_INTERNAL, MOCK_UID1));
-        assertTrue(mPermissionMonitor.hasPermission(
-                CONNECTIVITY_USE_RESTRICTED_NETWORKS, MOCK_UID2));
-        assertTrue(mPermissionMonitor.hasPermission(CONNECTIVITY_INTERNAL, MOCK_UID2));
+        app = systemPackageInfoWithPermissions(
+                CONNECTIVITY_USE_RESTRICTED_NETWORKS, CONNECTIVITY_INTERNAL);
+        assertFalse(mPermissionMonitor.hasPermission(app, CHANGE_NETWORK_STATE));
+        assertFalse(mPermissionMonitor.hasPermission(app, NETWORK_STACK));
+        assertTrue(mPermissionMonitor.hasPermission(app, CONNECTIVITY_USE_RESTRICTED_NETWORKS));
+        assertTrue(mPermissionMonitor.hasPermission(app, CONNECTIVITY_INTERNAL));
+
+        app = packageInfoWithPermissions(REQUESTED_PERMISSION_REQUIRED, new String[] {
+                CONNECTIVITY_USE_RESTRICTED_NETWORKS, CONNECTIVITY_INTERNAL, NETWORK_STACK },
+                PARTITION_SYSTEM);
+        assertFalse(mPermissionMonitor.hasPermission(app, CHANGE_NETWORK_STATE));
+        assertFalse(mPermissionMonitor.hasPermission(app, NETWORK_STACK));
+        assertFalse(mPermissionMonitor.hasPermission(app, CONNECTIVITY_USE_RESTRICTED_NETWORKS));
+        assertFalse(mPermissionMonitor.hasPermission(app, CONNECTIVITY_INTERNAL));
+
+        app = systemPackageInfoWithPermissions(CHANGE_NETWORK_STATE);
+        app.requestedPermissions = null;
+        assertFalse(mPermissionMonitor.hasPermission(app, CHANGE_NETWORK_STATE));
+
+        app = systemPackageInfoWithPermissions(CHANGE_NETWORK_STATE);
+        app.requestedPermissionsFlags = null;
+        assertFalse(mPermissionMonitor.hasPermission(app, CHANGE_NETWORK_STATE));
     }
 
     @Test
     public void testIsVendorApp() {
-        PackageInfo app = packageInfoWithPartition(PARTITION_SYSTEM);
+        PackageInfo app = systemPackageInfoWithPermissions();
         assertFalse(mPermissionMonitor.isVendorApp(app.applicationInfo));
-        app = packageInfoWithPartition(PARTITION_OEM);
+        app = packageInfoWithPermissions(REQUESTED_PERMISSION_GRANTED,
+                new String[] {}, PARTITION_OEM);
         assertTrue(mPermissionMonitor.isVendorApp(app.applicationInfo));
-        app = packageInfoWithPartition(PARTITION_PRODUCT);
+        app = packageInfoWithPermissions(REQUESTED_PERMISSION_GRANTED,
+                new String[] {}, PARTITION_PRODUCT);
         assertTrue(mPermissionMonitor.isVendorApp(app.applicationInfo));
-        app = packageInfoWithPartition(PARTITION_VENDOR);
+        app = vendorPackageInfoWithPermissions();
         assertTrue(mPermissionMonitor.isVendorApp(app.applicationInfo));
     }
 
     @Test
+    public void testHasNetworkPermission() {
+        PackageInfo app = systemPackageInfoWithPermissions();
+        assertFalse(mPermissionMonitor.hasNetworkPermission(app));
+        app = systemPackageInfoWithPermissions(CHANGE_NETWORK_STATE);
+        assertTrue(mPermissionMonitor.hasNetworkPermission(app));
+        app = systemPackageInfoWithPermissions(NETWORK_STACK);
+        assertFalse(mPermissionMonitor.hasNetworkPermission(app));
+        app = systemPackageInfoWithPermissions(CONNECTIVITY_USE_RESTRICTED_NETWORKS);
+        assertFalse(mPermissionMonitor.hasNetworkPermission(app));
+        app = systemPackageInfoWithPermissions(CONNECTIVITY_INTERNAL);
+        assertFalse(mPermissionMonitor.hasNetworkPermission(app));
+    }
+
+    @Test
     public void testHasRestrictedNetworkPermission() {
         assertFalse(hasRestrictedNetworkPermission(PARTITION_SYSTEM, VERSION_P, MOCK_UID1));
         assertFalse(hasRestrictedNetworkPermission(
@@ -288,27 +323,30 @@
     private void assertBackgroundPermission(boolean hasPermission, String name, int uid,
             String... permissions) throws Exception {
         when(mPackageManager.getPackageInfo(eq(name), anyInt()))
-                .thenReturn(buildPackageInfo(PARTITION_SYSTEM, uid, MOCK_USER1));
-        addPermissions(uid, permissions);
+                .thenReturn(packageInfoWithPermissions(
+                        REQUESTED_PERMISSION_GRANTED, permissions, PARTITION_SYSTEM));
         mPermissionMonitor.onPackageAdded(name, uid);
         assertEquals(hasPermission, mPermissionMonitor.hasUseBackgroundNetworksPermission(uid));
     }
 
     @Test
     public void testHasUseBackgroundNetworksPermission() throws Exception {
-        doReturn(VERSION_Q).when(mDeps).getDeviceFirstSdkInt();
         assertFalse(mPermissionMonitor.hasUseBackgroundNetworksPermission(SYSTEM_UID));
-        assertBackgroundPermission(false, "system1", SYSTEM_UID);
-        assertBackgroundPermission(false, "system2", SYSTEM_UID, CONNECTIVITY_INTERNAL);
-        assertBackgroundPermission(true, "system3", SYSTEM_UID, CHANGE_NETWORK_STATE);
+        assertBackgroundPermission(false, SYSTEM_PACKAGE1, SYSTEM_UID);
+        assertBackgroundPermission(false, SYSTEM_PACKAGE1, SYSTEM_UID, CONNECTIVITY_INTERNAL);
+        assertBackgroundPermission(true, SYSTEM_PACKAGE1, SYSTEM_UID, CHANGE_NETWORK_STATE);
+        assertBackgroundPermission(true, SYSTEM_PACKAGE1, SYSTEM_UID, NETWORK_STACK);
 
         assertFalse(mPermissionMonitor.hasUseBackgroundNetworksPermission(MOCK_UID1));
-        assertBackgroundPermission(false, "mock1", MOCK_UID1);
-        assertBackgroundPermission(true, "mock2", MOCK_UID1, CONNECTIVITY_USE_RESTRICTED_NETWORKS);
+        assertBackgroundPermission(false, MOCK_PACKAGE1, MOCK_UID1);
+        assertBackgroundPermission(true, MOCK_PACKAGE1, MOCK_UID1,
+                CONNECTIVITY_USE_RESTRICTED_NETWORKS);
 
         assertFalse(mPermissionMonitor.hasUseBackgroundNetworksPermission(MOCK_UID2));
-        assertBackgroundPermission(false, "mock3", MOCK_UID2, CONNECTIVITY_INTERNAL);
-        assertBackgroundPermission(true, "mock4", MOCK_UID2, NETWORK_STACK);
+        assertBackgroundPermission(false, MOCK_PACKAGE2, MOCK_UID2);
+        assertBackgroundPermission(false, MOCK_PACKAGE2, MOCK_UID2,
+                CONNECTIVITY_INTERNAL);
+        assertBackgroundPermission(true, MOCK_PACKAGE2, MOCK_UID2, NETWORK_STACK);
     }
 
     private class NetdMonitor {
@@ -378,14 +416,13 @@
         // MOCK_UID1: MOCK_PACKAGE1 only has network permission.
         // SYSTEM_UID: SYSTEM_PACKAGE1 has system permission.
         // SYSTEM_UID: SYSTEM_PACKAGE2 only has network permission.
-        doReturn(SYSTEM).when(mPermissionMonitor).highestPermissionForUid(eq(SYSTEM),
-                anyString(), anyInt());
+        doReturn(SYSTEM).when(mPermissionMonitor).highestPermissionForUid(eq(SYSTEM), anyString());
         doReturn(SYSTEM).when(mPermissionMonitor).highestPermissionForUid(any(),
-                eq(SYSTEM_PACKAGE1), anyInt());
+                eq(SYSTEM_PACKAGE1));
         doReturn(NETWORK).when(mPermissionMonitor).highestPermissionForUid(any(),
-                eq(SYSTEM_PACKAGE2), anyInt());
+                eq(SYSTEM_PACKAGE2));
         doReturn(NETWORK).when(mPermissionMonitor).highestPermissionForUid(any(),
-                eq(MOCK_PACKAGE1), anyInt());
+                eq(MOCK_PACKAGE1));
 
         // Add SYSTEM_PACKAGE2, expect only have network permission.
         mPermissionMonitor.onUserAdded(MOCK_USER1);
@@ -436,15 +473,13 @@
     public void testUidFilteringDuringVpnConnectDisconnectAndUidUpdates() throws Exception {
         when(mPackageManager.getInstalledPackages(eq(GET_PERMISSIONS | MATCH_ANY_USER))).thenReturn(
                 Arrays.asList(new PackageInfo[] {
-                        buildPackageInfo(PARTITION_SYSTEM, SYSTEM_UID1, MOCK_USER1),
-                        buildPackageInfo(PARTITION_SYSTEM, MOCK_UID1, MOCK_USER1),
-                        buildPackageInfo(PARTITION_SYSTEM, MOCK_UID2, MOCK_USER1),
-                        buildPackageInfo(PARTITION_SYSTEM, VPN_UID, MOCK_USER1)
+                        buildPackageInfo(/* SYSTEM */ true, SYSTEM_UID1, MOCK_USER1),
+                        buildPackageInfo(/* SYSTEM */ false, MOCK_UID1, MOCK_USER1),
+                        buildPackageInfo(/* SYSTEM */ false, MOCK_UID2, MOCK_USER1),
+                        buildPackageInfo(/* SYSTEM */ false, VPN_UID, MOCK_USER1)
                 }));
         when(mPackageManager.getPackageInfo(eq(MOCK_PACKAGE1), eq(GET_PERMISSIONS))).thenReturn(
-                buildPackageInfo(PARTITION_SYSTEM, MOCK_UID1, MOCK_USER1));
-        addPermissions(SYSTEM_UID,
-                CHANGE_NETWORK_STATE, NETWORK_STACK, CONNECTIVITY_USE_RESTRICTED_NETWORKS);
+                buildPackageInfo(false, MOCK_UID1, MOCK_USER1));
         mPermissionMonitor.startMonitoring();
         // Every app on user 0 except MOCK_UID2 are under VPN.
         final Set<UidRange> vpnRange1 = new HashSet<>(Arrays.asList(new UidRange[] {
@@ -489,11 +524,11 @@
     public void testUidFilteringDuringPackageInstallAndUninstall() throws Exception {
         when(mPackageManager.getInstalledPackages(eq(GET_PERMISSIONS | MATCH_ANY_USER))).thenReturn(
                 Arrays.asList(new PackageInfo[] {
-                        buildPackageInfo(PARTITION_SYSTEM, SYSTEM_UID1, MOCK_USER1),
-                        buildPackageInfo(PARTITION_SYSTEM, VPN_UID, MOCK_USER1)
+                        buildPackageInfo(true, SYSTEM_UID1, MOCK_USER1),
+                        buildPackageInfo(false, VPN_UID, MOCK_USER1)
                 }));
         when(mPackageManager.getPackageInfo(eq(MOCK_PACKAGE1), eq(GET_PERMISSIONS))).thenReturn(
-                        buildPackageInfo(PARTITION_SYSTEM, MOCK_UID1, MOCK_USER1));
+                        buildPackageInfo(false, MOCK_UID1, MOCK_USER1));
 
         mPermissionMonitor.startMonitoring();
         final Set<UidRange> vpnRange = Collections.singleton(UidRange.createForUser(MOCK_USER1));
@@ -598,10 +633,10 @@
 
     private PackageInfo setPackagePermissions(String packageName, int uid, String[] permissions)
             throws Exception {
-        final PackageInfo packageInfo = buildPackageInfo(PARTITION_SYSTEM, uid, MOCK_USER1);
+        PackageInfo packageInfo = packageInfoWithPermissions(
+                REQUESTED_PERMISSION_GRANTED, permissions, PARTITION_SYSTEM);
         when(mPackageManager.getPackageInfo(eq(packageName), anyInt())).thenReturn(packageInfo);
         when(mPackageManager.getPackagesForUid(eq(uid))).thenReturn(new String[]{packageName});
-        addPermissions(uid, permissions);
         return packageInfo;
     }
 
@@ -628,13 +663,14 @@
     public void testPackageInstallSharedUid() throws Exception {
         final NetdServiceMonitor mNetdServiceMonitor = new NetdServiceMonitor(mNetdService);
 
-        addPackage(MOCK_PACKAGE1, MOCK_UID1, new String[] {INTERNET, UPDATE_DEVICE_STATS});
+        PackageInfo packageInfo1 = addPackage(MOCK_PACKAGE1, MOCK_UID1,
+                new String[] {INTERNET, UPDATE_DEVICE_STATS});
         mNetdServiceMonitor.expectPermission(INetd.PERMISSION_INTERNET
                 | INetd.PERMISSION_UPDATE_DEVICE_STATS, new int[]{MOCK_UID1});
 
         // Install another package with the same uid and no permissions should not cause the UID to
         // lose permissions.
-        final PackageInfo packageInfo2 = buildPackageInfo(PARTITION_SYSTEM, MOCK_UID1, MOCK_USER1);
+        PackageInfo packageInfo2 = systemPackageInfoWithPermissions();
         when(mPackageManager.getPackageInfo(eq(MOCK_PACKAGE2), anyInt())).thenReturn(packageInfo2);
         when(mPackageManager.getPackagesForUid(MOCK_UID1))
               .thenReturn(new String[]{MOCK_PACKAGE1, MOCK_PACKAGE2});
@@ -665,7 +701,6 @@
                 | INetd.PERMISSION_UPDATE_DEVICE_STATS, new int[]{MOCK_UID1});
 
         when(mPackageManager.getPackagesForUid(MOCK_UID1)).thenReturn(new String[]{});
-        removeAllPermissions(MOCK_UID1);
         mPermissionMonitor.onPackageRemoved(MOCK_PACKAGE1, MOCK_UID1);
         mNetdServiceMonitor.expectPermission(INetd.PERMISSION_UNINSTALLED, new int[]{MOCK_UID1});
 
@@ -693,12 +728,10 @@
                 | INetd.PERMISSION_UPDATE_DEVICE_STATS, new int[]{MOCK_UID1});
 
         // Mock another package with the same uid but different permissions.
-        final PackageInfo packageInfo2 = buildPackageInfo(PARTITION_SYSTEM, MOCK_UID1, MOCK_USER1);
+        PackageInfo packageInfo2 = systemPackageInfoWithPermissions(INTERNET);
         when(mPackageManager.getPackageInfo(eq(MOCK_PACKAGE2), anyInt())).thenReturn(packageInfo2);
         when(mPackageManager.getPackagesForUid(MOCK_UID1)).thenReturn(new String[]{
                 MOCK_PACKAGE2});
-        removeAllPermissions(MOCK_UID1);
-        addPermissions(MOCK_UID1, INTERNET);
 
         mPermissionMonitor.onPackageRemoved(MOCK_PACKAGE1, MOCK_UID1);
         mNetdServiceMonitor.expectPermission(INetd.PERMISSION_INTERNET, new int[]{MOCK_UID1});
@@ -710,6 +743,9 @@
         // necessary permission.
         final Context realContext = InstrumentationRegistry.getContext();
         final PermissionMonitor monitor = new PermissionMonitor(realContext, mNetdService);
-        assertTrue(monitor.hasPermission(CONNECTIVITY_USE_RESTRICTED_NETWORKS, SYSTEM_UID));
+        final PackageManager manager = realContext.getPackageManager();
+        final PackageInfo systemInfo = manager.getPackageInfo(REAL_SYSTEM_PACKAGE_NAME,
+                GET_PERMISSIONS | MATCH_ANY_USER);
+        assertTrue(monitor.hasPermission(systemInfo, CONNECTIVITY_USE_RESTRICTED_NETWORKS));
     }
 }
diff --git a/tests/net/java/com/android/server/connectivity/VpnTest.java b/tests/net/java/com/android/server/connectivity/VpnTest.java
index 4ccf79a..e8c4ee9 100644
--- a/tests/net/java/com/android/server/connectivity/VpnTest.java
+++ b/tests/net/java/com/android/server/connectivity/VpnTest.java
@@ -30,6 +30,7 @@
 import static android.net.NetworkCapabilities.TRANSPORT_VPN;
 import static android.net.NetworkCapabilities.TRANSPORT_WIFI;
 
+import static org.junit.Assert.assertArrayEquals;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
@@ -49,6 +50,7 @@
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
+import android.annotation.NonNull;
 import android.annotation.UserIdInt;
 import android.app.AppOpsManager;
 import android.app.NotificationManager;
@@ -65,6 +67,7 @@
 import android.net.IpPrefix;
 import android.net.IpSecManager;
 import android.net.LinkProperties;
+import android.net.LocalSocket;
 import android.net.Network;
 import android.net.NetworkCapabilities;
 import android.net.NetworkInfo.DetailedState;
@@ -74,6 +77,7 @@
 import android.net.VpnService;
 import android.os.Build.VERSION_CODES;
 import android.os.Bundle;
+import android.os.ConditionVariable;
 import android.os.INetworkManagementService;
 import android.os.Looper;
 import android.os.Process;
@@ -101,13 +105,20 @@
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 
+import java.io.BufferedWriter;
+import java.io.File;
+import java.io.FileWriter;
+import java.io.IOException;
 import java.net.Inet4Address;
+import java.net.InetAddress;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collections;
 import java.util.HashMap;
 import java.util.Map;
 import java.util.Set;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.TimeUnit;
 import java.util.stream.Stream;
 
 /**
@@ -133,7 +144,8 @@
         managedProfileA.profileGroupId = primaryUser.id;
     }
 
-    static final String TEST_VPN_PKG = "com.dummy.vpn";
+    static final String EGRESS_IFACE = "wlan0";
+    static final String TEST_VPN_PKG = "com.testvpn.vpn";
     private static final String TEST_VPN_SERVER = "1.2.3.4";
     private static final String TEST_VPN_IDENTITY = "identity";
     private static final byte[] TEST_VPN_PSK = "psk".getBytes();
@@ -258,12 +270,12 @@
     }
 
     @Test
-    public void testUidWhiteAndBlacklist() throws Exception {
+    public void testUidAllowAndDenylist() throws Exception {
         final Vpn vpn = createVpn(primaryUser.id);
         final UidRange user = UidRange.createForUser(primaryUser.id);
         final String[] packages = {PKGS[0], PKGS[1], PKGS[2]};
 
-        // Whitelist
+        // Allowed list
         final Set<UidRange> allow = vpn.createUserAndRestrictedProfilesRanges(primaryUser.id,
                 Arrays.asList(packages), null);
         assertEquals(new ArraySet<>(Arrays.asList(new UidRange[] {
@@ -271,7 +283,7 @@
             new UidRange(user.start + PKG_UIDS[1], user.start + PKG_UIDS[2])
         })), allow);
 
-        // Blacklist
+        // Denied list
         final Set<UidRange> disallow = vpn.createUserAndRestrictedProfilesRanges(primaryUser.id,
                 null, Arrays.asList(packages));
         assertEquals(new ArraySet<>(Arrays.asList(new UidRange[] {
@@ -342,11 +354,11 @@
     }
 
     @Test
-    public void testLockdownWhitelist() throws Exception {
+    public void testLockdownAllowlist() throws Exception {
         final Vpn vpn = createVpn(primaryUser.id);
         final UidRange user = UidRange.createForUser(primaryUser.id);
 
-        // Set always-on with lockdown and whitelist app PKGS[2] from lockdown.
+        // Set always-on with lockdown and allow app PKGS[2] from lockdown.
         assertTrue(vpn.setAlwaysOnPackage(
                 PKGS[1], true, Collections.singletonList(PKGS[2]), mKeyStore));
         verify(mNetService).setAllowOnlyVpnForUids(eq(true), aryEq(new UidRange[] {
@@ -356,7 +368,7 @@
         assertBlocked(vpn, user.start + PKG_UIDS[0], user.start + PKG_UIDS[3]);
         assertUnblocked(vpn, user.start + PKG_UIDS[1], user.start + PKG_UIDS[2]);
 
-        // Change whitelisted app to PKGS[3].
+        // Change allowed app list to PKGS[3].
         assertTrue(vpn.setAlwaysOnPackage(
                 PKGS[1], true, Collections.singletonList(PKGS[3]), mKeyStore));
         verify(mNetService).setAllowOnlyVpnForUids(eq(false), aryEq(new UidRange[] {
@@ -383,7 +395,7 @@
         assertBlocked(vpn, user.start + PKG_UIDS[1], user.start + PKG_UIDS[2]);
         assertUnblocked(vpn, user.start + PKG_UIDS[0], user.start + PKG_UIDS[3]);
 
-        // Remove the whitelist.
+        // Remove the list of allowed packages.
         assertTrue(vpn.setAlwaysOnPackage(PKGS[0], true, null, mKeyStore));
         verify(mNetService).setAllowOnlyVpnForUids(eq(false), aryEq(new UidRange[] {
                 new UidRange(user.start + PKG_UIDS[0] + 1, user.start + PKG_UIDS[3] - 1),
@@ -396,7 +408,7 @@
                 user.start + PKG_UIDS[3]);
         assertUnblocked(vpn, user.start + PKG_UIDS[0]);
 
-        // Add the whitelist.
+        // Add the list of allowed packages.
         assertTrue(vpn.setAlwaysOnPackage(
                 PKGS[0], true, Collections.singletonList(PKGS[1]), mKeyStore));
         verify(mNetService).setAllowOnlyVpnForUids(eq(false), aryEq(new UidRange[] {
@@ -409,12 +421,12 @@
         assertBlocked(vpn, user.start + PKG_UIDS[2], user.start + PKG_UIDS[3]);
         assertUnblocked(vpn, user.start + PKG_UIDS[0], user.start + PKG_UIDS[1]);
 
-        // Try whitelisting a package with a comma, should be rejected.
+        // Try allowing a package with a comma, should be rejected.
         assertFalse(vpn.setAlwaysOnPackage(
                 PKGS[0], true, Collections.singletonList("a.b,c.d"), mKeyStore));
 
-        // Pass a non-existent packages in the whitelist, they (and only they) should be ignored.
-        // Whitelisted package should change from PGKS[1] to PKGS[2].
+        // Pass a non-existent packages in the allowlist, they (and only they) should be ignored.
+        // allowed package should change from PGKS[1] to PKGS[2].
         assertTrue(vpn.setAlwaysOnPackage(
                 PKGS[0], true, Arrays.asList("com.foo.app", PKGS[2], "com.bar.app"), mKeyStore));
         verify(mNetService).setAllowOnlyVpnForUids(eq(false), aryEq(new UidRange[]{
@@ -1012,31 +1024,190 @@
         // a subsequent CL.
     }
 
-    @Test
-    public void testStartLegacyVpn() throws Exception {
+    public Vpn startLegacyVpn(final VpnProfile vpnProfile) throws Exception {
         final Vpn vpn = createVpn(primaryUser.id);
         setMockedUsers(primaryUser);
 
         // Dummy egress interface
-        final String egressIface = "DUMMY0";
         final LinkProperties lp = new LinkProperties();
-        lp.setInterfaceName(egressIface);
+        lp.setInterfaceName(EGRESS_IFACE);
 
         final RouteInfo defaultRoute = new RouteInfo(new IpPrefix(Inet4Address.ANY, 0),
-                        InetAddresses.parseNumericAddress("192.0.2.0"), egressIface);
+                        InetAddresses.parseNumericAddress("192.0.2.0"), EGRESS_IFACE);
         lp.addRoute(defaultRoute);
 
-        vpn.startLegacyVpn(mVpnProfile, mKeyStore, lp);
+        vpn.startLegacyVpn(vpnProfile, mKeyStore, lp);
+        return vpn;
+    }
 
+    @Test
+    public void testStartPlatformVpn() throws Exception {
+        startLegacyVpn(mVpnProfile);
         // TODO: Test the Ikev2VpnRunner started up properly. Relies on utility methods added in
-        // a subsequent CL.
+        // a subsequent patch.
+    }
+
+    @Test
+    public void testStartRacoonNumericAddress() throws Exception {
+        startRacoon("1.2.3.4", "1.2.3.4");
+    }
+
+    @Test
+    public void testStartRacoonHostname() throws Exception {
+        startRacoon("hostname", "5.6.7.8"); // address returned by deps.resolve
+    }
+
+    public void startRacoon(final String serverAddr, final String expectedAddr)
+            throws Exception {
+        final ConditionVariable legacyRunnerReady = new ConditionVariable();
+        final VpnProfile profile = new VpnProfile("testProfile" /* key */);
+        profile.type = VpnProfile.TYPE_L2TP_IPSEC_PSK;
+        profile.name = "testProfileName";
+        profile.username = "userName";
+        profile.password = "thePassword";
+        profile.server = serverAddr;
+        profile.ipsecIdentifier = "id";
+        profile.ipsecSecret = "secret";
+        profile.l2tpSecret = "l2tpsecret";
+        when(mConnectivityManager.getAllNetworks())
+            .thenReturn(new Network[] { new Network(101) });
+        when(mConnectivityManager.registerNetworkAgent(any(), any(), any(), any(),
+                anyInt(), any(), anyInt())).thenAnswer(invocation -> {
+                    // The runner has registered an agent and is now ready.
+                    legacyRunnerReady.open();
+                    return new Network(102);
+                });
+        final Vpn vpn = startLegacyVpn(profile);
+        final TestDeps deps = (TestDeps) vpn.mDeps;
+        try {
+            // udppsk and 1701 are the values for TYPE_L2TP_IPSEC_PSK
+            assertArrayEquals(
+                    new String[] { EGRESS_IFACE, expectedAddr, "udppsk",
+                            profile.ipsecIdentifier, profile.ipsecSecret, "1701" },
+                    deps.racoonArgs.get(10, TimeUnit.SECONDS));
+            // literal values are hardcoded in Vpn.java for mtpd args
+            assertArrayEquals(
+                    new String[] { EGRESS_IFACE, "l2tp", expectedAddr, "1701", profile.l2tpSecret,
+                            "name", profile.username, "password", profile.password,
+                            "linkname", "vpn", "refuse-eap", "nodefaultroute", "usepeerdns",
+                            "idle", "1800", "mtu", "1400", "mru", "1400" },
+                    deps.mtpdArgs.get(10, TimeUnit.SECONDS));
+            // Now wait for the runner to be ready before testing for the route.
+            legacyRunnerReady.block(10_000);
+            // In this test the expected address is always v4 so /32
+            final RouteInfo expectedRoute = new RouteInfo(new IpPrefix(expectedAddr + "/32"),
+                    RouteInfo.RTN_THROW);
+            assertTrue("Routes lack the expected throw route (" + expectedRoute + ") : "
+                    + vpn.mConfig.routes,
+                    vpn.mConfig.routes.contains(expectedRoute));
+        } finally {
+            // Now interrupt the thread, unblock the runner and clean up.
+            vpn.mVpnRunner.exitVpnRunner();
+            deps.getStateFile().delete(); // set to delete on exit, but this deletes it earlier
+            vpn.mVpnRunner.join(10_000); // wait for up to 10s for the runner to die and cleanup
+        }
+    }
+
+    private static final class TestDeps extends Vpn.Dependencies {
+        public final CompletableFuture<String[]> racoonArgs = new CompletableFuture();
+        public final CompletableFuture<String[]> mtpdArgs = new CompletableFuture();
+        public final File mStateFile;
+
+        private final HashMap<String, Boolean> mRunningServices = new HashMap<>();
+
+        TestDeps() {
+            try {
+                mStateFile = File.createTempFile("vpnTest", ".tmp");
+                mStateFile.deleteOnExit();
+            } catch (final IOException e) {
+                throw new RuntimeException(e);
+            }
+        }
+
+        @Override
+        public void startService(final String serviceName) {
+            mRunningServices.put(serviceName, true);
+        }
+
+        @Override
+        public void stopService(final String serviceName) {
+            mRunningServices.put(serviceName, false);
+        }
+
+        @Override
+        public boolean isServiceRunning(final String serviceName) {
+            return mRunningServices.getOrDefault(serviceName, false);
+        }
+
+        @Override
+        public boolean isServiceStopped(final String serviceName) {
+            return !isServiceRunning(serviceName);
+        }
+
+        @Override
+        public File getStateFile() {
+            return mStateFile;
+        }
+
+        @Override
+        public void sendArgumentsToDaemon(
+                final String daemon, final LocalSocket socket, final String[] arguments,
+                final Vpn.RetryScheduler interruptChecker) throws IOException {
+            if ("racoon".equals(daemon)) {
+                racoonArgs.complete(arguments);
+            } else if ("mtpd".equals(daemon)) {
+                writeStateFile(arguments);
+                mtpdArgs.complete(arguments);
+            } else {
+                throw new UnsupportedOperationException("Unsupported daemon : " + daemon);
+            }
+        }
+
+        private void writeStateFile(final String[] arguments) throws IOException {
+            mStateFile.delete();
+            mStateFile.createNewFile();
+            mStateFile.deleteOnExit();
+            final BufferedWriter writer = new BufferedWriter(
+                    new FileWriter(mStateFile, false /* append */));
+            writer.write(EGRESS_IFACE);
+            writer.write("\n");
+            // addresses
+            writer.write("10.0.0.1/24\n");
+            // routes
+            writer.write("192.168.6.0/24\n");
+            // dns servers
+            writer.write("192.168.6.1\n");
+            // search domains
+            writer.write("vpn.searchdomains.com\n");
+            // endpoint - intentionally empty
+            writer.write("\n");
+            writer.flush();
+            writer.close();
+        }
+
+        @Override
+        @NonNull
+        public InetAddress resolve(final String endpoint) {
+            try {
+                // If a numeric IP address, return it.
+                return InetAddress.parseNumericAddress(endpoint);
+            } catch (IllegalArgumentException e) {
+                // Otherwise, return some token IP to test for.
+                return InetAddress.parseNumericAddress("5.6.7.8");
+            }
+        }
+
+        @Override
+        public boolean checkInterfacePresent(final Vpn vpn, final String iface) {
+            return true;
+        }
     }
 
     /**
      * Mock some methods of vpn object.
      */
     private Vpn createVpn(@UserIdInt int userId) {
-        return new Vpn(Looper.myLooper(), mContext, mNetService,
+        return new Vpn(Looper.myLooper(), mContext, new TestDeps(), mNetService,
                 userId, mKeyStore, mSystemServices, mIkev2SessionCreator);
     }
 
diff --git a/tests/net/java/com/android/server/net/NetworkStatsCollectionTest.java b/tests/net/java/com/android/server/net/NetworkStatsCollectionTest.java
index e83d2a9..fb0cfc0 100644
--- a/tests/net/java/com/android/server/net/NetworkStatsCollectionTest.java
+++ b/tests/net/java/com/android/server/net/NetworkStatsCollectionTest.java
@@ -28,7 +28,7 @@
 import static android.text.format.DateUtils.HOUR_IN_MILLIS;
 import static android.text.format.DateUtils.MINUTE_IN_MILLIS;
 
-import static com.android.testutils.MiscAssertsKt.assertThrows;
+import static com.android.testutils.MiscAsserts.assertThrows;
 
 import static org.junit.Assert.assertArrayEquals;
 import static org.junit.Assert.assertEquals;
diff --git a/tests/net/java/com/android/server/net/NetworkStatsObserversTest.java b/tests/net/java/com/android/server/net/NetworkStatsObserversTest.java
index a6f7a36..291efc7 100644
--- a/tests/net/java/com/android/server/net/NetworkStatsObserversTest.java
+++ b/tests/net/java/com/android/server/net/NetworkStatsObserversTest.java
@@ -53,7 +53,7 @@
 import androidx.test.runner.AndroidJUnit4;
 
 import com.android.server.net.NetworkStatsServiceTest.LatchedHandler;
-import com.android.testutils.HandlerUtilsKt;
+import com.android.testutils.HandlerUtils;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -440,7 +440,7 @@
     }
 
     private void waitForObserverToIdle() {
-        HandlerUtilsKt.waitForIdle(mObserverHandlerThread, WAIT_TIMEOUT_MS);
-        HandlerUtilsKt.waitForIdle(mHandler, WAIT_TIMEOUT_MS);
+        HandlerUtils.waitForIdle(mObserverHandlerThread, WAIT_TIMEOUT_MS);
+        HandlerUtils.waitForIdle(mHandler, WAIT_TIMEOUT_MS);
     }
 }
diff --git a/tests/net/java/com/android/server/net/NetworkStatsServiceTest.java b/tests/net/java/com/android/server/net/NetworkStatsServiceTest.java
index 1307a84..7abe189 100644
--- a/tests/net/java/com/android/server/net/NetworkStatsServiceTest.java
+++ b/tests/net/java/com/android/server/net/NetworkStatsServiceTest.java
@@ -109,7 +109,7 @@
 import com.android.internal.util.test.BroadcastInterceptingContext;
 import com.android.server.net.NetworkStatsService.NetworkStatsSettings;
 import com.android.server.net.NetworkStatsService.NetworkStatsSettings.Config;
-import com.android.testutils.HandlerUtilsKt;
+import com.android.testutils.HandlerUtils;
 import com.android.testutils.TestableNetworkStatsProviderBinder;
 
 import libcore.io.IoUtils;
@@ -700,7 +700,7 @@
         when(mNetworkStatsSubscriptionsMonitor.getRatTypeForSubscriberId(anyString()))
                 .thenReturn(ratType);
         mService.handleOnCollapsedRatTypeChanged();
-        HandlerUtilsKt.waitForIdle(mHandlerThread, WAIT_TIMEOUT);
+        HandlerUtils.waitForIdle(mHandlerThread, WAIT_TIMEOUT);
     }
 
     @Test
@@ -1065,7 +1065,7 @@
         long minThresholdInBytes = 2 * 1024 * 1024; // 2 MB
         assertEquals(minThresholdInBytes, request.thresholdInBytes);
 
-        HandlerUtilsKt.waitForIdle(mHandlerThread, WAIT_TIMEOUT);
+        HandlerUtils.waitForIdle(mHandlerThread, WAIT_TIMEOUT);
 
         // Make sure that the caller binder gets connected
         verify(mBinder).linkToDeath(any(IBinder.DeathRecipient.class), anyInt());
@@ -1203,7 +1203,7 @@
 
         // Simulates alert quota of the provider has been reached.
         cb.notifyAlertReached();
-        HandlerUtilsKt.waitForIdle(mHandlerThread, WAIT_TIMEOUT);
+        HandlerUtils.waitForIdle(mHandlerThread, WAIT_TIMEOUT);
 
         // Verifies that polling is triggered by alert reached.
         provider.expectOnRequestStatsUpdate(0 /* unused */);
@@ -1264,7 +1264,7 @@
         // Call handleOnCollapsedRatTypeChanged manually to simulate the callback fired
         // when stopping monitor, this is needed by NetworkStatsService to trigger updateIfaces.
         mService.handleOnCollapsedRatTypeChanged();
-        HandlerUtilsKt.waitForIdle(mHandlerThread, WAIT_TIMEOUT);
+        HandlerUtils.waitForIdle(mHandlerThread, WAIT_TIMEOUT);
         // Create some traffic.
         incrementCurrentTime(MINUTE_IN_MILLIS);
         // Append more traffic on existing snapshot.
@@ -1286,7 +1286,7 @@
         setCombineSubtypeEnabled(false);
 
         mService.handleOnCollapsedRatTypeChanged();
-        HandlerUtilsKt.waitForIdle(mHandlerThread, WAIT_TIMEOUT);
+        HandlerUtils.waitForIdle(mHandlerThread, WAIT_TIMEOUT);
         // Create some traffic.
         incrementCurrentTime(MINUTE_IN_MILLIS);
         // Append more traffic on existing snapshot.
@@ -1520,7 +1520,7 @@
     }
 
     private void waitForIdle() {
-        HandlerUtilsKt.waitForIdle(mHandlerThread, WAIT_TIMEOUT);
+        HandlerUtils.waitForIdle(mHandlerThread, WAIT_TIMEOUT);
     }
 
     static class LatchedHandler extends Handler {
diff --git a/tests/net/java/com/android/server/net/NetworkStatsSubscriptionsMonitorTest.java b/tests/net/java/com/android/server/net/NetworkStatsSubscriptionsMonitorTest.java
index 6dc4fce..8f09377 100644
--- a/tests/net/java/com/android/server/net/NetworkStatsSubscriptionsMonitorTest.java
+++ b/tests/net/java/com/android/server/net/NetworkStatsSubscriptionsMonitorTest.java
@@ -30,6 +30,7 @@
 import static org.mockito.Mockito.when;
 
 import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.content.Context;
 import android.net.NetworkTemplate;
 import android.os.test.TestLooper;
@@ -135,6 +136,11 @@
         mMonitor.onSubscriptionsChanged();
     }
 
+    private void updateSubscriberIdForTestSub(int subId, @Nullable final String subscriberId) {
+        when(mTelephonyManager.getSubscriberId(subId)).thenReturn(subscriberId);
+        mMonitor.onSubscriptionsChanged();
+    }
+
     private void removeTestSub(int subId) {
         // Remove subId from TestSubList.
         mTestSubList.removeIf(it -> it == subId);
@@ -268,4 +274,54 @@
         listener.onServiceStateChanged(serviceState);
         assertRatTypeNotChangedForSub(TEST_IMSI1, TelephonyManager.NETWORK_TYPE_NR);
     }
+
+    @Test
+    public void testSubscriberIdUnavailable() {
+        final ArgumentCaptor<RatTypeListener> ratTypeListenerCaptor =
+                ArgumentCaptor.forClass(RatTypeListener.class);
+
+        mMonitor.start();
+        // Insert sim1, set subscriberId to null which is normal in SIM PIN locked case.
+        // Verify RAT type is NETWORK_TYPE_UNKNOWN and service will not perform listener
+        // registration.
+        addTestSub(TEST_SUBID1, null);
+        verify(mTelephonyManager, never()).listen(any(), anyInt());
+        assertRatTypeNotChangedForSub(TEST_IMSI1, TelephonyManager.NETWORK_TYPE_UNKNOWN);
+
+        // Set IMSI for sim1, verify the listener will be registered.
+        updateSubscriberIdForTestSub(TEST_SUBID1, TEST_IMSI1);
+        verify(mTelephonyManager, times(1)).listen(ratTypeListenerCaptor.capture(),
+                eq(PhoneStateListener.LISTEN_SERVICE_STATE));
+        reset(mTelephonyManager);
+        when(mTelephonyManager.createForSubscriptionId(anyInt())).thenReturn(mTelephonyManager);
+
+        // Set RAT type of sim1 to UMTS. Verify RAT type of sim1 is changed.
+        setRatTypeForSub(ratTypeListenerCaptor.getAllValues(), TEST_SUBID1,
+                TelephonyManager.NETWORK_TYPE_UMTS);
+        assertRatTypeChangedForSub(TEST_IMSI1, TelephonyManager.NETWORK_TYPE_UMTS);
+        reset(mDelegate);
+
+        // Set IMSI to null again to simulate somehow IMSI is not available, such as
+        // modem crash. Verify service should not unregister listener.
+        updateSubscriberIdForTestSub(TEST_SUBID1, null);
+        verify(mTelephonyManager, never()).listen(any(), anyInt());
+        assertRatTypeNotChangedForSub(TEST_IMSI1, TelephonyManager.NETWORK_TYPE_UMTS);
+        reset(mDelegate);
+
+        // Set RAT type of sim1 to LTE. Verify RAT type of sim1 is still changed even if the IMSI
+        // is not available. The monitor keeps the listener even if the IMSI disappears because
+        // the IMSI can never change for any given subId, therefore even if the IMSI is updated
+        // to null, the monitor should continue accepting updates of the RAT type. However,
+        // telephony is never actually supposed to do this, if the IMSI disappears there should
+        // not be updates, but it's still the right thing to do theoretically.
+        setRatTypeForSub(ratTypeListenerCaptor.getAllValues(), TEST_SUBID1,
+                TelephonyManager.NETWORK_TYPE_LTE);
+        assertRatTypeChangedForSub(TEST_IMSI1, TelephonyManager.NETWORK_TYPE_LTE);
+        reset(mDelegate);
+
+        mMonitor.stop();
+        verify(mTelephonyManager, times(1)).listen(eq(ratTypeListenerCaptor.getValue()),
+                eq(PhoneStateListener.LISTEN_NONE));
+        assertRatTypeChangedForSub(TEST_IMSI1, TelephonyManager.NETWORK_TYPE_UNKNOWN);
+    }
 }
diff --git a/tests/utils/hostutils/src/com/android/internal/util/test/SystemPreparer.java b/tests/utils/hostutils/src/com/android/internal/util/test/SystemPreparer.java
index f30c35a..f80af03 100644
--- a/tests/utils/hostutils/src/com/android/internal/util/test/SystemPreparer.java
+++ b/tests/utils/hostutils/src/com/android/internal/util/test/SystemPreparer.java
@@ -34,6 +34,8 @@
 import java.io.FileOutputStream;
 import java.io.IOException;
 import java.io.InputStream;
+import java.nio.file.Path;
+import java.nio.file.Paths;
 import java.util.ArrayList;
 
 import javax.annotation.Nullable;
@@ -49,7 +51,7 @@
 public class SystemPreparer extends ExternalResource {
     private static final long OVERLAY_ENABLE_TIMEOUT_MS = 30000;
 
-    // The paths of the files pushed onto the device through this rule.
+    // The paths of the files pushed onto the device through this rule to be removed after.
     private ArrayList<String> mPushedFiles = new ArrayList<>();
 
     // The package names of packages installed through this rule.
@@ -60,12 +62,22 @@
     private final RebootStrategy mRebootStrategy;
     private final TearDownRule mTearDownRule;
 
+    // When debugging, it may be useful to run a test case without rebooting the device afterwards,
+    // to manually verify the device state.
+    private boolean mDebugSkipAfterReboot;
+
     public SystemPreparer(TemporaryFolder hostTempFolder, DeviceProvider deviceProvider) {
         this(hostTempFolder, RebootStrategy.FULL, null, deviceProvider);
     }
 
     public SystemPreparer(TemporaryFolder hostTempFolder, RebootStrategy rebootStrategy,
             @Nullable TestRuleDelegate testRuleDelegate, DeviceProvider deviceProvider) {
+        this(hostTempFolder, rebootStrategy, testRuleDelegate, false, deviceProvider);
+    }
+
+    public SystemPreparer(TemporaryFolder hostTempFolder, RebootStrategy rebootStrategy,
+            @Nullable TestRuleDelegate testRuleDelegate, boolean debugSkipAfterReboot,
+            DeviceProvider deviceProvider) {
         mHostTempFolder = hostTempFolder;
         mDeviceProvider = deviceProvider;
         mRebootStrategy = rebootStrategy;
@@ -73,6 +85,7 @@
         if (testRuleDelegate != null) {
             testRuleDelegate.setDelegate(mTearDownRule);
         }
+        mDebugSkipAfterReboot = debugSkipAfterReboot;
     }
 
     /** Copies a file within the host test jar to a path on device. */
@@ -81,7 +94,7 @@
         final ITestDevice device = mDeviceProvider.getDevice();
         remount();
         assertTrue(device.pushFile(copyResourceToTemp(filePath), outputPath));
-        mPushedFiles.add(outputPath);
+        addPushedFile(device, outputPath);
         return this;
     }
 
@@ -91,10 +104,23 @@
         final ITestDevice device = mDeviceProvider.getDevice();
         remount();
         assertTrue(device.pushFile(file, outputPath));
-        mPushedFiles.add(outputPath);
+        addPushedFile(device, outputPath);
         return this;
     }
 
+    private void addPushedFile(ITestDevice device, String outputPath)
+            throws DeviceNotAvailableException {
+        Path pathCreated = Paths.get(outputPath);
+
+        // Find the top most parent that is new to the device
+        while (pathCreated.getParent() != null
+                && !device.doesFileExist(pathCreated.getParent().toString())) {
+            pathCreated = pathCreated.getParent();
+        }
+
+        mPushedFiles.add(pathCreated.toString());
+    }
+
     /** Deletes the given path from the device */
     public SystemPreparer deleteFile(String file) throws DeviceNotAvailableException {
         final ITestDevice device = mDeviceProvider.getDevice();
@@ -157,7 +183,9 @@
             case USERSPACE_UNTIL_ONLINE:
                 device.rebootUserspaceUntilOnline();
                 break;
-            case START_STOP:
+            // TODO(b/159540015): Make this START_STOP instead of default once it's fixed. Can't
+            //  currently be done because START_STOP is commented out.
+            default:
                 device.executeShellCommand("stop");
                 device.executeShellCommand("start");
                 ITestDevice.RecoveryMode cachedRecoveryMode = device.getRecoveryMode();
@@ -203,7 +231,7 @@
 
     /** Removes installed packages and files that were pushed to the device. */
     @Override
-    protected void after() {
+    public void after() {
         final ITestDevice device = mDeviceProvider.getDevice();
         try {
             remount();
@@ -213,7 +241,9 @@
             for (final String packageName : mInstalledPackages) {
                 device.uninstallPackage(packageName);
             }
-            reboot();
+            if (!mDebugSkipAfterReboot) {
+                reboot();
+            }
         } catch (DeviceNotAvailableException e) {
             Assert.fail(e.toString());
         }
@@ -340,6 +370,7 @@
     /**
      * How to reboot the device. Ordered from slowest to fastest.
      */
+    @SuppressWarnings("DanglingJavadoc")
     public enum RebootStrategy {
         /** @see ITestDevice#reboot() */
         FULL,
@@ -359,7 +390,15 @@
          *
          * TODO(b/159540015): There's a bug with this causing unnecessary disk space usage, which
          *  can eventually lead to an insufficient storage space error.
+         *
+         * This can be uncommented for local development, but should be left out when merging.
+         * It is done this way to hopefully be caught by code review, since merging this will
+         * break all of postsubmit. But the nearly 50% reduction in test runtime is worth having
+         * this option exist.
+         *
+         * @deprecated do not use this in merged code until bug is resolved
          */
-        START_STOP
+//        @Deprecated
+//        START_STOP
     }
 }
diff --git a/tools/aapt2/Resources.proto b/tools/aapt2/Resources.proto
index ab9ce66..b1e1a77 100644
--- a/tools/aapt2/Resources.proto
+++ b/tools/aapt2/Resources.proto
@@ -168,6 +168,7 @@
     ODM = 6;
     OEM = 7;
     ACTOR = 8;
+    CONFIG_SIGNATURE = 9;
   }
 
   // The location of the <item> declaration in source.
diff --git a/tools/aapt2/cmd/Link.cpp b/tools/aapt2/cmd/Link.cpp
index f9c54f6..d84ca3d 100644
--- a/tools/aapt2/cmd/Link.cpp
+++ b/tools/aapt2/cmd/Link.cpp
@@ -78,6 +78,8 @@
 
 namespace aapt {
 
+constexpr uint8_t kAndroidPackageId = 0x01;
+
 class LinkContext : public IAaptContext {
  public:
   explicit LinkContext(IDiagnostics* diagnostics)
@@ -1805,7 +1807,7 @@
 
     // Override the package ID when it is "android".
     if (context_->GetCompilationPackage() == "android") {
-      context_->SetPackageId(0x01);
+      context_->SetPackageId(kAndroidPackageId);
 
       // Verify we're building a regular app.
       if (context_->GetPackageType() != PackageType::kApp) {
@@ -1862,7 +1864,8 @@
 
     if (context_->GetPackageType() != PackageType::kStaticLib) {
       PrivateAttributeMover mover;
-      if (!mover.Consume(context_, &final_table_)) {
+      if (context_->GetPackageId() == kAndroidPackageId &&
+          !mover.Consume(context_, &final_table_)) {
         context_->GetDiagnostics()->Error(DiagMessage() << "failed moving private attributes");
         return 1;
       }
diff --git a/tools/aapt2/format/binary/TableFlattener_test.cpp b/tools/aapt2/format/binary/TableFlattener_test.cpp
index 59627ce..6932baf 100644
--- a/tools/aapt2/format/binary/TableFlattener_test.cpp
+++ b/tools/aapt2/format/binary/TableFlattener_test.cpp
@@ -776,6 +776,7 @@
   OverlayableItem overlayable_item_three(group_one);
   overlayable_item_three.policies |= PolicyFlags::SIGNATURE;
   overlayable_item_three.policies |= PolicyFlags::ACTOR_SIGNATURE;
+  overlayable_item_three.policies |= PolicyFlags::CONFIG_SIGNATURE;
 
   std::unique_ptr<ResourceTable> table =
       test::ResourceTableBuilder()
@@ -830,7 +831,8 @@
   EXPECT_EQ(result_overlayable.overlayable->name, "OtherName");
   EXPECT_EQ(result_overlayable.overlayable->actor, "overlay://customization");
   EXPECT_EQ(result_overlayable.policies, PolicyFlags::SIGNATURE
-                                           | PolicyFlags::ACTOR_SIGNATURE);
+                                           | PolicyFlags::ACTOR_SIGNATURE
+                                           | PolicyFlags::CONFIG_SIGNATURE);
 }
 
 TEST_F(TableFlattenerTest, FlattenOverlayableNoPolicyFails) {
diff --git a/tools/aapt2/format/proto/ProtoDeserialize.cpp b/tools/aapt2/format/proto/ProtoDeserialize.cpp
index 582bd39..06ac9e5 100644
--- a/tools/aapt2/format/proto/ProtoDeserialize.cpp
+++ b/tools/aapt2/format/proto/ProtoDeserialize.cpp
@@ -404,6 +404,9 @@
       case pb::OverlayableItem::ACTOR:
         out_overlayable->policies |= PolicyFlags::ACTOR_SIGNATURE;
         break;
+      case pb::OverlayableItem::CONFIG_SIGNATURE:
+        out_overlayable->policies |= PolicyFlags::CONFIG_SIGNATURE;
+        break;
       default:
         *out_error = "unknown overlayable policy";
         return false;
diff --git a/tools/aapt2/format/proto/ProtoSerialize.cpp b/tools/aapt2/format/proto/ProtoSerialize.cpp
index 5ab43b7..98c5175 100644
--- a/tools/aapt2/format/proto/ProtoSerialize.cpp
+++ b/tools/aapt2/format/proto/ProtoSerialize.cpp
@@ -325,6 +325,9 @@
   if (overlayable_item.policies & PolicyFlags::ACTOR_SIGNATURE) {
     pb_overlayable_item->add_policy(pb::OverlayableItem::ACTOR);
   }
+  if (overlayable_item.policies & PolicyFlags::CONFIG_SIGNATURE) {
+    pb_overlayable_item->add_policy(pb::OverlayableItem::CONFIG_SIGNATURE);
+  }
 
   if (source_pool != nullptr) {
     SerializeSourceToPb(overlayable_item.source, source_pool,
diff --git a/tools/aosp/aosp_sha.sh b/tools/aosp/aosp_sha.sh
index f25fcdc..99aaa3c 100755
--- a/tools/aosp/aosp_sha.sh
+++ b/tools/aosp/aosp_sha.sh
@@ -11,7 +11,7 @@
         if (( count == 0 )); then
             echo
         fi
-        echo -e "\033[0;31mThe source of truth for '$file' is in AOSP.\033[0m"
+        echo -e "\033[0;31;47mThe source of truth for '$file' is in AOSP.\033[0m"
         (( count++ ))
     done < <(git show --name-only --pretty=format: $1 | grep -- "$2")
     if (( count != 0 )); then
diff --git a/tools/hiddenapi/generate_hiddenapi_lists.py b/tools/hiddenapi/generate_hiddenapi_lists.py
index 8a282e5..da64402 100755
--- a/tools/hiddenapi/generate_hiddenapi_lists.py
+++ b/tools/hiddenapi/generate_hiddenapi_lists.py
@@ -34,26 +34,6 @@
 FLAG_SYSTEM_API = 'system-api'
 FLAG_TEST_API = 'test-api'
 
-OLD_FLAG_SDK = "whitelist"
-OLD_FLAG_UNSUPPORTED = "greylist"
-OLD_FLAG_BLOCKED = "blacklist"
-OLD_FLAG_MAX_TARGET_O = "greylist-max-o"
-OLD_FLAG_MAX_TARGET_P = "greylist-max-p"
-OLD_FLAG_MAX_TARGET_Q = "greylist-max-q"
-OLD_FLAG_MAX_TARGET_R = "greylist-max-r"
-
-OLD_FLAGS_TO_NEW = {
-    OLD_FLAG_SDK: FLAG_SDK,
-    OLD_FLAG_UNSUPPORTED: FLAG_UNSUPPORTED,
-    OLD_FLAG_BLOCKED: FLAG_BLOCKED,
-    OLD_FLAG_MAX_TARGET_O: FLAG_MAX_TARGET_O,
-    OLD_FLAG_MAX_TARGET_P: FLAG_MAX_TARGET_P,
-    OLD_FLAG_MAX_TARGET_Q: FLAG_MAX_TARGET_Q,
-    OLD_FLAG_MAX_TARGET_R: FLAG_MAX_TARGET_R,
-}
-
-NEW_FLAGS_TO_OLD = dict(zip(OLD_FLAGS_TO_NEW.values(), OLD_FLAGS_TO_NEW.keys()))
-
 # List of all known flags.
 FLAGS_API_LIST = [
     FLAG_SDK,
@@ -205,36 +185,6 @@
             "Please visit go/hiddenapi for more information.").format(
                 source, "\n".join(flags_subset - ALL_FLAGS_SET))
 
-    def convert_to_new_flag(self, flag):
-      """Converts old flag to a new variant.
-
-      Flags that are considered old are replaced with new versions.
-      Otherwise, it is a no-op.
-
-      Args:
-        flag: a string, representing SDK flag.
-
-      Returns:
-         A string. Result of conversion.
-
-      """
-      return OLD_FLAGS_TO_NEW.get(flag, flag)
-
-    def convert_to_old_flag(self, flag):
-      """Converts a new flag to a old variant.
-
-      No-op if there is no suitable old flag.
-      Only used to support backwards compatibility.
-
-      Args:
-        flag: a string, representing SDK flag.
-
-      Returns:
-         A string. Result of conversion.
-
-      """
-      return NEW_FLAGS_TO_OLD.get(flag, flag)
-
     def filter_apis(self, filter_fn):
         """Returns APIs which match a given predicate.
 
@@ -272,7 +222,7 @@
         """
         lines = []
         for api in self._dict:
-          flags = sorted([self.convert_to_old_flag(flag) for flag in self._dict[api]])
+          flags = sorted(self._dict[api])
           lines.append(",".join([api] + flags))
         return sorted(lines)
 
@@ -298,12 +248,12 @@
         # Check that all flags are known.
         csv_flags = set()
         for csv in csv_values:
-          csv_flags.update([self.convert_to_new_flag(flag) for flag in csv[1:]])
+          csv_flags.update(csv[1:])
         self._check_flags_set(csv_flags, source)
 
         # Iterate over all CSV lines, find entry in dict and append flags to it.
         for csv in csv_values:
-            flags = [self.convert_to_new_flag(flag) for flag in csv[1:]]
+            flags = csv[1:]
             if (FLAG_PUBLIC_API in flags) or (FLAG_SYSTEM_API in flags):
                 flags.append(FLAG_SDK)
             self._dict[csv[0]].update(flags)
diff --git a/tools/hiddenapi/generate_hiddenapi_lists_test.py b/tools/hiddenapi/generate_hiddenapi_lists_test.py
index 321c400..82d117f 100755
--- a/tools/hiddenapi/generate_hiddenapi_lists_test.py
+++ b/tools/hiddenapi/generate_hiddenapi_lists_test.py
@@ -35,7 +35,7 @@
         flags.parse_and_merge_csv(['A,' + FLAG_SDK, 'B', 'C'])
         flags.assign_flag(FLAG_UNSUPPORTED, set(['C']))
         self.assertEqual(flags.generate_csv(),
-            [ 'A,' + OLD_FLAG_SDK, 'B', 'C,' + OLD_FLAG_UNSUPPORTED ])
+            [ 'A,' + FLAG_SDK, 'B', 'C,' + FLAG_UNSUPPORTED ])
 
         # Check three things:
         # (1) B is selected as valid unassigned
@@ -50,8 +50,7 @@
         # Test empty CSV entry.
         self.assertEqual(flags.generate_csv(), [])
 
-        # Test new additions. CSV generator produces values with old flags
-        # to be backwards compatible.
+        # Test new additions.
         flags.parse_and_merge_csv([
             'A,' + FLAG_UNSUPPORTED,
             'B,' + FLAG_BLOCKED + ',' + FLAG_MAX_TARGET_O,
@@ -60,11 +59,11 @@
             'E,' + FLAG_BLOCKED + ',' + FLAG_TEST_API,
         ])
         self.assertEqual(flags.generate_csv(), [
-            'A,' + OLD_FLAG_UNSUPPORTED,
-            'B,' + OLD_FLAG_BLOCKED + "," + OLD_FLAG_MAX_TARGET_O,
-            'C,' + FLAG_SYSTEM_API + ',' + OLD_FLAG_SDK,
-            'D,' + OLD_FLAG_UNSUPPORTED + ',' + FLAG_TEST_API,
-            'E,' + OLD_FLAG_BLOCKED + ',' + FLAG_TEST_API,
+            'A,' + FLAG_UNSUPPORTED,
+            'B,' + FLAG_BLOCKED + "," + FLAG_MAX_TARGET_O,
+            'C,' + FLAG_SYSTEM_API + ',' + FLAG_SDK,
+            'D,' + FLAG_UNSUPPORTED + ',' + FLAG_TEST_API,
+            'E,' + FLAG_BLOCKED + ',' + FLAG_TEST_API,
         ])
 
         # Test unknown flag.
@@ -78,7 +77,7 @@
         # Test new additions.
         flags.assign_flag(FLAG_UNSUPPORTED, set([ 'A', 'B' ]))
         self.assertEqual(flags.generate_csv(),
-            [ 'A,' + OLD_FLAG_UNSUPPORTED + "," + OLD_FLAG_SDK, 'B,' + OLD_FLAG_UNSUPPORTED ])
+            [ 'A,' + FLAG_UNSUPPORTED + "," + FLAG_SDK, 'B,' + FLAG_UNSUPPORTED ])
 
         # Test invalid API signature.
         with self.assertRaises(AssertionError):
diff --git a/tools/protologtool/Android.bp b/tools/protologtool/Android.bp
index ce551bd..0be80d3 100644
--- a/tools/protologtool/Android.bp
+++ b/tools/protologtool/Android.bp
@@ -2,9 +2,9 @@
     name: "protologtool-lib",
     srcs: [
         "src/com/android/protolog/tool/**/*.kt",
+        ":protolog-common-src",
     ],
     static_libs: [
-        "protolog-common",
         "javaparser",
         "platformprotos",
         "jsonlib",
diff --git a/tools/protologtool/src/com/android/protolog/tool/LogParser.kt b/tools/protologtool/src/com/android/protolog/tool/LogParser.kt
index a59038f..645c567 100644
--- a/tools/protologtool/src/com/android/protolog/tool/LogParser.kt
+++ b/tools/protologtool/src/com/android/protolog/tool/LogParser.kt
@@ -16,16 +16,15 @@
 
 package com.android.protolog.tool
 
+import com.android.internal.protolog.ProtoLogFileProto
+import com.android.internal.protolog.ProtoLogMessage
+import com.android.internal.protolog.common.InvalidFormatStringException
+import com.android.internal.protolog.common.LogDataType
 import com.android.json.stream.JsonReader
-import com.android.server.protolog.common.InvalidFormatStringException
-import com.android.server.protolog.common.LogDataType
-import com.android.server.protolog.ProtoLogMessage
-import com.android.server.protolog.ProtoLogFileProto
 import java.io.BufferedReader
 import java.io.InputStream
 import java.io.InputStreamReader
 import java.io.PrintStream
-import java.lang.Exception
 import java.text.SimpleDateFormat
 import java.util.Date
 import java.util.Locale
diff --git a/tools/protologtool/src/com/android/protolog/tool/ProtoLogGroupReader.kt b/tools/protologtool/src/com/android/protolog/tool/ProtoLogGroupReader.kt
index 75493b6..42b628b 100644
--- a/tools/protologtool/src/com/android/protolog/tool/ProtoLogGroupReader.kt
+++ b/tools/protologtool/src/com/android/protolog/tool/ProtoLogGroupReader.kt
@@ -17,7 +17,7 @@
 package com.android.protolog.tool
 
 import com.android.protolog.tool.Constants.ENUM_VALUES_METHOD
-import com.android.server.protolog.common.IProtoLogGroup
+import com.android.internal.protolog.common.IProtoLogGroup
 import java.io.File
 import java.net.URLClassLoader
 
diff --git a/tools/protologtool/src/com/android/protolog/tool/SourceTransformer.kt b/tools/protologtool/src/com/android/protolog/tool/SourceTransformer.kt
index 36ea411..27e61a1 100644
--- a/tools/protologtool/src/com/android/protolog/tool/SourceTransformer.kt
+++ b/tools/protologtool/src/com/android/protolog/tool/SourceTransformer.kt
@@ -16,7 +16,7 @@
 
 package com.android.protolog.tool
 
-import com.android.server.protolog.common.LogDataType
+import com.android.internal.protolog.common.LogDataType
 import com.github.javaparser.StaticJavaParser
 import com.github.javaparser.ast.CompilationUnit
 import com.github.javaparser.ast.NodeList
@@ -89,7 +89,7 @@
             // Out: ProtoLog.e(GROUP, 1234, 0, null, arg)
             newCall.arguments.add(2, IntegerLiteralExpr(typeMask))
             // Replace call to a stub method with an actual implementation.
-            // Out: com.android.server.protolog.ProtoLogImpl.e(GROUP, 1234, null, arg)
+            // Out: ProtoLogImpl.e(GROUP, 1234, null, arg)
             newCall.setScope(protoLogImplClassNode)
             // Create a call to ProtoLog$Cache.GROUP_enabled
             // Out: com.android.server.protolog.ProtoLog$Cache.GROUP_enabled
@@ -119,9 +119,9 @@
             }
             blockStmt.addStatement(ExpressionStmt(newCall))
             // Create an IF-statement with the previously created condition.
-            // Out: if (com.android.server.protolog.ProtoLogImpl.isEnabled(GROUP)) {
+            // Out: if (ProtoLogImpl.isEnabled(GROUP)) {
             //          long protoLogParam0 = arg;
-            //          com.android.server.protolog.ProtoLogImpl.e(GROUP, 1234, 0, null, protoLogParam0);
+            //          ProtoLogImpl.e(GROUP, 1234, 0, null, protoLogParam0);
             //      }
             ifStmt = IfStmt(isLogEnabled, blockStmt, null)
         } else {
diff --git a/tools/protologtool/tests/com/android/protolog/tool/CommandOptionsTest.kt b/tools/protologtool/tests/com/android/protolog/tool/CommandOptionsTest.kt
index cf36651c..3cfbb43 100644
--- a/tools/protologtool/tests/com/android/protolog/tool/CommandOptionsTest.kt
+++ b/tools/protologtool/tests/com/android/protolog/tool/CommandOptionsTest.kt
@@ -31,7 +31,7 @@
         private const val TEST_PROTOLOG_CLASS = "com.android.server.wm.ProtoLog"
         private const val TEST_PROTOLOGIMPL_CLASS = "com.android.server.wm.ProtoLogImpl"
         private const val TEST_PROTOLOGCACHE_CLASS = "com.android.server.wm.ProtoLog\$Cache"
-        private const val TEST_PROTOLOGGROUP_CLASS = "com.android.server.wm.ProtoLogGroup"
+        private const val TEST_PROTOLOGGROUP_CLASS = "com.android.internal.protolog.ProtoLogGroup"
         private const val TEST_PROTOLOGGROUP_JAR = "out/soong/.intermediates/frameworks/base/" +
                 "services/core/services.core.wm.protologgroups/android_common/javac/" +
                 "services.core.wm.protologgroups.jar"
diff --git a/tools/protologtool/tests/com/android/protolog/tool/EndToEndTest.kt b/tools/protologtool/tests/com/android/protolog/tool/EndToEndTest.kt
index dd8a0b1..0d2b91d 100644
--- a/tools/protologtool/tests/com/android/protolog/tool/EndToEndTest.kt
+++ b/tools/protologtool/tests/com/android/protolog/tool/EndToEndTest.kt
@@ -33,8 +33,8 @@
         val output = run(
                 src = "frameworks/base/org/example/Example.java" to """
                     package org.example;
-                    import com.android.server.protolog.common.ProtoLog;
-                    import static com.android.server.wm.ProtoLogGroup.GROUP;
+                    import com.android.internal.protolog.common.ProtoLog;
+                    import static com.android.internal.protolog.ProtoLogGroup.GROUP;
 
                     class Example {
                         void method() {
@@ -46,11 +46,11 @@
                 """.trimIndent(),
                 logGroup = LogGroup("GROUP", true, false, "TAG_GROUP"),
                 commandOptions = CommandOptions(arrayOf("transform-protolog-calls",
-                        "--protolog-class", "com.android.server.protolog.common.ProtoLog",
-                        "--protolog-impl-class", "com.android.server.protolog.ProtoLogImpl",
+                        "--protolog-class", "com.android.internal.protolog.common.ProtoLog",
+                        "--protolog-impl-class", "com.android.internal.protolog.ProtoLogImpl",
                         "--protolog-cache-class",
-                        "com.android.server.protolog.ProtoLog${"\$\$"}Cache",
-                        "--loggroups-class", "com.android.server.wm.ProtoLogGroup",
+                        "com.android.server.wm.ProtoLogCache",
+                        "--loggroups-class", "com.android.internal.protolog.ProtoLogGroup",
                         "--loggroups-jar", "not_required.jar",
                         "--output-srcjar", "out.srcjar",
                         "frameworks/base/org/example/Example.java"))
@@ -64,8 +64,8 @@
         val output = run(
                 src = "frameworks/base/org/example/Example.java" to """
                     package org.example;
-                    import com.android.server.protolog.common.ProtoLog;
-                    import static com.android.server.wm.ProtoLogGroup.GROUP;
+                    import com.android.internal.protolog.common.ProtoLog;
+                    import static com.android.internal.protolog.ProtoLogGroup.GROUP;
 
                     class Example {
                         void method() {
@@ -77,8 +77,8 @@
                 """.trimIndent(),
                 logGroup = LogGroup("GROUP", true, false, "TAG_GROUP"),
                 commandOptions = CommandOptions(arrayOf("generate-viewer-config",
-                        "--protolog-class", "com.android.server.protolog.common.ProtoLog",
-                        "--loggroups-class", "com.android.server.wm.ProtoLogGroup",
+                        "--protolog-class", "com.android.internal.protolog.common.ProtoLog",
+                        "--loggroups-class", "com.android.internal.protolog.ProtoLogGroup",
                         "--loggroups-jar", "not_required.jar",
                         "--viewer-conf", "out.json",
                         "frameworks/base/org/example/Example.java"))
diff --git a/tools/protologtool/tests/com/android/protolog/tool/LogParserTest.kt b/tools/protologtool/tests/com/android/protolog/tool/LogParserTest.kt
index 04a3bfa..67a31da 100644
--- a/tools/protologtool/tests/com/android/protolog/tool/LogParserTest.kt
+++ b/tools/protologtool/tests/com/android/protolog/tool/LogParserTest.kt
@@ -17,8 +17,8 @@
 package com.android.protolog.tool
 
 import com.android.json.stream.JsonReader
-import com.android.server.protolog.ProtoLogMessage
-import com.android.server.protolog.ProtoLogFileProto
+import com.android.internal.protolog.ProtoLogMessage
+import com.android.internal.protolog.ProtoLogFileProto
 import org.junit.Assert.assertEquals
 import org.junit.Before
 import org.junit.Test
diff --git a/tools/stats_log_api_gen/main.cpp b/tools/stats_log_api_gen/main.cpp
index d210184..2830249 100644
--- a/tools/stats_log_api_gen/main.cpp
+++ b/tools/stats_log_api_gen/main.cpp
@@ -15,8 +15,6 @@
 #include "native_writer.h"
 #include "utils.h"
 
-using namespace google::protobuf;
-
 namespace android {
 namespace stats_log_api_gen {
 
diff --git a/wifi/api/current.txt b/wifi/api/current.txt
index 53c3b33..ee7320f 100644
--- a/wifi/api/current.txt
+++ b/wifi/api/current.txt
@@ -487,6 +487,7 @@
     method @Nullable public String getPassphrase();
     method @Nullable public android.net.wifi.hotspot2.PasspointConfiguration getPasspointConfig();
     method @IntRange(from=0) public int getPriority();
+    method public int getPriorityGroup();
     method @Nullable public String getSsid();
     method public boolean isAppInteractionRequired();
     method public boolean isCredentialSharedWithUser();
@@ -513,6 +514,7 @@
     method @NonNull public android.net.wifi.WifiNetworkSuggestion.Builder setIsUserInteractionRequired(boolean);
     method @NonNull public android.net.wifi.WifiNetworkSuggestion.Builder setPasspointConfig(@NonNull android.net.wifi.hotspot2.PasspointConfiguration);
     method @NonNull public android.net.wifi.WifiNetworkSuggestion.Builder setPriority(@IntRange(from=0) int);
+    method @NonNull public android.net.wifi.WifiNetworkSuggestion.Builder setPriorityGroup(int);
     method @NonNull public android.net.wifi.WifiNetworkSuggestion.Builder setSsid(@NonNull String);
     method @NonNull public android.net.wifi.WifiNetworkSuggestion.Builder setUntrusted(boolean);
     method @NonNull public android.net.wifi.WifiNetworkSuggestion.Builder setWapiEnterpriseConfig(@NonNull android.net.wifi.WifiEnterpriseConfig);
diff --git a/wifi/api/system-current.txt b/wifi/api/system-current.txt
index 53c69c4..eff64a3 100644
--- a/wifi/api/system-current.txt
+++ b/wifi/api/system-current.txt
@@ -326,6 +326,8 @@
     field @Deprecated public static final int METERED_OVERRIDE_METERED = 1; // 0x1
     field @Deprecated public static final int METERED_OVERRIDE_NONE = 0; // 0x0
     field @Deprecated public static final int METERED_OVERRIDE_NOT_METERED = 2; // 0x2
+    field @Deprecated public static final int RANDOMIZATION_AUTO = 3; // 0x3
+    field @Deprecated public static final int RANDOMIZATION_ENHANCED = 2; // 0x2
     field @Deprecated public static final int RANDOMIZATION_NONE = 0; // 0x0
     field @Deprecated public static final int RANDOMIZATION_PERSISTENT = 1; // 0x1
     field @Deprecated public static final int RECENT_FAILURE_AP_UNABLE_TO_HANDLE_NEW_STA = 17; // 0x11
diff --git a/wifi/java/android/net/wifi/IWifiManager.aidl b/wifi/java/android/net/wifi/IWifiManager.aidl
index 3cdfb00..e493789 100644
--- a/wifi/java/android/net/wifi/IWifiManager.aidl
+++ b/wifi/java/android/net/wifi/IWifiManager.aidl
@@ -122,7 +122,7 @@
 
     DhcpInfo getDhcpInfo();
 
-    void setScanAlwaysAvailable(boolean isAvailable);
+    void setScanAlwaysAvailable(boolean isAvailable, String packageName);
 
     boolean isScanAlwaysAvailable();
 
@@ -142,9 +142,9 @@
 
     void updateInterfaceIpState(String ifaceName, int mode);
 
-    boolean startSoftAp(in WifiConfiguration wifiConfig);
+    boolean startSoftAp(in WifiConfiguration wifiConfig, String packageName);
 
-    boolean startTetheredHotspot(in SoftApConfiguration softApConfig);
+    boolean startTetheredHotspot(in SoftApConfiguration softApConfig, String packageName);
 
     boolean stopSoftAp();
 
diff --git a/wifi/java/android/net/wifi/SoftApConfiguration.java b/wifi/java/android/net/wifi/SoftApConfiguration.java
index f919ea4..393fe8d 100644
--- a/wifi/java/android/net/wifi/SoftApConfiguration.java
+++ b/wifi/java/android/net/wifi/SoftApConfiguration.java
@@ -582,6 +582,7 @@
                 wifiConfig.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.NONE);
                 break;
             case SECURITY_TYPE_WPA2_PSK:
+            case SECURITY_TYPE_WPA3_SAE_TRANSITION:
                 wifiConfig.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.WPA2_PSK);
                 break;
             default:
diff --git a/wifi/java/android/net/wifi/WifiConfiguration.java b/wifi/java/android/net/wifi/WifiConfiguration.java
index 71f0ab8..1588bf7 100644
--- a/wifi/java/android/net/wifi/WifiConfiguration.java
+++ b/wifi/java/android/net/wifi/WifiConfiguration.java
@@ -1130,7 +1130,9 @@
     @Retention(RetentionPolicy.SOURCE)
     @IntDef(prefix = {"RANDOMIZATION_"}, value = {
             RANDOMIZATION_NONE,
-            RANDOMIZATION_PERSISTENT})
+            RANDOMIZATION_PERSISTENT,
+            RANDOMIZATION_ENHANCED,
+            RANDOMIZATION_AUTO})
     public @interface MacRandomizationSetting {}
 
     /**
@@ -1147,14 +1149,30 @@
     public static final int RANDOMIZATION_PERSISTENT = 1;
 
     /**
+     * Use a randomly generated MAC address for connections to this network.
+     * This option does not persist the randomized MAC address.
+     * @hide
+     */
+    @SystemApi
+    public static final int RANDOMIZATION_ENHANCED = 2;
+
+    /**
+     * Let the wifi framework automatically decide the MAC randomization strategy.
+     * @hide
+     */
+    @SystemApi
+    public static final int RANDOMIZATION_AUTO = 3;
+
+    /**
      * Level of MAC randomization for this network.
-     * One of {@link #RANDOMIZATION_NONE} or {@link #RANDOMIZATION_PERSISTENT}.
-     * By default this field is set to {@link #RANDOMIZATION_PERSISTENT}.
+     * One of {@link #RANDOMIZATION_NONE}, {@link #RANDOMIZATION_AUTO},
+     * {@link #RANDOMIZATION_PERSISTENT} or {@link #RANDOMIZATION_ENHANCED}.
+     * By default this field is set to {@link #RANDOMIZATION_AUTO}.
      * @hide
      */
     @SystemApi
     @MacRandomizationSetting
-    public int macRandomizationSetting = RANDOMIZATION_PERSISTENT;
+    public int macRandomizationSetting = RANDOMIZATION_AUTO;
 
     /**
      * @hide
diff --git a/wifi/java/android/net/wifi/WifiManager.java b/wifi/java/android/net/wifi/WifiManager.java
index ae834f9..b28b902 100644
--- a/wifi/java/android/net/wifi/WifiManager.java
+++ b/wifi/java/android/net/wifi/WifiManager.java
@@ -2802,7 +2802,7 @@
     @RequiresPermission(android.Manifest.permission.NETWORK_SETTINGS)
     public void setScanAlwaysAvailable(boolean isAvailable) {
         try {
-            mService.setScanAlwaysAvailable(isAvailable);
+            mService.setScanAlwaysAvailable(isAvailable, mContext.getOpPackageName());
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
@@ -3035,7 +3035,7 @@
     })
     public boolean startSoftAp(@Nullable WifiConfiguration wifiConfig) {
         try {
-            return mService.startSoftAp(wifiConfig);
+            return mService.startSoftAp(wifiConfig, mContext.getOpPackageName());
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
@@ -3059,7 +3059,7 @@
     })
     public boolean startTetheredHotspot(@Nullable SoftApConfiguration softApConfig) {
         try {
-            return mService.startTetheredHotspot(softApConfig);
+            return mService.startTetheredHotspot(softApConfig, mContext.getOpPackageName());
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
diff --git a/wifi/java/android/net/wifi/WifiNetworkSuggestion.java b/wifi/java/android/net/wifi/WifiNetworkSuggestion.java
index 68eb1bb..a3c4ae7 100644
--- a/wifi/java/android/net/wifi/WifiNetworkSuggestion.java
+++ b/wifi/java/android/net/wifi/WifiNetworkSuggestion.java
@@ -102,10 +102,15 @@
          */
         private int mMeteredOverride;
         /**
-         * Priority of this network among other network suggestions provided by the app.
+         * Priority of this network among other network suggestions from same priority group
+         * provided by the app.
          * The lower the number, the higher the priority (i.e value of 0 = highest priority).
          */
         private int mPriority;
+        /**
+         * Priority group ID, while suggestion priority will only effect inside the priority group.
+         */
+        private int mPriorityGroup;
 
         /**
          * The carrier ID identifies the operator who provides this network configuration.
@@ -165,6 +170,7 @@
             mWapiPskPassphrase = null;
             mWapiEnterpriseConfig = null;
             mIsNetworkUntrusted = false;
+            mPriorityGroup = 0;
         }
 
         /**
@@ -329,6 +335,18 @@
         }
 
         /**
+         * Set the priority group ID, {@link #setPriority(int)} will only impact the network
+         * suggestions from the same priority group within the same app.
+         *
+         * @param priorityGroup priority group id, if not set default is 0.
+         * @return Instance of {@link Builder} to enable chaining of the builder method.
+         */
+        public @NonNull Builder setPriorityGroup(int priorityGroup) {
+            mPriorityGroup = priorityGroup;
+            return this;
+        }
+
+        /**
          * Set the ASCII WAPI passphrase for this network. Needed for authenticating to
          * WAPI-PSK networks.
          *
@@ -411,8 +429,9 @@
 
         /**
          * Specify the priority of this network among other network suggestions provided by the same
-         * app (priorities have no impact on suggestions by different apps). The higher the number,
-         * the higher the priority (i.e value of 0 = lowest priority).
+         * app (priorities have no impact on suggestions by different apps) and within the same
+         * priority group, see {@link #setPriorityGroup(int)}.
+         * The higher the number, the higher the priority (i.e value of 0 = lowest priority).
          * <p>
          * <li>If not set, defaults a lower priority than any assigned priority.</li>
          *
@@ -696,7 +715,8 @@
                     mIsAppInteractionRequired,
                     mIsUserInteractionRequired,
                     mIsSharedWithUser,
-                    mIsInitialAutojoinEnabled);
+                    mIsInitialAutojoinEnabled,
+                    mPriorityGroup);
         }
     }
 
@@ -739,6 +759,12 @@
      */
     public final boolean isInitialAutoJoinEnabled;
 
+    /**
+     * Priority group ID.
+     * @hide
+     */
+    public final int priorityGroup;
+
     /** @hide */
     public WifiNetworkSuggestion() {
         this.wifiConfiguration = new WifiConfiguration();
@@ -747,6 +773,7 @@
         this.isUserInteractionRequired = false;
         this.isUserAllowedToManuallyConnect = true;
         this.isInitialAutoJoinEnabled = true;
+        this.priorityGroup = 0;
     }
 
     /** @hide */
@@ -755,7 +782,7 @@
                                  boolean isAppInteractionRequired,
                                  boolean isUserInteractionRequired,
                                  boolean isUserAllowedToManuallyConnect,
-                                 boolean isInitialAutoJoinEnabled) {
+                                 boolean isInitialAutoJoinEnabled, int priorityGroup) {
         checkNotNull(networkConfiguration);
         this.wifiConfiguration = networkConfiguration;
         this.passpointConfiguration = passpointConfiguration;
@@ -764,6 +791,7 @@
         this.isUserInteractionRequired = isUserInteractionRequired;
         this.isUserAllowedToManuallyConnect = isUserAllowedToManuallyConnect;
         this.isInitialAutoJoinEnabled = isInitialAutoJoinEnabled;
+        this.priorityGroup = priorityGroup;
     }
 
     public static final @NonNull Creator<WifiNetworkSuggestion> CREATOR =
@@ -776,7 +804,8 @@
                             in.readBoolean(), // isAppInteractionRequired
                             in.readBoolean(), // isUserInteractionRequired
                             in.readBoolean(), // isSharedCredentialWithUser
-                            in.readBoolean()  // isAutojoinEnabled
+                            in.readBoolean(),  // isAutojoinEnabled
+                            in.readInt() // priorityGroup
                     );
                 }
 
@@ -799,6 +828,7 @@
         dest.writeBoolean(isUserInteractionRequired);
         dest.writeBoolean(isUserAllowedToManuallyConnect);
         dest.writeBoolean(isInitialAutoJoinEnabled);
+        dest.writeInt(priorityGroup);
     }
 
     @Override
@@ -842,6 +872,7 @@
                 .append(", isCredentialSharedWithUser=").append(isUserAllowedToManuallyConnect)
                 .append(", isInitialAutoJoinEnabled=").append(isInitialAutoJoinEnabled)
                 .append(", isUnTrusted=").append(!wifiConfiguration.trusted)
+                .append(", priorityGroup=").append(priorityGroup)
                 .append(" ]");
         return sb.toString();
     }
@@ -962,4 +993,11 @@
         }
         return WifiInfo.removeDoubleQuotes(wifiConfiguration.preSharedKey);
     }
+
+    /**
+     * @see Builder#setPriorityGroup(int)
+     */
+    public int getPriorityGroup() {
+        return priorityGroup;
+    }
 }
diff --git a/wifi/java/android/net/wifi/hotspot2/pps/Credential.java b/wifi/java/android/net/wifi/hotspot2/pps/Credential.java
index fa806e7..282757a 100644
--- a/wifi/java/android/net/wifi/hotspot2/pps/Credential.java
+++ b/wifi/java/android/net/wifi/hotspot2/pps/Credential.java
@@ -448,6 +448,16 @@
                     return new UserCredential[size];
                 }
             };
+
+        /**
+         * Get a unique identifier for UserCredential.
+         *
+         * @hide
+         * @return a Unique identifier for a UserCredential object
+         */
+        public int getUniqueId() {
+            return Objects.hash(mUsername);
+        }
     }
     private UserCredential mUserCredential = null;
     /**
@@ -1037,7 +1047,8 @@
      * @return a Unique identifier for a Credential object
      */
     public int getUniqueId() {
-        return Objects.hash(mUserCredential, mCertCredential, mSimCredential, mRealm);
+        return Objects.hash(mUserCredential != null ? mUserCredential.getUniqueId() : 0,
+                mCertCredential, mSimCredential, mRealm);
     }
 
     @Override
diff --git a/wifi/java/android/net/wifi/hotspot2/pps/HomeSp.java b/wifi/java/android/net/wifi/hotspot2/pps/HomeSp.java
index 224c4be..8f34579 100644
--- a/wifi/java/android/net/wifi/hotspot2/pps/HomeSp.java
+++ b/wifi/java/android/net/wifi/hotspot2/pps/HomeSp.java
@@ -313,9 +313,7 @@
      * @return a Unique identifier for a HomeSp object
      */
     public int getUniqueId() {
-        return Objects.hash(mFqdn, mFriendlyName, mHomeNetworkIds, Arrays.hashCode(mMatchAllOis),
-                Arrays.hashCode(mMatchAnyOis), Arrays.hashCode(mOtherHomePartners),
-                Arrays.hashCode(mRoamingConsortiumOis));
+        return Objects.hash(mFqdn);
     }
 
 
diff --git a/wifi/java/android/net/wifi/p2p/IWifiP2pManager.aidl b/wifi/java/android/net/wifi/p2p/IWifiP2pManager.aidl
index bfdd45d..fd89d3b 100644
--- a/wifi/java/android/net/wifi/p2p/IWifiP2pManager.aidl
+++ b/wifi/java/android/net/wifi/p2p/IWifiP2pManager.aidl
@@ -25,7 +25,7 @@
  */
 interface IWifiP2pManager
 {
-    Messenger getMessenger(in IBinder binder);
+    Messenger getMessenger(in IBinder binder, in String packageName);
     Messenger getP2pStateMachineMessenger();
     oneway void close(in IBinder binder);
     void setMiracastMode(int mode);
diff --git a/wifi/java/android/net/wifi/p2p/WifiP2pManager.java b/wifi/java/android/net/wifi/p2p/WifiP2pManager.java
index 724ccf0..ad38c5a 100644
--- a/wifi/java/android/net/wifi/p2p/WifiP2pManager.java
+++ b/wifi/java/android/net/wifi/p2p/WifiP2pManager.java
@@ -1156,8 +1156,8 @@
      */
     public Channel initialize(Context srcContext, Looper srcLooper, ChannelListener listener) {
         Binder binder = new Binder();
-        Channel channel = initalizeChannel(srcContext, srcLooper, listener, getMessenger(binder),
-                binder);
+        Channel channel = initializeChannel(srcContext, srcLooper, listener,
+                getMessenger(binder, srcContext.getOpPackageName()), binder);
         return channel;
     }
 
@@ -1167,12 +1167,12 @@
      */
     public Channel initializeInternal(Context srcContext, Looper srcLooper,
                                       ChannelListener listener) {
-        return initalizeChannel(srcContext, srcLooper, listener, getP2pStateMachineMessenger(),
+        return initializeChannel(srcContext, srcLooper, listener, getP2pStateMachineMessenger(),
                 null);
     }
 
-    private Channel initalizeChannel(Context srcContext, Looper srcLooper, ChannelListener listener,
-                                     Messenger messenger, Binder binder) {
+    private Channel initializeChannel(Context srcContext, Looper srcLooper,
+            ChannelListener listener, Messenger messenger, Binder binder) {
         if (messenger == null) return null;
 
         Channel c = new Channel(srcContext, srcLooper, listener, binder, this);
@@ -1814,6 +1814,14 @@
         }
     }
 
+    private Messenger getMessenger(@NonNull Binder binder, @Nullable String packageName) {
+        try {
+            return mService.getMessenger(binder, packageName);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
     /**
      * Get a reference to WifiP2pService handler. This is used to establish
      * an AsyncChannel communication with WifiService
@@ -1824,11 +1832,8 @@
      * @hide
      */
     public Messenger getMessenger(Binder binder) {
-        try {
-            return mService.getMessenger(binder);
-        } catch (RemoteException e) {
-            throw e.rethrowFromSystemServer();
-        }
+        // No way to determine package name in this case.
+        return getMessenger(binder, null);
     }
 
     /**
diff --git a/wifi/tests/src/android/net/wifi/FakeKeys.java b/wifi/tests/src/android/net/wifi/FakeKeys.java
index c0d60c3..641b891 100644
--- a/wifi/tests/src/android/net/wifi/FakeKeys.java
+++ b/wifi/tests/src/android/net/wifi/FakeKeys.java
@@ -214,6 +214,35 @@
     };
     public static final PrivateKey RSA_KEY1 = loadPrivateRSAKey(FAKE_RSA_KEY_1);
 
+    private static final String CLIENT_SUITE_B_RSA3072_CERT_STRING =
+            "-----BEGIN CERTIFICATE-----\n"
+                    + "MIIERzCCAq8CFDopjyNgaj+c2TN2k06h7okEWpHJMA0GCSqGSIb3DQEBDAUAMF4x\n"
+                    + "CzAJBgNVBAYTAlVTMQswCQYDVQQIDAJDQTEMMAoGA1UEBwwDTVRWMRAwDgYDVQQK\n"
+                    + "DAdBbmRyb2lkMQ4wDAYDVQQLDAVXaS1GaTESMBAGA1UEAwwJdW5pdGVzdENBMB4X\n"
+                    + "DTIwMDcyMTAyMjkxMVoXDTMwMDUzMDAyMjkxMVowYjELMAkGA1UEBhMCVVMxCzAJ\n"
+                    + "BgNVBAgMAkNBMQwwCgYDVQQHDANNVFYxEDAOBgNVBAoMB0FuZHJvaWQxDjAMBgNV\n"
+                    + "BAsMBVdpLUZpMRYwFAYDVQQDDA11bml0ZXN0Q2xpZW50MIIBojANBgkqhkiG9w0B\n"
+                    + "AQEFAAOCAY8AMIIBigKCAYEAwSK3C5K5udtCKTnE14e8z2cZvwmB4Xe+a8+7QLud\n"
+                    + "Hooc/lQzClgK4MbVUC0D3FE+U32C78SxKoTaRWtvPmNm+UaFT8KkwyUno/dv+2XD\n"
+                    + "pd/zARQ+3FwAfWopAhEyCVSxwsCa+slQ4juRIMIuUC1Mm0NaptZyM3Tj/ICQEfpk\n"
+                    + "o9qVIbiK6eoJMTkY8EWfAn7RTFdfR1OLuO0mVOjgLW9/+upYv6hZ19nAMAxw4QTJ\n"
+                    + "x7lLwALX7B+tDYNEZHDqYL2zyvQWAj2HClere8QYILxkvktgBg2crEJJe4XbDH7L\n"
+                    + "A3rrXmsiqf1ZbfFFEzK9NFqovL+qGh+zIP+588ShJFO9H/RDnDpiTnAFTWXQdTwg\n"
+                    + "szSS0Vw2PB+JqEABAa9DeMvXT1Oy+NY3ItPHyy63nQZVI2rXANw4NhwS0Z6DF+Qs\n"
+                    + "TNrj+GU7e4SG/EGR8SvldjYfQTWFLg1l/UT1hOOkQZwdsaW1zgKyeuiFB2KdMmbA\n"
+                    + "Sq+Ux1L1KICo0IglwWcB/8nnAgMBAAEwDQYJKoZIhvcNAQEMBQADggGBAMYwJkNw\n"
+                    + "BaCviKFmReDTMwWPRy4AMNViEeqAXgERwDEKwM7efjsaj5gctWfKsxX6UdLzkhgg\n"
+                    + "6S/T6PxVWKzJ6l7SoOuTa6tMQOZp+h3R1mdfEQbw8B5cXBxZ+batzAai6Fiy1FKS\n"
+                    + "/ka3INbcGfYuIYghfTrb4/NJKN06ZaQ1bpPwq0e4gN7800T2nbawvSf7r+8ZLcG3\n"
+                    + "6bGCjRMwDSIipNvOwoj3TG315XC7TccX5difQ4sKOY+d2MkVJ3RiO0Ciw2ZbEW8d\n"
+                    + "1FH5vUQJWnBUfSFznosGzLwH3iWfqlP+27jNE+qB2igEwCRFgVAouURx5ou43xuX\n"
+                    + "qf6JkdI3HTJGLIWxkp7gOeln4dEaYzKjYw+P0VqJvKVqQ0IXiLjHgE0J9p0vgyD6\n"
+                    + "HVVcP7U8RgqrbIjL1QgHU4KBhGi+WSUh/mRplUCNvHgcYdcHi/gHpj/j6ubwqIGV\n"
+                    + "z4iSolAHYTmBWcLyE0NgpzE6ntp+53r2KaUJA99l2iGVzbWTwqPSm0XAVw==\n"
+                    + "-----END CERTIFICATE-----\n";
+    public static final X509Certificate CLIENT_SUITE_B_RSA3072_CERT =
+            loadCertificate(CLIENT_SUITE_B_RSA3072_CERT_STRING);
+
     private static X509Certificate loadCertificate(String blob) {
         try {
             final CertificateFactory certFactory = CertificateFactory.getInstance("X.509");
diff --git a/wifi/tests/src/android/net/wifi/SoftApConfigurationTest.java b/wifi/tests/src/android/net/wifi/SoftApConfigurationTest.java
index c2d0d6d..254434b 100644
--- a/wifi/tests/src/android/net/wifi/SoftApConfigurationTest.java
+++ b/wifi/tests/src/android/net/wifi/SoftApConfigurationTest.java
@@ -310,12 +310,6 @@
                 .build();
 
         assertNull(band_6g_config.toWifiConfiguration());
-        SoftApConfiguration sae_transition_config = new SoftApConfiguration.Builder()
-                .setPassphrase("secretsecret",
-                        SoftApConfiguration.SECURITY_TYPE_WPA3_SAE_TRANSITION)
-                .build();
-
-        assertNull(sae_transition_config.toWifiConfiguration());
     }
 
     @Test
@@ -358,5 +352,16 @@
         assertThat(wifiConfig_2g5g.apBand).isEqualTo(WifiConfiguration.AP_BAND_ANY);
         assertThat(wifiConfig_2g5g.apChannel).isEqualTo(0);
         assertThat(wifiConfig_2g5g.hiddenSSID).isEqualTo(true);
+
+        SoftApConfiguration softApConfig_sae_transition = new SoftApConfiguration.Builder()
+                .setPassphrase("secretsecret",
+                SoftApConfiguration.SECURITY_TYPE_WPA3_SAE_TRANSITION)
+                .build();
+
+        WifiConfiguration wifiConfig_sae_transition =
+                softApConfig_sae_transition.toWifiConfiguration();
+        assertThat(wifiConfig_sae_transition.getAuthType())
+                .isEqualTo(WifiConfiguration.KeyMgmt.WPA2_PSK);
+        assertThat(wifiConfig_sae_transition.preSharedKey).isEqualTo("secretsecret");
     }
 }
diff --git a/wifi/tests/src/android/net/wifi/WifiClientTest.java b/wifi/tests/src/android/net/wifi/WifiClientTest.java
index 42cab55..7a3baf9 100644
--- a/wifi/tests/src/android/net/wifi/WifiClientTest.java
+++ b/wifi/tests/src/android/net/wifi/WifiClientTest.java
@@ -16,8 +16,8 @@
 
 package android.net.wifi;
 
-import static com.android.testutils.MiscAssertsKt.assertFieldCountEquals;
-import static com.android.testutils.ParcelUtilsKt.assertParcelSane;
+import static com.android.testutils.MiscAsserts.assertFieldCountEquals;
+import static com.android.testutils.ParcelUtils.assertParcelSane;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotEquals;
diff --git a/wifi/tests/src/android/net/wifi/WifiManagerTest.java b/wifi/tests/src/android/net/wifi/WifiManagerTest.java
index cba1690..e7f1916c 100644
--- a/wifi/tests/src/android/net/wifi/WifiManagerTest.java
+++ b/wifi/tests/src/android/net/wifi/WifiManagerTest.java
@@ -218,10 +218,10 @@
      */
     @Test
     public void testStartSoftApCallsServiceWithWifiConfig() throws Exception {
-        when(mWifiService.startSoftAp(eq(mApConfig))).thenReturn(true);
+        when(mWifiService.startSoftAp(mApConfig, TEST_PACKAGE_NAME)).thenReturn(true);
         assertTrue(mWifiManager.startSoftAp(mApConfig));
 
-        when(mWifiService.startSoftAp(eq(mApConfig))).thenReturn(false);
+        when(mWifiService.startSoftAp(mApConfig, TEST_PACKAGE_NAME)).thenReturn(false);
         assertFalse(mWifiManager.startSoftAp(mApConfig));
     }
 
@@ -231,10 +231,10 @@
      */
     @Test
     public void testStartSoftApCallsServiceWithNullConfig() throws Exception {
-        when(mWifiService.startSoftAp(eq(null))).thenReturn(true);
+        when(mWifiService.startSoftAp(null, TEST_PACKAGE_NAME)).thenReturn(true);
         assertTrue(mWifiManager.startSoftAp(null));
 
-        when(mWifiService.startSoftAp(eq(null))).thenReturn(false);
+        when(mWifiService.startSoftAp(null, TEST_PACKAGE_NAME)).thenReturn(false);
         assertFalse(mWifiManager.startSoftAp(null));
     }
 
@@ -257,10 +257,12 @@
     @Test
     public void testStartTetheredHotspotCallsServiceWithSoftApConfig() throws Exception {
         SoftApConfiguration softApConfig = generatorTestSoftApConfig();
-        when(mWifiService.startTetheredHotspot(eq(softApConfig))).thenReturn(true);
+        when(mWifiService.startTetheredHotspot(softApConfig, TEST_PACKAGE_NAME))
+                .thenReturn(true);
         assertTrue(mWifiManager.startTetheredHotspot(softApConfig));
 
-        when(mWifiService.startTetheredHotspot(eq(softApConfig))).thenReturn(false);
+        when(mWifiService.startTetheredHotspot(softApConfig, TEST_PACKAGE_NAME))
+                .thenReturn(false);
         assertFalse(mWifiManager.startTetheredHotspot(softApConfig));
     }
 
@@ -270,10 +272,10 @@
      */
     @Test
     public void testStartTetheredHotspotCallsServiceWithNullConfig() throws Exception {
-        when(mWifiService.startTetheredHotspot(eq(null))).thenReturn(true);
+        when(mWifiService.startTetheredHotspot(null, TEST_PACKAGE_NAME)).thenReturn(true);
         assertTrue(mWifiManager.startTetheredHotspot(null));
 
-        when(mWifiService.startTetheredHotspot(eq(null))).thenReturn(false);
+        when(mWifiService.startTetheredHotspot(null, TEST_PACKAGE_NAME)).thenReturn(false);
         assertFalse(mWifiManager.startTetheredHotspot(null));
     }
 
@@ -2375,7 +2377,7 @@
     @Test
     public void testScanAvailable() throws Exception {
         mWifiManager.setScanAlwaysAvailable(true);
-        verify(mWifiService).setScanAlwaysAvailable(true);
+        verify(mWifiService).setScanAlwaysAvailable(true, TEST_PACKAGE_NAME);
 
         when(mWifiService.isScanAlwaysAvailable()).thenReturn(false);
         assertFalse(mWifiManager.isScanAlwaysAvailable());
diff --git a/wifi/tests/src/android/net/wifi/WifiNetworkSuggestionTest.java b/wifi/tests/src/android/net/wifi/WifiNetworkSuggestionTest.java
index f0839e9..3744a51 100644
--- a/wifi/tests/src/android/net/wifi/WifiNetworkSuggestionTest.java
+++ b/wifi/tests/src/android/net/wifi/WifiNetworkSuggestionTest.java
@@ -16,7 +16,12 @@
 
 package android.net.wifi;
 
-import static org.junit.Assert.*;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
 
 import android.net.MacAddress;
 import android.net.wifi.hotspot2.PasspointConfiguration;
@@ -39,6 +44,8 @@
     private static final String TEST_FQDN = "fqdn";
     private static final String TEST_WAPI_CERT_SUITE = "suite";
     private static final String TEST_DOMAIN_SUFFIX_MATCH = "domainSuffixMatch";
+    private static final int DEFAULT_PRIORITY_GROUP = 0;
+    private static final int TEST_PRIORITY_GROUP = 1;
 
     /**
      * Validate correctness of WifiNetworkSuggestion object created by
@@ -612,7 +619,7 @@
         configuration.BSSID = TEST_BSSID;
         configuration.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.NONE);
         WifiNetworkSuggestion suggestion = new WifiNetworkSuggestion(
-                configuration, null, false, true, true, true);
+                configuration, null, false, true, true, true, TEST_PRIORITY_GROUP);
 
         Parcel parcelW = Parcel.obtain();
         suggestion.writeToParcel(parcelW, 0);
@@ -683,14 +690,16 @@
         configuration.BSSID = TEST_BSSID;
         configuration.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.WPA_PSK);
         WifiNetworkSuggestion suggestion =
-                new WifiNetworkSuggestion(configuration, null, true, false, true, true);
+                new WifiNetworkSuggestion(configuration, null, true, false, true, true,
+                        TEST_PRIORITY_GROUP);
 
         WifiConfiguration configuration1 = new WifiConfiguration();
         configuration1.SSID = TEST_SSID;
         configuration1.BSSID = TEST_BSSID;
         configuration1.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.WPA_PSK);
         WifiNetworkSuggestion suggestion1 =
-                new WifiNetworkSuggestion(configuration1, null, false, true, true, true);
+                new WifiNetworkSuggestion(configuration1, null, false, true, true, true,
+                        DEFAULT_PRIORITY_GROUP);
 
         assertEquals(suggestion, suggestion1);
         assertEquals(suggestion.hashCode(), suggestion1.hashCode());
@@ -706,13 +715,15 @@
         configuration.SSID = TEST_SSID;
         configuration.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.NONE);
         WifiNetworkSuggestion suggestion =
-                new WifiNetworkSuggestion(configuration, null, false, false, true, true);
+                new WifiNetworkSuggestion(configuration, null, false, false, true, true,
+                        DEFAULT_PRIORITY_GROUP);
 
         WifiConfiguration configuration1 = new WifiConfiguration();
         configuration1.SSID = TEST_SSID_1;
         configuration.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.NONE);
         WifiNetworkSuggestion suggestion1 =
-                new WifiNetworkSuggestion(configuration1, null, false, false, true, true);
+                new WifiNetworkSuggestion(configuration1, null, false, false, true, true,
+                        DEFAULT_PRIORITY_GROUP);
 
         assertNotEquals(suggestion, suggestion1);
     }
@@ -728,13 +739,15 @@
         configuration.BSSID = TEST_BSSID;
         configuration.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.NONE);
         WifiNetworkSuggestion suggestion =
-                new WifiNetworkSuggestion(configuration, null,  false, false, true, true);
+                new WifiNetworkSuggestion(configuration, null, false, false, true, true,
+                        DEFAULT_PRIORITY_GROUP);
 
         WifiConfiguration configuration1 = new WifiConfiguration();
         configuration1.SSID = TEST_SSID;
         configuration1.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.NONE);
         WifiNetworkSuggestion suggestion1 =
-                new WifiNetworkSuggestion(configuration1, null, false, false, true, true);
+                new WifiNetworkSuggestion(configuration1, null, false, false, true, true,
+                        DEFAULT_PRIORITY_GROUP);
 
         assertNotEquals(suggestion, suggestion1);
     }
@@ -749,13 +762,15 @@
         configuration.SSID = TEST_SSID;
         configuration.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.NONE);
         WifiNetworkSuggestion suggestion =
-                new WifiNetworkSuggestion(configuration, null, false, false, true, true);
+                new WifiNetworkSuggestion(configuration, null, false, false, true, true,
+                        DEFAULT_PRIORITY_GROUP);
 
         WifiConfiguration configuration1 = new WifiConfiguration();
         configuration1.SSID = TEST_SSID;
         configuration1.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.WPA_PSK);
         WifiNetworkSuggestion suggestion1 =
-                new WifiNetworkSuggestion(configuration1, null, false, false, true, true);
+                new WifiNetworkSuggestion(configuration1, null, false, false, true, true,
+                        DEFAULT_PRIORITY_GROUP);
 
         assertNotEquals(suggestion, suggestion1);
     }
diff --git a/wifi/tests/src/android/net/wifi/hotspot2/PasspointConfigurationTest.java b/wifi/tests/src/android/net/wifi/hotspot2/PasspointConfigurationTest.java
index 638efb9..8270d64 100644
--- a/wifi/tests/src/android/net/wifi/hotspot2/PasspointConfigurationTest.java
+++ b/wifi/tests/src/android/net/wifi/hotspot2/PasspointConfigurationTest.java
@@ -23,6 +23,8 @@
 import static org.junit.Assert.assertNotEquals;
 import static org.junit.Assert.assertTrue;
 
+import android.net.wifi.EAPConstants;
+import android.net.wifi.FakeKeys;
 import android.net.wifi.hotspot2.pps.Credential;
 import android.net.wifi.hotspot2.pps.HomeSp;
 import android.os.Parcel;
@@ -32,6 +34,11 @@
 import org.junit.Test;
 
 import java.nio.charset.StandardCharsets;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.security.PrivateKey;
+import java.security.cert.CertificateEncodingException;
+import java.security.cert.X509Certificate;
 import java.util.Arrays;
 import java.util.HashMap;
 import java.util.Map;
@@ -383,19 +390,39 @@
     }
 
     /**
-     * Verify that the unique identifier generated is different for two instances with different
-     * HomeSp node
+     * Verify that the unique identifier generated is the same for two instances with different
+     * HomeSp node but same FQDN
      *
      * @throws Exception
      */
     @Test
-    public void validateUniqueIdDifferentHomeSp() throws Exception {
+    public void validateUniqueIdDifferentHomeSpSameFqdn() throws Exception {
         PasspointConfiguration config1 = PasspointTestUtils.createConfig();
 
-        // Modify config2's RCOIs to a different set of values
+        // Modify config2's RCOIs and friendly name to a different set of values
         PasspointConfiguration config2 = PasspointTestUtils.createConfig();
         HomeSp homeSp = config2.getHomeSp();
         homeSp.setRoamingConsortiumOis(new long[] {0xaa, 0xbb});
+        homeSp.setFriendlyName("Some other name");
+        config2.setHomeSp(homeSp);
+
+        assertEquals(config1.getUniqueId(), config2.getUniqueId());
+    }
+
+    /**
+     * Verify that the unique identifier generated is different for two instances with the same
+     * HomeSp node but different FQDN
+     *
+     * @throws Exception
+     */
+    @Test
+    public void validateUniqueIdSameHomeSpDifferentFqdn() throws Exception {
+        PasspointConfiguration config1 = PasspointTestUtils.createConfig();
+
+        // Modify config2's FQDN to a different value
+        PasspointConfiguration config2 = PasspointTestUtils.createConfig();
+        HomeSp homeSp = config2.getHomeSp();
+        homeSp.setFqdn("fqdn2.com");
         config2.setHomeSp(homeSp);
 
         assertNotEquals(config1.getUniqueId(), config2.getUniqueId());
@@ -403,15 +430,15 @@
 
     /**
      * Verify that the unique identifier generated is different for two instances with different
-     * Credential node
+     * SIM Credential node
      *
      * @throws Exception
      */
     @Test
-    public void validateUniqueIdDifferentCredential() throws Exception {
+    public void validateUniqueIdDifferentSimCredential() throws Exception {
         PasspointConfiguration config1 = PasspointTestUtils.createConfig();
 
-        // Modify config2's RCOIs to a different set of values
+        // Modify config2's realm and SIM credential to a different set of values
         PasspointConfiguration config2 = PasspointTestUtils.createConfig();
         Credential credential = config2.getCredential();
         credential.setRealm("realm2.example.com");
@@ -422,6 +449,157 @@
     }
 
     /**
+     * Verify that the unique identifier generated is different for two instances with different
+     * Realm in the Credential node
+     *
+     * @throws Exception
+     */
+    @Test
+    public void validateUniqueIdDifferentRealm() throws Exception {
+        PasspointConfiguration config1 = PasspointTestUtils.createConfig();
+
+        // Modify config2's realm to a different set of values
+        PasspointConfiguration config2 = PasspointTestUtils.createConfig();
+        Credential credential = config2.getCredential();
+        credential.setRealm("realm2.example.com");
+        config2.setCredential(credential);
+
+        assertNotEquals(config1.getUniqueId(), config2.getUniqueId());
+    }
+
+    /**
+     * Verify that the unique identifier generated is the same for two instances with different
+     * password and same username in the User Credential node
+     *
+     * @throws Exception
+     */
+    @Test
+    public void validateUniqueIdSameUserInUserCredential() throws Exception {
+        PasspointConfiguration config1 = PasspointTestUtils.createConfig();
+        Credential credential = createCredentialWithUserCredential("user", "passwd");
+        config1.setCredential(credential);
+
+        // Modify config2's Passpowrd to a different set of values
+        PasspointConfiguration config2 = PasspointTestUtils.createConfig();
+        credential = createCredentialWithUserCredential("user", "newpasswd");
+        config2.setCredential(credential);
+
+        assertEquals(config1.getUniqueId(), config2.getUniqueId());
+    }
+
+    /**
+     * Verify that the unique identifier generated is different for two instances with different
+     * username in the User Credential node
+     *
+     * @throws Exception
+     */
+    @Test
+    public void validateUniqueIdDifferentUserCredential() throws Exception {
+        PasspointConfiguration config1 = PasspointTestUtils.createConfig();
+        Credential credential = createCredentialWithUserCredential("user", "passwd");
+        config1.setCredential(credential);
+
+        // Modify config2's username to a different value
+        PasspointConfiguration config2 = PasspointTestUtils.createConfig();
+        credential = createCredentialWithUserCredential("user2", "passwd");
+        config2.setCredential(credential);
+
+        assertNotEquals(config1.getUniqueId(), config2.getUniqueId());
+    }
+
+    /**
+     * Verify that the unique identifier generated is different for two instances with different
+     * Cert Credential node
+     *
+     * @throws Exception
+     */
+    @Test
+    public void validateUniqueIdDifferentCertCredential() throws Exception {
+        PasspointConfiguration config1 = PasspointTestUtils.createConfig();
+        Credential credential = createCredentialWithCertificateCredential(true, true);
+        config1.setCredential(credential);
+
+        // Modify config2's cert credential to a different set of values
+        PasspointConfiguration config2 = PasspointTestUtils.createConfig();
+        credential = createCredentialWithCertificateCredential(false, false);
+        config2.setCredential(credential);
+
+        assertNotEquals(config1.getUniqueId(), config2.getUniqueId());
+    }
+
+    /**
+     * Helper function for generating certificate credential for testing.
+     *
+     * @return {@link Credential}
+     */
+    private static Credential createCredentialWithCertificateCredential(Boolean useCaCert0,
+            Boolean useCert0)
+            throws NoSuchAlgorithmException, CertificateEncodingException {
+        Credential.CertificateCredential certCred = new Credential.CertificateCredential();
+        certCred.setCertType("x509v3");
+        if (useCert0) {
+            certCred.setCertSha256Fingerprint(
+                    MessageDigest.getInstance("SHA-256").digest(FakeKeys.CLIENT_CERT.getEncoded()));
+        } else {
+            certCred.setCertSha256Fingerprint(MessageDigest.getInstance("SHA-256")
+                    .digest(FakeKeys.CLIENT_SUITE_B_RSA3072_CERT.getEncoded()));
+        }
+        return createCredential(null, certCred, null, new X509Certificate[] {FakeKeys.CLIENT_CERT},
+                FakeKeys.RSA_KEY1, useCaCert0 ? FakeKeys.CA_CERT0 : FakeKeys.CA_CERT1);
+    }
+
+    /**
+     * Helper function for generating user credential for testing.
+     *
+     * @return {@link Credential}
+     */
+    private static Credential createCredentialWithUserCredential(String username, String password) {
+        Credential.UserCredential userCred = new Credential.UserCredential();
+        userCred.setUsername(username);
+        userCred.setPassword(password);
+        userCred.setMachineManaged(true);
+        userCred.setAbleToShare(true);
+        userCred.setSoftTokenApp("TestApp");
+        userCred.setEapType(EAPConstants.EAP_TTLS);
+        userCred.setNonEapInnerMethod("MS-CHAP");
+        return createCredential(userCred, null, null, null, null, FakeKeys.CA_CERT0);
+    }
+
+    /**
+     * Helper function for generating Credential for testing.
+     *
+     * @param userCred Instance of UserCredential
+     * @param certCred Instance of CertificateCredential
+     * @param simCred Instance of SimCredential
+     * @param clientCertificateChain Chain of client certificates
+     * @param clientPrivateKey Client private key
+     * @param caCerts CA certificates
+     * @return {@link Credential}
+     */
+    private static Credential createCredential(Credential.UserCredential userCred,
+            Credential.CertificateCredential certCred,
+            Credential.SimCredential simCred,
+            X509Certificate[] clientCertificateChain, PrivateKey clientPrivateKey,
+            X509Certificate... caCerts) {
+        Credential cred = new Credential();
+        cred.setCreationTimeInMillis(123455L);
+        cred.setExpirationTimeInMillis(2310093L);
+        cred.setRealm("realm");
+        cred.setCheckAaaServerCertStatus(true);
+        cred.setUserCredential(userCred);
+        cred.setCertCredential(certCred);
+        cred.setSimCredential(simCred);
+        if (caCerts != null && caCerts.length == 1) {
+            cred.setCaCertificate(caCerts[0]);
+        } else {
+            cred.setCaCertificates(caCerts);
+        }
+        cred.setClientCertificateChain(clientCertificateChain);
+        cred.setClientPrivateKey(clientPrivateKey);
+        return cred;
+    }
+
+    /**
      * Verify that the unique identifier API generates an exception if HomeSP is not initialized.
      *
      * @throws Exception
diff --git a/wifi/tests/src/android/net/wifi/hotspot2/pps/CredentialTest.java b/wifi/tests/src/android/net/wifi/hotspot2/pps/CredentialTest.java
index 829d8f0..a44df40 100644
--- a/wifi/tests/src/android/net/wifi/hotspot2/pps/CredentialTest.java
+++ b/wifi/tests/src/android/net/wifi/hotspot2/pps/CredentialTest.java
@@ -593,10 +593,10 @@
     }
 
     /**
-     * Verify that unique identifiers are different for a credential with different values
+     * Verify that unique identifiers are different for a credential with different username
      */
     @Test
-    public void testUniqueIdDifferentForUserCredentialsWithDifferentValues() throws Exception {
+    public void testUniqueIdDifferentForUserCredentialsWithDifferentUsername() throws Exception {
         Credential userCred1 = createCredentialWithUserCredential();
         Credential userCred2 = createCredentialWithUserCredential();
         userCred2.getUserCredential().setUsername("anotheruser");
@@ -605,7 +605,24 @@
     }
 
     /**
-     * Verify that unique identifiers are different for a credential with different values
+     * Verify that unique identifiers are different for a credential with different password and
+     * other values other than username
+     */
+    @Test
+    public void testUniqueIdSameForUserCredentialsWithDifferentPassword() throws Exception {
+        Credential userCred1 = createCredentialWithUserCredential();
+        Credential userCred2 = createCredentialWithUserCredential();
+        userCred2.getUserCredential().setPassword("someotherpassword!");
+        userCred2.getUserCredential().setMachineManaged(false);
+        userCred2.getUserCredential().setAbleToShare(false);
+        userCred2.getUserCredential().setSoftTokenApp("TestApp2");
+        userCred2.getUserCredential().setNonEapInnerMethod("PAP");
+
+        assertEquals(userCred1.getUniqueId(), userCred2.getUniqueId());
+    }
+
+    /**
+     * Verify that unique identifiers are different for a cert credential with different values
      */
     @Test
     public void testUniqueIdDifferentForCertCredentialsWithDifferentValues() throws Exception {